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

问题描述 投票:1回答:2

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

LEA使用displacement不同

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 x86 switch-statement x86-64 jump-table
2个回答
2
投票

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

如果使用-fno-pie -no-pie进行编译,gcc可能会选择使用jmp [table + rdi*8]的跳转目标表

像x86-64 Linux这样的目标确实支持运行时数据修正,因此可以使用简单的跳转表。但有些目标根本不支持修正,这就是为什么gcc -fPIC / -fpie完全避免它。这种潜在的优化是gcc bug 84011。请参阅那里的讨论。


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

这是一个单独的错过优化,我报告为bug 85585。 (这提醒我,我有一个半成品的后续跟进,我应该完成并发布。)


1
投票

MOVSXD和ADD是最好的方法,

它可以通过addqword内存操作数来完成。当然,缺点是它使得桌子的重量增加了一倍。

与使用位移的LEA不同

不,lea无法访问内存。

为什么我们两次添加RDX?

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

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

mov edi, edi     ; truncate rdi to 32bit

在当前架构上不能移动自动移动,因此移动到其他寄存器会更好。

© www.soinside.com 2019 - 2024. All rights reserved.