符合者在EAX上来回产生一个移动

complier generating a mov back and forth on eax

本文关键字:移动 一个 EAX      更新时间:2023-10-16
int test1(int a, int b) {
    if (__builtin_expect(a < b, 0))
        return a / b;
    return b;
}

-O3 -march=native

编译
test1(int, int):                             # @test1(int, int)
        cmp     edi, esi
        jl      .LBB0_1
        mov     eax, esi
        ret
.LBB0_1:
        mov     eax, edi
        cdq
        idiv    esi
        mov     esi, eax
        mov     eax, esi  # moving eax back and forth
        ret

为什么eaxidiv之后来回移动?

GCC具有相似的行为,因此似乎是打算的。

gcc with -O3 -march=native将代码符合到

test1(int, int):
        mov     r8d, esi
        cmp     edi, esi
        jl      .L4
        mov     eax, r8d
        ret
.L4:
        mov     eax, edi
        cdq
        idiv    esi
        mov     r8d, eax
        mov     eax, r8d  #back and forth mov
        ret

Godbolt

这不是拼图的完整解决方案,而应提供一些线索。

没有 __builtin_expect,clang生成:

test2(int, int):                             # @test2(int, int)
        mov     ecx, esi
        cmp     edi, esi
        jge     .LBB1_2
        mov     eax, edi
        cdq
        idiv    ecx
        mov     ecx, eax
.LBB1_2:
        mov     eax, ecx
        ret

虽然在这里寄存器分配仍然很奇怪,但这至少是有道理的:如果采用分支,则将b中的CC_6值传输到eax作为返回值。如果不采取任何措施,则必须将划分的结果(在eax中)转移到ecx,以与其他情况相同的寄存器。

可能是__builtin_expect说服编译器对特殊情况下分支在编译蛋白过程后期进行,孤立.LBB1_2标签并最终导致组装中缺少该分支的情况。

idiv esi是32位操作数尺寸,因此EAX已经将零扩展以填充RAX。因此,复制到ESI或R8D和背部对EAX中的值没有影响。(无论如何,调用约定不需要零扩展或签名扩展到64位;在32位寄存器中返回32位类型,上部32中可能垃圾。)

这看起来纯粹是错过的优化。(也没有微体系绩效的原因,这也是一件好事。)