局部变量未在内联函数中对齐

时间:2016-07-23 11:03:34

标签: c linux gcc memory-alignment intrinsics

使用Intrinsics进行编程时出现了以下问题。 当我想加载或存储局部变量时,在内联函数中,我得到了内存冲突错误,但仅当函数内联时。我不知道为什么在内联函数中堆栈变量没有对齐。

我已经使用许多不同版本的GCC 4.9,5.3,6.1进行了测试。

失败的示例:

static inline foo(double *phi){
  double localvar[4];
  __m256d var = _mm256_load_pd (phi);
  __m256d res = _mm256_mul_pd(var, var);
  _mm256_store_pd (localvar, res); // <-  failed due to memory violation
  ...
}

如果我添加__attribute__ ((aligned (32)))或删除inline,则该功能可以正常工作。

那么有人可以解释一下(请详细说明),为什么局部变量一般是在没有添加__attribute__ ((aligned (32)))的情况下对齐而内联函数中的局部变量没有?

2 个答案:

答案 0 :(得分:2)

_mm256_store_pd要求您存储的内存地址必须与32字节边界对齐。但是在C中我只认为8字节双精度的标准对齐是8字节边界。

如果我不得不猜测函数是否未内联,则会在32字节边界上启动localvar数组。我不确定这是保证还是运气。我猜运气,因为在理论上内联函数不应该改变任何东西。编译器可能正在将正确数量的字节推入堆栈,以使其变得对齐。此外,我认为没有理由保证32字节对齐。

当它被内联时,它就像在你调用函数的地方输入代码一样。因此,只保证localvar将是8字节对齐而不是保证32字节对齐。我认为正确的解决方案是使用对齐的属性来解决您的问题。您也可以使用_mm256_storeu_pd内在函数,它在没有对齐要求的情况下执行相同的操作。根据我对我的haswell CPU的体验,它同样快。

答案 1 :(得分:2)

提供32字节对齐需要额外的指令(因为ABI仅保证16字节对齐;只需查看带有alignas(32)__attribute__((aligned(32)))的版本的asm。 当然,如果你没有要求它,编译器就不会这样做,因为它不是免费的。 (另请参阅gcc&#39; s -mpreferred-stack-boundary which controls this代码wiki以获取ABI文档的链接。)

对于要自然对齐的每个元素,

double localvar[4];只需要8字节对齐。 SysV x86-64 ABI确保C99可变大小阵列的16字节对齐。我不确定普通编译时常量大小的数组是否默认为16-B对齐。

但是,当前版本的gcc由于某种原因在具有__m256d局部变量的测试函数中将堆栈与32B对齐。在-O3,它不会将它们泄漏到堆栈中,因此它们会被浪费掉(除了制作这样的错误代码之外)。 gcc没有删除这些东西的事实是一个错过的优化。 (-O0需要它,gcc会将所有内容泄漏到内存中。)

由于我的测试函数版本(实际编译的版本)没有任何其他本地版本,因此双精度数组也是32B对齐的。据推测,您将其内联到具有其他本地人的调用者中,这会导致阵列的对齐方式不同。

Here's the code on the Godbolt compiler explorer

extern void use_buffer(double*);
// static inline
void no_alignment(const double *phi){
  double localvar[4];
  __m256d var = _mm256_load_pd (phi);
  __m256d res = _mm256_mul_pd(var, var);
  _mm256_storeu_pd (localvar, res);         // use an unaligned store since we didn't request alignment for the buffer
  use_buffer(localvar);
}

    lea     r10, [rsp+8]                 // save old RSP (in a clumsy way)
    and     rsp, -32                     // truncate RSP to the next 32B boundary
    push    QWORD PTR [r10-8]            // save more stuff
    push    rbp
    mov     rbp, rsp
    push    r10
    sub     rsp, 40
    ...         vmovupd YMMWORD PTR [rbp-48], ymm0     ...   // function body
    add     rsp, 40
    pop     r10
    pop     rbp
    lea     rsp, [r10-8]

这就是为什么你的代码没有内联时就能正常工作的原因。尽管很奇怪,即使没有inline关键字,它也没有内联,除非你没有优化编译,或者你没有使用static让编译器知道不需要单独的定义。