Você está na página 1de 13

UNIVERSIDAD DE LA INTEGRACIÓN DE LAS AMERICAS

“PROCESADORES DE LENGUAJES (COMPILADORES)”

TEMA: Generación de Códigos


Integrantes
 Alessandra Fernández
 Felipe Armoa
 Raúl Maldonado
 Tania Cáceres
INTRODUCCION

La generación de código es la fase más compleja de un compilador, puesto que no sólo depende
de las características del lenguaje fuente sino también de contar con información detallada acerca
de la arquitectura objetivo, la estructura del ambiente de ejecución y el sistema operativo que
esté corriendo en la máquina objetivo. La generación de código por lo regular implica también
algún intento por optimizar, o mejorar, la velocidad y/o el tamaño del código objetivo
recolectando más información acerca del programa fuente y adecuando el código generado para
sacar ventaja de las características especiales de la máquina objetivo, tales como registros, modos
de direccionamiento, distribución y memoria caché.
Compiladores:
Generación de Código
En programación, la generación de código es una de las fases mediante el cual un compilador
convierte un programa sintácticamente correcto en una serie de instrucciones a ser interpretadas
por una máquina.

Las fase final de un compilador es la generación de código objeto, que por lo general consiste en
código de maquina relocalizable o código ensamblador. Las posiciones de memoria se seleccionan
para cada una de las variables usadas por el programa.

Mediante la generación de código, proceso de optimización se puede aplicar en el código, pero


que puede ser visto como parte de generación de código propia fase. El código generado por el
compilador es un código de objeto de algunos de menor nivel de lenguaje de programación, por
ejemplo, lenguaje ensamblador. Hemos visto que el código fuente escrito en un lenguaje de mayor
nivel se transforma en un menor nivel de idioma que resulta en un menor nivel de código objeto,
que debe tener las siguientes propiedades mínimas:

 Debe llevar el significado exacto del código fuente.


 Debe ser eficiente en términos de uso de la CPU y la administración de la memoria

Después, cada una de las instrucciones intermedias se traduce a una secuencia de instrucciones de
máquina que ejecuta la misma tarea. Un aspecto decisivo es la asignación de variables a registros.

Por ejemplo, utilizando los registros 1 y 2, la traducción del código podría convertirse en:

El primero y segundo operandos de cada instrucción especifica una fuente y un destino,


respectivamente. La F de cada instrucción indica que las instrucciones trabajan con número de
punto flotante. Este código traslada el contenido de la dirección id3 al registro 2, después lo
multiplica por la constante real 60.0. El signo # significa que 60.0 se trata como una constante. La
tercera instrucción pasa id2 al registro1. La cuarta instrucción le suma el valor previamente
calculado en el registro 2. Por último, el valor del registro 1 se pasa a la dirección de idl, de modo
que el código aplica la asignación.

El generador de código recibe como entrada una representación intermedia del programa fuente y
la asigna al lenguaje destino. Si el lenguaje destino es código máquina, se seleccionan registros o
ubicaciones (localidades) de memoria para cada una de las variables que utiliza el programa.
Después, las instrucciones intermedias se traducen en secuencias de instrucciones de máquina
que realizan la misma tarea.

Podríamos entonces decir que la generación de código tiene como objetivo generar el ejecutable
que después empleara el usuario, entonces el compilador recibe una entrada de caracteres, por lo
general escrita por el programador; el compilador realiza los análisis: léxico, sintáctico y
semántico, para generar seguidamente el código intermedio, el código intermedio se genera con
principios de búsqueda de patrones y aplicación de reglas. Después se hace la optimización del
código intermedio; seguidamente se realiza la generación de código objeto en lenguaje de
máquina.

Un generador de código se espera que tenga una comprensión de la máquina de destino entorno
de ejecución y su conjunto de instrucciones. El generador de código debe tomar las siguientes
cosas en cuenta para generar el código:

 Idioma de destino: el generador de código ha de ser consciente de la naturaleza del


