generación de código introduccion

Anuncio
Universidad Nacional del Santa
Curso: Teoría de Compiladores
GENERACIÓN DE CÓDIGO
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é.
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.
Docente: Ing. Mirko Manrique Ronceros
~1~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
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.
CÓDIGO DE TERCETOS
Para facilitar la comprensión de esta fase, no generaremos código máquina puro, sino un
código intermedio cercano a la máquina, que además facilitará la optimización de código.
El código intermedio que vamos a usar , posee cuatro apartados:
ü Operando 1º
ü Operando 2º
ü Operador
ü Resultado
Y se denomina código de 3 direcciones, de tercetos, o máximo 3 operandos.
Las instrucciones de tres direcciones son análogas al código ensamblador, pueden tener
etiquetas simbólicas y existen instrucciones para el flujo de control.
Hay algunas instrucciones que carecen de algunos de estos apartados; los tercetos que
podemos usar son:
ü Asignación binaria: x := y op z, donde op es una operación binaria aritmética
lógica.
ü Asignación unaria: x := op, donde op es una operación unaria. Las operaciones
unarias principales incluyen el menos unario, la negación lógica, los operadores de
desplazamiento y operadores de conversión de tipos.
ü Asignación simple o copia: x := y, donde el valor de y se asigna a x.
ü Salto incondicional: goto etiqueta.
ü Saltos condicionales: if x oprelacional y goto etiqueta
ü Para llamar a un procedimiento se tienen códigos para meter los parámetros en la
pila de llamadas, para llamar al procedimiento indicando el número de parámetros
que debe recoger de la pila, para tomar un valor de la pila, y para retornar.
param x Mete al parámetro real x en la pila.
call p, n Llama al procedimiento p, y le dice que tome n
parámetros de la cima de la pila
pop x Toma un parámetro de la pila
return y Retorna el valor y
Docente: Ing. Mirko Manrique Ronceros
~2~
Universidad Nacional del Santa
ü Asignación indexada: x := y[i];
donde i es el desplazamiento
ü Asignación indirecta: x:=&y;
asignaciones de punteros
Curso: Teoría de Compiladores
x[i] := y, donde x o y es la dirección base, y
x:=*y;
Son
asignaciones
de
direcciones
y
La elección de operadores permisibles es un aspecto importante en el diseño de código
intermedio. El conjunto de operadores debe ser lo bastante rico como para implantar las
operaciones del lenguaje fuente. Un conjunto de operadores pequeño es más fácil de
implantar en una nueva máquina objeto. Sin embargo un conjunto de instrucciones
limitado puede obligar a la etapa inicial a generar largas secuencias de proposiciones para
algunas operaciones del lenguaje fuente. En tal caso, el optimizador y el generador de
código tendrán que trabajar más si se desea producir un buen código.
Ejemplo de código de tercetos
c=a
label etqBucle
if b = 0 goto etqFin
b = b-1
c = c+1
goto etqBucle
label etqFin
Para ilustrar como se utiliza el código de tercetos en una gramática vamos a suponer que
nuestra calculadora en vez de ser una calculadora interpretada es una calculadora
compilada, es decir, en vez de interpretar las expresiones vamos a generar código
intermedio equivalente.
Tendremos en cuenta la posibilidad de utilizar variables.
Las variables temporales
sirven para almacenar
resultados intermedios a
medida
que
vamos
calculando el resultado
final.
Cuando se genera código de tercetos, se
construyen variables temporales para los nodos
interiores del árbol sintáctico. Así, si por ejemplo
tuviéramos la entrada: a = 3 + b + 5 + c
Docente: Ing. Mirko Manrique Ronceros
~3~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
La pregunta es ¿Y son equivalentes? - Sí, y además es muy parecido a un código
máquina.
Lo importante es saber hacer las reducciones y
tener una gramática adecuada. El no terminal tiene
que tener unos atributos asociados que los
representen.
temp1 representa a 3 + b
temp2 representa a temp1 + 5
temp 3 representa a temp2 +c
Lo importante es saber qué representa cada atributo
y como puedo generar código.
Hasta ahora, para generar el código de tercetos no hemos tenido que tener en cuenta los
valores de a, b, y c. Por lo tanto no nos ha hecho falta ninguna tabla de símbolos para
generar el código de terceto. Según el problema que tengamos planteado hay que ver si
hace falta o no una tabla de símbolos. En el ejemplo anterior no hace falta una tabla de
símbolos, porque no hay chequeo de tipos. Pero si tenemos un problema en el que exista
una zona de declaración y una zona de definición, por ejemplo hacer un chequeo de tipos
y un código de terceto, entonces sí nos haría falta tener una tabla de símbolos, o por
ejemplo si decimos que el contenido de la dirección de memoria 12h = a, entonces
también necesitamos una tabla de símbolos.
Más adelante veremos un ejemplo implementado, en el que haremos la generación de
código de tercetos para nuestra calculadora. Comentaremos algunos aspectos de dicho
programa.
Sólo vamos a necesitar hacer una traducción, es decir, entra un texto y sale otro
resultado de hacer la traducción del texto que entra.
Ejemplo:
Entrada:
a := 0
Salida:
tmp1 = 0
a = tmp1
Lo único que vamos a hacer es sacarlo por pantalla, mediante printf .
ü Código LEX. El 0 de a:=0 no necesitamos pasarlo a entero con atoi, ya que solo lo
vamos a imprimir en pantalla. Al no terminal ID y al no terminal NUMERO, le vamos
a asignar un atributo del tipo cadena llamado nombre.
ü Código YACC:
Docente: Ing. Mirko Manrique Ronceros
~4~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
Los atributos asociados van a ser
Por este motivo tengo que asignarle el tipo nombre a expr. Mediante la instrucción:
%type <nombre> asig expr
asig necesita tener asociado un tipo, para poder hacer la propagación en las asignaciones
múltiples. Ejemplo:
Vemos la regla prog
prog
:
|
|
|
;
asig ‘\n’
prog asig ‘\n’
prog error ‘\n’
Si no me interesa que un prog sea vacío ( ), no me basta con quitar la regla . Veamos
qué ocurre si lo hiciésemos así
prog
:
|
|
;
asig ‘\n’
prog asig ‘\n’
prog error ‘\n’
Docente: Ing. Mirko Manrique Ronceros
~5~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
Esto tiene problemas.
Ejemplo: Si el error da en la segunda asignación.
Pero, si el error da en la primera asignación. No funcionaría el compilador ya que nunca
entraría por prog.
¿Cómo se soluciona esto? - Añadiendo una regla
prog
:
|
|
|
;
asig ‘\n’
prog asig ‘\n’
prog error ‘\n’
error ‘\n’
otra_etq( ): Genera variables temporales nuevas. En nuestro ejemplos se llaman var1,
var2, ...
Resumiendo, los objetivos fundamentales en el código de tercetos son:
ü Realizar una secuencia de printf (para la traducción)
ü Gestionar adecuadamente los atributos
Generación de código de tercetos en Sentencias de Control
Utilizando código de tercetos, vamos a generar ahora el código correspondiente no sólo a
las expresiones, sino también el correspondiente a las sentencias de control.
En el caso de las expresiones, nos basábamos en las variables temporales para generar
código. Ahora el problema son los cambios de flujos
Docente: Ing. Mirko Manrique Ronceros
~6~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
IF - THEN- ELSE
CASE
WHILE
REPEAT
Vamos a trabajar con cambios de flujos mediante condiciones:
WHILE Mientras se cumple la condición
REPEAT Hasta que se cumpla la condición
Por ejemplo, si tenemos una instrucción como:
Entrada
Salida
IF A > 0 THEN
S1 := 1;
ELSE
S2 := 2;
FIN SI;
if A > 0 goto etq1
goto etq2
label etq1
S1 = 1
goto etq3
label etq2
S2 = 2
label etq3
Ahora vamos a ver cómo vamos a generar el código anterior.
Primero identificamos los bloques principales de la entrada
sent
:
|
IF cond THEN sent ELSE sent { Generar código de tercetos}
ID ASIG expr {Generar código de tercetos}
Antes de reducir el IF, tiene que tener el código generado de las dos sent. De forma que
en pantalla vamos a tener.
código generado sent 1
código generado sent 2
S1 = 1
S2 = 2
Esto es un problema porque entre las dos sentencias existe código
…
S1 = 1 Código generado por sent1
goto etq3
label etq2
S2 = 2 Código generado por sent2
….
Docente: Ing. Mirko Manrique Ronceros
~7~
Universidad Nacional del Santa
Curso: Teoría de Compiladores
Hay que poner el código que existe entre las dos sentencias, esto podríamos hacerlo
mediante la inclusión de reglas intermedias.
Otro problema que nos encontramos es que tenemos que generar el código asociado a la
condición.
cond:
expr ‘>’ expr
{genero código de tercetos}
printf (“if %s > %s goto etq_verdad”, $1,$3);
printf(“goto etq_falso”);
Docente: Ing. Mirko Manrique Ronceros
~8~
Descargar