在GCC中如何在32字节边界对齐堆栈?
我正在为Windows 64位目标使用基于GCC4.6.1的MinGW64构建。我在玩弄新的英特尔AVX指令。我的命令行参数是在GCC中如何在32字节边界对齐堆栈?,gcc,stack,sse,avx,Gcc,Stack,Sse,Avx,我正在为Windows 64位目标使用基于GCC4.6.1的MinGW64构建。我在玩弄新的英特尔AVX指令。我的命令行参数是-march=corei7 avx-mtune=corei7 avx-mavx 但在堆栈上分配局部变量时,我开始遇到分段错误。GCC使用对齐移动VMOVAPS和VMOVAPD来移动\uuuuuM256和\uuuuuM256d,这些指令需要32字节对齐。但是,Windows 64位的堆栈只有16字节对齐 如何将GCC的堆栈对齐方式更改为32字节? 我尝试过使用-mstack
-march=corei7 avx-mtune=corei7 avx-mavx
但在堆栈上分配局部变量时,我开始遇到分段错误。GCC使用对齐移动VMOVAPS
和VMOVAPD
来移动\uuuuuM256
和\uuuuuM256d
,这些指令需要32字节对齐。但是,Windows 64位的堆栈只有16字节对齐
如何将GCC的堆栈对齐方式更改为32字节?
我尝试过使用-mstackreallign
,但没有效果,因为它只对齐16个字节。我也无法使\uuuuuuu属性((强制对齐\u参数\u指针))
工作,它仍然对齐到16字节。我还没有找到任何其他编译器选项来解决这个问题。非常感谢您的帮助
编辑:
我尝试使用
-mprefered stack boundary=5
,但GCC表示此目标不支持5。我没有主意了 我一直在研究这个问题,提交了一份GCC错误报告,发现这是一个与MinGW64相关的问题。看见显然,GCC不支持Windows上的32字节堆栈对齐。这有效地防止了256位AVX指令的使用
我研究了几种处理这个问题的方法。最简单、最简单的解决方案是用未对齐的替代方案VMOVUPS等替换对齐内存访问VMOVPS/PD/DQA。因此,我昨晚学习了Python(顺便说一下,这是一个非常好的工具),并编写了以下脚本,该脚本使用GCC生成的输入汇编程序文件执行此任务:
import re
import fileinput
import sys
# fix aligned stack access
# replace aligned vmov* by unaligned vmov* with 32-byte aligned operands
# see Intel's AVX programming guide, page 39
vmova = re.compile(r"\s*?vmov(\w+).*?((\(%r.*?%ymm)|(%ymm.*?\(%r))")
aligndict = {"aps" : "ups", "apd" : "upd", "dqa" : "dqu"};
for line in fileinput.FileInput(sys.argv[1:],inplace=1):
m = vmova.match(line)
if m and m.group(1) in aligndict:
s = m.group(1)
print line.replace("vmov"+s, "vmov"+aligndict[s]),
else:
print line,
这种方法是非常安全和万无一失的。虽然我在很少的情况下看到了一个表现的惩罚。当堆栈未对齐时,内存访问将跨越缓存线边界。幸运的是,代码在大多数情况下的执行速度与对齐的访问速度一样快。我的建议:关键循环中的内联函数
我还尝试使用另一个Python脚本修复每个函数prolog中的堆栈分配,尝试将其始终在32字节边界处对齐。这似乎适用于某些代码,但不适用于其他代码。我必须依靠GCC的善意,它将分配对齐的局部变量(相对于堆栈指针),这通常是这样做的。情况并非总是如此,尤其是在函数调用之前需要保存所有ymm寄存器而导致严重的寄存器溢出时。(所有ymm寄存器都被称为“保存”)。如果有兴趣的话,我可以发布剧本
最好的解决方案是修复GCC MinGW64构建。不幸的是,我不知道它的内部工作原理,上周才开始使用它。你可以通过
unsigned char*align_to_32B(unsigned char*a){
uint64_t u=(unit64_t)a;
mask_aligned32B=(1我刚刚遇到了同样的问题,即在我的函数中使用AVX时出现分段错误。这也是由于堆栈未对齐。鉴于这是一个编译器问题(并且Windows中没有提供可能有帮助的选项),我通过以下方法解决了堆栈使用问题:
使用静态变量(请参见此)。鉴于它们未存储在堆栈中,您可以通过在声明中使用\uuuu属性(align(32))
强制对齐它们。例如:静态uu m256i r\uu属性(aligned(32))
内联接收/返回AVX数据的函数/方法。您可以通过添加内联
和\uuu属性((始终内联))强制GCC内联您的函数/方法
添加到函数原型/声明中。函数内联会增加程序的大小,但它们也会阻止函数使用堆栈(从而避免堆栈对齐问题)。例如:内联uuuuuuuum256i myAvxFunction(void)uuuu属性uuuuuuuuuuuuuuuuuuuuuuuuu((始终inline));
请注意,如参考资料中所述,静态变量的使用不是线程安全的。如果您正在编写多线程应用程序,您可能必须为关键路径添加一些保护。这是否意味着\uuuuuu属性(对齐(32))
也不受尊重?例如,如果您使用\uuum256 x\uu属性(对齐(32))
Linux也不会将堆栈对齐32。针对Linux的gcc使用和$-32,%rsp
(或任何更高的对齐方式)在需要溢出\uuuuuuum256
、\uuuuuum512
,或使用对齐(32)声明的任何对象的函数中对齐堆栈
或高于16的任何内容。MinGW gcc不使用相同的顺序保存原始的rsp
并对齐它,这似乎是一个奇怪的错误。您可以共享您的prolog重新编写脚本吗?还有,如何从汇编文件(由-S生成)获取到可执行文件?Thanks@NobertP.随着MinGW64的后期发布,情况是否有所好转?因为GCC似乎正在掩盖这一缺陷(它已经6年了!),我们决定走另一条路。一份很好的老式请愿书,请签名。在macOS中,编译器将任何数组对齐到16字节。GCC在64位系统上也这样做吗?您好。在使用GCC在64b windows机器上进行实验后,我发现数组的第一个元素是由d对齐的16字节
void foo() {
struct I_wish_these_were_32B_aligned {
vec32B foo;
char bar[32];
}; // not - no variable definition, just the struct declaration.
unsigned char a[sizeof(I_wish_these_were_32B_aligned) + 32)];
unsigned char* a_aligned_to_32B = align_to_32B(a);
I_wish_these_were_32B_aligned* s = (I_wish_these_were_32B_aligned)a_aligned_to_32B;
s->foo = ...
}
unsigned char* align_to_32B(unsiged char* a) {
uint64_t u = (unit64_t)a;
mask_aligned32B = (1 << 5) - 1;
if (u & mask_aligned32B == 0) return (unsigned char*)u;
return (unsigned char*)((u|mask_aligned_32B) + 1);
}