GCC 跳转表初始化代码生成 movsxd 并添加?



当我在 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 0x85c

MOVSXDADD是最好的方法吗?

movsxd rax, dword [rdx + rdi*4]
add rax, rdx

这不和用LEAdisplacement一样

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;
}

GCC 在其跳转表中使用相对位移(相对于表的底部(,而不是绝对地址。因此,跳转表本身与位置无关,并且在重新定位时不需要修正,例如作为加载 PIE 可执行文件或 PIC 共享库的一部分。

如果你使用-fno-pie -no-pie编译,gcc 可能会选择使用带有jmp [table + rdi*8]

像x86-64 Linux这样的目标确实支持运行时数据修复,因此一个简单的跳转表是可能的。 但是有些目标根本不支持修复,这就是为什么 gcc-fPIC/-fpie完全避免它的原因。 这个潜在的优化是 gcc 错误 84011。 有关详细信息,请参阅那里的讨论。


不幸的是,gcc 使用的是跳转表,而不是意识到每种情况之间的唯一区别是数据,而不是代码。 所以实际上它只需要字符串指针的表查找。 (如果愿意,可以用相对位移来完成。

这是一个单独的错过优化,我将其报告为错误 85585。 (这提醒了我,我有一个后续的半写,我应该完成并发布。

MOVSXD 和 ADD 是最好的方法吗?

只需一个具有qword内存操作数的add即可完成此操作。当然,缺点是它使桌子大了两倍。

这和用 LEA 位移不一样

否,lea不访问内存。

为什么我们要添加两次RDX?

第一次将其用作表的基础以索引到表中。该表保存相对于自身的地址,因此将 RDX 添加到表中的值会创建一个绝对地址。

顺便说一下,这可以很容易地改进:

mov edi, edi     ; truncate rdi to 32bit

在当前架构上无法消除自移动,因此最好将自移动移动到其他寄存器。

最新更新