一次avx2在gcc上core dump的排查经历

Posted by 111qqz on Thursday, July 22, 2021

TOC

背景

起因是同事在实现int4的功能,结果流水线有一条死活过不了(gcc版本为4.8.5),一直core dump 经过初步排查,找出了如下最小可以复现的代码:


#include <immintrin.h>

class Test{
    public:
    Test(){
        tmp = _mm256_set_epi32(0,0,0,0,0,0,0,0);
    }
    private:
    __m256i tmp;
};
int main(){
    auto *tmp = new Test();
    return 0;
}

gcc版本为4.8.5 其中编译选项为

g++ -std=c++11 -mavx2 a.cpp 

现象为会core在 tmp = _mm256_set_epi32(0,0,0,0,0,0,0,0);

但是同样的代码,同样的编译选项,在gcc7.3上就不会发生core的问题。

初步排查

查看汇编代码,gcc4.8.5生成的如下:


main:
        push    rbp
        mov     rbp, rsp
        mov     edi, 32
        call    operator new(unsigned long)
        vpxor   xmm0, xmm0, xmm0
        vmovdqa YMMWORD PTR [rax], ymm0
        mov     eax, 0
        pop     rbp
        ret
   

链接在这里

然而在gcc7.3下,生成的汇编代码如下:


main:
        push    rbp
        mov     rbp, rsp
        push    r10
        sub     rsp, 8
        mov     esi, 32
        mov     edi, 32
        call    operator new(unsigned long, std::align_val_t)
        vpxor   xmm0, xmm0, xmm0
        vmovdqa YMMWORD PTR [rax], ymm0
        mov     eax, 0
        add     rsp, 8
        pop     r10
        pop     rbp
        ret

链接在这里

发现调用的new operator竟然不是同一个。-std=c++17下带了一个类型为 std::align_val_t的参数

同时观察到,如果不用new来创建Object, 也不会发生core dump

此时基本确定,问题和new有关。

new的对齐规则

然后在公司大佬的指引下,看到了-faligned-new

-faligned-new Enable support for C++17 new of types that require more alignment than void* ::operator new(std::size_t) provides. A numeric argument such as -faligned-new=32 can be used to specify how much alignment (in bytes) is provided by that function, but few users will need to override the default of alignof(std::max_align_t).

This flag is enabled by default for -std=c++17.

这个参数的作用其实是用来设置

__STDCPP_DEFAULT_NEW_ALIGNMENT__

这个值默认为“alignof(std::max_align_t)”

可以用如下代码来验证:

#include <immintrin.h>
#include <iostream>

class Test{
    public:
    Test(){
        tmp = _mm256_set_epi32(0,0,0,0,0,0,0,0);
    }
    private:
    __m256i tmp;
};
int main(){
    auto *tmp = new Test();
 std::cout<<__STDCPP_DEFAULT_NEW_ALIGNMENT__;
    return 0;
}

编译选项为:

g++ -std=c++17 -mavx2  -faligned-new=32 c.cpp

设置了有什么作用呢?

编译器会根据

__STDCPP_DEFAULT_NEW_ALIGNMENT__

的值来判断是调用哪个版本的new. 具体来说,如果type的aligment大于这个值,就会调用带对齐参数版本的new:

 operator new(unsigned long, std::align_val_t)

否则就调用不带对齐参数版本的new:

 operator new(unsigned long)

按照如上的推断,在gcc7.3,c++17下,通过设置-faligned-new,使得编译器不去调用带对齐参数的new,那么也应该发生core才对。

。。 然而实际上并没有 使用如下编译参数,无事发生

g++ -std=c++17 -mavx2  -faligned-new=32 c.cpp

Why??? 为什么没有core?

If you’re compiling in [c++17] mode only with a sufficiently recent compiler (e.g., GCC>=7, clang>=5, MSVC>=19.12), then everything is taken care by the compiler and you can stop reading.

因为gcc7以后的编译器已经把对齐之类的事情帮我们做了。。

按照这个想法,在gcc6下,总会core吧?

然后发现也没有。。。

继续排查发现,gcc 4.9.4仍然会core 但是gcc 5就没有问题了。

怀疑是gcc5做了什么修复,或者是gcc5对应的glibc做了什么修复。。

不过暂时没有找到。这里待补充。

解决办法

手动对齐一下就好了


template <size_t ALIGNMENT>
struct alignas(ALIGNMENT) AlignedNew {
  static_assert(ALIGNMENT > 0, "ALIGNMENT must be positive");
  static_assert((ALIGNMENT & (ALIGNMENT - 1)) == 0,
      "ALIGNMENT must be a power of 2");
  static_assert((ALIGNMENT % sizeof(void*)) == 0,
      "ALIGNMENT must be a multiple of sizeof(void *)");
  static void* operator new(size_t count) { return Allocate(count); }
  static void* operator new[](size_t count) { return Allocate(count); }
  static void operator delete(void* ptr) { free(ptr); }
  static void operator delete[](void* ptr) { free(ptr); }

 private:
  static void* Allocate(size_t count) {
    void* result = nullptr;
    const auto alloc_failed = posix_memalign(&result, ALIGNMENT, count);
    if (alloc_failed)  throw ::std::bad_alloc();
    return result;
  }
};
class Test: public AlignedNew<32> {
    public:
    Test(){
        tmp = _mm256_set_epi32(0,0,0,0,0,0,0,0);
    }
    private:
	  __m256i tmp;
};

参考链接