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
为什么eax
在idiv
之后来回移动?
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
这不是解决难题的完整解决方案,但应该提供一些线索。
如果没有__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
中ecx
的值将转换为eax
作为返回值。如果不采用,则除法的结果(在eax
中)必须转移到ecx
,与其他情况下的寄存器相同。
可能是__builtin_expect
说服编译器特殊情况下,在compilatin过程中延迟分支,使.LBB1_2
标签孤立并使其最终不在程序集中。
idiv esi
是32位操作数大小,因此EAX已经零扩展以填充RAX。因此,复制到ESI或R8D并返回对EAX中的值没有影响。 (并且调用约定不需要对64位进行零扩展或符号扩展;在32位寄存器中返回32位类型,并且在上层32中可能存在垃圾。)
这看起来纯粹是错过了优化。 (没有微架构性能原因,这也是一件好事。)