C++ 编译器在eax上来回生成mov
为什么在C++ 编译器在eax上来回生成mov,c++,gcc,assembly,x86-64,micro-optimization,C++,Gcc,Assembly,X86 64,Micro Optimization,为什么在idiv之后前后移动eax gcc也有类似的行为,所以这似乎是有意的 具有-O3-march=native的gcc符合规范要求 test1(int, int): # @test1(int, int) cmp edi, esi jl .LBB0_1 mov eax, esi ret .LBB0_1: mov eax, ed
idiv
之后前后移动eax
gcc也有类似的行为,所以这似乎是有意的
具有-O3-march=native
的gcc符合规范要求
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
这不是一个完整的谜题解决方案,但应该能提供一些线索 如果没有
\u内置的\u expect
,clang会生成:
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
虽然寄存器分配在这里仍然很奇怪,但至少有意义:如果执行分支,则ecx
中b
的值将作为返回值转移到eax
。如果未进行除法,除法结果(在eax
中)必须传输到ecx
中,以便与其他情况下的结果位于同一寄存器中
可能是
\uuuu内置的expect
使编译器相信在特殊情况下,分支在编译过程的后期执行,孤立了.LBB1\u2
标签,并导致它最终从程序集中消失。idiv esi
是32位操作数大小,所以EAX已经是零扩展来填充RAX。因此,复制到ESI或R8D并返回对EAX中的值没有影响。(而且调用约定不需要对64位进行零扩展或符号扩展;32位类型在32位寄存器中返回,可能的垃圾在32位上限中。)
这看起来纯粹是一个遗漏的优化。(也没有微体系结构性能原因说明这是一件好事。)我不认为这是有意的。似乎
expect
混淆了两个编译器。@geza我发现两个编译器不太可能有相同的问题。如果删除expect,您可以看到额外移动的来源。有趣的是,如果分支不在代码中,编译器不会发出任何移动。查看我的答案,了解编译器是如何得到这个想法的。有趣的是,可能在某一点上,这些指令之间有一个基本的块边界。Clang的输出将注释放在不是分支目标的基本块边界上,如#%bb.2:
,但是如果这个假设是正确的,那么必须在这个遗漏的优化被确定之后才能确定。我们在mov指令之间看不到%bb
注释,因此至少在最终asm输出时,clang知道这不是bb边界。@PeterCordes如果关闭标签筛选,孤立标签实际上就在那里。添加volatile int i=0代码>在返回b之前
避免了gcc和clang都错过的优化(代价是在红色区域中建立一个存储,所以您实际上不希望这样)。但它不会改变整体功能布局。
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
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