代码之家  ›  专栏  ›  技术社区  ›  Evan Carroll

GCC跳转表初始化代码生成movsxd和add?

  •  1
  • Evan Carroll  · 技术社区  · 6 年前

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

    是吗 MOVSXD ADD 最好的方法是,

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

    这和使用 LEA 具有 displacement

    lea rax, [rdx + rdi*4 + rdx]
    

    我突然想到,我可能不明白这里发生了什么。 RDX 似乎是跳台的开始。 RDI 是switch语句的传入参数。我们为什么要加上 RDX 两次?

    这是我编译的switch语句 -O3 ,

    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;
    }
    
    2 回复  |  直到 6 年前
        1
  •  3
  •   Peter Cordes Steve Bohrer    6 年前

    GCC正在使用 相对的 跳转表中的位移(相对于表的底部),而不是绝对地址。 所以跳转台本身是位置独立的,在重新定位时不需要修正, e、 g.作为加载PIE可执行文件或PIC共享库的一部分。

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

    像x86-64 Linux这样的目标确实支持运行时数据修正,因此一个简单的跳转表是可能的。但有些目标根本不支持修复,这就是gcc的原因 -fPIC / -fpie 完全避免。这种潜在的优化是 gcc bug 84011 .有关更多信息,请参见此处的讨论。


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

    这是一个单独的遗漏优化,我报告为 bug 85585 (这提醒了我,我有一篇关于那篇写了一半的文章的后续文章,我应该完成并发布。)

        2
  •  1
  •   user555045    6 年前

    是MOVSXD和ADD的最佳方式,

    只需要一个 add 用一个 qword 内存操作数。当然,缺点是它使桌子变大了一倍。

    这和使用LEA和位移不一样吗

    lea 不访问内存。

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

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

    顺便说一句,这很容易得到改善:

    mov edi, edi     ; truncate rdi to 32bit
    

    在当前的体系结构上,不能消除自mov,所以最好将其转移到其他寄存器。