Generación de código intermedio.
2.1 Notaciones
• Las notaciones sirven de base para expresar sentencias bien definidas.
• El uso más extendido de las notaciones sirve para expresar operaciones aritméticas.
• Las expresiones aritméticas se pueden expresar de tres formas distintas: infija, prefija y postfija. Notaciones
• La diversidad de notaciones corresponde en que para algunos casos es más sencillo un tipo de notación.
• Las notaciones también dependen de cómo se recorrerá el árbol sintáctico, el cual puede ser en inorden, preorden o postorden; teniendo una relación de uno a uno con la notación de los operadores.
2.1.1 Prefija
• La notación prefija pone el operador primero que los dos operando, por lo que la expresión anterior queda: +ab-5. Esto se representa con una estructura del tipo FIFO (First In First Out) o cola.
• Las estructuras FIFO son ampliamente utilizadas, pero tienen problemas con el anidamiento aritmético.
2.1.2 Infija
• La notación infija es la más utilizada por los humanos porque es la más comprensible ya que ponen el operador entre los dos operando. Por ejemplo, a+b-5.
• No existe una estructura simple para representar este tipo de notación en la computadora por esta razón se utilizan otras notaciones. Venustiano Carranza, Chiapas, México. 19 de octubre de 2021
2.2.3 Postfija
• La notación postfija pone el operador al final de los dos operando, por lo que la expresión queda: ab+5-
• La notación posftfija utiliza una estructura del tipo LIFO (Last In First Out) pila, la cual es la más utilizada para la implementación.
2.2 Representaciones de código Intermedio.
El código intermedio se genera para una máquina virtual. Estas máquinas se definen con dos objetivos: Ser lo suficientemente simples como para poder generar código para ellas de manera sencilla, pero con la suficiente riqueza para poder expresar las construcciones del lenguaje fuente.
2.2.1 Notación Polaca.
La notación polaca, también conocida como notación de prefijo o notación prefija, es una forma de notación para la lógica, la aritmética, el álgebra y la computación. Su característica distintiva es que coloca los operadores a la izquierda de sus operandos. Si la aridad de los operadores es fija, el resultado es una sintaxis que carece de paréntesis u otros signos de agrupación, y todavía puede ser analizada sin ambigüedad. El lógico polaco Jan Łukasiewicz inventó esta notación alrededor de 1920 para simplificar la lógica proposicional.
2.2.2 Código P.
2.2.3 Triplos.
Las proposiciones de tres direcciones se parecen mucho al ensamblador, el cual es un lenguaje intermedio más entendible para la máquina.
• Las estructuras de control (if, switch, while, do-while, for) son realmente etiquetas goto disfrazadas.
2.2.4 Cuádruplos.
Es una estructura tipo registro con cuatros campos que se llaman: op, arg1, arg2 y resultado. OP tiene un código intermedio.
Los operadores unarios como x:=-y no utilizan arg2. Generalmente arg1, arg2 y resultado son valores de tipo puntero y apuntan a una entrada en la tabla de símbolos.
Constituida por 4 elementos: un código de operación, dos operando de entrada y otro de salida para almacenar el resultado.
2.3 Esquema de generación.Los esquemas de generación son las estrategias o acciones que se deberán realizarse y tomarse en cuenta en el momento de generar código intermedio. Los esquemas de generación dependen de cada lenguaje. Tomaremos algunos esquemas de generación del lenguaje C.
Las declaraciones de variables y constantes deben separarse de tal manera que queden las expresiones una por una de manera simple. Por ejemplo int a,b,c; se descompone a int a; int b; intc; respectivamente.
2.3.2 Expresiones.2.3.3 Instrucción de asignación.
2.3.4 Instrucciones de control.
Permiten variar o alterar la secuencia normal de ejecución de un programa.
Tipos:
· Instrucciones condicionales o alternativas-
· Instrucciones de salto.
· Instrucciones repetitivas.
2.3.5 Funciones.
2.3.6 Estructuras.
Estructura
y fases de un compilador (2) Análisis lineal También conocido como: análisis
léxico o exploración. En la proposición de asignación: posicion = inicial +
velocidad * 60 Se identifican los siguientes componentes léxicos Identificador Símbolo
de asignación (=) Identificador (inicial) Signo de suma (+) Identificador Signo
de multiplicación (*) Número (60)
Estructura y fases de un compilador (3) Análisis jerárquico
También llamado análisis sintáctico. Implica agrupar los componentes léxicos en
frases gramaticales que el compilador utiliza para sintetizar la salida. Por lo
general, las frases gramaticales se representan mediante un árbol de análisis
sintáctico.
UNIDAD 3 Optimizacion
3.1.1 Optimización Local
• Las optimizaciones locales se realizan sobre el bloque básico
• Optimizaciones locales
– Folding
– Propagación de constantes
– Reducción de potencia
– Reducción de subexpresiones comunes
Bloque Básico
• Un bloque básico es un fragmento de código que tiene una única entrada y salida, y cuyas instrucciones se ejecutan secuencialmente. Implicaciones:
– Si se ejecuta una instrucción del bloque se ejecutan todas en un orden conocido en tiempo de compilación.
• La idea del bloque básico es encontrar partes del programa cuyo análisis necesario para la optimización sea lo más simple posible.
Ensamblamiento (Folding)
• El ensamblamiento es remplazar las expresiones por su resultado cuando se pueden evaluar en tiempo de compilación (resultado constante).
– Ejemplo: A=2+3+A+C -> A=5+A+C
• Estas optimizaciones permiten que el programador utilice cálculos entre constantes representados explícitamente sin introducir ineficiencias.
Implementación del Folding
• Implementación del floding durante la generación de código realizada conjuntamente con el análisis sintáctico.
– Se añade el atributo de constante temporal a los símbolos no terminales y a las variables de la tabla de símbolos.
– Se añade el procesamiento de las constantes a las reglas de análisis de expresiones.
– Optimiza: 2+3+b -> 5+b
• Hay una suma de constantes (2+3)+b
– No optimiza: 2+b+3 -> 2+b+3
• No hay una suma de constantes (2+b)+3
Implementación del Folding
• Implementación posterior a la generación de código
– Buscar partes del árbol donde se puede aplicar la propiedad conmutativa:
• Sumas/restas: como la resta no es conmutativa se transforma en sumas: a+b-c+d -> a+b+(-c)+d
• Productos/divisiones: como la división no es conmutativa se transforma en productos: a*b/c*e -> a*b*(1/c)*e
– Buscar las constantes y operarlas
– Reconstruir el árbol.
Propagación de constantes
• Desde que se asigna a una variable un valor constante hasta la siguiente asignación, se considera a la variable equivalente a la constante.
• Ejemplo: Propagación Ensamblamiento
PI=3.14 -> PI=3.14 -> PI=3.14
G2R=PI/180 -> G2R=3.14/180 -> G2R=0.017
PI y G2R se consideran constantes hasta la próxima asignación.
• Estas optimizaciones permiten que el programador utilice variables como constantes sin introducir ineficiencias. Por ejemplo en C no hay constantes y será lo mismo utilizar
– Int a=10;
– #define a 10
Con la ventaja que la variable puede ser local.
• Actualmente en C se puede definir const int a=10;
implementación de la Propagación de Constantes
• Separar el árbol en bloques básicos
– Cada bloque básico será una lista de expresiones y asignaciones
• Para cada bloque básico
– Inicializar el conjunto de definiciones a conjunto vacío.
• Definición: (variable,constante)
– Procesar secuencialmente la lista de expresiones y asignaciones
– Para expresión y asignación
• Sustituir las apariciones de las variables que se encuentran en el conjunto de definiciones por sus constantes asociadas.
– Para asignaciones
• Eliminar del conjunto de definiciones la definición de la variable asignada
• Añadir la definición de la variable asignada si se le asigna una constante.
Ejecuci on en tiempo de compilaci on
Precalcular expresiones constantes (con constantes o variables cuyo valor no cambia)
i = 2 + 3 ! i = 5
j = 4
f = j + 2.5
!
j = 4
f = 6.5
2. Reutilizaci on de expresiones comunes
a = b + c
d = a - d
e = b + c
f = a - d
!
a = b + c
d = a - d
e = a
f = a - d
3. Propagaci on de copias
Ante instrucciones f = a, sustituir todos los usos de f por a
a = 3 + i
f = a
b = f + c
d = a + m
m = f + d
!
a = 3 + i
b = a + c
d = a + m
m = a + d
3.1.2 Ciclos
3.1.3 Globales
3.1.4 De mirilla
3.2 Cotos
Los costos son el factor más importante a tomar en cuenta a la hora de optimizar ya que en ocasiones la mejora obtenida puede verse no reflejada en el programa final pero si ser perjudicial para el equipo de desarrollo. La optimización de una pequeña mejora tal vez tenga una pequeña ganancia en tiempo o en espacio pero sale muy costosa en tiempo en generarla. Pero en cambio si esa optimización se hace por ejemplo en un ciclo, la mejora obtenida puede ser N veces mayor por lo cual el costo se minimiza y es benéfico la mejora.
3.2.1 Costos de Ejecución. (memoria, registros, pilas)
Los costos de ejecución son aquellos que vienen implícitos al ejecutar el programa. En algunos programas se tiene un mínimo para ejecutar el programa, por lo que el espacio y la velocidad de los microprocesadores son elementos que se deben optimizar para tener un mercado potencial más amplio. Las aplicaciones multimedia como los videojuegos tienen un costo de ejecución alto por lo cual la optimización de su desempeño es crítico, la gran mayoría de las veces requieren de procesadores rápidos (e.g. tarjetas de video) o de mucha memoria. Otro tipo de aplicaciones que deben optimizarse son las aplicaciones para dispositivos móviles. Los dispositivos móviles tienen recursos más limitados que un dispositivo de cómputo convencional razón por la cual, el mejor uso de memoria y otros recursos de hardware tiene mayor rendimiento. En algunos casos es preferible tener la lógica del negocio más fuerte en otros dispositivos y hacer uso de arquitecturas descentralizadas como cliente/servidor o P2P.
3.2.2 Criterios para mejorar el código
La mejor manera de optimizar el código es hacer ver a los programadores que optimicen su código desde el inicio, el problema radica en que el costo podría ser muy grande ya que tendría que codificar más y/o hacer su código más legible. Los criterios de optimización siempre están definidos por el compilador. Muchos de estos criterios pueden modificarse con directivas del compilador desde el código o de manera externa. Este proceso lo realizan algunas herramientas del sistema como los ofuscadores para código móvil y código para dispositivos móviles.
3.2.3 Herramientas para el análisis del flujo de datos
Existen algunas herramientas que permiten el análisis de los flujos de datos, entre ellas tenemos los depuradores y desambladores. La optimización al igual que la programación es un arte y no se ha podido sistematizar del todo.
Comentarios
Publicar un comentario