如何在GCC中将堆栈对齐到32字节边界?

时间:2011-05-12 19:27:14

标签: gcc stack sse avx

我正在使用基于GCC 4.6.1的MinGW64构建Windows 64bit目标。我正在玩新的英特尔AVX指令。我的命令行参数是-march=corei7-avx -mtune=corei7-avx -mavx

但是当我在堆栈上分配局部变量时,我开始遇到分段错误错误。 GCC使用对齐的移动VMOVAPSVMOVAPD来移动__m256__m256d,这些指令需要32字节对齐。但是,Windows 64bit的堆栈只有16字节对齐。

如何将GCC的堆栈对齐更改为32个字节?

我尝试使用-mstackrealign但无济于事,因为它只对齐16个字节。我也无法使__attribute__((force_align_arg_pointer))工作,无论如何它都与16个字节对齐。我无法找到任何其他可以解决此问题的编译器选项。非常感谢任何帮助。

编辑: 我尝试使用-mpreferred-stack-boundary=5,但GCC表示此目标不支持5。我没有想法。

3 个答案:

答案 0 :(得分:15)

我一直在探索这个问题,提交了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脚本修复每个函数prolog中的堆栈分配,尝试始终在32字节边界对齐它。这似乎适用于某些代码,但不适用于其他代码。我必须依赖GCC的良好意愿,它将分配对齐的局部变量(相对于堆栈指针),它通常会这样做。情况并非总是如此,特别是当由于在函数调用之前需要保存所有ymm寄存器而导致严重的寄存器溢出时。 (所有ymm寄存器都是被调用者保存)。如果有兴趣,我可以发布脚本。

最好的解决方案是修复GCC MinGW64版本。不幸的是,我不知道它的内部工作,上周就开始使用它了。

答案 1 :(得分:1)

你可以通过

获得你想要的效果
  1. 将变量声明为变量,而不是结构
  2. 中的字段
  3. 通过适当的填充量声明一个大于结构的数组
  4. 执行指针/地址算术以在数组一侧找到32字节对齐的地址
  5. 将该地址转换为指向结构的指针
  6. 最后使用结构的数据成员
  7. 当malloc()没有适当地对齐堆上的东西时,你可以使用相同的技术。

    E.g。

    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);
    }
    

答案 2 :(得分:1)

在我的函数中使用AVX时,我遇到了同样的问题。这也是由于堆栈错位。鉴于这是一个编译器问题(以及可能有帮助的选项在Windows中不可用),我通过以下方式解决了堆栈使用问题:

  1. 使用静态变量(请参阅此issue)。鉴于它们未存储在堆栈中,您可以在声明中使用__attribute__((align(32)))强制对齐它们。例如:static __m256i r __attribute__((aligned(32)))

  2. 内联接收/返回AVX数据的功能/方法。您可以通过将inline__attribute__((always_inline))添加到函数原型/声明中来强制GCC内联您的函数/方法。内联函数会增加程序的大小,但它们也会阻止函数使用堆栈(因此,避免了堆栈对齐问题)。示例:inline __m256i myAvxFunction(void) __attribute__((always_inline));

  3. 请注意,静态变量的使用不是线程安全的,如参考文献中所述。如果您正在编写多线程应用程序,则可能需要为关键路径添加一些保护。

相关问题