首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >当gcc想要额外的堆栈对齐时,奇怪的堆栈操作是怎么回事?

当gcc想要额外的堆栈对齐时,奇怪的堆栈操作是怎么回事?
EN

Stack Overflow用户
提问于 2017-07-31 10:55:04
回答 1查看 365关注 0票数 4

我见过几次r10的怪异之处,所以让我们看看是否有人知道发生了什么。

用这个简单的函数:

代码语言:javascript
运行
AI代码解释
复制
#define SZ 4

void sink(uint64_t *p);

void andpop(const uint64_t* a) {
    uint64_t result[SZ];
    for (unsigned i = 0; i < SZ; i++) {
        result[i] = a[i] + 1;
    }

    sink(result);
}

它只是在传入数组的4个64位元素中每个添加1个,并将其存储在一个本地中,并对结果调用sink() (以避免对整个函数进行优化)。

下面是相对应程序集:

代码语言:javascript
运行
AI代码解释
复制
andpop(unsigned long const*):
        lea     r10, [rsp+8]
        and     rsp, -32
        push    QWORD PTR [r10-8]
        push    rbp
        mov     rbp, rsp
        push    r10
        sub     rsp, 40
        vmovdqa ymm0, YMMWORD PTR .LC0[rip]
        vpaddq  ymm0, ymm0, YMMWORD PTR [rdi]
        lea     rdi, [rbp-48]
        vmovdqa YMMWORD PTR [rbp-48], ymm0
        vzeroupper
        call    sink(unsigned long*)
        add     rsp, 40
        pop     r10
        pop     rbp
        lea     rsp, [r10-8]
        ret

几乎所有关于r10的事情都很难理解。首先,r10被设置为指向rsp + 8,然后是push QWORD PTR [r10-8],据我所知,push QWORD PTR [r10-8]在堆栈上推送返回地址的副本。在此之后,将rbp设置为正常,然后最终推送r10本身。

为了解除所有这些,r10被从堆栈中弹出并用于将rsp还原到它的原始值。

一些意见:

  • 从整个函数的角度来看,所有这些似乎都是简单地将rsp还原到ret之前的原始值的一种完全迂回的方式--但是通常mov rsp, rpb的epilog也会做得很好(参见clang)!
  • 尽管如此,(昂贵的) push QWORD PTR [r10-8]甚至在这个任务中都没有帮助:这个值(返回地址?)显然从未被使用过。
  • 为什么r10会被推和弹出呢?该值不会在非常小的函数体中被重击,并且没有寄存器压力。

这是怎么回事?我以前见过几次,它通常希望使用r10,有时使用r13。这似乎与将堆栈对齐为32个字节有关,因为如果将SZ更改为小于4,则使用xmm操作,问题就消失了。

例如,下面是SZ == 2

代码语言:javascript
运行
AI代码解释
复制
andpop(unsigned long const*):
        sub     rsp, 24
        vmovdqa xmm0, XMMWORD PTR .LC0[rip]
        vpaddq  xmm0, xmm0, XMMWORD PTR [rdi]
        mov     rdi, rsp
        vmovaps XMMWORD PTR [rsp], xmm0
        call    sink(unsigned long*)
        add     rsp, 24
        ret

好多了!

EN

回答 1

Stack Overflow用户

发布于 2017-07-31 11:08:07

那么,您已经回答了您的问题:堆栈指针需要对齐到32个字节,然后才能使用对齐的AVX2加载和存储进行访问,但是ABI只提供了16个字节对齐。由于编译器无法知道对齐度有多大,所以必须将堆栈指针保存在划痕寄存器中,然后再恢复。但是保存的值必须超过函数调用,所以它必须放在堆栈上,并且必须创建堆栈帧。

一些x86-64 ABI有一个红色区域(堆栈指针下面的一个区域,信号处理程序不使用它),所以完全不为这样的短函数更改堆栈指针是可行的,但是GCC显然没有实现这个优化,而且由于最后的函数调用,它也不适用于这里。

此外,默认的堆栈对齐实现也相当糟糕。在这种情况下,-maccumulate-outgoing-args使用GCC 6生成外观更好的代码,只需在保存RBP后对齐RSP,而不是在保存RBP之前复制返回地址:

代码语言:javascript
运行
AI代码解释
复制
andpop:
        pushq   %rbp
        movq    %rsp, %rbp            # make a traditional stack frame
        andq    $-32, %rsp            # reserve 0 or 16 bytes
        subq    $32, %rsp

        vmovdqu (%rdi), %xmm0         # split unaligned load from tune=generic
        vinserti128     $0x1, 16(%rdi), %ymm0, %ymm0   # use -march=haswell instead
        movq    %rsp, %rdi
        vpaddq  .LC0(%rip), %ymm0, %ymm0
        vmovdqa %ymm0, (%rsp)

        vzeroupper
        call    sink@PLT
        leave
        ret

(编者注: gcc8和后来使asm默认为这样(使用gcc8、clang7、ICC19和MSVC的戈德波特编译器浏览器),即使没有-maccumulate-outgoing-args)

这个问题(GCC为堆栈对齐生成了糟糕的代码)最近出现了,当时我们不得不为GCC __tls_get_addr ABI错误实现一个解决方案,最后我们手工编写了堆栈重新对齐。

编辑还有另一个问题,与RTL传递顺序有关:在最终确定堆栈是否实际需要之前,选择堆栈对齐方式,正如BeeOnRope的第二个例子所示

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45423338

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档