System V ABI-AMD64-GCC发出的程序集中的堆栈对齐

一世

对于下面的C代码,来自Compiler Explorer的GCC x86-64 10.2发出了我在下面进一步粘贴的程序集。

一条指令是subq $40, %rsp问题是,如何从中减去40个字节%rsp不会使堆栈未对齐?我的理解是:

  • 在此之前call foo,堆栈是对齐的16个字节;
  • call foo 在堆栈上放置一个8字节的返回地址,因此堆栈未对齐;
  • 但是pushq %rbpfoo开始时,在堆栈上又放置了8个字节,因此它又得到了16个字节的对齐;
  • 因此,堆栈是16个字节,紧接在之前subq $40, %rsp结果,减少%rsp40个字节必须中断对齐吗?

显然,就保持堆栈对齐而言,GCC发出了有效的程序集,因此我必须缺少一些东西。

(我曾尝试用CLANG替换GCC,然后CLANG发出subq $48, %rsp-就像我直观地期望的那样。)

那么,我在GCC生成的程序集中缺少什么?如何使堆栈保持16个字节对齐?

int bar(int i) { return i; }
int foo(int p0, int p1, int p2, int p3, int p4, int p5, int p6) {
    int sum = p0 + p1 + p2 + p3 + p4 + p5 + p6;
    return bar(sum);
}
int main() {
    return foo(0, 1, 2, 3, 4, 5, 6);
}
bar:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %edi, -4(%rbp)
        movl    -4(%rbp), %eax
        popq    %rbp
        ret
foo:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $40, %rsp
        movl    %edi, -20(%rbp)
        movl    %esi, -24(%rbp)
        movl    %edx, -28(%rbp)
        movl    %ecx, -32(%rbp)
        movl    %r8d, -36(%rbp)
        movl    %r9d, -40(%rbp)
        movl    -20(%rbp), %edx
        movl    -24(%rbp), %eax
        addl    %eax, %edx
        movl    -28(%rbp), %eax
        addl    %eax, %edx
        movl    -32(%rbp), %eax
        addl    %eax, %edx
        movl    -36(%rbp), %eax
        addl    %eax, %edx
        movl    -40(%rbp), %eax
        addl    %eax, %edx
        movl    16(%rbp), %eax
        addl    %edx, %eax
        movl    %eax, -4(%rbp)
        movl    -4(%rbp), %eax
        movl    %eax, %edi
        call    bar
        leave
        ret
main:
        pushq   %rbp
        movq    %rsp, %rbp
        pushq   $6
        movl    $5, %r9d
        movl    $4, %r8d
        movl    $3, %ecx
        movl    $2, %edx
        movl    $1, %esi
        movl    $0, %edi
        call    foo
        addq    $8, %rsp
        leave
        ret
紫罗兰色

16字节对齐的目的是使函数可以在当前级别以下任何级别调用,如果它们需要对齐的本地变量,则不必担心对齐其堆栈。

如果没有ABI保证,则需要此功能的每个函数都必须向and堆栈指针提供一些值以确保其正确对齐,例如:

and %rsp, $0xfffffffffffffff0

但是,在这种特殊情况下没有理由没有必要这样做-bar()函数是叶子的,这意味着编译器完全了解其级别或更低级别的任何对齐要求(它没有局部变量,并且不调用任何函数,因此没有要求)。

foo()函数在下面也没有要求,因为它调用的唯一是bar()它还似乎在确定自己的本地人也不需要这种级别的对齐。

即使bar()或是foo()从即时翻译单元外部调用的(也可以是,因为它们没有被标记static),但这并不会改变不需要对齐的事实。

例如,如果bar是在单独的翻译单元中,或者在无法确定不需要对齐的情况下调用了其他函数,情况将有所不同

那将意味着gcc不会完全了解其对齐要求。而且,的确,如果您bar在Godbolt中注释掉了定义行(有效地隐藏了定义),您将看到该行的变化:

// int bar(int i) { return i; }
   --> subq $48, %rsp             ; no longer $40

顺便说一句,尽管在这种情况下16字节对齐在技术上不是必需的,但我认为这可能会使gcc使用System V AMD64 ABI的主张无效在该ABI中似乎没有任何允许这种偏离的内容,文本(PDF)指出(略作解释,并加粗体):

输入自变量区域的末尾应在16(如果__m256在堆栈上传递,则为32 )字节边界对齐换句话说,该值%rsp + 8始终16(或32)时控制转移到函数入口点的倍数。堆栈指针%rsp始终指向最新分配的堆栈帧的末尾。

尽管以已知的方式在这种情况下不会造成问题,但以使观察到的行为兼容的任何方式进行解释似乎没有什么余地。

是否有人认为足够重要而不必担心,超出了此答案的范围,我对此不作任何判断:-)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章