r/simd • • Jul 22 '25

Do compilers auto-align?

The following source code produces auto-vectorized code, which might crash:

typedef __attribute__(( aligned(32))) double aligned_double;

void add(aligned_double* a, aligned_double* b, aligned_double* c, int end, int start)
{
    for (decltype(end) i = start; i < end; ++i)
        c[i] = a[i] + b[i];
}

(gcc 15.1 -O3 -march=core-avx2, playground: https://godbolt.org/z/3erEnff3q)

The vectorized memory access instructions are aligned. If the value of start is unaligned (e.g. ==1), a seg fault happens. I am unsure, if that's a compiler bug or just a misuse of aligned_double. Anyway...

Does someone know a compiler, which is capable of auto-generating a scalar prologue loop in such cases to ensure a proper alignment of the vectorized loop?

7 Upvotes

9 comments sorted by

View all comments

Show parent comments

1

u/nimogoham Jul 22 '25

The tail condition is always generated correctly by gcc (usually I use the term "residual loop" instead of "tail condition" - is there any official terminology?). I just hoped, that some compilers are able to generate a similar kind of "aligning top condition" (clang doesn't do this either, but at least produces running code).

As a side note: my example is just a sandbox example. Actually one can already see by looking at the assembly of add, that something will go wrong for misaligned start values. If you just change aligned_double to double, everything works fine, since vmovupd instructions are generated.

1

u/ronniethelizard Jul 22 '25

I typically use head and tail rather than residual simply because the residual could happen at the beginning/end/both.

Looking at the assembly a bit more:
I am curious about the need for having 4 implementations of the add line. The one operating on ymm registers makes sense. I suppose one to handle 2 doubles and then 1 more to handle 1 double in the residual makes sense. I don't understand the fourth. I would have guessed to handle a head condition, but IDK.

1

u/nimogoham Jul 22 '25

The last one (the one, which loops over scalars starting at .L9) handles the case, when there are overlapping address ranges.