idioma de destino para que el código se ha transformado. Que el lenguaje puede facilitar
algunas de las instrucciones específicas para ayudar al compilador generar el código en
una forma más cómoda. El equipo de destino puede tener procesador RISC o CISC
arquitectura.

 Tipo de infrarrojos: representación intermedia tiene diversas formas. Puede ser en


Abstract Syntax Tree (AST) estructura, Notación Polaca Inversa, o 3-código de dirección.

 Selección de la instrucción: el generador de código tiene representación intermedia como


entrada y convierte (mapas) en la máquina de destino. Una representación puede tener
muchas formas (instrucciones) para convertir, por lo que se convierte en la
responsabilidad del generador de código para elegir sabiamente las instrucciones
adecuadas.

 Asignación de registros: un programa tiene un número de valores que se mantiene


durante la ejecución. La arquitectura de la máquina de destino no puede permitir que
todos los valores que se guardan en la memoria de la CPU o de los registros. Generador
de código decide qué valores para mantener en los registros. Asimismo, se decide los
registros con el fin de ser utilizado para mantener estos valores.
 Orden de las instrucciones: Por último, el generador de código determina el orden en que
las instrucciones se ejecutan. Crea listas de instrucciones que se van a ejecutar

La generación de código es usada para construir programas de una manera automática evitando
que los programadores tengan que escribir el código a mano. La generación de código puede
realizarse en tiempo de ejecución, Tiempo de carga, o Tiempo de compilación. Los compiladores
JIT son un ejemplo de generadores de código.

En el modelo de análisis y síntesis de un compilador, la etapa inicial traduce un programa fuente a


una representación intermedia a partir de la cual la etapa final genera el código objeto. Los
detalles del lenguaje objeto se confinan en la etapa final, si esto es posible. Aunque un programa
fuente se puede traducir directamente al lenguaje objeto, algunas ventajas de utilizar una forma
intermedia independiente de la máquina son:

1. Se facilita la redestinación; se puede crear un compilador para una máquina distinta uniendo
una etapa final para la nueva máquina a una etapa inicial ya existente.

2. Se puede aplicar a la representación intermedia un optimizador de código independiente de la


máquina.

Hay lenguajes que son pseudointerpretados que utilizan un código intermedio llamado código-P
que utiliza lo que se denomina bytecodes (sentencias de un µP hipotético). Por ejemplo Java utiliza
los ficheros .class, éstos tienen unos bytecodes que se someten a una JavaVirtualMachine, para
que interprete esas sentencias. Universidad Nacional del Santa Curso: Teoría de Compiladores
Docente: Ing. Mirko Manrique Ronceros ~ 2 ~ En este capítulo se muestra cómo se pueden utilizar
los métodos de analizadores dirigidos por la sintaxis para traducir a un código intermedio,
construcciones de lenguajes de programación como declaraciones, asignaciones y proposiciones
de flujo de control. La generación de código intermedio se puede intercalar en el análisis
sintáctico.

La fase final del modelo de compilador es el generador de código. Toma como entrada una
representación intermedia del programa fuente y produce como salida un programa objeto
equivalente. Las técnicas de generación de código se pueden utilizar con independencia, de si se
produce o no una fase de optimización antes de la generación de código, como en algunos
compiladores denominados “de optimización”. Dicha fase intenta transformar el código
intermedio en una forma de la que se pueda producir código objeto mas eficiente. En el siguiente
capitulo se tratara con detalles la optimización de código.

Las exigencias tradicionalmente impuestas a un compilador son duras. El código de salida debe ser
correcto y de gran calidad, lo que significa que debe utilizar de forma eficaz los recursos de la
maquina objeto. Además, el generador de código mismo debe ejecutarse eficientemente.

Matemáticamente, el problema de generar código óptimo es indecidible. En la práctica, hay que


conformarse con técnicas heurísticas que generan código bueno pero no siempre óptimo. La
elección de las heurísticas es importante, ya que un algoritmo de generación de código
cuidadosamente diseñado puede producir fácilmente código que sea varias veces más rápido que
el producido por un algoritmo diseñado precipitadamente.

