如何在 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 使用对齐的移动 VMOVAPSVMOVAPD 来移动 __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编译命令

gcc x86-32堆栈对齐并调用printf

使用 GCC 但没有使用 Clang 的堆栈帧太大(过度对齐?)

使用 GCC/G++/AS 在固定大小的内存边界上对齐本机代码?

C++结构对齐问题