You are on page 1of 17

ANTOLOGÍA

COMPILADORES

Otoño 2009

1 de 17
INDICE
1.1 Introducción 2
1.2 Optimización de código y generación de código objeto
2 1.2.1 Optimización de código
2
1.2.1.1 Cálculo previo de constantes
3
1.2.1.2 Reducción de fuerza y frecuencia 3
1.2.1.3 Optimización de lazos
4
1.2.1.4 Eliminación de código
5
1.2.1.5 Reacomodos 5
1.2.1.6 Optimización local 5
1.2.2 Generador de código objeto 6
1.2.2.1 Diseño de un generador de código
6
Entrada al generador de código
6
Programas objeto 7
Administración de la memoria
7
Selección de instrucciones 7
Asignación de registros 8
1.2.2.2 La máquina objeto 9
Costos de las instrucciones 9
1.2.2.3 Bloques 10
Bloques básicos 10
Transformaciones en bloques básicos
11
Grafos de flujo 12
1.2.2.4 Un generador de código simple
13
Algoritmo para generación de código
13
Generación de código para otro tipo de
proposiciones14
1.3 Conclusión 15
1.4 Bibliografía 16
1.5 Referencia 16

2 de 17
1.1 INTRODUCCIÓN
A menudo se puede lograr que el código directamente producido
por los algoritmos de compilación se ejecute más rápidamente o que
ocupe menos espacio, o ambas cosas. Esta mejora se consigue
mediante transformaciones de programas que se denominan
optimizaciones.

La generación de código no solo depende del código intermedio,


sino también del hardware destino y de sus instrucciones. Pero la
generación de código es la parte más importante del compilador,
porque la ejecución de buen código puede ser más rápida que la de
código mal generado.

Posición del optimizador y del generador de código:

Programa Etapa Código Optimador Código Generador Programa


fuente inicial intermedio de código intermedio de código objeto

Tabla de
símbolos

1.2 OPTIMIZACIÓN DE CÓDIGO Y


GENERACIÓN DE CÓDIGO
OBJETO

1.2.1 OPTIMIZACIÓN DE CÓDIGO

Las técnicas de optimización se basan en un extenso análisis de


la estructura del programa y del flujo de datos. Durante la

3 de 17
optimización suele subdividirse el programa en regiones de
optimización, y las técnicas empleadas pueden categorizarse como
independientes de la máquina y dependientes de la máquina. Las
técnicas de optimización independientes de la máquina no tienen
porque considerar el conocimiento de la estructura de hardware ni el
conjunto de instrucciones de la máquina destino, por lo que son de
carácter general. En cambio, las técnicas dependientes de la máquina
deben conocer el hardware y el conjunto de instrucciones, ya que
afectan cosas como la asignación de registros o a selección de
instrucciones.
Entre las técnicas de optimización están las siguientes:
 Cálculo previo de constantes;
 Reducción de fuerza y frecuencia;
 Optimización de lazos(ciclos);
 Eliminación de código;
 Reacomodos;
 Optimización local.

1.2.1.1 CALCULO PREVIO DE CONSTANTES

Cuando aparecen varias constantes en una expresión aritmética,


puede ser posible combinarlas para formar una sola constante. Por
ejemplo, una proposición de asignación de la forma
i := mayor – menor + 5,

donde mayor y menor son las constantes mayor = 10 y menor =


1, se puede simplificar a

i := 14

Este cálculo puede efectuarse en el momento de la compilación.


Su efecto es que el código generado requiere menos memoria y que
aumenta la velocidad de ejecución del programa.
El cálculo previo de constantes no solo tiene que ser con
proposiciones simples, sino que además puede aplicarse a pares de
proposiciones. Por supuesto, esto requiere un análisis previo del flujo
de datos. Ejemplo:
i := 10;
k := i * 4;

que puede simplificarse a

i := 10;
k := 40;

si no hay ningún otro código entre estas dos proposiciones que


cambie el valor de i.
Una vez más, el código generado requerirá menos memoria y se
reducirá el tiempo de ejecución del programa.

4 de 17
1.2.1.2 REDUCCIÓN DE FUERZA Y FRECUENCIA

La reducción de fuerza es el proceso por el cual una operación


costosa (en términos de tiempo de ejecución) se reemplaza por una
más barata; la reducción de frecuencia es un proceso por el cual
ciertos cálculos se mueven de una posición a otra para que se
ejecuten con menos frecuencia.
Las optimizaciones representativas que usan la reducción de
fuerza son:
 Reemplazar una operación de exponenciación por una de
