9

clang を使用してより大きなプロジェクトをコンパイルしているときに、厄介なバグに遭遇しました。

次の小さな例を考えてみましょう。

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
}

私の知る限りでは、このコードは C 標準にまったく違反していませんが、最後の行はぎこちないように見えます (実際のプロジェクトでは、このようなコードはプリプロセッサ マクロを過度に使用したために表示されます)。

これを最適化レベル -O1 以上で clang (バージョン 3.1 以上) でコンパイルすると、メモリ内の間違った位置にコードが書き込まれます。

clang/LLVM によって生成されるアセンブリ ファイルの重要な部分は次のようになります: (これは GAS 構文なので、Intel に慣れている人は注意してください!)

    [...]
    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*)最後の行でキャストを省略すると、バグが消えることにも注意してください。一般に、このバグは変更に対して非常に敏感であるように見えます。

4

1 に答える 1

5

これはオプティマイザのバグだと確信しています。これは、私がアクセスできる唯一のバージョンである LLVM-2.7 および LLVM-3.1 で再現されます。

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

印刷する必要があります

1111111111111000000000000000011111111111111111111111111111111111

GCCおよび「clang -O0」の下。LLVM で観察される誤った出力は次のとおりです。

0000000011111111111110000000011111111111111111111111111111111111

これに気づいてくれてありがとう!

于 2013-03-07T22:11:02.273 に答える