GCC -O0将寄存器args放置在堆栈中,且其间隙低于局部变量?

问题描述 投票:1回答:1

我试图查看一个非常简单的程序的汇编代码。

int func(int x) {
    int z = 1337;
    return z;
} 

[使用GCC -O0,每个C变量都有一个未优化的内存地址,因此gcc溢出了其寄存器arg:(Godbolt, gcc5.5 -O0 -fverbose-asm

func:
        pushq   %rbp  #
        movq    %rsp, %rbp      #,
        movl    %edi, -20(%rbp) # x, x
        movl    $1337, -4(%rbp) #, z
        movl    -4(%rbp), %eax  # z, D.2332
        popq    %rbp    #
        ret

函数参数x放置在局部变量下方的堆栈上的原因是什么?为什么不将其放置在-4(%rbp)及其附近的地方?

并且将其放置在局部变量下方时,为什么不将其放置在-8(%rbp)

[为什么要留出空白,使用比必要多的?此操作是否不能触及本叶子函数无法触及的新缓存行?

c gcc assembly x86 compiler-optimization
1个回答
0
投票

(首先,不要指望在-O0处做出有效的决策。事实证明,如果我们使用-O0或其他方式来强制编译器执行以下操作,则您在-O3处注意到的事情仍然会在volatile处发生。分配堆栈空间,否则这个问题就不会那么有趣了。)

函数参数x放置在局部变量下方的堆栈上的原因是什么?

选择是100%任意的,取决于编译器内部。 GCC和clang都碰巧做出了这样的选择,但这基本上无关紧要。 args到达寄存器,基本上are只是局部变量,因此完全由编译器来决定将它们溢出的位置(或者,如果启用优化则根本不溢出)。

但是为什么要在真正必要的时间之后将其保存在堆栈中呢?

由于已知的GCC遗失优化错误导致浪费堆栈空间。(待办事项:找到gcc bugzilla的链接;我知道我以前见过有关此错误的报告。)

注意,x86-64 System V ABI在call之前要求16字节堆栈对齐。在push %rbp并将RBP设置为帧指针之后,RBP和RSP对齐16字节。 -20(%rbp)-8(%rbp)位于同一对齐的16字节堆栈空间块中,因此该间隙不会冒接触我们尚未接触过的新缓存行或页面的风险。 (自然对齐的内存块不能跨越比其自身更宽的任何边界,x86-64高速缓存行始终至少为32个字节;如今,始终为64个字节。)

但是,如果我们添加第二个参数int y,此does会成为错过的优化方法:gcc5.5(和当前的gcc9.2 -O0)会将其溢出到-24(%rbp),而这可能在新的缓存中行。


事实证明,错过的优化是not,只是因为您使用了-O0(快速编译,跳过大多数优化过程,make bad asm)。除非它们仍然以任何人关心的优化级别出现,尤其是-O0-Os-O2,否则在-O3输出中查找错过的优化是没有意义的。

[[我们可以用使用volatile的代码仍然证明gcc在-O3处为args / locals分配堆栈空间的代码来证明这一点>另一个选择是将其地址传递给另一个函数,但是GCC会保留空间,而不仅仅是使用RSP下方的红色区域。

int *volatile sink; int func(int x, int y) { sink = &x; sink = &y; int z = 1337; sink = &z; return z; }
Godbolt, gcc9.2

gcc9.2 -O3 (hand-edited comments) func(int, int): leaq -20(%rsp), %rax # &x movq %rax, sink(%rip) # tmp84, sink leaq -24(%rsp), %rax # &y movq %rax, sink(%rip) # tmp86, sink leaq -4(%rsp), %rax # &z movq %rax, sink(%rip) # tmp88, sink movl $1337, %eax #, ret sink: .zero 8

有趣的事实:clang -O3在将其地址存储到sink之前溢出了堆栈args,就像它是该地址的std::atomic释放存储区一样,另一个线程可能在从sink获取指针后可能会加载它们的值。但是对于z,它不这样做。实际溢出xy只是一个错过的优化,我只能推测应该归咎于clang内部机械的哪一部分。

无论如何,clang确实将z分配给-4(%rsp),将-8分配给x,将y分配给-12。因此,无论出于何种原因,

clang都选择将args的溢出位置放置在本地变量下方。

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