multiplicación, más rápida; por ejemplo, y ** 2 puede
sustituirse por y * y.
 Reemplazar una operación de multiplicación en un lazo por
una operación de suma, más rápida; por ejemplo podemos
sustituir
FOR i := 1 TO 10000 DO h[i] := i * 2 END; por
FOR i := 1 TO 10000 DO h[i] := i * i END;
Esta reducción siempre es posible para multiplicaciones
entre el contador del lazo y una variable que nunca cambia
dentro del lazo.
 Reemplazar las operaciones de multiplicación por una
potencia de dos (por ejemplo, x * 2, x * 4, x * 8, etc.) por
operaciones de desplazamiento aritmético.

La reducción de frecuencia también se conoce como traslado de


código. Si un lazo contiene cálculos que producen el mismo resultado
cada vez que se ejecuta el lazo, es posible sacar los cálculos del lazo
(introduciendo variables temporales, en ciertas circunstancias). Por
ejemplo:

i := 0;
REPEAT
x := x + h[i + j – k]; i := i + 3;
UNTIL (i >max – 3)

puede reemplazarse por

t1 := j – k;
t2 := max – 3;
i := 0;
REPEAT
x := x + h[i + ti] ; i := i + 3 ;
UNTIL (i > t2)

De esta manera se aprovecha la memoria temporal para obtener


un considerable aumento en la velocidad de ejecución.

5 de 17
1.2.1.3 OPTIMIZACIÓN DE LAZOS

La optimización de lazos es probablemente la entidad que


produce los mejores resultados en cuanto al tiempo de ejecución de
un programa. Consideremos la siguiente proposición FOR.
FOR i := 1 TO 10000 DO h[i] := i + x * y END;

Como x y y no cambian en el lazo, es posible reemplazar 10000


multiplicaciones por una sola si se mueve la multiplicación fuera del
lazo. Por supuesto, con esto aumentará la velocidad de ejecución. Así,
es obvio que la aplicación de las técnicas de reducción de fuerza y
frecuencia pueden servir para optimizar lazos.
Otras técnicas para la optimización de lazos pueden describirse
como optimización por combinación y optimización por desarrollo.
Una secuencia de proposiciones de lazo, como
FOR i := 1 TO 10000 DO h[i] := 1 END;
FOR j := 1 TO 10000 DO s[j] := j END;

puede combinarse en un mismo lazo si operan en el mismo


intervalo:

FOR i := 1 TO 10000 DO h[i] := 1; s[i] := i END;

Si el cuerpo del lazo es muy pequeño y el número de veces que


se ejecuta este cuerpo también es pequeño y conocido en el
momento de la compilación, como
FOR i := 1 TO 5 DO h[i] := 1 END;

es posible evitar el código de control de lazo sustituyendo el lazo


por:
h[1] := 1; h[2] := 1; h[3] := 1; h[4] := 1; h[5] := 1;

que es un código secuencial equivalente.

1.2.1.4 ELIMINACIÓN DE CÓDIGO

Otra técnica más de optimización se basa en la eliminación de


código redundante y en subexpresiones comunes. Un código es
redundante si nunca se puede llegar a él o si una expresión contiene
electos superfluos.
Los elementos redundantes en expresiones en expresiones
aritméticas son la suma de 0 o la multiplicación pro 1. Es probable
que el programador no escriba estos elementos redundantes, pero sí
pueden aparecer como resultado de la optimización por cálculo previo
de constantes.

Las subexpresiones comunes pueden ser especificadas en forma


explícita por el programador, como en
x := a / (j * k) – b / (j * k) ;

6 de 17
donde j * k es la subexpresión común. Esta subexpresión se
puede eliminar como sigue:
t := j * k; x := a / t – b / t;

No obstante, las subexpresiones comunes también pueden


aparecer en forma implícita al indizar matrices que comprendan
operaciones de multiplicación o suma.

1.2.1.5 REACOMODOS

De acuerdo con las reglas de asociatividad y distributividad, es


posible efectuar ciertos reacomodos en las expresiones lógicas y
aritméticas para obtener una reducción en la memoria temporal o un
incremento en la velocidad de ejecución.

1.2.1.6 OPTIMIZACIÓN LOCAL

Esta técnica puede ser dependiente de la maquina si la


