martes, 4 de abril de 2017

Generacion de codigo

GENERACION DE CODIGO
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 re destinació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.

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.

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 o 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

a = 3 + b + 5 + c
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

Generación de código en 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
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

Condición compuesta: Si se enlazan condiciones mediante operadores lógicos AND u
OR emplearemos la técnica del cortocircuito, de manera que si enlazamos cond1 y cond2 con un AND, (cond1 AND cond2), pues si cond1 es falso, no evaluaremos cond2, dado que su función será falsa sea cual sea el valor de cond2.
Si el conector es OR, (cond1 OR cond2), la cond2 sólo se evaluará si la cond1 es falsa, pues en caso contrario, su disyunción será verdad para cualquier valor de cond2.

Sentencias que nos permite nuestro lenguaje
Ahora se trata de utilizar estas condiciones y sus etiquetas asociadas, para generar el código de sentencias que implican condiciones, como son IF, WHILE y REPEAT.
Sentencia IF-THEN-ELSE
El caso del IF es el caso más simple. Aquí basta, con indicar que la etiqueta de verdad de la condición está asociada al código a continuación del THEN, y la etiqueta de falso se asocia al código que puede haber tras el ELSE. En cualquier caso, una vez acabadas las sentencias del THEN se debe producir un salto al final del IF, porque no queremos que se ejecuten también las sentencias del ELSE. Por tanto, tras la sentencias del THEN, creamos una nueva etiqueta a la cual produciremos un salto, y colocamos el destino de tal etiqueta al final del código del IF

Sentencia WHILE
El caso de WHILE y REPEAT es muy similar. En ambos creamos una etiqueta al comienzo del bucle, a la que se saltará para repetir cada iteración.
En el caso del WHILE, a continuación se genera el código de la condición. La etiqueta de verdad se pone justo antes de las sentencias del WHILE, que es lo que se debe ejecutar si la condición es cierta. Al final de las sentencias se pondrá un salto al inicio del bucle, donde de nuevo se comprobará la condición. La etiqueta de falso de la condición, se pondrá al final de todo lo relacionado con el WHILE, o lo que es lo mismo, al principio del código generado para las sentencias que siguen al WHILE.

IF cond THEN sent ELSE sent FIN IF
| WHILE cond DO sent FIN WHILE
Sentencia REPEAT
Como ya se dijo, creamos una etiqueta al comienzo del bucle, a la que se saltará para repetir cada iteración
En el caso del REPEAT, a continuación de la etiqueta de comienzo del bucle colocamos el código de las sentencias, ya que la condición se evalúa al final. Tras las sentencias colocamos el código de la condición. Ahora, debemos hacer coincidir la etiqueta de comienzo del bucle con la etiqueta de falso de la condición,. Como ambos nombres ya están asignados, la solución es colocar la etiqueta de falso, y en ella un goto al comienzo del bucle.
Al final de todo el código asociado al REPEAT pondremos la etiqueta de verdad.
IF cond THEN sent ELSE sent FIN IF
| WHILE cond DO sent FIN WHILE
| REPEAT sent UNTIL cond

Sentencia CASE
La sentencia más compleja de todas es la sentencia CASE, ya que ésta permite un número indeterminado, y potencialmente infinito de condiciones, lo cual obliga a arrastrar una serie de parámetros a medida que se van efectuando reducciones.
El problema es que la sentencia CASE necesita una recursión (no podemos utilizar una única regla de producción). Es necesario una regla de producción para la sentencia CASE diferente.

AST (Abstract Syntax Trees): forma condensada de árboles de análisis, con sólo nodos semánticos y
sin nodos para símbolos terminales (se supone que el programa es sintácticamente correcto).
• Ventajas: unificación de pasos de compilación
– Creación del árbol y la tablade símbolos
– Análisis semántico
– Optimización
– Generación de código objeto
• Desventaja:
– espacio para almacenamiento

DAG (Directed Acyclic Graphs): árboles sintácticos concisos

Ahorran algo de espacio
– Resaltan operaciones duplicadas en el código
– Difíciles de construir
TAC (Three-Address Code): secuencia de instrucciones de la forma:

– operador: aritmético / lógico
– operandos/resultado: constantes, nombres, temporales.
• Se corresponde con instrucciones del tipo:
A := b + c

• Las operaciones más complejas requieren varias instrucciones:
• Este ‘desenrollado’ facilita la optimización y generación de código final.
• Ensamblador general y simplificado para una máquina virtual: incluye etiquetas, instrucciones de flujo de control…
• Incluye referencias explícitas a las direcciones de los resultados intermedios (se les da nombre).
• La utilización de nombres permite la reorganización (hasta cierto punto).
• Algunos compiladores generan este código como código final; se puede interpretar fácilmente (UCSD PCODE, Java).

Notación posfija
• Polaca inversa, código de cero direcciones:
– notación matemática libre de paréntesis
– los operadores aparecen después de los operandos
• Ventajas:
– Código generado conciso.
– No hacen falta temporales.
– Algunas optimizaciones sencillas.
– Mantiene la estructura sintáctica.
• Desventajas:
– Código dependiente de la posición.
– solo efectiva si el ‘target’ es de pila.

Generación de código intermedio
• Consideración fundamental: generación de código correcto.
• no se genera código (hay excepciones).
• se limita a resolver problemas relacionados con almacenamiento de los objetos:
– Espacio requerido
– Lugar en la memoria
– Valor
• hay información explícita e implícita en el fuente.
• ¿Y si se permite mezclar declaraciones de variables y procedimientos?
• ¿Y si necesitas mantener información sobre el tamaño de cada bloque?
• C: este problema no existe.
• Pascal: muchos compiladores lo prohíben.

Expresiones y Asignación
• De los operandos:
– Determinar su localización
– Efectuar conversiones implícitas
• De los operadores:
– Respetar su precedencia
– Respetar su asociatividad
– Respetar orden de evaluación (si definido)
– Determinar interpretación correcta (si sobrecargado)
• Instrucciones generadas
– Acceso a datos
» Variables simples
» Registros
» Vectores
– Manipulación de datos
» Conversiones
» Operaciones
– Flujo de control
» Validación de subrangos
» Operadores complejos
Reutilización de temporales
• Una vez que una variable temporal aparece como operando, deja de utilizarse.
• Al traducir el código intermedio, en lo posible se utilizarán registros (habrá una cantidad limitada); de lo contrario, posiciones de memoria.
• Reducción de temporales requeridas:

Invocación de Procedimientos
• Cuando se evalúan las expresiones correspondientes a los argumentos, éstos van almacenándose en la pila.
• Al crear el BA del procedimiento, debe respetarse el tamaño del bloque actual
• El cambio de nivel es la diferencia entre el nivel actual y el invocado.
• La dirección del procedimiento o función se determina al introducir el símbolo en la tabla.

Utilización de parámetros
1. valor de un parámetro por referencia
2. dirección de un parámetro por referencia
3. valor de un parámetro por valor y otro por referencia
4. parámetro por valor utilizado como argumento a parámetro por referencia
5. parámetro por referencia utilizado como argumento a parámetro por referencia
6. variables utilizadas como parámetros por valor y referencia respectivamente