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.
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
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'. –
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