मुझे लगता है कि क्लैंग 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++ टेम्पलेट पैरामीटर पर निर्भर है)।
कोई विचार?
लग रहा है। कोड पूरी तरह से 256-बिट होने पर 'xmm' रजिस्टरों का उपयोग करने का कोई कारण नहीं है। (कैली-सेव रजिस्टर्स को अलग करें) – Mysticial
जब तक कि यह सख्त-अलियासिंग नहीं हो रहा है।लेकिन हर आधुनिक कंपाइलर सिम रजिस्टरों को समेकित करता है, इसलिए इसे लागू नहीं करना चाहिए। – Mysticial
इनपुट के लिए धन्यवाद। इसके लायक होने के लिए, मैंने '-फनो-सख्त-एलियासिंग' के साथ क्लैंग का आह्वान करने का प्रयास किया है, लेकिन यह जेनरेट कोड को प्रभावित नहीं करता है। –