2015-02-11 7 views
11

मुझे लगता है कि क्लैंग 3.4, 3.5, और 3.6 ट्रंक के साथ गलत कोड पीढ़ी के कारण एक बग प्रतीत होता है। स्रोत है कि वास्तव में समस्या शुरू हो रहा काफी जटिल है, लेकिन मैं इस संयमी उदाहरण के लिए इसे कम कर लिया है:क्या यह गलत कोड पीढ़ी __m256 मानों के साथ एक क्लैंग बग है?

#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() पर कॉल शून्य लौटा देना चाहिए। मैं निम्नलिखित का उपयोग करके उपरोक्त संकलन:

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

मुझे लगता है कि लगता है अनुकूलन स्तरों -O0 और -O1 पर, परीक्षण गुजरता है, जबकि स्तरों -O2 और -O3 पर, परीक्षण विफल रहता है। मैंने जीसीसी 4.4, 4.6, 4.7, और 4.8, साथ ही इंटेल सी ++ 13.0 के साथ एक ही फाइल को संकलित करने का प्रयास किया है, और परीक्षण सभी अनुकूलन स्तरों पर गुजरता है।

उत्पन्न कोड को करीब से देख लेते हुए यहां अनुकूलन स्तर -O3 पर उत्पन्न विधानसभा है:

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) 

इस तरह की है:

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) 

मैं जोर देने के लिए महत्वपूर्ण हिस्सा पुन: पेश करेंगे सिर खरोंच। यह पहले 256 बिट्स को ymm0 में अनलिखित चाल का उपयोग करके लोड करता है, फिर यह स्टैक पर xmm0 (जिसमें केवल पढ़ने वाले डेटा के निचले 128 बिट्स होते हैं) स्टोर करते हैं, फिर तुरंत स्टैक स्थान से ymm0 में 256 बिट्स पढ़ते हैं वह सिर्फ लिखा गया था। प्रभाव यह है कि ymm0 की ऊपरी 128 बिट्स (जो आउटपुट बफर में लिखी जाती हैं) कचरा होती है, जिससे परीक्षण विफल हो जाता है।

क्या कोई अच्छा कारण है कि यह क्यों हो सकता है, केवल एक कंपाइलर बग के अलावा? क्या मैं simd_pack टाइप करके __m256i मानों की एक सरणी रखकर कुछ नियमों का उल्लंघन कर रहा हूं? यह निश्चित रूप से उससे संबंधित प्रतीत होता है; अगर मैं _val को किसी सरणी के बजाय एकल मान के रूप में बदलता हूं, तो जेनरेट कोड इच्छित के रूप में कार्य करता है। हालांकि, मेरे आवेदन को एक सरणी होने के लिए _val की आवश्यकता है (इसकी लंबाई एक C++ टेम्पलेट पैरामीटर पर निर्भर है)।

कोई विचार?

+0

लग रहा है। कोड पूरी तरह से 256-बिट होने पर 'xmm' रजिस्टरों का उपयोग करने का कोई कारण नहीं है। (कैली-सेव रजिस्टर्स को अलग करें) – Mysticial

+0

जब तक कि यह सख्त-अलियासिंग नहीं हो रहा है।लेकिन हर आधुनिक कंपाइलर सिम रजिस्टरों को समेकित करता है, इसलिए इसे लागू नहीं करना चाहिए। – Mysticial

+0

इनपुट के लिए धन्यवाद। इसके लायक होने के लिए, मैंने '-फनो-सख्त-एलियासिंग' के साथ क्लैंग का आह्वान करने का प्रयास किया है, लेकिन यह जेनरेट कोड को प्रभावित नहीं करता है। –

उत्तर

5

यह क्लैंग में एक बग है। तथ्य यह है कि यह -00 पर हुआ था, यह एक अच्छा संकेत है कि बग सामने के अंत में है, और इस मामले में, यह x86-64 एबीआई कार्यान्वयन का एक अंधेरा कोने है जिसमें एक वेक्टर सरणी है जिसमें एक वेक्टर सरणी है बिल्कुल आकार 1!

बग वर्षों से मौजूद है, लेकिन यह पहली बार है कि किसी ने इसे मारा है, इसे देखा है, और इसकी सूचना दी है। धन्यवाद! संकलक ओर से बड़े पैमाने पर असफल तरह

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

+1

दरअसल यह एबीआई का एक अंधेरा कोने है। मैं उस गति से बहुत प्रभावित हूं जिस पर बग का निदान किया गया था और एक प्रस्ताव प्रस्तावित किया गया था। वर्तमान में कोड समीक्षा में पैच है, उम्मीद है कि आगामी क्लैंग 3.6 रिलीज से पहले उपलब्ध होना चाहिए। –

+1

बग के लिए फ़िक्स अब विलय कर दिया गया है, इसलिए ऐसा लगता है कि यह इसे क्लैंग 3.6 में बना देगा। –

संबंधित मुद्दे