为什么这个 AVX 内在原因会导致 "Segmentation fault" with clang,而不是 GCC?

Why does this AVX intrinsic cause "Segmentation fault" with clang, but not GCC?

似乎以下两个函数在使用 -mavx(或 -march=sandybridge -> skylake)通过 clang 编译时会导致分段错误。

void _mm256_mul_double_intrin(double* a, double* b, int N)
{
    int nb_iters = N / ( sizeof(__m256d) / sizeof(double) );

    __m256d* l = (__m256d*)a;
    __m256d* r = (__m256d*)b;

    for (int i = 0; i < nb_iters; ++i, ++l, ++r)
        _mm256_store_pd((double *)l, _mm256_mul_pd(*l, *r));

}

void _mm256_mul_double(double* a, double* b, int N)
{
    int nb_iters = N / ( sizeof(__m256d) / sizeof(double) );

    __m256d* l = (__m256d*)a;
    __m256d* r = (__m256d*)b;

    for (int i = 0; i < nb_iters; ++i, ++l, ++r)
        __asm__(
            "vmulpd %[r], %[l], %[l] \t\n"
            : [l] "+x" (*l)
            : [r] "m" (*r)
            :
        );
}

当N为4的2倍或更多(ymm寄存器宽度/双倍宽度)时,clang编译的代码有时会导致段错误。 (见下方的魔杖盒 link)

GCC 编译后的代码似乎没问题。

godbolt.org/g/YPa7mU

wandbox.org/permlink/kex4e3lRCKfPAq2J

** 我在 whosebug.com

上找到了原始源代码

可能归结为内存对齐,但是,现代处理器可以 read/write 未对齐内存与未对齐内存一样高效(效率非常接近)所以使用 _mm256_loadu_pd(r) 而不是 *r_mm256_loadu_pd(l) 而不是 *l_mm256_storeu_pd 来存储变量。

答案就在您在 Godbolt 上链接的 asm 中:

gcc 使用 andq $-32, %rsp 将堆栈对齐 32,因此代码中所有需要对齐的加载和存储都不会出错。 (取消引用 __m256d*_mm256_store_pd 而不是 _mm256_storeu_pd)。 AVX 指令通常不需要对齐,但对齐移动指令(如vmovapd)需要。


这对于 gcc 可能,因为您的测试用例允许函数使用 double a[]double b[] 上的 __m256d 操作内联到在堆栈上分配数组的函数。

例如:

void ext(double *);
void foo(void) {
    double tmp [1024];
    ext(tmp);
}

编译为简单分配,不会过度对齐堆栈。

    subq    00, %rsp
    movq    %rsp, %rdi
    call    ext(double*)
    addq    00, %rsp
    ret

x86-64 SysV ABI 只需要 16B 堆栈对齐。 (并且 gcc 不会选择维护更多。)因此,如果 ext() 实际上是需要 double* 的 32 字节对齐的函数之一,它就会出错。

gcc 不知道 32B 对齐会提升 ext() 的性能,因此它不会花费指令来对齐所有自动存储数组。如果出现正确性问题,那是你的错!


Clang 甚至在内联之后也不做任何对齐,只是在堆栈上保留 space 和 subq 8, %rsp。因此,即使在您的测试用例中,堆栈地址-space 随机化也只会在一半时间内为您提供 32B 对齐的堆栈。


如果您使用alignas(32) double a[],所有编译器都需要对齐数组。 (alignas 不适用于像 newmalloc 这样的动态存储,但它确实适用于自动和静态数组。对于动态,请参阅 )。