2011-05-12 176 views
6

我正在使用基于GCC 4.6.1的Windows 64位目标的MinGW64版本。我正在玩新的英特尔AVX指令。我的命令行参数是-march=corei7-avx -mtune=corei7-avx -mavx如何在GCC的32字节边界对齐堆栈?

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

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

我试过使用-mstackrealign,但无济于事,因为它只对齐到16个字节。我无法使__attribute__((force_align_arg_pointer))工作,无论如何它都对齐到16个字节。我一直没能找到解决这个问题的其他编译器选项。任何帮助是极大的赞赏。

编辑: 我试着用-mpreferred-stack-boundary=5,但海湾合作委员会说,5不支持此目标。我没有想法。

+1

这是否意味着'__attribute__((aligned(32)))'不被尊重呢?例如如果使用'__m256 x __attribute__((aligned(32)))' – nos 2014-06-05 12:57:38

+1

Linux不会将堆栈对齐32。针对Linux的gcc使用'和$ -32,%rsp'(或任何更高的对齐方式)将堆栈对齐到需要溢出'__m256','__m512'或用'alignas(32)声明的任何对象的函数中'或者高于16的任何东西。这似乎是一个奇怪的bug,MinGW gcc不使用相同的序列来保存原始的'rsp'并对齐它。 – 2017-11-07 15:06:25

回答

14

我一直在探索这个问题,提交了一份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的构建。不幸的是,我不了解其内部工作原理,上周刚开始使用它。

+4

+1“因此我昨晚学Python” – hirschhornsalz 2012-05-05 09:04:24

+0

你能分享你的序言重写脚本吗?另外,如何从汇编文件(由-S生成)到可执行文件?谢谢 – user1649948 2015-09-11 21:50:48

+0

GCC的后续版本有哪些改进? – Royi 2018-02-21 14:48:58

1

您可以通过

  1. 声明的变量不是变量,而是作为一个结构
  2. 域声明一个数组,它是通过填充
  3. 适量比结构较大的得到你想要的效果
  4. 做指针/地址运算找到侧的32字节对齐地址阵列
  5. 铸造该地址的指针你的结构
  6. 最后使用的数据成员你的struct

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

我只是在使用AVX在我的函数时有段错误的同样的问题跑了。这也是由于堆栈错位。鉴于这是一个编译器的问题(这可以帮助是不是在Windows中的可用选项),我工作围绕堆栈使用情况:

  1. 使用静态变量(见本issue)。鉴于它们没有存储在堆栈中,您可以在声明中使用__attribute__((align(32)))来强制它们的对齐。例如:static __m256i r __attribute__((aligned(32)))

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

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

+0

在macOS中,编译器将任何数组对齐到16字节。 GCC在64位系统上也能做到这一点吗? – Royi 2017-08-04 23:36:19

+0

你好。在使用GCC在64b windows机器上做实验之后,我发现默认情况下数组的第一个元素是16字节对齐的。数组的其余元素根据数组中元素的数据类型进行对齐。例如,_n_chars(1字节宽)的数组A将具有&A [_n_] =&A [0] + _n_,与&A [_n_] 16字节对齐。 – 2017-08-07 17:17:34

+0

很好的发现。我想知道,Visual Studio是否也这样做?谢谢。 – Royi 2017-08-07 17:52:48