2013-03-07 5 views
9

При компиляции большого проекта с clang я наткнулся на раздражающую ошибку.Ошибка оптимизации 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 в любом случае, хотя последняя строка кажется неуклюжим (в реальном проекте, такой код появляется из-за чрезмерного использования макросов препроцессора).

Компиляция с помощью clang (версия 3.1 или выше) на уровне оптимизации -O1 или выше приводит к записи кода в неправильное положение в памяти.

Решающим частей файла сборки создаваемого лязгом/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] 
    [...] 

В других слова, инструкции

(*table)[i*(e+5) + j] = 0; 

вместо последней строки, указанной выше. Выбор + 5 произволен, добавление (или вычитание) других целых чисел приводит к такому же поведению. Итак - это ошибка в оптимизации LLVM или существует неопределенное поведение здесь?

Редактировать: Отметьте также, что ошибка исчезнет, ​​если я не оставляю литой (unsigned char*) в последней строке. В общем, ошибка кажется довольно чувствительной к любым изменениям.

+1

Невозможно увидеть умножение на 5 в ассемблерном коде выше (но тогда я больше привык к ARM-ассемблеру, чем Intel, если это Intel :-)), но последняя строка кода C переводится в '* ((unsigned char *) (* table) + 5 + i * e + j) ', поэтому ... вы уверены, что правильно поместите эти фигурные скобки вокруг« e + 5 »в своей интерпретации выхода ассемблера? – user2116939

+0

Да, я уверен. Это синтаксис GAS, а не Intel, поэтому 'movq% r8,% rsi' и' imulq% rdx,% rsi' означают, что '% rsi' будет удерживать' (% rbx + 6) *% rdx = (e + 5) *% rdx'. –

+0

Да, теперь я вижу это. Это похоже на ошибку оптимизатора, так как код достаточно кошерный, даже если немного странно (но тогда макросы могут генерировать странный вывод). – user2116939

ответ

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 

Спасибо, что заметили это!

Смежные вопросы