2015-02-11 23 views
11

Hatalı görünen bir hatayla karşılaşıyorum, clang 3.4, 3.5 ve 3.6 trunk ile yanlış kod oluşturma. Aslında sorunu tetikleyen kaynak oldukça karmaşıktır, ama bu kendi kendine yeten örnekle azaltmak mümkün oldum:Bu yanlış kod üretimi, bir clang hatası olan __m256 değer dizileriyle mi?

#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; 
} 

yukarıda bir özeti: Ne var simd_pack denilen basit bir tip birini içeren üye, bir __m256i değeri. Uygulamamda, bu türleri alan işleçler ve işlevler var, ancak sorun yukarıdaki örnekle açıklanabilir. Spesifik olarak, test_broken()in1 dizisinden okumalı ve daha sonra değerini out dizisine kopyalamalıdır. Bu nedenle, main() numaralı telefona memcmp() numaralı çağrıya sıfır değeri döndürülmelidir. Aşağıdaki kullanarak yukarıdaki derleme:

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

O optimizasyon seviyeleri -O0 ve -O1 bulduğunuz, seviyeler -O2 ve -O3 üzerine, Test başarısız olurken testi, geçer. Aynı dosyayı gcc 4.4, 4.6, 4.7 ve 4.8, ayrıca Intel C++ 13.0 ile derlemeyi denedim ve test tüm optimizasyon seviyelerinden geçiyor. Bu tür bir

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) 

Ben vurgu anahtar rol çoğaltmak gerekir: oluşturulan koda daha yakından bakmak alarak

, burada optimizasyon seviyesine -O3 oluşturulan montaj var kafa kaşıma. Önceden istediğim hizalanmamış hareketi kullanarak ymm0'a 256 bit yükler, sonra xmm0 (yalnızca okunan verinin alt 128 bitini içerir) yığına depolar, ardından yığın konumundan hemen sonra ymm0'a 256 bit okur. Bu sadece yazılmıştır. Efekt, ymm0 'un üst 128 biti (çıktı tamponuna yazılır), testin başarısız olmasına neden olan çöptür.

Sadece derleyici bir hatadan başka, bunun neden olabileceğinin iyi bir nedeni var mı? simd_pack türünde bir tutmayı __m256i değerler dizisine sahip olarak alarak bazı kuralları ihlal ediyor muyum? Kesinlikle bununla ilgili gibi görünüyor; _val'u bir dizi yerine tek bir değer olarak değiştirirsem, oluşturulan kod tasarlandığı gibi çalışır. Ancak, benim uygulama bir dizi olmak için _val gerektirir (uzunluğu bir C++ şablon parametresine bağlıdır).

Herhangi bir fikrin var mı?

+0

görünüyor. Kod tamamen 256-bit olduğunda 'xmm' kayıtlarını kullanması için bir neden yoktur. (callee-save registerları bir kenara bırakarak) – Mysticial

+0

Yolculuğa karışan katı bir taklit olmadıkça.Fakat her modern derleyici, SIMD kayıtlarını toplu olarak ele alır, bu yüzden bu uygulama yapılmamalıdır. – Mysticial

+0

Giriş için teşekkürler. Değeri için, clang'ı -fno-strict-aliasing ile çağırmayı denedim, ancak oluşturulan kodu etkilemez. –

cevap

5

Bu, argoda bir hatadır. -O0'da olmasının, hatanın ön uçta olduğu ve bu durumda, bir vektör dizisi içeren bir yapının işlenmesiyle ilgili x86-64 ABI uygulamasının karanlık bir köşesi olduğu iyi bir ipucudur. tam olarak 1 numara!

Hata yıllardır mevcut, ancak bu, herkesin ilk kez vurduğu, fark ettiği ve bildirdiği ilk zamandır. Teşekkürler! masif derleyici kısmında başarısız gibi

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

+1

Gerçekten de bu ABI'nin karanlık bir köşesi. Böceğin teşhis edildiği hızda çok etkilendim ve bir düzeltme önerildi. Şu anda kod gözden geçirmede bir yama var, umarım önümüzdeki clang 3.6 sürümünden önce mevcut olacak. –

+1

Hata düzeltmesi şimdi birleştirildi, bu yüzden clang 3.6 yapacak gibi görünüyor. –