ASPECTOS DEL DISEÑO DE UN GENERADOR DE CODIGO


En tanto que los detalles dependen de la maquina objeto y del sistema operativo, aspectos como
el manejo de la memoria, la selección de instrucciones, la asignación de registros y el orden de
evaluación son inherentes en casi todos los problemas de generación de código. En esta sección,
se examinaran los aspectos genéricos del diseño de generadores de código.

ENTRADA AL GENERADOR DE CODIGO


La entrada para el generador de código consta de la representación intermedia del programa
fuente producida por la etapa inicial, junto con información de la tabla de símbolos que se utiliza
para determinar las direcciones durante la ejecución de los objetos de datos denotados por los
nombres de la representación intermedia.
Hay varias opciones para el lenguaje intermedio: representaciones lineales como la notación
postfija, representaciones de tres direcciones como los cuádruplos, representaciones de una
máquina virtual como el código de una máquina de pila y representaciones graficas como los
arboles sintácticos y los GDA. Aunque los algoritmos de este capítulo se expresan desde el punto
de vista de código de tres direcciones, árboles y GDA, muchas de las técnicas también se aplican a
otras presentaciones intermedias.

Se asume que antes de la generación de código, la etapa inicial ha hecho los análisis léxicos y
sintácticos, y traducido el programa fuente a una representación intermedia razonablemente
detallada, así que los valores de los nombres que aparecen en el lenguaje intermedio pueden ser
representados por cantidades que la maquina objeto puede manipular directamente (bits,
enteros, reales, apuntadores, etc.). Tambien se supone que ya ha tenido lugar la comprobación de
tipos necesaria de modo que los operadores de conversión de tipos
GENERACIÓN DE CÓDIGO INTERMEDIO

Después de los análisis sintácticos y semánticos, algunos compiladores generan una


representación intermedia explicita del programa fuente. Se puede considerar esta representación
intermedia como un programa para una maquina abstracta. Esta representación intermedia debe
tener dos propiedades importantes, debe ser fácil de producir y fácil de traducir al programa
objeto.

El código intermedió es particularmente utilizado cuando el objetivo de compilador es producir


código muy eficiente, ya que para hacerlo así se requiere una cantidad importante del análisis de
las propiedades del código objetivo, y esto se facilita mediante el uso del código intermedio.

El código intermedio también puede ser útil al hacer que un compilador sea mas fácilmente re
dirigible: si el código intermedio es hasta cierto punto independiente de la maquina objetivo,
entonces genera código para una maquina objetivo diferente solo requiere volver a escribir el
traductor de código intermedio a código objetivo, y por lo regular esto es mas fácil que volver a
escribir todo un generador de código.

La representación intermedia puede tener diversas formas, se trata una forma intermedia llamada
"CÓDIGO DE TRES DIRRECCIONES" y el "CÓDIGO P"

El código de tres direcciones consiste en una secuencia de instrucciones, cada una de las cuales
tiene como máximo tres operadores.

temp1 := entareal(60)

temp2 := id3 * temp1

temp3 := id2 + temp2

id1 := temp3

Esta representación intermedia tiene varias propiedades.

- primera, cada instrucción de tres direcciones tiene a lo sumo un operador, además de la


asignación. Por tanto, cuando genera esas instrucciones, el compilador tienes de decidir el orden
en que deben efectuarse las operaciones; la multiplicación precede a la adicción en el programa
fuente.

- segunda, el compilador debe generar un nombre temporal para guardar los valores calculados
por cada instrucción.

- tercera, algunas instrucciones de "tres direcciones" tiene menos de tres operadores, por
ejemplo, la primera y la ultima instrucción.
EL CÓDIGO P
El código P comenzó como un código ensamblador objetivo estándar producido por varios
compiladores Pascal en la década de 1970 y principios de la de 1980. Fue diseñado para código
real para una maquina de pila hipotética la idea era hacer que los compiladores de Pascal se
transportaran fácilmente requiriendo solo que se volviera a escribir el interprete de la maquina P
para una plataforma, el código P también a probado ser útil como código intermedio y sean
utilizado varias extensiones y modificaciones del mismo en diverso compiladores de código nativo,

