20 de septiembre de 2010

Diseño de un generador de código objeto

El generador de código objeto puede considerarse como la penúltima fase de un compilador, la cual se encarga de tomar como entrada el código intermedio generado por el front-end, y producir código objeto de la arquitectura target para luego entrar en la fase de optimización de código.

Para el generador de código objeto, se puede asumir que el front-end ha hecho los análisis léxico y sintáctico, y ha traducido a una representación intermedia suficientemente detallada (LLVM?), en la que los valores de los nombres que aparecen en código intermedio pueden ser representados por cantidades que la máquina objeto puede manejar directamente. Además, se supone que se hecho el chequeo de tipos necesario y se han detectado los errores semánticos. Por lo tanto, la fase de generación de código trabaja bajo el supuesto que su entrada no contiene errores.

La salida del generador de código consiste en el programa objeto. Existe un variedad de formas para el código objeto : lenguaje de máquina absoluto (imagen de memoria), lenguaje de máquina relocalizable (.OBJ en Intel Relocatable Format, ELF, etc.), lenguaje ensamblador o incluso otro lenguaje de programación.

La naturaleza del conjunto de instrucciones de la máquina objeto determina la dificultad de la selección de instrucciones. Es importante que el conjunto de instrucciones sea uniforme y completo, para generar reglas fáciles de traducción, por ejemplo : una arquitectura como la de Intel x86 permite que los compiladores tengan reglas bien definidas y cortas para casi todas las operaciones, mientras que una máquina RISC como el SPARC no posee un conjunto de instrucciones completo (desventaja para el constructor de compiladores, ventaja para los usuarios) y hace que la generación de código sea muy compleja. Otras arquitecturas, como la de MIPS, presenta un problema aparte, puesto que requiere que se reordene el código de ciertas maneras y se evite el uso de ciertas instrucciones para evitar hazards en el pipeline.

Otra de las consideraciones a tener para el generador de código consiste en la asignación de registros. Los registros son los elementos más valiosos y escasos en la fase de generación de código, puesto que el CPU solamente puede procesar datos que se encuentren en registros. Además, las instrucciones que implican operandos en registros son más cortas y rápidas que las de operandos en memoria. El uso de registros se divide generalmente en dos subproblemas :


1)Durante la asignación de registros, se selecciona el conjunto de variables y/o constantes que residirán en los registros en un momento del programa.
2)Durante una fase posterior a la anterior, se escoje el registro específico en el que residirá una variable.

Es muy difícil encontrar una asignación óptima de registros a variables. Matemáticamente, el problema es NP-completo. Este problema se complica todavía más debido a restricciones de hardware, de sistema operativo o ambos. Puede ser que el conjunto de instrucciones de la máquina no sea ortogonal (problema que existe en los x86 pero no en los Power por ejemplo). Algunas máquinas además requieren para ciertas operaciones el uso de un conjunto de registros para algunos operandos y resultados.

19 de septiembre de 2010