Bug de otimização da LLVM ou comportamento indefinido?

9

Enquanto compilava um projeto maior com clang, tropecei em um bug irritante.

Considere o seguinte pequeno exemplo:

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
}

No melhor de meu conhecimento, este código não viola o padrão C de qualquer forma, embora a última linha pareça estranha (no projeto atual, código como este aparece devido ao uso excessivo de macros de pré-processador).

Compilar isso com clang (versão 3.1 ou superior) no nível de otimização -O1 ou superior resulta em código escrito para a posição errada na memória.

As partes cruciais do arquivo de montagem produzido pelo clang / LLVM são as seguintes: (Esta é a sintaxe do GAS, então para aqueles que estão acostumados com a Intel: Cuidado!)

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

Em outras palavras, as instruções são

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

em vez da última linha escrita acima. A escolha de + 5 é arbitrária, adicionando (ou subtraindo) outros inteiros resulta no mesmo comportamento. Então - isso é um bug na otimização do LLVM ou há um comportamento indefinido acontecendo aqui?

Editar: Note também que o bug desaparece se eu deixar de fora o elenco (unsigned char*) na última linha. Em geral, o bug parece ser bastante sensível a qualquer alteração.

    
por m_l 07.03.2013 в 20:55
fonte

1 resposta

5

Tenho certeza de que esse é um bug otimizador. O repro está no LLVM-2.7 e no LLVM-3.1, as únicas versões que eu tenho acesso.

Eu postei um bug no Bugzilla do LLVM.

O bug é demonstrado por este 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;
}

Deve imprimir

1111111111111000000000000000011111111111111111111111111111111111

no GCC e "clang -O0". A saída incorreta observada com o LLVM é

0000000011111111111110000000011111111111111111111111111111111111

Obrigado por perceber isso!

    
por nneonneo 07.03.2013 / 23:11
fonte