编译器在eax上来回生成一个mov

问题描述 投票:5回答:2
int test1(int a, int b) {
    if (__builtin_expect(a < b, 0))
        return a / b;
    return b;
}

是由-O3 -march=native与clang编译的

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有类似的行为,所以这似乎是有意的。

-O3 -march=native的gcc符合代码

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

c++ gcc assembly x86-64 micro-optimization
2个回答
2
投票

这不是解决难题的完整解决方案,但应该提供一些线索。

如果没有__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

虽然寄存器分配在这里仍然很奇怪,但它至少是有意义的:如果采用分支,becx的值将转换为eax作为返回值。如果不采用,则除法的结果(在eax中)必须转移到ecx,与其他情况下的寄存器相同。

可能是__builtin_expect说服编译器特殊情况下,在compilatin过程中延迟分支,使.LBB1_2标签孤立并使其最终不在程序集中。


1
投票

idiv esi是32位操作数大小,因此EAX已经零扩展以填充RAX。因此,复制到ESI或R8D并返回对EAX中的值没有影响。 (并且调用约定不需要对64位进行零扩展或符号扩展;在32位寄存器中返回32位类型,并且在上层32中可能存在垃圾。)

这看起来纯粹是错过了优化。 (没有微架构性能原因,这也是一件好事。)

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