Generación de código (compilador)
En informática, la generación de código es parte de la cadena de proceso de un compilador y convierte la representación intermedia del código fuente en una forma (por ejemplo, código de máquina) que el sistema de destino puede ejecutar fácilmente.
Los compiladores sofisticados normalmente realizan varias pasadas sobre varias formas intermedias. Este proceso de varias etapas se usa porque muchos algoritmos para la optimización del código son más fáciles de aplicar uno a la vez, o porque la entrada a una optimización depende del procesamiento completo realizado por otra optimización. Esta organización también facilita la creación de un solo compilador que puede apuntar a múltiples arquitecturas, ya que solo la última de las etapas de generación de código (el backend) necesita cambiar de un objetivo a otro. (Para obtener más información sobre el diseño del compilador, consulte Compilador).
La entrada al generador de código generalmente consiste en un árbol de análisis o un árbol de sintaxis abstracta. El árbol se convierte en una secuencia lineal de instrucciones, generalmente en un lenguaje intermedio, como un código de tres direcciones. Las etapas posteriores de compilación pueden o no denominarse "generación de código", dependiendo de si implican un cambio significativo en la representación del programa. (Por ejemplo, un pase de optimización de mirilla probablemente no se llamaría "generación de código", aunque un generador de código podría incorporar un pase de optimización de mirilla).
Tareas principales
Además de la conversión básica de una representación intermedia a una secuencia lineal de instrucciones de máquina, un generador de código típico intenta optimizar el código generado de alguna manera.
Tareas que normalmente forman parte de la "generación de código" de un compilador sofisticado. fase incluyen:
- Selección de instrucciones: qué instrucciones utilizar.
- Programación de instrucciones: en qué orden poner esas instrucciones. La programación es una optimización de velocidad que puede tener un efecto crítico en las máquinas oleadas.
- Asignación del registro: asignación de variables a registros del procesador
- Debug data generation if required so the code can be debugged.
La selección de instrucciones generalmente se lleva a cabo mediante un recorrido recursivo posterior al orden en el árbol de sintaxis abstracta, comparando configuraciones de árboles particulares con plantillas; por ejemplo, el árbol W:= ADD(X,MUL(Y,Z))
podría transformarse en una secuencia lineal de instrucciones generando recursivamente las secuencias para t1:= X
y t2:= MUL(Y,Z)
, y luego emitiendo la instrucción ADD W, t1, t2
.
En un compilador que usa un lenguaje intermedio, puede haber dos etapas de selección de instrucciones: una para convertir el árbol de análisis en código intermedio y una segunda fase, mucho más tarde, para convertir el código intermedio en instrucciones del conjunto de instrucciones del objetivo. máquina. Esta segunda fase no requiere un recorrido de árbol; se puede hacer de forma lineal y, por lo general, implica un reemplazo simple de operaciones de lenguaje intermedio con sus códigos de operación correspondientes. Sin embargo, si el compilador es en realidad un traductor de idiomas (por ejemplo, uno que convierte Java a C++), entonces la segunda fase de generación de código puede implicar construir un árbol a partir del código intermedio lineal.
Generación de código de tiempo de ejecución
Cuando la generación de código ocurre en tiempo de ejecución, como en la compilación justo a tiempo (JIT), es importante que todo el proceso sea eficiente con respecto al espacio y el tiempo. Por ejemplo, cuando se interpretan y utilizan expresiones regulares para generar código en tiempo de ejecución, a menudo se genera una máquina de estados finitos no determinista en lugar de una determinista, porque normalmente la primera se puede crear más rápidamente y ocupa menos espacio de memoria que la segunda. A pesar de que generalmente genera código menos eficiente, la generación de código JIT puede aprovechar la información de creación de perfiles que está disponible solo en tiempo de ejecución.
Conceptos relacionados
La tarea fundamental de tomar información en un idioma y producir resultados en un idioma no trivialmente diferente puede entenderse en términos de las operaciones de transformación centrales de la teoría del lenguaje formal. En consecuencia, algunas técnicas que se desarrollaron originalmente para su uso en compiladores también se han empleado de otras formas. Por ejemplo, YACC (Yet Another Compiler-Compiler) toma la entrada en formato Backus-Naur y la convierte en un analizador en C. Aunque originalmente se creó para la generación automática de un analizador para un compilador, yacc también se usa a menudo para automatizar la escritura. código que debe modificarse cada vez que se modifican las especificaciones.
Muchos entornos de desarrollo integrados (IDE) admiten alguna forma de generación automática de código fuente, a menudo usando algoritmos en común con los generadores de código de compilación, aunque normalmente son menos complicados. (Ver también: Transformación de programas, Transformación de datos.)
Reflexión
En general, un analizador de sintaxis y semántica intenta recuperar la estructura del programa del código fuente, mientras que un generador de código usa esta información estructural (por ejemplo, tipos de datos) para producir código. En otras palabras, el primero añade información mientras que el segundo pierde parte de la información. Una consecuencia de esta pérdida de información es que la reflexión se vuelve difícil o incluso imposible. Para contrarrestar este problema, los generadores de código a menudo incorporan información sintáctica y semántica además del código necesario para la ejecución.
Contenido relacionado
Analizador de Earley
Error de ráfaga
Perl