La mayor parte para lenguaje tipo Pascal.

Como el código P fue diseñado para ser directamente ejecutable, contiene una descripción
implícita de un ambiente de ejecución particular que incluye tamaños de datos, además de mucha
información especifica para la maquina P, que debe conocer si se desea que un programa de
código P se comprensible. La maquina P esta compuesta por una memoria de código, una
memoria de datos no específica para variables nombre das y una pila para datos temporales, junto
como cualquiera registro que sea necesario para mantener la pila y apoyar la ejecución.

COMPARACIÓN
El código P en muchos aspectos está más código de maquina real que al código de tres
direcciones. Las instrucciones en código P también requiere menos de tres direcciones: tosas las
instrucciones que hemos visto son instrucciones de "una dirección" o "cero direcciones". Por otra
parte, el código P es menos compacto que el código de tres direcciones en términos de números
de instrucciones, y el código P no esta "auto contenido" en el sentido que las instrucciones
funciones implícitas en una pila (y las localidades de pila implícitas son de hecho las direcciones
"perdidas"). La ventaja respecto a la pila es que contiene los valores temporales necesarios en
cada punto del código, y el compilador no necesita asignar nombre a ninguno de ellos, como el
código de tres direcciones.

Ejemplo

Se puede considerar esta una representación intermedia como un programa para una maquina
abstracta.

—Traducción de una proposición

CÓDIGO DE TRES DIRECCIONES


Las reglas semánticas para generar código de tres direcciones a partir de construcciones de
lenguaje de programación comunes son similares a las reglas para construir arboles sintácticos o
para notación posfija.

El código de tres direcciones es una secuencia de preposiciones de la forma general

x := y op z
Donde 'x', 'y' y 'z' son nombres, constates o variables temporales generadas por el
compilador, op representa cualquier valor, como un operador aritmético de punto fijo o flotante,
o un operador lógico sobre datos con valores booleanos. Obsérvese que no se permite ninguna
expresión aritmética compuesta, pues solo hay un operador en el lado derecho de una
proposición. Por tanto, una expresión del lenguaje fuente como x+y*z se puede traducir en una
secuencia

t1 := y * z

t2 := x + t1

Donde t1 y t2 son nombres temporales generados por el compilador.

El código de tres direcciones es una reprehensión linealizada de un árbol sintáctico o un GDA en la


que lo nombres explícitos corresponde a los nodos interiores del grafo.

t1 := -c t1 := -c

t2 := b * t1 t2 := b*t1

t3 := -c t5 := t2 + t2

t4 := b * t3 a := t5

t5 := t2 +t4

a := t5

(a) Código para el árbol sintáctico (b) Código para el GDA

Las proposiciones de tres direcciones son análogas al código ensamblador. Las proposiciones
pueden tener etiquetas simbólicas y existen proposiciones para el flujo del control. Una etiqueta
simbólica representa el índice de una proposición de tres direcciones en la matriz que contiene
código intermedio. Los índices reales se pueden sustituir por las etiquetas ya sea realizado una
pasada independiente o mediante "relleno en retroceso".

La notación gen(x ':=' y '+' z) en la figura (1) para representar la proposición de tres direcciones x
:= y + z

Se puede añadir proposiciones de flujo del control al lenguaje de asignaciones de la figura (1)
mediante producciones y reglas semánticas como las de las proposiciones while de la figura (2).
• Ventajas:

– Permite abstraer la má- quina, separar operaciones de alto nivel de su implementación a bajo
nivel.

– Permite la reutilización de los front-ends y backends.

– Permite optimizaciones generales.

• Desventajas:

– Implica una pasada más para el compilador (no se puede utilizar el modelo de una pasada,
conceptualmente simple).

