Articulo de referencia

Generación de código (compilador)

En informática , la generación de código forma parte de la cadena de procesos de un compilador , en la que una representación intermedia del código fuente se convierte en una fo...

En informática , la generación de código forma parte de la cadena de procesos de un compilador , en la que una representación intermedia del código fuente se convierte en una forma (por ejemplo, código máquina ) que el sistema de destino puede ejecutar fácilmente.

Los compiladores sofisticados suelen realizar múltiples pasadas sobre diversas formas intermedias. Este proceso de varias etapas se utiliza porque muchos algoritmos de optimización de código son más fáciles de aplicar de forma individual, o porque la entrada de una optimización depende del procesamiento completado por otra. Esta organización también facilita la creación de un único compilador que puede funcionar con múltiples arquitecturas, ya que solo la última etapa de generación de código (el backend ) necesita variar según la arquitectura. (Para obtener más información sobre el diseño de compiladores, consulte Compiler ).

La entrada al generador de código suele consistir en un árbol de análisis sintáctico o un árbol de sintaxis abstracta . [ 1 ] El árbol se convierte en una secuencia lineal de instrucciones, generalmente en un lenguaje intermedio como el código de tres direcciones . Las etapas posteriores de la 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, una optimización de paso de inspección probablemente no se llamaría "generación de código", aunque un generador de código podría incorporarla).

Tareas principales

Además de convertir una representación intermedia en una secuencia lineal de instrucciones de máquina, un generador de código típico intenta optimizar el código generado.

Las tareas que suelen formar parte de la fase de "generación de código" de un compilador sofisticado incluyen:

La selección de instrucciones se lleva a cabo normalmente mediante un recorrido recursivo en postorden del árbol de sintaxis abstracta, comparando configuraciones particulares del árbol con plantillas; por ejemplo, el árbol podría transformarse en una secuencia lineal de instrucciones generando recursivamente las secuencias para y , y luego emitiendo la instrucción .W := ADD(X,MUL(Y,Z))t1 := Xt2 := MUL(Y,Z)ADD W, t1, t2

En un compilador que utiliza un lenguaje intermedio, puede haber dos etapas de selección de instrucciones : una para convertir el árbol de análisis sintáctico en código intermedio y una segunda fase, mucho más tarde, para convertir el código intermedio en instrucciones del conjunto de instrucciones de la máquina de destino. Esta segunda fase no requiere un recorrido del árbol; puede realizarse linealmente y, por lo general, implica una simple sustitución de las operaciones del lenguaje intermedio por sus correspondientes códigos de operación . Sin embargo, supongamos que el compilador es un traductor de lenguaje (por ejemplo, uno que convierte Java a C++ ). En ese caso, la segunda fase de generación de código puede implicar la construcción de un árbol a partir del código intermedio lineal.

Generación de código en tiempo de ejecución

Cuando la generación de código se produce en tiempo de ejecución , como en la compilación justo a tiempo (JIT), todo el proceso debe ser eficiente en términos de espacio y tiempo. Por ejemplo, cuando se interpretan expresiones regulares y se utilizan 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, ya que la primera suele crearse más rápidamente y ocupa menos memoria que la segunda. A pesar de generar generalmente código menos eficiente, la generación de código JIT puede aprovechar la información de perfilado disponible solo en tiempo de ejecución.

La tarea fundamental de tomar una entrada en un idioma y producir una salida en un idioma significativamente diferente puede entenderse en términos de las operaciones de transformación básicas de la teoría de lenguajes formales . En consecuencia, algunas técnicas desarrolladas inicialmente para su uso en compiladores se han empleado de otras maneras. Por ejemplo, YACC (Yet Another Compiler-Compiler ) toma una entrada en forma de Backus-Naur y la convierte en un analizador sintáctico en C. Aunque se creó inicialmente para la generación automática de un analizador sintáctico para un compilador, YACC también se usa a menudo para automatizar la escritura de código que necesita modificarse cada vez que cambian las especificaciones. [ 3 ]

Muchos entornos de desarrollo integrados (IDE) admiten algún tipo de generación automática de código fuente , a menudo utilizando algoritmos comunes con los generadores de código de compilador, aunque generalmente menos complejos. (Véase también: Transformación de programas y Transformación de datos ).

Reflexión

En general, un analizador sintáctico y semántico intenta recuperar la estructura del programa a partir del código fuente, mientras que un generador de código utiliza esta información estructural (por ejemplo, los tipos de datos ) para producir código. En otras palabras, el primero añade información, mientras que el segundo la pierde . 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 suelen incorporar información sintáctica y semántica, así como el código necesario para la ejecución.

Véase también

Referencias

  1. Steven Muchnick; Muchnick and Associates (15 de agosto de 1997). Diseño e implementación avanzados de compiladores . Morgan Kaufmann. ISBN 978-1-55860-320-2. generación de código.
  2. Aho, Alfred V.; Ravi Sethi; Jeffrey D. Ullman (1987). Compiladores: Principios, técnicas y herramientas . Addison-Wesley. pág. 15. ISBN  0-201-10088-6.
  3. Generación de código: La verdadera lección de Rails . Artima.com (16 de marzo de 2006). Consultado el 10 de agosto de 2013.