¿Por qué LLVM asigna una variable redundante?

9

Aquí hay un archivo C simple con una definición de enumeración y una mainfunción:

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

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

Se transmite al siguiente 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
}

%2evidentemente es la dvariable, a la que se le asignan 2. ¿A qué %1corresponde si se devuelve cero directamente?

macleginn
fuente
1
¿Qué banderas usaste para producir este IR?
arrowd
@arrowd, instalé la última suite estable de LLVM y corríclang-9 -S -emit-llvm simple.c
macleginn el
1
Creo que tiene algo que ver con la inicialización antes main( godbolt.org/z/kEtS-s ). El enlace muestra cómo se asigna el ensamblado a la fuente
Pradeep Kumar
2
@PradeepKumar: De hecho, si cambia el nombre de la función a otra cosa main, la misteriosa variable adicional desaparece. Curiosamente, también desaparece si omite la returndeclaración por completo (que es legal mainen C y equivalente a return 0;).
Nate Eldredge
1
@macleginn: no estoy tan seguro. Si declaras mainlo int main(int argc, char **argv)que ves argcy lo argvcopias en la pila, pero la misteriosa variable cero sigue ahí además de ellas.
Nate Eldredge

Respuestas:

3

Este %1registro fue generado por clang para manejar múltiples declaraciones de retorno en una función . Imagine que tiene una función para calcular el factorial de un entero. En lugar de escribirlo así

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

Probablemente harías esto

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

¿Por qué? Porque Clang insertará esa resultvariable que contiene el valor de retorno para usted. Hurra. Ese es el propósito exacto de eso %1. Mire el ir para una versión ligeramente modificada de su 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
}

Ahora lo ves %1haciéndose útil, ¿eh? Como los otros han señalado, para las funciones con una sola declaración de retorno, esta variable probablemente será eliminada por uno de los pasos de optimización de llvm.

caida superior
fuente
1

¿Por qué importa esto? ¿Cuál es el problema real?

Creo que la respuesta más profunda que está buscando podría ser: la arquitectura de LLVM se basa en interfaces bastante simples y muchos pases. Las interfaces tienen que generar el código correcto, pero no tiene que ser un buen código. Pueden hacer lo más simple que funciona.

En este caso, Clang genera un par de instrucciones que resultan no ser utilizadas para nada. Eso generalmente no es un problema, porque alguna parte de LLVM eliminará las instrucciones superfluas. Clang confía en que eso suceda. Clang no necesita evitar emitir código muerto; su implementación puede centrarse en la corrección, simplicidad, comprobabilidad, etc.

arnt
fuente
1

Porque Clang se hace con análisis de sintaxis, pero LLVM ni siquiera ha comenzado con la optimización.

El front-end de Clang ha generado IR (representación intermedia) y no código de máquina. Esas variables son SSA (Asignaciones estáticas individuales); aún no están vinculados a registros y, en realidad, después de la optimización, nunca lo serán porque son redundantes.

Ese código es una representación algo literal de la fuente. Es lo que golpea a LLVM para su optimización. Básicamente, LLVM comienza con eso y se optimiza a partir de ahí. De hecho, para la versión 10 y x86_64, llc -O2 eventualmente generará:

main: # @main
  xor eax, eax
  ret
Olsonista
fuente
Entiendo el proceso en este nivel. Para empezar, quería saber por qué se generó este IR.
macleginn
Puede estar pensando en un compilador como un solo paso. Hay una serie de pases que comienzan con el extremo frontal de Clang que genera IR. Ni siquiera generó este IR textual que, en cambio, alguien solicitó con clang -emit-llvm -S file.cpp Clang en realidad generó una versión de código de bits serializable binaria del IR. LLVM está estructurado como múltiples pases, cada uno tomando y optimizando IR. El primer pase LLVM toma IR de Clang. Se necesita IR porque puede reemplazar Clang con Fortran FE para admitir otro idioma con el mismo optimizador + generador de código.
Olsonista