2013-03-07 10 views
9

Podczas kompilowania większego projektu z brzękiem natknąłem się na irytujący błąd.Błąd optymalizacji LLVM lub niezdefiniowane zachowanie?

Rozważmy następujący mały przykład:

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 
} 

Zgodnie z moją najlepszą wiedzą kod ten nie narusza standard C w żaden sposób, choć ostatnia linia wydaje się niewygodne (w rzeczywistym projekcie, kod tak pojawia się z powodu nadmiernego użycia makr preprocesora).

Kompilowanie tego z klangiem (wersja 3.1 lub wyższa) na poziomie optymalizacji -O1 lub wyższym powoduje zapisywanie kodu do niewłaściwej pozycji w pamięci.

kluczowej części pliku zespołu wytwarzanego przez brzękiem/LLVM brzmienie: (Jest to składnia GAS, więc do tych z was, którzy są przyzwyczajeni do Intel: Strzeż się!)

[...] 
    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] 
    [...] 

w innych słowa, instrukcje sĘ ... wykonywane zamiast ostatniej linii zapisanej powyżej. Wybór + 5 jest arbitralny, dodanie (lub odjęcie) innych liczb całkowitych powoduje to samo zachowanie. Czyli jest to błąd w optymalizacji LLVM, czy tu dzieje się niezdefiniowane zachowanie?

Edytuj: Należy również zauważyć, że błąd znika, jeśli pominięto obsadę (unsigned char*) w ostatnim wierszu. Ogólnie błąd wydaje się być bardzo wrażliwy na wszelkie zmiany.

+1

Nie można zobaczyć mnożenia przez 5 w powyższym kodzie asemblera (ale wtedy jestem bardziej przyzwyczajony do asemblera ARM niż Intel, jeśli to Intel :-)), ale ostatnia linia kodu C tłumaczy się na '* ((unsigned char *) (tabela *) + 5 + i * e + j) ', więc ... czy na pewno umieściłeś te nawiasy wokół" e + 5 "w swojej interpretacji wyjścia asemblera poprawnie? – user2116939

+0

Tak, jestem prawie pewien. To jest składnia GAS, a nie Intel, więc 'movq% r8,% rsi' i' imulq% rdx,% rsi' oznaczają, że '% rsi' utrzyma' (% rbx + 6) *% rdx = (e + 5) *% rdx'. –

+0

Tak, teraz widzę to. Wygląda jak błąd optymalizatora, ponieważ kod jest wystarczająco koszerny, nawet jeśli jest nieco dziwny (ale wtedy makra mogą generować dziwne wyniki). – user2116939

Odpowiedz

5

Jestem pewien, że jest to błąd optymalizatora. Jest to repro w LLVM-2.7 i LLVM-3.1, jedyne wersje, do których mam dostęp.

Wysłałem a bug do Bugzilli LLVM.

Błąd Świadczy o tym 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; 
} 

Należy wydrukować

1111111111111000000000000000011111111111111111111111111111111111 

pod GCC i "dzyń -O0". Niepoprawna moc obserwowana dla LLVM to

0000000011111111111110000000011111111111111111111111111111111111 

Dzięki za zwrócenie na to uwagę!

Powiązane problemy