optimización se basa en el conjunto de instrucciones de la máquina
destino. Sin embargo, esta técnica también puede ser independiente
de la máquina si, por ejemplo la optimización se basa en el código
intermedio.
Considerando un grupo de instrucciones, puede efectuarse una
optimización de varias maneras.
 Reemplazar las instrucciones costosas por otras más
baratas, por ejemplo,
MOV 0, R0

puede ser sustituida por una instrucción de puesta a cero


que se ejecuta con mayor rapidez:

CLR R0

En forma similar, podemos emplear instrucciones de


desplazamiento en lugar de operaciones de multiplicación,
o bien usar simplificaciones algebraicas.
 Reducir las transformaciones de datos redundantes que
frecuentemente aparecen en proposiciones consecutivas;
por ejemplo,
MOV R0, x
MOV x, R0
 Reemplazar cadenas de instrucciones de ramificación por
una sola instrucción de ramificación; es decir, si se
transfiere el control a otra instrucción de ramificación,
podemos reemplazar la dirección por la del destino final.

7 de 17
Las optimizaciones pueden dar lugar a otras optimizaciones o
mejoras del código, de manera que es necesario repetir varias veces
la optimización local para obtener la máxima mejora del código.

1.2.2 GENERADOR DE CÓDIGO OBJETO

1.2.2.1 DISEÑO DE UN GENERADOR DE CÓDIGO

ENTRADA AL GENERADOR DE CÓDIGO

La entrada para el generador de código consta de la


representación intermedia del programa fuente producida por la
etapa inicial (que consta del analizador léxico, el analizador sintáctico
y se ha traducido el programa fuente a una representación intermedia
razonablemente detallada), 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.

PROGRAMAS OBJETO

La salida del generador de código es el programa objeto. Esta


salida puede adoptar una variedad de formas: lenguaje de máquina
absoluto, lenguaje de máquina relocalizable o lenguaje ensamblador.
Producir como salida un programa en lenguaje máquina
absoluto tiene la ventaja de que se puede colocar en una
posición fija de memoria y ejecutarse inmediatamente.
Producir como salida un programa en lenguaje máquina
relocalizable (módulo objeto) permite que los subprogramas
se compilen por separado. Aunque se tenga que pagar el
costo añadido de enlazar y cargar si se producen módulos
objeto relocalizables, se gana mucha flexibilidad al poder
compilar subrutinas por separado y llamar desde un módulo
objeto a otros programas previamente compilados.
Producir como salida un programa en lenguaje ensamblador
facilita el proceso de generación de código. Se pueden
generar instrucciones simbólicas y utilizar las macros del
ensamblador para ayudar a generar el código. Como producir
código ensamblador no duplica la tarea completa del
compilador, esta elección es otra alternativa razonable,
especialmente para una máquina con memoria pequeña,
donde un compilador debe utilizar varias pasadas.

8 de 17
ADMINISTRACIÓN DE LA MEMORIA

La correspondencia entre los nombres del programa fuente con


direcciones de objetos de datos en la memoria durante la ejecución la
realiza la etapa inicial en cooperación con el generador de código. Se
usa la suposición de que un nombre en una proposición de tres
direcciones se refiere a una entrada en la tabla de símbolos para el
nombre. Las entradas de la tabla de símbolos se van creando
conforme se examinan las declaraciones de un procedimiento. El tipo
en una declaración determina el ancho, es decir, la cantidad de
memoria necesaria para el nombre declarado. Según la información
de la tabla de símbolos, se puede determinar una dirección relativa
para el nombre dentro de un área de datos para el procedimiento.

SELECCIÓN DE INSTRUCCIONES

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. Si la
máquina objeto no apoya cada tipo de datos de una manera
uniforme, entonces cada excepción a la regla general exige un
tratamiento especial.
Las velocidades de las instrucciones y las expresiones
particulares de la máquina son otros factores importantes. Para cada
tipo de proposición de tres direcciones, se puede diseñar un esqueleto
de código que perfila el código objeto que ha de generarse para esa
construcción.

La calidad del código generado viene determinada por su


velocidad y tamaño. Las velocidades de las instrucciones son
necesarias para diseñar nuevas secuencias de código pero es difícil
obtener información exacta de los tiempos de ejecución. Decidir cual
es la mejor secuencia de código de máquina para una determinada
construcción de tres direcciones también puede exigir conocer el
contexto en el que aparece esa construcción.

