为什么llvm和gcc在x86 64上使用不同的函数序言?

Bjö*_*ist 6 c assembly gcc x86-64 llvm

我用gcc和clang编译的一个简单的函数:

void test() {
    printf("hm");
    printf("hum");
}
Run Code Online (Sandbox Code Playgroud)


$ gcc test.c -fomit-frame-pointer -masm=intel -O3 -S

sub rsp, 8
.cfi_def_cfa_offset 16
mov esi, OFFSET FLAT:.LC0
mov edi, 1
xor eax, eax
call    __printf_chk
mov esi, OFFSET FLAT:.LC1
mov edi, 1
xor eax, eax
add rsp, 8
.cfi_def_cfa_offset 8
jmp __printf_chk
Run Code Online (Sandbox Code Playgroud)

$ clang test.c -mllvm --x86-asm-syntax=intel -fomit-frame-pointer -O3 -S    

# BB#0:
push    rax
.Ltmp1:
.cfi_def_cfa_offset 16
mov edi, .L.str
xor eax, eax
call    printf
mov edi, .L.str1
xor eax, eax
pop rdx
jmp printf                  # TAILCALL
Run Code Online (Sandbox Code Playgroud)

我感兴趣的区别是gcc使用sub rsp, 8/ add rsp, 8用于函数prolog和clang使用push rax/ pop rdx.

为什么编译器使用不同的函数序言?哪种变体更好?push并且pop一定要进行编码的指令更短,但他们快于或慢addsub

堆栈摆弄的原因似乎是abi要求rsp为16字节对齐非叶子过程.我无法找到任何删除它们的编译器标志.

从你的答案来看,似乎push&pop更好.push rax + pop rdx = 1 + 1 = 2sub rsp, 8 + add rsp, 8 = 4 + 4 = 8.所以前一对节省了6个字节.

Pet*_*des 9

在Intel上,sub/ add将触发堆栈引擎插入额外的uop以同步%rsp管道的无序执行部分.(参见Agner Fog的microarch doc,特别是第91页,关于堆栈引擎.AFAIK,它在Haswell和Pentium M上的工作方式相同,只要它需要插入额外的uops.

push/ pop会少走融合域微指令,因此可能会效率更高,即使他们使用的存储/装载港口.它们介于呼叫/转换对之间.

因此,push/ pop至少不会变慢,但占用较少的指令字节.更好的I-cache密度是好的.

顺便说一句,我认为这对insn的意思是在call推送8B返回地址后保持堆栈16B对齐.这是ABI最终需要半无用指令的一种情况.更复杂的函数需要一些堆栈空间来溢出本地,然后在函数调用后重新加载它们,通常sub $something, %rsp会保留空间.

SystemV(Linux)amd64 ABI保证在函数入口处, (%rsp + 8)堆栈中的args(如果有的话)将是16B对齐的.(http://x86-64.org/documentation/abi.pdf).你必须安排你所使用的任何功能的情况,或者如果他们使用SSE对齐的负载进行分段,则是你的错.或者因为假设他们如何使用AND掩盖地址或其他东西而崩溃.