Por que o LLVM aloca uma variável redundante?


9

Aqui está um arquivo C simples com uma definição de enumeração e uma mainfunção:

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    return 0;
}

Transpila para o seguinte LLVM IR:

define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 2, i32* %2, align 4
  ret i32 0
}

%2é evidentemente a dvariável, que recebe 2 atribuídos a ela. A que %1corresponde se zero é retornado diretamente?


11
Quais sinalizadores você usou para produzir esse RI?
— arrowd 6/01

@arrowd, instalei a última suíte LLVM estável e executeiclang-9 -S -emit-llvm simple.c
— macleginn

11
Eu acho que tem algo a ver com a inicialização antes main( godbolt.org/z/kEtS-s ). O link mostra como o assembly é mapeado para a fonte
— Pradeep Kumar

2
@PradeepKumar: De fato, se você alterar o nome da função para algo diferente main, a variável extra misteriosa desaparecerá. Curiosamente, ele também desaparece se você omitir returncompletamente a declaração (o que é legal mainem C e equivalente a return 0;).
— Nate Eldredge

11
@ macleginn: Não tenho tanta certeza. Se você declara maincomo int main(int argc, char **argv)vê argce argvcopia na pilha, mas a variável zero misteriosa ainda está lá além delas.
— Nate Eldredge

Respostas:


3

Esse %1registro foi gerado pelo clang para manipular várias instruções de retorno em uma função . Imagine que você tivesse uma função para calcular o fatorial de um número inteiro. Em vez de escrever assim

int factorial(int n){
    int result;
    if(n < 2)
      result = 1;
    else{
      result = n * factorial(n-1);
    }
    return result;
}

Você provavelmente faria isso

int factorial(int n){
    if(n < 2)
      return 1;
    return n * factorial(n-1);
}

Por quê? Porque Clang irá inserir a resultvariável que contém o valor de retorno para você. Yay. Esse é o propósito exato disso %1. Veja no ir uma versão ligeiramente modificada do seu código.

Código modificado,

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    if(d) return 1;
    return 0;
}

IR,

define dso_local i32 @main() #0 !dbg !15 {
    %1 = alloca i32, align 4
    %2 = alloca i32, align 4
    store i32 0, i32* %1, align 4
    store i32 2, i32* %2, align 4, !dbg !22
    %3 = load i32, i32* %2, align 4, !dbg !23
    %4 = icmp ne i32 %3, 0, !dbg !23
    br i1 %4, label %5, label %6, !dbg !25

 5:                                                ; preds = %0
   store i32 1, i32* %1, align 4, !dbg !26
   br label %7, !dbg !26

 6:                                                ; preds = %0
  store i32 0, i32* %1, align 4, !dbg !27
  br label %7, !dbg !27

 7:                                                ; preds = %6, %5
  %8 = load i32, i32* %1, align 4, !dbg !28
  ret i32 %8, !dbg !28
}

Agora você vê isso %1se tornando útil, hein? Como os outros apontaram, para funções com apenas uma instrução de retorno, essa variável provavelmente será removida por um dos passes otimizados do llvm.


1

Por que isso importa - qual é o problema real?

Eu acho que a resposta mais profunda que você está procurando pode ser: a arquitetura do LLVM é baseada em frontends bastante simples e em muitas passagens. Os frontends precisam gerar o código correto, mas não precisa ser um bom código. Eles podem fazer a coisa mais simples que funciona.

Nesse caso, o Clang gera algumas instruções que acabam não sendo usadas para nada. Isso geralmente não é um problema, porque alguma parte do LLVM se livra de instruções supérfluas. Clang confia que isso aconteça. Clang não precisa evitar emitir código morto; sua implementação pode se concentrar na correção, simplicidade, testabilidade etc.


1

Porque o Clang é feito com análise de sintaxe, mas o LLVM nem começou com a otimização.

O front-end do Clang gerou IR (Representação Intermediária) e não código de máquina. Essas variáveis ​​são SSAs (atribuições estáticas únicas); eles ainda não foram vinculados a registros e, na verdade, após a otimização, nunca serão porque são redundantes.

Esse código é uma representação literal da fonte. É o que chama a atenção do LLVM para otimização. Basicamente, o LLVM começa com isso e otimiza a partir daí. De fato, para a versão 10 e x86_64, o llc -O2 acabará gerando:

main: # @main
  xor eax, eax
  ret

Eu entendo o processo nesse nível. Eu queria saber por que esse RI foi gerado para começar.
— macleginn

Você pode estar pensando em um compilador como uma única passagem. Há um pipeline de passes começando com o front-end do Clang, que gera IR. Ele nem sequer gerou esse IR textual que alguém solicitou com clang -emit-llvm -S file.cpp O Clang realmente gerou uma versão de código de bits serializável binária do IR. O LLVM é estruturado como várias passagens, cada uma capturando e otimizando o IR. O primeiro passe LLVM obtém IR de Clang. É necessário IR porque você pode substituir o Clang pelo Fortran FE para oferecer suporte a outro idioma com o mesmo otimizador + gerador de código.
— Olsonist
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.