我见过几次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()
(以避免对整个函数进行优化)。
下面是相对应程序集:
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
:
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
好多了!
发布于 2017-07-31 11:08:07
那么,您已经回答了您的问题:堆栈指针需要对齐到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默认为这样(使用gcc8、clang7、ICC19和MSVC的戈德波特编译器浏览器),即使没有-maccumulate-outgoing-args
)
这个问题(GCC为堆栈对齐生成了糟糕的代码)最近出现了,当时我们不得不为GCC __tls_get_addr
ABI错误实现一个解决方案,最后我们手工编写了堆栈重新对齐。
编辑还有另一个问题,与RTL传递顺序有关:在最终确定堆栈是否实际需要之前,选择堆栈对齐方式,正如BeeOnRope的第二个例子所示。
https://stackoverflow.com/questions/45423338
复制相似问题