2013-03-07 29 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 

感謝您的注意!