ASIGNACIÓN DE REGISTROS

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 en 2 subproblemas:

1. Durante la asignación de los registros, se selecciona el


conjunto de variables que residirá en los registros en un
momento del programa.
2. Durante una fase posterior de asignación a los registros, se
escoge el registro específico en el que residirá una
variable.

9 de 17
Es difícil encontrar una asignación óptima de registros a
variables, incluso con valores en un solo registro. Matemáticamente,
el problema es NP completo. Este problema se complica aún más el
hardware, el sistema operativo o ambos, en la máquina objeto
pueden exigir que se cumplan ciertas convenciones del uso de
registros.

Algunas máquinas exigen parejas de registros (un registro con


número par y el siguiente con número impar) para algunos operandos
y resultados.

Ejemplo:

Secuencia de código de tres direcciones.

t := a + b
t := a * c
t := t / d

Secuencia óptima de código de máquina.

L R1, a (L = carga)
A R1, b (A = suma)
M R0, c (M = multiplica)
D R0, d (D = divide)
ST R1, t (ST = almacena)

1.2.2.2 LA MÁQUINA OBJETO

Se utilizará como computador objeto una máquina de registros


representativa de varios minicomputadores: el lenguaje ensamblador.

COSTOS DE LAS INSTRUCCIONES

Los modos de direccionamiento junto con sus formas en lenguaje


ensamblador y costos asociados son como sigue:

MODO FORMA DIRECCIÓN COSTO


AÑADIDO
Absoluto M M 1
registro R R 0
Indizado c (R) c+ contenido(R) 1
Registro *R contenido(R) 0
indirecto
Indizado *c (R) contenido (c + contenido(R)) 1

10 de 17
indirecto

Se considera que el costo de una instrucción es uno más los


costos asociados con los modos de dirección fuente y destino (que se
indican como costo añadido en la tabla anterior). Este costo
corresponde a la longitud (en palabras) de la instrucción. Los modos
de direccionamiento que implican registros tienen costo cero,
mientras que los de una posición de memoria o una literal tienen
costo uno, porque dichos operandos tienen que almacenarse con la
instrucción. En la mayoría de las máquinas y de las instrucciones, el
tiempo empleado en traer una instrucción de la memoria excede al
tiempo empleado en ejecutar la instrucción. Por tanto, si se minimiza
la longitud de las instrucciones también se tiende a minimizar el
tiempo empleado en ejecutar las instrucciones.

Algunas de las dificultades de generar código para esta máquina


se ven cuando se considera el código que se debe generar para una
proposición de tres direcciones de la forma a := b + c. Esta
proposición se puede implantar con muchas secuencias distintas de
instrucciones. Ejemplos:

1. MOV b, R0
ADD c, R0 costo = 6
MOV R0, a

2. MOV b ,a
ADD c, a costo = 6

Suponiendo que R0, R1 y R2 contienen las direcciones de a, b y c,


respectivamente, se puede utilizar:

3. MOV *R1, *R0


ADD *R2, *R0 costo = 2

Suponiendo que R1 y R2 contienen los valores de b y c,


respectivamente, y que no se necesita el valor de b después de la
asignación, se puede utilizar:

4. ADD R2, R1
MOV *R1, a costo = 3

Se observa que para generar buen código para esta máquina,


hay que utilizar sus capacidades eficientemente. Es preferible
conservar el valor de lado izquierdo o de lado derecho de un nombre
en un registro, si es posible, si se va a utilizar en un futuro cercano.

1.2.2.3 BLOQUES

11 de 17
Una representación de grafos de proposiciones de tres
direcciones, llamado grafo de flujo, es útil para entender los
algoritmos de generación de código. Los nodos del grafo de flujo
representan cálculos y las aristas representan el flujo de control.

BLOQUES BÁSICOS

Un bloque básico es una secuencia de proposiciones


consecutivas en las que el flujo de control entra al principio y sale al
final sin detenerse y sin posibilidad de saltar excepto al final.
Ejemplo de bloque básico:

t1 := a*a
t2 := a*b
t3 := 2 * t2
t4 := t1 + t3
t5 := b*b
t6 := t4 + t5

Una proposición de tres direcciones x := y + z define x y usa (o


se refiere a) y y z. Se dice que un nombre en un bloque básico está
activo en un punto dado si su valor se utiliza después de ese punto en
el programa, tal vez en otro bloque básico.

