How to align stack at 32 byte boundary in GCC?
Asked Answered
F

3

11

I'm using MinGW64 build based on GCC 4.6.1 for Windows 64bit target. I'm playing around with the new Intel's AVX instructions. My command line arguments are -march=corei7-avx -mtune=corei7-avx -mavx.

But I started running into segmentation fault errors when allocating local variables on the stack. GCC uses the aligned moves VMOVAPS and VMOVAPD to move __m256 and __m256d around, and these instructions require 32-byte alignment. However, the stack for Windows 64bit has only 16 byte alignment.

How can I change the GCC's stack alignment to 32 bytes?

I have tried using -mstackrealign but to no avail, since that aligns only to 16 bytes. I couldn't make __attribute__((force_align_arg_pointer)) work either, it aligns to 16 bytes anyway. I haven't been able to find any other compiler options that would address this. Any help is greatly appreciated.

EDIT: I tried using -mpreferred-stack-boundary=5, but GCC says that 5 is not supported for this target. I'm out of ideas.

Febricity answered 12/5, 2011 at 19:27 Comment(2)
Does this mean __attribute__ ((aligned (32))) isn't honored too ? e.g. if you use __m256 x __attribute__ ((aligned (32)))Aricaarick
Linux doesn't align the stack by 32 either. gcc targeting Linux uses and $-32, %rsp (or whatever higher alignment) to align the stack in functions that need to spill an __m256, __m512, or any objects you declared with alignas(32) or anything higher than 16. It seems like a weird bug that MinGW gcc doesn't use the same sequence to save the original rsp and align it.Iona
F
17

I have been exploring the issue, filed a GCC bug report, and found out that this is a MinGW64 related problem. See GCC Bug#49001. Apparently, GCC doesn't support 32-byte stack alignment on Windows. This effectively prevents the use of 256-bit AVX instructions.

I investigated a couple ways how to deal with this issue. The simplest and bluntest solution is to replace of aligned memory accesses VMOVAPS/PD/DQA by unaligned alternatives VMOVUPS etc. So I learned Python last night (very nice tool, by the way) and pulled off the following script that does the job with an input assembler file produced by 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,

This approach is pretty safe and foolproof. Though I observed a performance penalty on rare occasions. When the stack is unaligned, the memory access crosses the cache line boundary. Fortunately, the code performs as fast as aligned accesses most of the time. My recommendation: inline functions in critical loops!

I also attempted to fix the stack allocation in every function prolog using another Python script, trying to align it always at the 32-byte boundary. This seems to work for some code, but not for other. I have to rely on the good will of GCC that it will allocate aligned local variables (with respect to the stack pointer), which it usually does. This is not always the case, especially when there is a serious register spilling due to the necessity to save all ymm register before a function call. (All ymm registers are callee-save). I can post the script if there's an interest.

The best solution would be to fix GCC MinGW64 build. Unfortunately, I have no knowledge of its internal workings, just started using it last week.

Febricity answered 17/5, 2011 at 2:19 Comment(4)
Could you share your prolog re-writing script? Also, how to get from the assembly file (generated by -S) to an executable? ThanksBefit
@NobertP. Has the situation gor any better with later releases of MinGW64?Mohl
Because GCC seems to be sweeping this bug under the rug (it's 6 years old!), we decided to go another route. A good old fashioned petition, please sign it. change.org/p/gnu-project-gcc-compiler-fix-bug-54412Antherozoid
MinGW GCC does support over-aligned types on the stack, like alignas(32) int foo[8];. If you look at the asm, you'll see and rsp, -32 in there. It just fails to align the stack pointer when the only such types are __m256 / __m256i.Iona
S
1

You can get the effect you want by

  1. Declaring your variables not as variables, but as fields in a struct
  2. Declaring an array that is larger than the structure by an appropriate amount of padding
  3. Doing pointer/address arithmetic to find a 32 byte aligned address in side the array
  4. Casting that address to a pointer to your struct
  5. Finally using the data members of your struct

You can use the same technique when malloc() does not align stuff on the heap appropriately.

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 = ...
}

where

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);
}
Skinflint answered 26/4, 2012 at 6:20 Comment(0)
E
1

I just ran in the same issue of having segmentation faults when using AVX inside my functions. And it was also due to the stack misalignment. Given the fact that this is a compiler issue (and the options that could help are not available in Windows), I worked around the stack usage by:

  1. Using static variables (see this issue). Given the fact that they are not stored in the stack, you can force their alignment by using __attribute__((align(32))) in your declaration. For example: static __m256i r __attribute__((aligned(32))).

  2. Inlining the functions/methods receiving/returning AVX data. You can force GCC to inline your function/method by adding inline and __attribute__((always_inline)) to your function prototype/declaration. Inlining your functions increase the size of your program, but they also prevent the function from using the stack (and hence, avoids the stack-alignment issue). Example: inline __m256i myAvxFunction(void) __attribute__((always_inline));.

Be aware that the usage of static variables is no thread-safe, as mentioned in the reference. If you are writing a multi-threaded application you may have to add some protection for your critical paths.

Electrotherapy answered 23/5, 2017 at 16:42 Comment(3)
In macOS the compiler align any array to 16 Byte. Does GCC do that as well on 64 Bit system?Mohl
Hi there. After making an experiment in a 64b windows machine, using GCC, I found that the first element of an array is 16-byte aligned by default. The rest of the elements of the array are aligned depending on the data-type of the elements in the array. For example, an array A of n chars (1-byte wide) would have &A[n] = &A[0] + n, being &A[n] 16-byte aligned.Electrotherapy
Does later versions of MinGW64 with GCC 7.x solve this problem?Mohl

© 2022 - 2024 — McMap. All rights reserved.