这种带有 __m256 值数组的错误代码生成是 clang 错误吗?
Is this incorrect code generation with arrays of __m256 values a clang bug?
我遇到了一个错误,该错误导致 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
数组中。因此,main()
中对 memcmp()
的调用应该 return 为零。我使用以下内容编译以上内容:
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 [=13=]xffffffffffffffe0,%rsp
400a4b: 48 83 ec 40 sub [=13=]x40,%rsp
400a4f: 48 83 fa 20 cmp [=13=]x20,%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 [=13=]x3f,%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++ 模板参数)。
有什么想法吗?
这是 clang 中的一个错误。它发生在 -O0 的事实是一个很好的线索,表明错误在前端,在这种情况下,它是 x86-64 ABI 实现的一个黑暗角落,与处理包含矢量数组的结构有关正好尺寸 1!
该错误已存在多年,但这是第一次有人遇到它、注意到它并报告它。谢谢!
我遇到了一个错误,该错误导致 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
数组中。因此,main()
中对 memcmp()
的调用应该 return 为零。我使用以下内容编译以上内容:
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 [=13=]xffffffffffffffe0,%rsp
400a4b: 48 83 ec 40 sub [=13=]x40,%rsp
400a4f: 48 83 fa 20 cmp [=13=]x20,%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 [=13=]x3f,%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++ 模板参数)。
有什么想法吗?
这是 clang 中的一个错误。它发生在 -O0 的事实是一个很好的线索,表明错误在前端,在这种情况下,它是 x86-64 ABI 实现的一个黑暗角落,与处理包含矢量数组的结构有关正好尺寸 1!
该错误已存在多年,但这是第一次有人遇到它、注意到它并报告它。谢谢!