2015-02-11 10 views
11

我遇到了一個可能導致錯誤代碼生成的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++模板參數)。

任何想法?

+0

看起來像編譯器部分的大規模失敗。當代碼完全是256位時,沒有理由應該使用'xmm'寄存器。 (撇開被調用者 - 保存寄存器) – Mysticial 2015-02-11 19:58:16

+0

除非是嚴格別名,否則會阻礙它。但是每個現代編譯器都將SIMD寄存器視爲聚合,所以這不適用。 – Mysticial 2015-02-11 20:00:59

+0

感謝您的輸入。對於它的價值,我試着用'-fno-strict-aliasing'調用clang,但它不影響生成的代碼。 – 2015-02-11 20:12:39

回答

5

這是叮噹聲中的一個錯誤。事實上它發生在-O0處是一個很好的線索,說明該錯誤位於前端,在這種情況下,它是x86-64 ABI實現的一個黑暗角落,它與處理包含向量數組完全大小1!

該錯誤已存在多年,但這是第一次任何人打它,注意到它,並報告它。謝謝!

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

+1

事實上,這是ABI的一個黑暗角落。我對這個bug被診斷出來的速度和提出的修正印象深刻。他們目前在代碼審查中有一個補丁,希望在即將發佈的clang 3.6發佈之前提供。 – 2015-02-13 16:23:32

+1

現在已經合併了該錯誤的修復程序,因此它看起來會將它變爲3.6版。 – 2015-02-19 03:34:02