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

在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

我正在为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字节?

我尝试过使用
-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构建。不幸的是,我不知道它的内部工作原理,上周才开始使用它。

你可以通过

  • 不是将变量声明为变量,而是将变量声明为结构中的字段
  • 声明比结构大适当填充量的数组
  • 执行指针/地址算法以在数组中查找32字节对齐的地址
  • 将该地址强制转换为指向结构的指针
  • 最后,使用结构的数据成员
  • 当malloc()未正确对齐堆上的内容时,可以使用相同的技术

    例如

    在哪里

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