Está en la página 1de 21

Compiladores

Generación de código
Introducción
• La fase final de nuestro modelo de
compilador es el generador de código
• Toma por entrada una RI del fuente y
produce su equivalente en código objeto
• Las técnicas que veremos son
independientes de si se realiza o no
optimización
Introducción
• Los requerimientos que, tradicionalmente,
se imponen en un generador de código
son muy demandantes
• El código generado debe ser correcto y de
alta calidad (debe hacer uso efectivo de
los recursos de la máquina objeto)
• El generador, además, debe ser eficiente
Introducción
• El problema de generar código óptimo es
no decidible
• En la práctica, se utilizan heurísticas que
generan código bueno (aunque
probablemente no óptimo)
Aspectos generales
• Entrada al generador de código (RI)
• Programas objeto
• Administración de memoria
• Selección de instrucciones
• Adjudicación de registros
• Elección de orden de evaluación
Entrada al generador de código
• La entrada consiste en la RI del código fuente,
producida por el frontend, junto con la
información de la tabla de símbolos
• La TS se utiliza para determinar las direcciones
en tiempo de ejecución de los objetos de datos
denotados por los nombres en la RI
• Asumimos que la RI es de “suficiente bajo nivel”
con tipos de datos que se pueden mapear
razonablemente a la arquitectura objeto
Entrada al generador de código
• Asumimos que el chequeo de tipos ya fue
realizado, con la adecuada inserción de
operadores de cambio de tipos, y demás
• Asumimos que no hay errores semánticos
obvios (Ej., intentar indizar un arreglo con
un punto flotante)
Entrada al generador de código
• Esta fase se basa en la asunción de que
la RI no tiene errores (aunque en algunos
compiladores, el chequeo se realiza a la
vez que la generación de código)
Código objeto
• La salida del generador es el código o
programa objeto, el cual puede tomar
distintas formas:
– código máquina absoluto
– código máquina reubicable
– código assembler
Código objeto
• Producir código máquina absoluto como
salida conlleva la ventaja de que puede
ser ubicado en un lugar fijo de memoria y
ejecutado inmediatamente
• Código reubicable (también llamado
módulo objeto) permite tener módulos que
se compilan por separado. Luego se unen
(linking) y se cargan
Código objeto
• En este caso se gana mucha flexibilidad
en función del trabajo que conlleva la
unión y carga.
• Si la máquina objeto no se hace cargo de
la reubicación, el compilador debe
producir información explícita para el
linker
Código objeto
• Finalmente, producir código assembler
como salida facilita el proceso de
compilación. Podemos generar
instrucciones simbólicas y usar las macros
de assembler como ayudas.
• Finalmente, facilita la optimización “a
mano” del código generado.
• El precio es que aparece una etapa final
de ensamblado
Administración de memoria
• Consiste en determinar la posición de
memoria en la que los diferentes símbolos
del programa almacenan la información
• Depende de la estrategia utilizada para la
gestión de memoria, el mecanismo puede
variar
Selección de instrucciones
• La naturaleza del conjunto de instrucciones de
la máquina objeto, determina la dificultad de la
selección de instrucciones
• La uniformidad y completitud del conjunto de
instrucciones son factores muy importantes.
• Si la máquina objeto no soporta cada tipo de
datos de forma uniforme, entonces se necesitan
estrategias alternativas
Selección de instrucciones
• Si no nos interesa la eficiencia, para cada tipo
de sentencia en C3D podemos diseñar un
esqueleto de código que muestra el código
objeto a generar para esa construcción
• Ej.: x:=y+z
– MOV Y, R0
– ADD Z, R0
– MOV R0, X
• Esto genera código pobre
Selección de instrucciones
• Por ejemplo:
– a := b + c
– d := a + e

• Se traduce a
– MOV b, R0
– ADD c, R0
– MOV R0, a  redundante?
– MOV a, RO  redundante?
– ADD e, R0
– MOV R0, d
Selección de instrucciones
• La calidad del código generado se mide en
función de
– Tamaño
– Velocidad
• En función de la variedad de instrucciones uno
puede seleccionar la instrucción más
performante (ej. INC vs MOV - ADD - MOV).
• Para los casos no triviales, es un problema
muy complejo.
Asignación de registros
• Operar sobre registros es más rápido y
eficiente que operar sobre memoria

• Por ello, la adjudicación eficiente de


registros tiene un gran impacto en la
performance
Asignación de registros
• El uso de registros puede dividirse en
dos subproblemas:
– Durante la reserva de registros
(allocation), se seleccionan el conjunto de
variables que vivirá en registros en un punto
del programa.
– Durante la (posterior) asignación de
registros (assignation), se elige el registro
específico para cada variable.
Orden de evaluación
• El orden en que algunas computaciones
se llevan a cabo puede afectar la
eficiencia del código objeto
• Algunos ordenes requieren menos
registros para almacenar valores
intermedios
• Seleccionar el orden óptimo es también
NP-completo
Orden de evaluación
• El orden en que algunas computaciones
se llevan a cabo puede afectar la
eficiencia del código objeto
• Algunos ordenes requieren menos
registros para almacenar valores
intermedios
• Seleccionar el orden óptimo es también
NP-completo