我遇到了一個可能導致錯誤代碼生成的bug,它們是3.4,3.5和3.6中繼。實際上引發的問題的來源是相當複雜的,但我已經能夠將其降低到這個自成體系例如:這是不正確的代碼生成__m256值的數組是一個鏗鏘的bug?
#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()
的呼叫應返回零。我編譯上面使用下列內容:
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)
這是怎麼樣的頭劃傷。它首先使用我要求的未對齊移動將加載到ymm0
中,然後將xmm0
(僅包含讀取的數據的較低128位)存儲到堆棧,然後立即從堆棧位置讀入ymm0
那是剛剛寫的。結果是ymm0
的高128位(寫入輸出緩衝區)是垃圾,導致測試失敗。
除了僅僅是一個編譯器錯誤之外,是否有這個可能發生的一些很好的理由?我違反了一些規則,使simd_pack
類型擁有一個__m256i
值的數組?這似乎與此有關;如果我將_val
更改爲單個值而不是數組,則生成的代碼將按預期工作。但是,我的應用程序要求_val
是一個數組(其長度取決於C++模板參數)。
任何想法?
看起來像編譯器部分的大規模失敗。當代碼完全是256位時,沒有理由應該使用'xmm'寄存器。 (撇開被調用者 - 保存寄存器) – Mysticial 2015-02-11 19:58:16
除非是嚴格別名,否則會阻礙它。但是每個現代編譯器都將SIMD寄存器視爲聚合,所以這不適用。 – Mysticial 2015-02-11 20:00:59
感謝您的輸入。對於它的價值,我試着用'-fno-strict-aliasing'調用clang,但它不影響生成的代碼。 – 2015-02-11 20:12:39