如何在 GCC 的 32 字节边界处对齐堆栈?
Posted
技术标签:
【中文标题】如何在 GCC 的 32 字节边界处对齐堆栈?【英文标题】:How to align stack at 32 byte boundary in GCC? 【发布时间】:2011-08-24 09:54:52 【问题描述】:我正在为 Windows 64 位目标使用基于 GCC 4.6.1 的 MinGW64 构建。我正在玩新的英特尔 AVX 指令。我的命令行参数是-march=corei7-avx -mtune=corei7-avx -mavx
。
但是在堆栈上分配局部变量时,我开始遇到分段错误错误。 GCC 使用对齐的移动 VMOVAPS
和 VMOVAPD
来移动 __m256
和 __m256d
,这些指令需要 32 字节对齐。但是,Windows 64 位的堆栈只有 16 字节对齐。
如何将 GCC 的堆栈对齐更改为 32 字节?
我曾尝试使用-mstackrealign
,但无济于事,因为它仅与 16 个字节对齐。我也无法让__attribute__((force_align_arg_pointer))
工作,无论如何它都对齐到 16 个字节。我无法找到任何其他可以解决此问题的编译器选项。非常感谢任何帮助。
编辑:
我尝试使用-mpreferred-stack-boundary=5
,但 GCC 表示此目标不支持 5。我没主意了。
【问题讨论】:
这是否意味着__attribute__ ((aligned (32)))
也不被尊重?例如如果你使用__m256 x __attribute__ ((aligned (32)))
Linux 也不会将堆栈对齐 32。面向 Linux 的 gcc 使用 and $-32, %rsp
(或任何更高的对齐方式)来对齐需要溢出 __m256
、__m512
或您使用 alignas(32)
或任何高于 16 的任何对象声明的函数中的堆栈。看起来像一个奇怪的错误,MinGW gcc 没有使用相同的序列来保存原始的rsp
并对齐它。
【参考方案1】:
在我的函数中使用 AVX 时,我遇到了同样的分段错误问题。这也是由于堆栈未对齐。鉴于这是一个编译器问题(并且 Windows 中没有可能提供帮助的选项),我通过以下方式解决了堆栈使用问题:
使用静态变量(参见issue)。鉴于它们没有存储在堆栈中,您可以通过在声明中使用__attribute__((align(32)))
来强制它们对齐。例如:static __m256i r __attribute__((aligned(32)))
。
内联接收/返回 AVX 数据的函数/方法。您可以通过将inline
和__attribute__((always_inline))
添加到您的函数原型/声明中来强制GCC 内联您的函数/方法。内联函数会增加程序的大小,但它们也会阻止函数使用堆栈(因此,避免了堆栈对齐问题)。示例:inline __m256i myAvxFunction(void) __attribute__((always_inline));
。
请注意,静态变量的使用不是线程安全的,如参考中所述。如果您正在编写多线程应用程序,您可能需要为您的关键路径添加一些保护。
【讨论】:
在 macOS 中,编译器将任何数组对齐到 16 字节。 GCC 在 64 位系统上也能做到这一点吗? 您好。在64b的windows机器上做了实验,使用GCC,发现数组的第一个元素默认是16字节对齐的。数组的其余元素根据数组中元素的数据类型对齐。例如,n 个字符(1 字节宽)的数组 A 将具有 &A[n] = &A[0] + n, &A[n] 16 字节对齐。 带有 GCC 7.x 的 MinGW64 的更高版本是否解决了这个问题?【参考方案2】:你可以得到你想要的效果
-
不将变量声明为变量,而是声明为结构中的字段
声明一个比结构大适当填充量的数组
进行指针/地址运算以在数组中找到一个 32 字节对齐的地址
将该地址转换为指向您的结构的指针
最后使用结构的数据成员
当 malloc() 没有正确对齐堆上的内容时,您可以使用相同的技术。
例如
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);
【讨论】:
【参考方案3】:我一直在探索这个问题,提交了一份 GCC 错误报告,发现这是一个与 MinGW64 相关的问题。见GCC Bug#49001。显然,GCC 不支持 Windows 上的 32 字节堆栈对齐。这有效地防止了使用 256 位 AVX 指令。
我研究了几种解决此问题的方法。最简单和最直接的解决方案是用未对齐的替代方案 VMOVUPS 等替换对齐的内存访问 VMOVAPS/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 脚本修复每个函数序言中的堆栈分配,尝试始终将其对齐在 32 字节边界。这似乎适用于某些代码,但不适用于其他代码。我必须依靠 GCC 的善意,它会分配对齐的局部变量(相对于堆栈指针),它通常会这样做。情况并非总是如此,尤其是当由于需要在函数调用之前保存所有 ymm 寄存器而导致严重的寄存器溢出时。 (所有 ymm 寄存器都是被调用者保存的)。如果有兴趣,我可以发布脚本。
最好的解决方案是修复 GCC MinGW64 版本。不幸的是,我不知道它的内部工作原理,上周才开始使用它。
【讨论】:
你能分享你的序言重写脚本吗?另外,如何从程序集文件(由 -S 生成)获取可执行文件?谢谢 @NobertP。随着 MinGW64 的后续版本,情况是否有所好转? 因为 GCC 似乎正在扫除这个错误(它已经 6 岁了!),我们决定走另一条路。一份好的老式请愿书,请签名。 change.org/p/gnu-project-gcc-compiler-fix-bug-54412以上是关于如何在 GCC 的 32 字节边界处对齐堆栈?的主要内容,如果未能解决你的问题,请参考以下文章
GCC __attribute__ 在 32 字节处对齐的 AVX 矢量化代码中的段错误
使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)