ALGORITMO: Partición en bloques básicos.

Entrada: una secuencia de proposiciones de tres direcciones.


Salida: una lista de bloques básicos donde cada proposición de tres
direcciones está en un bloque exactamente.
Método.
1. Se determina el conjunto de líderes, la primera proposición de
cada bloque básico.
 La primera proposición es un líder.
 Cualquier proposición que sea el destino de un salto
condicional o incondicional es un líder.
 Cualquier proposición que vaya inmediatamente después
de un salto condicional o incondicional es un líder.
2. Para cada líder, su bloque básico consta del líder y de todas las
proposiciones hasta, pero sin incluirlo, el siguiente líder o el fin
del programa.

TRANSFORMACIONES EN BLOQUES BÁSICOS

Un bloque básico calcula un conjunto de expresiones. Estas


expresiones son los valores de los nombres activos al salir del bloque.
Se dice que dos bloques básicos son equivalentes si calculan el
mismo conjunto de expresiones.
Se pueden aplicar varias transformaciones a un bloque básico sin
modificar el conjunto de expresiones calculadas por el bloque. Existen
dos tipos de transformaciones:

12 de 17
TRANSFORMACIONES QUE PRESERVAN LA ESTRUCTURA

Las principales transformaciones que preservan la estructura en


bloques básicos son:
1. eliminación de subexpresiones comunes
2. eliminación de código inactivo
3. renombramiento de variables temporales
4. intercambio de dos proposiciones adyacentes
independientes

Se supone que los bloques básicos no tienen matrices


apuntadoras o llamadas a procedimientos.

1. Eliminación de subexpresiones comunes. Considérese el bloque


básico
a := b + c
b := a – d
c := b + c
d := a – d

La segunda y la cuarta proposiciones calculan la misma


expresión, b + c – d, y por tanto este bloque básico se puede
transformar en el bloque equivalente
a := b + c
b := a – d
c := b + c
d := b

2. Eliminación de código inactivo. Supóngase que x esta inactivo


en el punto en que la proposición x := y + z aparece en un
bloque básico. Entonces esta proposición se puede eliminar sin
modificar el valor del bloque básico.
3. Renombramiento de variables temporales. Siempre se puede
transformar un bloque básico en un bloque equivalente en el
que cada proposición que define un temporal define un
temporal nuevo. Dicho bloque básico se denomina bloque en
forma normal.
4. Intercambio de proposiciones. Si se tiene el siguiente bloque
t1 := b + c
t2 := x + y

Entonces se pueden intercambiar las dos proposiciones sin que


esto afecte al valor del bloque si, y solo si, ni x ni y son t1 y ni b ni c
son t2.

TRANSFORMACIONES ALGEBRAICAS

13 de 17
Las transformaciones útiles son las que simplifican las
expresiones o sustituyen operaciones caras por otras más baratas.
Por ejemplo, las proposiciones como
x := x + 0 ó
x := x * 1

Se pueden eliminar de un bloque básico sin cambiar el conjunto


de expresiones que calcula.

GRAFOS DE FLUJO

Los nodos del grafo de flujo son los bloques básicos. Un nodo se
distingue como inicial; es el bloque cuyo líder es la primera
proposición. Hay una arista dirigida del bloque B1 al bloque B2 si
1. hay un salto condicional o incondicional desde la última
proposición de B1 a la primera proposición de B2, ó
2. B2 sigue inmediatamente a B1 en el orden del programa, y
B1 no termina con un salto incondicional.

REPRESENTACIÓN DE BLOQUES BÁSICOS

Grafo de flujo para un programa:

prod := 0
i := 1 B1

t1 := 4 * i
t2 := a [t1]
t3 := 4 * i
t4 := b [t3] B2
t5 := t2 * t4
t6 := prod + t5
prod := t6
t7 := i + 1
i := t7
if i <= 20 goto B2

La arista no indica si el salto condicional al final al final de B (si es


que hay un salto condicional) va al líder de B’ cuando la condición se
cumple o cuando no se cumple.

1.2.2.4 UN GENERADOR DE CÓDIGO SIMPLE

Para cada operador dentro de una proposición hay un operador


correspondiente en el lenguaje objeto.
Los resultados calculados se pueden dejar en registros mientras
sea posible.

14 de 17
Para evitar un posible error, el algoritmo de generación de código
simple guarda todo en memoria cuando se traslada a través de los
límites de los bloques básicos así como cuando se hacen llamadas a
procedimientos.

