2013-03-07 32 views
9

在用clang编译一个更大的项目时,我偶然发现了一个恼人的bug。LLVM优化错误或未定义的行为?

考虑下面的小例子:

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或更高时编译此语句(版本3.1或更高版本)会导致代码写入内存中错误的位置。

由铛生成的汇编文件的关键部分/ LLVM如下: (这是GAS的语法,所以你们谁是用来英特尔:当心!)

[...] 
    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(但后来我比Intel更习惯ARM汇编,如果它是Intel :-)),但C代码的最后一行转换为* ((unsigned char *)(* table)+ 5 + i * e + j)',所以......你确定你把这些大括号放在“e + 5”的正确解释中吗? – user2116939 2013-03-07 20:13:53

+0

是的,我很确定。这是GAS语法,不是Intel,所以'movq%r8,%rsi'和'imulq%rdx,%rsi'表示'%rsi'将保存'(%rbx + 6)*%rdx =(e + 5 )*%rdx'。 – 2013-03-07 20:21:27

+0

是的,现在我可以看到这一点。它看起来像一个优化器错误,因为即使有点奇怪,代码仍然足够清晰(但是然后宏可以产生奇怪的输出)。 – user2116939 2013-03-07 20:38:17

回答

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

应该打印

1111111111111000000000000000011111111111111111111111111111111111 
下GCC和 “铛-O0”

。使用LLVM观察到的错误输出是

0000000011111111111110000000011111111111111111111111111111111111 

感谢您的注意!