我见过这个 r10
奇怪几次,所以让我们看看是否有人知道发生了什么。
以这个简单的函数为例:
#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()
结果(以避免整个功能被优化掉)。这是corresponding集会:
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]
,据我所知,它会将返回地址的副本推送到堆栈上。随后,rbp
正常设置然后最后r10
本身被推。为了放松这一切,
r10
从堆栈中弹出并用于恢复 rsp
到它的原始值(value)。一些观察:
rsp
到它之前的原始值 ret
- 但通常的结尾 mov rsp, rpb
也一样(见 clang
)! push QWORD PTR [r10-8]
在该任务中甚至没有帮助:这个值(返回地址?)显然从未使用过。 r10
推和弹出所有?该值在非常小的函数体中没有被破坏,并且没有注册压力。 那是怎么回事?之前看过好几次,一般都是想用
r10
,有时 r13
.这似乎与将堆栈对齐到 32 字节有关,因为如果您更改 SZ
小于 4 它使用 xmm
ops,问题就消失了。这是
SZ == 2
例如: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
好多了!
最佳答案
好吧,您已经回答了您的问题:堆栈指针需要对齐到 32 字节才能使用对齐的 AVX2 加载和存储进行访问,但 ABI 仅提供 16 字节对齐。由于编译器无法知道对齐有多少,它必须将堆栈指针保存在临时寄存器中并在之后恢复它。但是保存的值必须比函数调用更有效,因此必须将其放入堆栈,并且必须创建堆栈帧。
一些 x86-64 ABI 有一个红色区域(信号处理程序不使用的堆栈指针下方的堆栈区域),因此对于此类短函数根本不更改堆栈指针是可行的,但 GCC 显然没有实现这个优化,无论如何它都不会在这里应用,因为最后是函数调用。
此外,默认的堆栈对齐实现相当糟糕。对于这种情况,-maccumulate-outgoing-args
使用 GCC 6 生成更好看的代码,只需在保存 RBP 后对齐 RSP,而不是在保存 RBP 之前复制返回地址:
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 像这样( Godbolt compiler explorer with gcc8, clang7, ICC19, and MSVC ),即使没有
-maccumulate-outgoing-args
)这个问题(GCC 为堆栈对齐生成了糟糕的代码)最近出现在我们不得不为 GCC 实现解决方法时
__tls_get_addr
ABI 错误,我们最终手动编写了堆栈重新对齐。编辑 还有另一个问题,与 RTL pass ordering 相关:在最终确定是否确实需要堆栈之前选择堆栈对齐方式,as BeeOnRope's second example shows .
关于gcc - 当需要额外的堆栈对齐时,gcc 奇怪的堆栈操作是怎么回事?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45423338/