LLVM优化错误或未定义的行为?

m_l*_*m_l 9 c llvm clang

在用clang编译一个更大的项目时,我偶然发现了一个令人烦恼的bug.

考虑以下小例子:

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
}
Run Code Online (Sandbox Code Playgroud)

据我所知,这段代码没有以任何方式违反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]
    [...]
Run Code Online (Sandbox Code Playgroud)

换句话说,说明书

(*table)[i*(e+5) + j] = 0;
Run Code Online (Sandbox Code Playgroud)

而不是上面写的最后一行.选择+ 5是任意的,添加(或减去)其他整数会导致相同的行为.那么 - 这是LLVM优化中的一个错误,还是存在未定义的行为?

编辑:请注意,如果我(unsigned char*)在最后一行中省略了演员表,那么该错误就会消失.通常,该错误似乎对任何更改都非常敏感.

nne*_*neo 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;
}
Run Code Online (Sandbox Code Playgroud)

它应该打印

1111111111111000000000000000011111111111111111111111111111111111
Run Code Online (Sandbox Code Playgroud)

根据GCC和"clang -O0".使用LLVM观察到的错误输出是

0000000011111111111110000000011111111111111111111111111111111111
Run Code Online (Sandbox Code Playgroud)

感谢您注意到这一点!