– Dificulta llevar a cabo optimizaciones especí- ficas de la arquitectura destino.

– Suele ser ortogonal a la máquina destino, la traducción a una arquitectura específica será más
larga e ineficiente.
Optimización de código
Objetivo

– Obtener código que se ejecuta más eficientemente según los criterios

• Tiempo de ejecución (optimización temporal)

• Espacio de memoria utilizado (optimización espacial).

Funcionamiento

– Revisa el código generado a varios niveles de abstracción y realiza las optimizaciones aplicables
al nivel de abstracción.

Representaciones de código intermedio de más a menos abstractas

– Árbol sintáctico abstracto: optimizar subexpresiones redundantes, reducción de frecuencia, etc.

– Tuplas o cuadruplas: optimizar en uso de los registros o de las variables temporales.

–Ensamblador/Código máquina: convertir saltos a saltos cortos, reordenar instrucciones.

Condiciones que se han de cumplir

– El código optimizado se ha de comportar igual que el código de partida excepto por ser más
rápido o ocupar menos espacio.

– Hay que buscar transformaciones que no modifiquen el comportamiento del código según el
comportamiento definido para el lenguaje de programación. Ejemplo:

Si no se ha definido el orden de evaluación de los operandos la siguiente optimización es válida

B=2*A+(A=c*d);

Pasar a A=c*d;

B=A*3;

La fase de optimización de código trata de mejorar el código intermedio, de modo que resulte un
código de maquina más rápido de ejecutar. Algunas optimizaciones son triviales. Por ejemplo, un
algoritmo natural genera el código intermedio utilizando una instrucción para cada operador de la
representación de árbol después del análisis semántico, aunque hay una forma mejor de realizar
los mismos cálculos usando las dos instrucciones.
Este sencillo algoritmo no tiene nada de malo, puesto que el problema se puede solucionar en la
fase de optimización de código. Esto es, el compilador puede deducir que la conversión de 60 de
entero a real se puede hacer de una vez por todas en el momento de la compilación, de modo que
la operación entareal se puede eliminar. Además temp3 se usa solo una vez, para transmitir su
valor a idl. Entonces resulta seguro sustituir idl por temp3, a partir de lo cual la última proposición
de no se necesita y se obtiene el código.

Hay mucha variación en la cantidad de optimización de código que ejecutan los distintos
compiladores. En los que hacen mucha optimización, llamados “compiladores optimadores”, una
parte significativa del tiempo del compilador se ocupa en esta fase. Sin embargo, hay
optimizaciones sencillas que mejoran sensiblemente el tiempo de ejecución del programa objeto
sin retardar demasiado la compilación. Se estudian muchos de estos aspectos, mientras que se da
la tecnología utilizada por los compiladores optimadores más potentes.

Para la creación de generadores de código se deben considerar los siguientes


aspectos:

La arquitectura de software para la cual se va a desarrollar el generador Las características


especificas del lenguaje de programación para el cual se hará el generador. El lenguaje con el que
se desarrollará el propio generador Responder los interrogantes: ¿La generación de código se
realizará a partir de modelos como Uml1? ¿La generación de código se hará a partir de las tablas
de una base de datos?, ¿Se realizará un generador de código que su resultado sea fragmentos de
código que son de uso más frecuente en el software? ¿Se creará un generador genérico que
"genere" código para diferentes lenguajes. Las reglas de utilización del generador, en otras
palabras, la forma adecuada para que los usuarios del generador obtengan el mayor provecho.

En síntesis para crear un generador de código se deben hacer muchas de las tareas que realizan
los compiladores; algunas de estas tareas son: la búsqueda de patrones, la escritura de código, el
análisis sintáctico, el análisis léxico y la optimización de código. Estas tareas las realiza el
desarrollador una vez para una arquitectura específica.

1 UML . (Unified Modeling Planguage) Lenguaje Unificado de Modelado. Lenguaje gráfico, basado
en la teoría de objetos, para la representación gráfica de un sistema.