assembly gcc使用`莱亚`而不是`add`

iqxoj9l9  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(78)

我想记住编译器如何执行整数除以2。有趣的是,我发现了一个我不理解的特殊行为:
https://godbolt.org/z/87n3x5Gjv

int div2(int x)
    {
        return x / 2;
    }

字符串
第一个月

div2(int):
        mov     eax, edi
        shr     eax, 31
        lea     eax, [rax+rdi]
        sar     eax
        ret


我个人会将leaadd交换,-O 0和-O2可以做到这一点,clang也可以在任何级别上做到这一点。这与add指令可能修改的标志有关吗?我很想知道为什么会发生这样的事情,提前感谢!

m3eecexj

m3eecexj1#

我认为这里的莱亚更糟糕,可能是一些启发式错误的结果。
我也注意到了这一点,有些编译器似乎毫无理由地更喜欢莱亚,即使它们不需要将结果复制到不是两个输入的目的地。
莱亚在某些CPU上运行的端口更少,例如Ice Lake之前的Intel。至少它在所有现代CPU上仍然是一个“简单”的LEA,2个组件,索引上没有比例因子。否则它可以在更少的端口上运行,并且可能具有高于1个周期的延迟。(https://uops.info/)(但是使用更复杂的莱亚完成2到4条指令的工作通常仍然是值得的,除非它是循环承载依赖链的一部分。
在寻址模式下,莱亚确实会为SIB字节花费额外的代码大小。(对于3字节lea,操作码+ ModRM + SIB;对于2字节add eax, edi,仅操作码+ ModRM)
不写FLAGS在这里没有用。x86 CPU完全有效地处理FLAGS写,即使对FLAGS和整数结果使用相同的物理寄存器文件条目。(Sandybridge-family绝对可以做到这一点;我假设Zen和其他人也可以做到这一点,而不是需要另一个完整的寄存器文件和寄存器分配表或其他东西,或者需要uops有2个输出。我似乎记得有一个关于GCC bugzilla问题的评论,建议不修改FLAGS会有一些好处,比如CPU的工作量减少,不必重命名它。
当你已经写了一个寄存器目标,写FLAGS也没有额外的成本。它甚至可以释放一个旧的物理寄存器,它只保存FLAGS结果,而不是任何整数寄存器的当前值。

相关问题