11

我遇到了似乎是导致使用 clang 3.4、3.5 和 3.6 主干生成错误代码的错误。实际触发问题的来源非常复杂,但我已经能够将其简化为这个独立的示例:

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

struct simd_pack
{
    enum { num_vectors = 1 };
    __m256i _val[num_vectors];
};

simd_pack load_broken(int8_t *p)
{
    simd_pack pack;
    for (int i = 0; i < simd_pack::num_vectors; ++i) pack._val[i] = _mm256_loadu_si256(reinterpret_cast<__m256i *>(p + i * 32));
    return pack;
}

void store_broken(int8_t *p, simd_pack pack)
{
    for (int i = 0; i < simd_pack::num_vectors; ++i) _mm256_storeu_si256(reinterpret_cast<__m256i *>(p + i * 32), pack._val[i]);    
}

void test_broken(int8_t *out, int8_t *in1, size_t n)
{
    size_t i = 0;
    for (; i + 31 < n; i += 32)
    {
        simd_pack p1 = load_broken(in1 + i);
        store_broken(out + i, p1);
    }   
}

int main()
{
    int8_t in_buf[256];
    int8_t out_buf[256];
    for (size_t i = 0; i < 256; ++i) in_buf[i] = i;

    test_broken(out_buf, in_buf, 256);
    if (memcmp(in_buf, out_buf, 256)) std::cout << "test_broken() failed!" << std::endl;    

    return 0;
}

上面的总结:我有一个简单的类型simd_pack,它包含一个成员,一个__m256i值的数组。在我的应用程序中,存在采用这些类型的运算符和函数,但可以通过上面的示例说明问题。具体来说,test_broken()应该从in1数组中读取,然后将其值复制到out数组中。memcmp()因此,对in的调用main()应该返回零。我使用以下内容编译上述内容:

clang++-3.6 bug_test.cc -o bug_test -mavx -O3

我发现在优化级别-O0-O1上,测试通过,而在级别-O2和上-O3,测试失败。我尝试使用 gcc 4.4、4.6、4.7 和 4.8 以及 Intel C++ 13.0 编译相同的文件,并且测试通过了所有优化级别。

仔细查看生成的代码,这是在优化级别生成的程序集-O3

0000000000400a40 <test_broken(signed char*, signed char*, unsigned long)>:
  400a40:       55                      push   %rbp
  400a41:       48 89 e5                mov    %rsp,%rbp
  400a44:       48 81 e4 e0 ff ff ff    and    $0xffffffffffffffe0,%rsp
  400a4b:       48 83 ec 40             sub    $0x40,%rsp
  400a4f:       48 83 fa 20             cmp    $0x20,%rdx
  400a53:       72 2f                   jb     400a84 <test_broken(signed char*, signed char*, unsigned long)+0x44>
  400a55:       31 c0                   xor    %eax,%eax
  400a57:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  400a5e:       00 00 
  400a60:       c5 fc 10 04 06          vmovups (%rsi,%rax,1),%ymm0
  400a65:       c5 f8 29 04 24          vmovaps %xmm0,(%rsp)
  400a6a:       c5 fc 28 04 24          vmovaps (%rsp),%ymm0
  400a6f:       c5 fc 11 04 07          vmovups %ymm0,(%rdi,%rax,1)
  400a74:       48 8d 48 20             lea    0x20(%rax),%rcx
  400a78:       48 83 c0 3f             add    $0x3f,%rax
  400a7c:       48 39 d0                cmp    %rdx,%rax
  400a7f:       48 89 c8                mov    %rcx,%rax
  400a82:       72 dc                   jb     400a60 <test_broken(signed char*, signed char*, unsigned long)+0x20>
  400a84:       48 89 ec                mov    %rbp,%rsp
  400a87:       5d                      pop    %rbp
  400a88:       c5 f8 77                vzeroupper 
  400a8b:       c3                      retq   
  400a8c:       0f 1f 40 00             nopl   0x0(%rax)

我将重现重点部分:

  400a60:       c5 fc 10 04 06          vmovups (%rsi,%rax,1),%ymm0
  400a65:       c5 f8 29 04 24          vmovaps %xmm0,(%rsp)
  400a6a:       c5 fc 28 04 24          vmovaps (%rsp),%ymm0
  400a6f:       c5 fc 11 04 07          vmovups %ymm0,(%rdi,%rax,1)

这有点让人头疼。它首先使用我要求的未对齐移动加载 256 位ymm0,然后将xmm0(仅包含已读取数据的低 128 位)存储到堆栈中,然后立即ymm0从刚刚的堆栈位置读取 256 位写给。效果是ymm0的高 128 位(写入输出缓冲区)是垃圾,导致测试失败。

除了编译器错误之外,还有什么好的理由会发生这种情况吗?我是否违反了某些规则,让simd_pack类型包含一个值数组__m256i?这似乎与此有关;如果我更改_val为单个值而不是数组,则生成的代码将按预期工作。但是,我的应用程序需要_val是一个数组(其长度取决于 C++ 模板参数)。

有任何想法吗?

4

1 回答 1

5

这是clang中的一个错误。它发生在 -O0 的事实是一个很好的线索,表明该错误位于前端,在这种情况下,它是 x86-64 ABI 实现的一个黑暗角落,与处理包含向量数组的结构有关正好是 1 号!

该错误已经存在多年,但这是第一次有人发现它、注意到它并报告它。谢谢!

http://llvm.org/bugs/show_bug.cgi?id=22563

于 2015-02-13T16:11:45.900 回答