2013-03-07 13 views
9

もっと大きなプロジェクトをclangでコンパイルしているうちに、わずらわしいバグがありました。LLVM最適化のバグまたは未定義の動作?

次の小さな例で考えてみましょう:最後の行は厄介なようですが、このコードは、実際のプロジェクトでは(、どのような方法でこのようなコードをCの標準に違反していない私の知る限り

unsigned long int * * fee(); 

void foo(unsigned long int q) 
{ 
    unsigned long int i,j,k,e; 
    unsigned long int pows[7]; 
    unsigned long int * * table; 

    e = 0; 
    for (i = 1; i <= 256; i *= q) 
    pows[e++] = i; 
    pows[e--] = i; 

    table = fee(); // need to set table to something unknown 
        // here, otherwise the compiler optimises 
        // parts of the loops below away 
        // (and no bug occurs) 

    for (i = 0; i < q; i++) 
    for (j = 0; j < e; j++) 
     ((unsigned char*)(*table) + 5)[i*e + j] = 0; // bug here 
} 

をプリプロセッサマクロが過剰に使用されるために表示されます)。

最適化レベル-O1以上でclang(バージョン3.1以上)でこれをコンパイルすると、メモリ内の間違った位置にコードが書き込まれます。次のように打ち鳴らすによって生成されたアセンブリファイルの

重要な部分は、/ LLVMを読む: (これはインテルに使用されているあなたの人々になるように、ガスの構文は次のとおりです。!注意してください)他に

[...] 
    callq _fee 
    leaq 6(%rbx), %r8   ## at this point, %rbx == e-1 
    xorl %edx, %edx 
LBB0_4: 
    [...] 
    movq %r8, %rsi 
    imulq %rdx, %rsi 
    incq %rdx 
LBB0_6: 
    movq (%rax), %rcx   ## %rax == fee() 
    movb $0, (%rcx,%rsi) 
    incq %rsi 
    [conditional jumps back to LBB0_6 resp. LBB0_4] 
    [...] 

を上記の最後の行の代わりに

(*table)[i*(e+5) + j] = 0; 

の代わりに指示を実行します。 + 5の選択は任意ですが、他の整数を加算(または減算)すると同じ動作になります。だから、これはLLVMの最適化のバグか、ここで未定義の動作が起こっていますか?

編集:最後の行にキャスト(unsigned char*)を残しておけば、このバグは消えます。一般に、このバグは変更に非常に敏感であるように見えます。

+1

上記のアセンブラコードでは5倍の乗算はできません(しかし、インテルの場合はARMよりもARMアセンブラに慣れています:-))、Cコードの最後の行は ' (unsigned char *)(* table)+ 5 + i * e + j) 'だから、アセンブラの出力を正しく解釈するには、これらのブレースを" e + 5 "の周りに置いてください。 – user2116939

+0

はい、私はかなり確信しています。これは、インテルではなく、GASの構文であるため、 '%rsi'が'%rbx + 6%* rdx =(e + 5)を保持することを意味する 'movq%r8、%rsi'と' imulq%rdx、%rsi ' )*%rdx'。 –

+0

はい、今私はこれを見ることができます。ちょっと奇妙なことがあってもコードが充分に正当であるため、オプティマイザのバグのように見えます(しかし、マクロは奇妙な出力を生成する可能性があります)。 – user2116939

答えて

5

これはオプティマイザのバグです。 LLVM-2.7とLLVM-3.1は私がアクセスできる唯一のバージョンです。

私はa bugをLLVM Bugzillaに投稿しました。

バグは、このSSCCEによって証明されています

#include <stdio.h> 

unsigned long int * table; 

void foo(unsigned long int q) 
{ 
    unsigned long int i,j,e; 

    e = 0; 
    for (i = 1; i <= 256; i *= q) 
    e++; 
    e--; 

    for (i = 0; i < q; i++) 
    for (j = 0; j < e; j++) 
     ((unsigned char*)(table) + 13)[i*e + j] = 0; // bug here 
} 

int main() { 
    unsigned long int v[8]; 
    int i; 
    memset(v, 1, sizeof(v)); 

    table = v; 
    foo(2); 

    for(i=0; i<sizeof(v); i++) { 
     printf("%d", ((unsigned char*)v)[i]); 
    } 
    puts(""); 
    return 0; 
} 

それはGCCと "打ち鳴らすの-O0" の下

1111111111111000000000000000011111111111111111111111111111111111 

を印刷する必要があります。 LLVMで観測された誤った出力は

0000000011111111111110000000011111111111111111111111111111111111 

ありがとうございます!

関連する問題