我在GCC里面优化编译switch语句的时候,它是这样建立一个跳转表的,
(fcn) sym.foo 148
sym.foo (unsigned int arg1);
; arg unsigned int arg1 @ rdi
0x000006e0 83ff06 cmp edi, 6 ; arg1
0x000006e3 0f87a7000000 ja case.default.0x790
0x000006e9 488d156c0100. lea rdx, [0x0000085c]
0x000006f0 89ff mov edi, edi
0x000006f2 4883ec08 sub rsp, 8
0x000006f6 486304ba movsxd rax, dword [rdx + rdi*4]
0x000006fa 4801d0 add rax, rdx ; '('
;-- switch.0x000006fd:
0x000006fd ffe0 jmp rax ; switch table (7 cases) at 0x85cmovsxd rax, dword [rdx + rdi*4]
add rax, rdx这不就等同于在displacement中使用LEA吗
lea rax, [rdx + rdi*4 + rdx]我突然想到,我可能不明白这里发生了什么。RDX似乎是跳转表的开始。RDI是switch语句的传入参数。但是为什么我们要添加两次RDX呢?
这是我用-O3编译的switch语句,
int foo (int x) {
switch(x) {
//case 0: puts("\nzero"); break;
case 1: puts("\none"); break;
case 2: puts("\ntwo"); break;
case 3: puts("\nthree"); break;
case 4: puts("\nfour"); break;
case 5: puts("\nfive"); break;
case 6: puts("\nsix"); break;
}
return 0;
}发布于 2018-09-06 04:13:56
GCC在跳转表中使用相对位移(相对于表的基数),而不是绝对地址。因此跳转表本身是位置独立的,当它被重新定位时,不需要链接地址信息,,例如作为加载PIE可执行文件或PIC共享库的一部分。
如果使用-fno-pie -no-pie编译,则gcc可能会选择使用带有jmp [table + rdi*8]的跳转目标表
像x86-64Linux这样的目标确实支持运行时数据修正,因此一个简单的跳转表将是可能的。但有些目标根本不支持链接地址信息,这就是为什么gcc -fPIC / -fpie完全避免了它。这个潜在的优化是gcc bug 84011。有关更多信息,请参阅那里的讨论。
不幸的是,gcc使用的是跳转表,而没有意识到每种情况之间唯一的区别是数据,而不是代码。所以实际上它只需要一个字符串指针的表查找。(如果需要,可以使用相对位移完成此操作。)
这是一个单独的缺失优化,我将其报告为bug 85585。(这提醒了我,我有一个写了一半的后续文章,我应该完成并发布。)
发布于 2018-09-06 03:17:37
是MOVSXD,并添加了最好的实现方式,
只需使用带有qword内存操作数的add即可完成此操作。当然,缺点是它使表格变得两倍大。
与使用带位移的LEA不是一样的吗
不,lea不访问内存。
为什么我们要添加两次RDX?
第一次将其用作表的基础以对其进行索引。该表保存相对于其自身的地址,因此将RDX添加到表中的值将创建绝对地址。
顺便说一句,这可以很容易地改进:
mov edi, edi ; truncate rdi to 32bit自移位不能在当前架构上被移位消除,因此最好移到其他寄存器。
https://stackoverflow.com/questions/52190313
复制相似问题