ALGORITMO PARA GENERACIÓN DE CÓDIGO

Toma como entrada una secuencia de proposiciones de tres


direcciones que constituyen un bloque básico. Para cada proposición
de tres direcciones de la forma x := y op z se realizan las siguientes
operaciones:

1. Se invoca la función obtenreg para determinar la posición L


donde se debe guardar el resultado del calculo y op z.
2. Se consulta el descriptor de direcciones de y para
determinar y’, (una de) la(s) proposicion(es) en curso de y.
si el valor de y no esta en L, se genera la instrucción MOV
y’, L para colocar una copia de y en L.
3. Se genera la instrucción OP z’, L donde z’ es una posición
en curso de x. se actualiza el descriptor de direcciones de x
para indicar que x esta en la posición L.
4. Si los valores en curso de y o z, o ambos, no tienen usos
siguientes, no están activos a la salida del bloque, y están
en registros, se altera el descriptor de registros para indicar
que después de la ejecución de x := y op z, estos registros
ya no contendrán y o z, o ambos, respectivamente.

Función obtenreg

1. Si el nombre y esta en un registro que no contiene el valor


de otros nombres, e y no esta activa y no tiene uso
siguiente después de la ejecución de x := y op z, entonces
se devuelve el registro de y para L.
2. Si falla (1), devuélvase un registro vació para L si hay
alguno.
3. Si no se utiliza x en el bloque, o no se puede encontrar
ningún registro ocupado adecuado, selecciónese la posición
de memoria de x para L.

GENERACIÓN DE CÓDIGO PARA OTRO TIPO DE PROPOSICIONES

Secuencias de código para asignaciones con índices:

i EN EL REGISTRO Ri i EN MEMORIA Mi i EN LA PILA


PROPOSICIÓN
CÓDIGO COSTO CÓDIGO COSTO CÓDIGO COSTO
a := b [ i ] MOV b(Ri), R 2 MOV Mi, R 4 MOV Si(A),R 4
MOV b(R),R MOV b(R),R
a [ i ] := b MOV b,a(Ri) 3 MOV Mi, R 5 MOV Si(A),R 5
MOV b,a(Ri) MOV b,a(Ri)

15 de 17
Secuencias de código para asignaciones con apuntadores:

p EN EL REGISTRO Rp p EN MEMORIA Mp p EN LA PILA


PROPOSICIÓN
CÓDIGO COSTO CÓDIGO COSTO CÓDIGO COSTO
a := * p MOV *Rp, a 2 MOV Mp, R 3 MOV Sp(A),R 3
MOV *R,R MOV *R, R
* p := a MOV a, *Rp 2 MOV Mp, R 4 MOV a,R 4
MOV a, *R MOV R,*Sp(A)

Proposiciones condicionales:

Las máquinas implantan los saltos condicionales en una de dos


formas. Una manera es saltar si el valor de un registro designado
cumple una de seis condiciones: negativo, cero, positivo, no negativo,
no cero y no positivo. Un segundo enfoque utiliza un conjunto de
códigos de condición para indicar si a última cantidad calculada o
cargada en un registro es negativa, cero o positiva.

1.3 CONCLUSIÓN
Los objetivos principales de la optimización de código son reducir
el tamaño de un programa, aumentar la velocidad de ejecución de un
programa o ambos. Aunque en realidad no existe garantía alguna de
que después de la optimización el código que resulta de esta sea
óptimo.

Para la generación de código, es acertado producir como salida


un programa en lenguaje ensamblador ya que se pueden generar
instrucciones simbólicas y utilizar las macros del ensamblador,
además de que es muy útil si se tiene una máquina con memoria
pequeña.

La calidad del código generado viene determinada por su


velocidad y tamaño. Las velocidades de las instrucciones son
necesarias para diseñar nuevas secuencias de código pero no es
sencillo obtener información exacta de los tiempos de ejecución.

16 de 17
1.4 BIBLIOGRAFÍA
1. Aho, V, Compiladores, principios, técnicas y herramientas,
editorial addison wesley iberoamerica.

1.5 REFERENCIA
El lector interesado en la investigación anterior debe consultar Waite
[1976a,b], Aho y Sethi [1977], Graham [1980 y 1984], Ganapathi,
Fischer y Hennessy [1982], Lunell [1983] y Henry [1984].

17 de 17

You might also like