为什么这个AVX内在导致与clang“分段错误”,而不是GCC?

时间:2017-10-30 08:14:26

标签: c++ clang inline-assembly intrinsics avx

使用-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编译代码有时会导致分段错误。 (参见下面的wandbox链接)

GCC编译的代码似乎很好。

godbolt.org/g/YPa7mU

wandbox.org/permlink/kex4e3lRCKfPAq2J

**我在stackoverflow.com上找到了原始源代码

2 个答案:

答案 0 :(得分:4)

答案就在你与Godbolt联系的asm中:

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

这对于gcc来说只有 ,因为你的测试用例允许在__m256ddouble a[]上使用double b[]操作的函数内联到分配函数的函数中堆栈上的数组。

例如:

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

编译为简单分配,没有过度对齐堆栈。

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

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

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

即使在内联之后,Clang也没有进行任何对齐,只是使用subq $248, %rsp在堆栈上保留空间。因此,即使在您的测试用例中,堆栈地址空间随机化也只会在一半的时间内为您提供32B对齐的堆栈。

如果使用alignas(32) double a[],则需要所有编译器对齐数组。 (alignas不适用于动态存储,例如newmalloc,但它适用于自动和静态数组。有关动态,请参阅How to solve the 32-byte-alignment issue for AVX load/store operations?)。

答案 1 :(得分:2)

然而,现在的处理器可以像未对齐的内存一样有效地读取/写入未对齐的内存(非常非常有效),因此使用c代替_mm256_loadu_pd(r)和{{1而不是*r_mm256_loadu_pd(l)来存储变量。