- Anlisis semntico - Generacin de cdigo - Representacin de la informacin: gestin de la memoria - Estudio de algunos compiladores
Profesores: Bernardino Arcay Carlos Dafonte Departamento de Tecnoloxas da Informacin e as Comunicacins. Universidade da Corua
ii NDICE 5 ANLISIS SEMNTICO. ....................................................................................... 1 5.1 Definiciones dirigidas por la sintxis. ............................................................ 1 5.1.1 Grafos de dependencias..........................................................................................................3 5.2 Esquema de traduccin ................................................................................... 4 5.3 Comprobaciones en tiempo de compilacin. ................................................. 5 5.3.1 Sistemas de tipos. ...................................................................................................................6 5.3.2 Una gramtica y sus comprobaciones de tipos. ......................................................................6 5.3.3 Equivalencia de expresiones de tipos. ....................................................................................8 5.3.4 Codificacin de tipos..............................................................................................................8 5.3.5 Conversin de tipos................................................................................................................9 5.3.6 Sobrecarga de funciones y operadores..................................................................................10 6 GENERACIN DE CDIGO. .............................................................................. 13 6.1 Lenguajes intermedios................................................................................... 13 6.1.1 Notacin Polaca Inversa (RPN)............................................................................................13 6.1.2 Cuartetos...............................................................................................................................13 6.1.3 Tercetos. ...............................................................................................................................15 6.2 Generacin de cdigo intermedio. ................................................................ 16 6.2.1 Generacin de RPN desde expresiones aritmticas..............................................................16 6.2.2 Generacin de cuartetos........................................................................................................17 6.3 Generacin de cdigo desde lenguaje intermedio. ...................................... 18 6.3.1 Definicin de la mquina objeto...........................................................................................18 6.3.2 Generacin de cdigo desde RPN. .......................................................................................19 6.3.3 Generacin de cdigo desde cuartetos..................................................................................20 7 OPTIMIZACIN DE CDIGO. ........................................................................... 22 7.1 Algoritmo de Nakata. .................................................................................... 24 7.2 Un ejemplo de optimizacin manual. ........................................................... 28 7.3 Lazos en los grafos de flujo. .......................................................................... 28 7.4 Anlisis global del flujo de datos. ................................................................. 29 7.4.1 Alcance de definiciones en estructuras de control................................................................31 7.4.2 Notacin vectorial para representar genera y desactiva........................................................34 7.5 Solucin iterativa de las ecuaciones de flujo de datos. ............................... 37 7.5.1 Anlisis alcance de definiciones...........................................................................................37 7.5.2 Anlisis de expresiones disponibles. ....................................................................................40 7.5.3 Anlisis de variables activas.................................................................................................43 8 ERRORES. ............................................................................................................. 46 8.1 Tipos de errores. ............................................................................................ 46 8.2 Recuperacin de errores lxico-grficos. ..................................................... 47 8.2.1 Correccin de errores de sustitucin.....................................................................................47 8.2.2 Correccin de errores de borrado. ........................................................................................49 8.2.3 Correccin de errores de inclusin. ......................................................................................49 8.3 Anlisis sintctico en modo pnico........................................................... 50 9 INTRPRETES. .................................................................................................... 51 9.1 Estructura de un intrprete actual. .............................................................. 51 9.2 Arquitectura neutral de Java.................................................................... 53 iii BIBLIOGRAFA.
Aho, A.V.; Sethi, R. ; Ullman, J.D. "Compiladores: Principios, tcnicas y herramientas" Addison-Wesley, Reading, Massachussetts (1991).
Louden D. K. [2004], Construccin de compiladores. Principios y Prctica, Paraninfo Thomson Learning.
Garrido, A. ; Iesta J.M. ; Moreno F. ; Prez J.A. [2004] Diseo de compiladores, Publicaciones Universidad de Alicante.
Sanchis, F.J.; Galn, J.A. "Compiladores, teora y construccin" Ed. Paraninfo (1987).
Aho, A.V.; Ullman, J.D. "The theory of parsing, translation and compiling", I y II Prentice-Hall (1972).
Hopcroff, J.E. ; Motwani R. ; Ullman, J. D. [2002] Introduccin a la teora de autmatas, lenguajes y computacin, Addison-Wesley, Madrid.
Allen I.; Holub "Compiler design in C" Prentice-Hall (1991).
Snchez, G.; Valverde J.A. "Compiladores e Intrpretes" Ed. Daz de Santos (1984).
Sudkamp T.A. Languages and machines Addison-Wesley. 1
5 ANLISIS SEMNTICO.
En esta fase se pretende encontrar errores semnticos; en este captulo nos centraremos principalmente en la recopilacin de informacin sobre los tipos para posteriormente realizar la generacin de cdigo.
Para la realizacin del anlisis semntico se emplea la estructura jerrquica definida durante el anlisis sintctico para reconocer los operadores, operandos de expresiones y proposiciones.
Dentro del anlisis semntico, una de las funciones principales es la de verificar si los operadores tienen los operandos del tipo correcto segn el lenguaje. Por ejemplo, puede significar un error sumar un nmero real y un nmero entero en un lenguaje, sin embargo, en otro, es correcto pero se precisa realizar una conversin.
Las acciones semnticas nos van a permitir asociar informacin a las producciones gramaticales, de forma que incorporamos reglas semnticas e introducimos atributos a los smbolos de la gramtica.
Para trabajar con reglas o acciones semnticas se utilizan dos notaciones:
1) Definiciones dirigidas por la sintxis. 2) Esquemas de traduccin.
En general, el metodo consistir en construir el rbol sintctico, crear el grafo de dependencias y evaluar las reglas semnticas. Los pasos seran:
CADENA RBOL GRAFO DE DEPEN. EVALUACIN REGLAS SEMNTICAS
5.1 Definiciones dirigidas por la sintxis. Una definicin dirigida por la sintxis es una generalizacin de una gramtica independiente del contexto en la que cada smbolo gramatical tiene un conjunto de atributos asociados (pueden ser sintetizados o heredados).
Forma de una definicin dirigida por la sintxis.
Sea una GIC, G = (N, T, P, S), cada produccin A o tiene asociado un conjunto de reglas semnticas de la forma b = f(c 1 , c 2 , ..., c k ), donde f es una funcin, c 1 , c 2 , ..., c k son atributos de los smbolos gramaticales de la produccin y b puede ser: a) Un atributo sintetizado de A (a partir de atributos de la parte derecha). 2 b) Un atributo heredado de uno de los smbolos gramaticales que est en el lado derecho de la produccin (calculado a partir de atributos de la parte izquierda y/o de sus hermanos en la parte derecha).
Se dice que un atributo es sintetizado si su valor en un nodo del rbol de anlisis sintctico se determina a partir de los valores de los atributos de los hijos del nodo.
Se dice que un atributo es heredado si su valor en un nodo de un rbol de anlisis sintctico est definido a partir de los atributos en el padre y/o de los hermanos de dicho nodo.
Una definicin dirigida por la sintxis que usa exclusivamente atributos sintetizados se llama definicin con atributos sintetizados.
Ejemplo.- Definicin con atributos sintetizados.
L E \n Print (E.val) E E 1 + T E.val = E 1 .val + T.val E T E.val = T.val T T 1 * F T.val = T 1 .val x F.val F (E) F.val = E.val F dgito F.val = dgito.val T F T.val = F.val
Para la entrada 3*5+4\n , el rbol es:
Definicin de atributos heredados. Vemoslo con el siguiente ejemplo:
D TL L.her = T.tipo T int T.tipo = integer T real T.tipo = real L L 1 , id L 1 .her = L.her L
E. val = 19 E. val = 15 + T. val = 4 T. val = 15 F. val = 14 d gi t o. val = 4 T. val = 3 * F. val = 5 F. val = 3 d gi t o. val = 5 \ n
d gi t o. val = 3 3 Aadetipo (id.entrada, L.her) L id Aadetipo (id.entrada, L.her)
El procedimiento aadetipo aadira el tipo de cada identificador en su zona de la tabla de smbolos.
A continuacin mostramos el rbol sintctico con anotaciones para la entrada real id1, id2, id3. Los atributos L.her se obtienen a partir del valor del atributo T.tipo en el hijo izquierdo de la raz y evaluando despus L.her de forma descendente en los tres nodos de L en el subrbol derecho de la raz. En los tres nodos de L, adems, se llama al procedimiento aadetipo.
5.1.1 Grafos de dependencias. Si un atrituto d de un nodo del rbol sintctico es funcin de c 1 , c 2 , ..., c k ah tenemos una dependencia, pues tendremos que calcular antes los c 1 , c 2 , ..., c k que d. Estas interdependencias entre los atributos sintetizados y/o heredados se pueden representar por un grafo que llamaremos grafo de dependencias. Es decir, nos va a permitir determinar el orden de evaluacin de las acciones semnticas.
El algoritmo de construccin de este grafo es el siguiente:
for cada nodo n en el rbol de anlisis sintctico do For cada atributo a del smbolo gramatical en el nodo n do Construir un nodo en el grafo de dependencias para a; for cada nodo n en el rbol de anlisis sintctico do for cada regla semntica b = f(c 1 , c 2 , ..., c k ) asociada con la produccin utilizada en n do for i =1 to k do Construir una arista desde el nodo para c i hasta b;
Ejemplo.-
Produccin Regla semntica E E 1 + E 2 E.val = E 1 .val + E 2 .val
D T. t i po = r eal L. her = r eal r eal L. her = r eal , i d3 L. her = r eal , i d2 i d1 4 Los tres nodos del grafo de dependencias representan los atributos sintetizados E.val, E 1 .val y E 2 .val en los nodos correspondientes del rbol de anlisis.
Llamamos ordenamiento topolgico de un grafo dirigido a todo ordenamiento m 1 , m 2 , ..., m k de los nodos del grafo tal que las aristas vayan desde los nodos que aparecen primero en el ordenamiento a los que aparecen ms tarde.
Si tenemos m i m j , m i ha de aparecer antes que m j en el ordenamiento topolgico.
5.2 Esquema de traduccin Es otra forma de representar las acciones semnticas, en ella insertamos las acciones semnticas en las reglas entre llaves. Tambin se aaden atributos a los smbolos de la gramtica.
Ejemplo.-
E T R R Operador T {print (Operador.lexema)} R 1 | c T num {print (num.val)}
Cuando hablamos de Operador nos estamos refiriendo a un token que puede ser + o -.
E.val E 1 .val E 2 .val D T. t i po =r eal L. her = r eal r eal L. her = r eal , i d3 L. her = r eal , i d2 i d1 1 2 3 4 5 6 7 5 Vamos a ver el rbol de anlisis sintctico para la entrada 9-5+2, con cada accin semntica asociada como el hijo de su parte izquierda correspondiente:
La salida resultante es: 95-2+, es decir, la operacin en notacin postfija.
5.3 Comprobaciones en tiempo de compilacin. Ya hemos comentado que una tarea fundamental de un compilador, en esta etapa, es comprobar que los tipos de datos son los correctos. En relacin a esto, algunas de las comprobaciones a realizar en el llamado anlisis esttico (en tiempo de compilacin) son las siguientes:
a) Comprobaciones de tipos.- El compilador ha de ser capaz de detectar un error cuando se aplica a un operador un operando incompatible (sumar una matriz con un entero, por ejemplo).
b) Comprobaciones de unicidad.- Depender en gran medida del lenguaje, pero tiene que ver con donde se definen los objetos (por ejemplo, puede ser correcto la definicin de variables globales con el mismo nombre que una variable local en un procedimiento o una funcin, como ocurre en C).
c) Comprobacin de operadores sobrecargados.- Por ejemplo, el operador + significa algo distinto en el caso de 2+3 (operador de suma) que en el caso de a + b (concatenacin).
d) Polimorfismos en funciones.- Una funcin polimrfica es aquella que se puede ejecutar con distintos tipos de argumentos.
Adems de stas, tambin tenemos comprobaciones del flujo de control; es importante controlar proposiciones que cambian el flujo de control en el programa. Por ejemplo, la proposicin break en C hace que el control salte fuera de la proposicin que la engloba (un switch-case, for o while).
E T R 9 - T Pr i nt ( - ) Pr i nt ( 9) R + T Pr i nt ( +) R 5 Pr i nt ( 5) c 2 Pr i nt ( 2) 6 5.3.1 Sistemas de tipos. El comprobador de tipos se basar en las construcciones sintcticas del lenguaje, los tipos definidos y las reglas definidas para asignar los tipos a las construcciones del lenguaje. Todo ello definir el sistema de tipos.
Por ejemplo, si tenemos la siguiente construccin:
if a > b then a = 25
tendremos que determinar si a y b son comparables por su tipo, tenemos que asignarle a a > b el tipo boolean, debemos de saber si a la variable a le podemos asignar el valor 25 por su tipo y, finalmente, si a > b es true, para lo cual hay que manejar el flujo de control (esto ltimo ya en la ejecucin).
Expresiones de tipos: es la forma de denotar el tipo de una construccin de un lenguaje. Una expresin de tipo puede ser un tipo bsico o formarse aplicando un operador llamado constructor de tipos a otras expresiones de tipos. Nosotros utilizaremos la siguiente definicin de expresiones de tipos:
1) Tipos bsicos: algunos de ellos son los integer, boolean, real, char, error (identificacin de un error), null (ausencia de valor).
2) Constructores de tipos: a) Matrices: array(I,T), siendo I el ndice y T una expresin de tipo, indica un tipo matriz con ndices I y del tipo T. b) Productos: T 1 x T 2 , producto cartesiano del tipo T 1 y T 2 . c) Registros: Como un producto pero con campos con nombre. d) Punteros: pointer (T), es un apuntador a un objeto de tipo T. e) Funciones: Las funciones se pueden definir como transformaciones desde un dominio tipo D a un rango tipo R, de la forma D R. Por ejemplo int x int int.
3) Nombres de tipos.- Por ejemplo, si en Pascal definimos: type enlace = | nodo; type liga = | nodo; var p : | nodo; q : enlace; r : liga;
El problema es saber si son del mismo tipo p, q y r... depende de la implementacin pues en la definicin original de Pascal no se defini el concepto tipo idntico. 5.3.2 Una gramtica y sus comprobaciones de tipos. Para mostar el funcionamiento de los sistemas de comprobacin de tipos veamos a continuacin una gramtica que iremos ampliando paulatinamente a lo largo del presente tema, tanto en nmero de producciones como en acciones semnticas: 7
P D ; E D D ; D | id : T T char | integer | array [ num ] of T | |T E literal | num | id | E mod E | E [ E ] | E|
La parte del esquema de traduccin que almacena el tipo de un identificador sera la siguiente:
P D ; E D D ; D D id : T {AadeTipo (id.entrada, T.tipo)} T char {T.tipo := char} T integer {T.tipo := integer} T |T 1 {T.tipo := pointer (T 1 .tipo)} T array [ num ] of T 1 {T.tipo := array (1...num.val, T 1 .tipo)}
Reglas de comprobacin de tipos en expresiones:
E literal {E.tipo := char} E num {E.tipo := integer} E id {E.tipo := BuscaTipo(id)} E E 1 mod E 2 {E.tipo := IF E 1 .tipo = integer AND E 2 .tipo = integer THEN integer ELSE error} E E 1 [ E 2 ] {E.tipo := IF E 2 .tipo = integer AND E 1 .tipo = array (S, T) THEN T ELSE error} E E 1 | {E.tipo := IF E 1 .tipo = pointer(T) THEN T ELSE error}
Reglas de comprobacin de tipos en proposiciones:
S id := E {S.tipo := IF BuscaTipo(id) = E.tipo THEN null ELSE error} S if E then S 1 {S.tipo := IF E.tipo = boolean THEN S 1 .tipo ELSE error} S while E do S 1 {S.tipo := IF E.tipo = boolean THEN S 1 .tipo ELSE error} S S 1 ; S 2 {S.tipo := IF S 1 .tipo = null AND S 2 .tipo = null THEN null ELSE error}
Reglas de comprobacin de tipos en funciones:
E E 1 (E 2 ) {E.tipo := IF E 2 .tipo = s AND E 1 .tipo = s t THEN t ELSE error} 8 5.3.3 Equivalencia de expresiones de tipos. Es importante tener una definicin precisa sobre cuando dos expresiones de tipo son equivalentes. Surgen ambigedades especialmente cuando se asigna un nombre a estas expresiones de tipo.
Para poder implementar en un ordenador la equivalencia de tipos se utiliza la denominada equivalencia de nombres y la equivalencia estructural, es esta ltima en la que nos vamos a centrar.
Equivalencia estructural de tipos:
Como hemos visto las expresiones de tipos se construyen a partir de tipos bsicos y constructores de tipos. Partiendo de estos conceptos decimos que existe una equivalencia estructural de dos expresiones si son el mismo tipo bsico o estn formadas aplicando el mismo constructor a tipos estructuralmente equivalentes.
Una funcin vlida para saber si dos tipos son estructuralmente equivalentes puede ser la siguiente:
Function equival (s, t) : boolean BEGIN IF s y t son del mismo tipo bsico THEN return TRUE ELSE IF s = array (s 1 , t 1 ) AND t = array (s 2 , t 2 ) THEN return equival (s 1 , s 2 ) AND equival (t 1 , t 2 ) ELSE IF s = pointer (s 1 ) AND t = pointer (t1) THEN return equival (s 1 , t 1 ) ELSE IF s = s 1 s 2 and t = t 1 t 2
THEN return equival (s 1 , t 1 ) and equival (s 2 , t 2 ) ELSE ... ... ELSE return FALSE END. 5.3.4 Codificacin de tipos. Consideremos la siguiente codificacin para los tipos bsicos:
TIPO BSICO CODIFICACIN Bolean 0000 Char 0001 Integer 0010 Real 0011
Si adems, tenemos expresiones de tipos con los siguientes constructores:
a) pointer (t), un apuntador a tipo t. 9 b) freturns (t), que seala una funcin con argumentos y que devuelve un objeto de tipo t. c) array (t) que indica una matriz.
Las expresiones de tipos formadas mediante la aplicacin de dichos constructores tienen una estructura muy uniforme. Algunos ejemplos son los siguientes:
Como se puede observar los cuatro bits a la derecha codifican el tipo bsico y los colocados inmediatamente a su izquierda el constructor aplicado, ms a la izquierda el constructor aplicado a este y as sucesivamente. 5.3.5 Conversin de tipos. Si tenemos la expresin a+b tenemos que comprobar los tipos de a y b, bien para comprobar si existe una incompatibilidad de tipos o bien para realizar una conversin (la forma de sumar un entero y un real no es la misma que la forma de sumar dos enteros).
Las conversiones de tipos pueden ser:
a) Implcitas o coerciones.- son las que realiza el propio compilador sin intervencin del programador. b) Explcitas.- cuando el programador ha de intervenir para que la conversin se realice. Por ejemplo, las conversiones (tipo) variable de C.
Las conversiones es preferible realizarlas en tiempo de compilacin que en tiempo de ejecucin pues se ahorra tiempo, algo que es especialmente sencillo en el caso de las constantes.
10 Un ejemplo de gramtica para la conversin de tipos es la siguiente:
E num {E.tipo := integer} E num . num {E.tipo := real} E id {E.tipo := BuscaTipo(id)} E E 1 op E 2 {E.tipo := IF E 1 .tipo = integer AND E 2 .tipo = integer THEN integer ELSE IF E 1 .tipo = integer AND E 2 .tipo = real THEN real ELSE IF E 1 .tipo = real AND E 2 .tipo = integer THEN real ELSE IF E 1 .tipo = real AND E 2 .tipo = real THEN real ELSE error}
5.3.6 Sobrecarga de funciones y operadores. Un operador est sobrecargado cuando tiene distintos significados y acepta distintos tipos de datos. Es decir, podemos tener ms de un tipo posible en la operacin. Un ejemplo tpico es la operacin de suma.
Veamos un ejemplo con una funcin:
E E 1 (E 2 ) {E.tipo := IF E 2 .tipo = s AND E 1 .tipo = s t THEN t ELSE error}
Si existe una sobrecarga, una generalizacin de esto podra ser la siguiente:
E E {E.tipos := E.tipos} E id {E.tipos := BuscaTipo(id)} E E 1 (E 2 ) {E.tipos := t / - s t e E 1 .tipos, s e E 2 .tipos}
Miremos por ejemplo un rbol para la operacin de multiplicacin *, con la expresin 3 * 5: E: {i, c} E: {i, c} *: {ixii, ixic, cxcc } E: {i, c} 3: {i} 5: {i} 11
Como se ve en el rbol, si el nico tipo posible para 3 y 5 es integer, entonces el operador * se aplica a un par de enteros (sera integer x integer), sin embargo existen dos posibilidades para el operador *, una devuelve un entero y la otra un complejo, que sern los tipos posibles para la raz E.
Para reducir el tipo de E a un nico tipo (algo obligatorio en algunos lenguajes) se puede implantar una definicin dirigida por la sintaxis realizando dos recorridos en profundidad de un rbol sintctico para una expresin. Durante el primer recorrido, el atributo tipos se sintetiza de manera ascendente. Durante el segundo recorrido, el atributo unico se propaga de forma descendente (en cursiva). Las reglas son:
S id = E { E.unico := IF BuscaTipo(id) nos devuelve un solo tipo AND BuscaTipo(id) c E.tipos THEN BuscaTipo(id) ELSE error S.tipo := IF E.unico error THEN null ELSE error}
E id {E.tipos := BuscaTipo(id)}
E E 1 (E 2 ) {E.tipos := t / - s t e E 1 .tipos, -s e E 2 .tipos temp := {s / s e E 2 .tipos AND s E.unico e E 1 .tipos} E 2 .unico := IF temp contiene un solo tipo {s} THEN s ELSE error E 1 .unico := IF temp contiene un solo tipo {s} THEN s E.unico ELSE error}
Si tenemos c =mifuncion(b). Nos quedamos con el tipo de c (que ser el que correspondera a E en en anlisis descendente de los atributos .unico) y buscamos en el rbol para encontrar una combinacin de los tipos de mifuncion y de b que nos den el tipo que tenemos predefinido para c. Si hay ms de una combinacin, la comprobacin no se puede hacer y tendremos un error, y si no hay ninguna tambien se produce un error.
Por ejemplo, si tenemos una frase del lenguaje de la forma: x = f(y), siendo: x de tipo Ta fde tipo Sa Ta, Sa Tb, Sb Ta, Sb Tb y de tipo Sa
De forma ascendente tenemos que: En E id (de la f) E.tipos = {Sa Ta, Sa Tb, Sb Ta, Sb Tb} En E id (de la y) E.tipos = {Sa} En E E 1 (E 2 ), E 1 .tipos = {Sa Ta, Sa Tb} y E.tipos={Ta, Tb} En S id = E (id aqu es x, de tipo Ta), E.unico ={Ta} => S.tipo=null
De forma descendente tenemos que E.unico = {Ta}, y con ello podemos reducir a un nico tipo cada nodo del rbol (por ejemplo, en nuestro caso, la funcin ser de tipo Sa Ta).
12 Estos recorridos se pueden ver en la siguiente figura:
[ f es de tipo Sa Ta, Sa Tb, Sb Ta y Sb Tb en la TS ] S x E = E ( E ) f y E.tipos ={Sa Ta, Sa Tb} E.unico = {Sa Ta} E.tipos ={Ta, Tb } E.unico = {Ta} E.tipos ={Sa } E.unico = {Sa} [ x es de tipo Ta en la TS ] [ y es de tipo Sa en la TS ] S.tipo = {null} 13
6 GENERACIN DE CDIGO. La generacin de cdigo es una etapa muy relacionada con el procesador (tecnologas CISC y RISC, existencia o no de coprocesador matemtico, procesadores vectoriales, sistemas multiprocesador, etc), y precisamente su arquitectura va a influir profundamente en el rendimiento del programa objeto generado.
En esta etapa partimos del rbol resultante del anlisis sintctico y se generar:
1 cdigo intermedio para facilitar la optimizacin, diseo de fases, etc. Adems, si distintos lenguajes general el mismo cdigo intermedio las fases dirigidas a la generacin de cdigo mquina pueden unificarse reduciendo los costes de desarrollo.
2 cdigo, normalmente ser el cdigo objeto que entiende el procesador aunque podra tratarse tambin de otro lenguaje distinto (por ejemplo, el GNU Fortran es un traductor de FORTRAN a C).
6.1 Lenguajes intermedios.
6.1.1 Notacin Polaca Inversa (RPN) Esta notacin es muy apropiada para analizar y evaluar expresiones aritmticas pero no es demasiado cmoda para utilizarla como lenguaje intermedio de un lenguaje de programacin, especialmente para manejar las estructuras de salto.
Una gramtica simple para operaciones aritmticas, que genera mediante acciones semnticas cdigo intermedio es la siguiente:
S S 1 * S 2 , S 1 S 2 * S S 1 + S 2 , S 1 S 2 + S (S), S S S 1 / S 2 , S 1 S 2 / 6.1.2 Cuartetos Es la notacin ms adecuada para representar operaciones binarias. Consta de los siguientes elementos:
(<operador>, <op1>, <op2>, <resultado>) 14
A / B se escribira como (/, A, B, t i ), siendo t i una variable temporal.
Por ejemplo, la siguiente operacin:
A + B C * D | F
Se escribira con cuartetos de la siguiente forma:
1. (+, A, B, T 1 ) 2. (|, D, F, T 2 ) 3. (*, C, T 2 , T 3 ) 4. (-,T 1 , T 3 , T 4 )
Veremos tcnicas para ordenar los cuartetos optimizando las operaciones, incluso llegando a eliminar algunos.
La notacin de cuartetos que usaremos ser la siguiente:
a) +, -, *, /, |. b) mod (mdulo), abs (valor absoluto) y sqr (raz cuadrada). c) sin, cos. d) Asignacin (:=, E, , X) que representa X:=E. e) Entrada / Salida: (READ, , ,X), (WRITE, , , X) f) Salto absoluto: (JP, n, , ) salta al cuarteto nmero n. g) Salto relativo: (JR, n, , ) salta a la posicin actual + n. h) Saltos condicionales: - (JZ, n, E, ) Si E = 0 salta a n. - (JGZ, n, E, ) Si E > 0 salta a n. - (JLZ, n, E, ) Si E < 0 salta a n. - (JE, n, X 1 , X 2 ) Si X 1 =X 2 salta a n. - (JG, n, X 1 , X 2 ) Si X 1 >X 2 salta a n. - (JL, n, X 1 , X 2 ) Si X 1 <X 2 salta a n.
Ejemplo.- Veamos a continuacin un bloque de cdigo en Pascal y su correspondiente cdigo en forma de cuartetos:
BEGIN s:=0; i:=0; read (n); 100: i:=i+1; read (a); s:=s+sqr(a); if i<n then goto 100; write (s); END.
Cdigo en cuartetos: 15
1. (:=, 0, , s) 2. (:=, 0, , i) 3. (READ, , , n) 4. (+, i, 1, t 1 ) 5. (:=,t 1 , , i) 6. (READ, , , a) 7. (sqr, a, , t 2 ) 8. (+, s, t 2 , t 3 ) 9. (:=, t 3 , , s) 10. (JL, 4, i, n) 11. (WRITE, , , s) 6.1.3 Tercetos. La notacin de cuartetos es una de las ms utilizadas como lenguaje intermedio, sin embargo tiene el inconveniente de ocupar mucho espacio en memoria debido a la utilizacin de gran cantidad de variables auxiliares.
Los tercetos ahorran espacio eliminando el campo de resultado, quedando implcito, asociado a dicho terceto. El formato de un terceto es el siguiente:
(<operador>, <op1>, <op2>)
La expresin A+B/C, expresada en forma de tercetos, se representa como:
1. (/, B, C) 2. (+, A, [1])
Con el [1] hacemos referencia al resultado obtenido del primer terceto.
Debido a que el resultado de un terceto est implcito, al realizar una reordenacin de los tercetos, por ejemplo en una optimizacin, hace que estos sean difciles de manejar (hay que cambiar las referencias). Por ello, se ha definido una modificacin denominada tercetos Indirectos.
En el caso de tercetos indirectos disponemos de dos estructuras: una son los tercetos tal cual tenamos anteriormente y otra es el denominado vector secuencia, que indica la secuencia de ejecucin de los tercetos.
Con este esquema, al reordenar la ejecucin no es preciso cambiar en los propios tercetos las referencias a resultados de otros tercetos, y adems si tenemos tercetos repetidos en la ejecucin, stos se indicarn solamente en el vector de secuencia.
Ejemplo.- Vamos a escribir en formato de tercetos el mismo cdigo del apartado anterior.
Ntese que aqu no tenemos instrucciones JE, JG y JL, por el nmero de operandos de los tercetos. 6.2 Generacin de cdigo intermedio.
6.2.1 Generacin de RPN desde expresiones aritmticas. Para la generacin de la pila con la expresin en formato RPN, utilizaremos las acciones semnticas durante el parsing. A continuacin vemos un ejemplo de gramtica con reglas semnticas para realizar esta generacin (con P(p) indicamos la cabeza de la pila):
REGLAS BNF ACCIONES SEMNTICAS S E E T E E + T P(p) = +, p = p+1 E E T P(p) = -, p = p+1 E - T P(p) =@, p = p+1 T F T T * F P(p) = *, p = p+1 T T / F P(p) = /, p = p+1 F id P(p) = id, p = p+1 F (E)
Ejemplo.- Vamos a ver el rbol para a-b+c-(-c-d) 17
Haciendo el recorrido de izquierda-derecha-centro obtenemos el estado de la pila, que ser: ab-c+c@d--.
6.2.2 Generacin de cuartetos. A la hora de generar los cuartetos a partir del rbol sintctico, hay que tener en cuenta el problema del arrastre de la informacin semntica, para eso utilizamos la notacin .sem. Es decir, tendremos reglas semnticas de arrastre de atributos, as como reglas de generacin de cdigo. Adems, crearemos variables temporales Ti cuando tengamos resultados de operaciones.
Veamos ahora una gramtica y sus reglas asociadas: S E S.sem = E.sem E T E.sem = T.sem E 1 E 2 + T i=i+1; E 1 .sem=Ti; Genera [(+, E 2 .sem, T.sem, E 1 .sem)] E 1 E 2 T i=i+1; E 1 .sem=T i ; Genera [(-, E 2 .sem, T.sem, E 1 .sem)] E -T i=i+1; E 1 .sem=T i ; Genera [(@, C, T.sem, E 1 .sem)] T F T.sem = F.sem T 1 T 2 * F i=i+1; T 1 .sem=T i ; Genera[(*, T 2 .sem, F.sem, T 1 .sem)] T 1 T 2 / F i=i+1; T 1 .sem=T i ; Genera[(/, T 2 .sem, F.sem, T 1 .sem)] F id F.sem = id.sem F (E) F.sem = E.sem
a - b + c - ( - c d ) E E T E E T F T F F E T F E T F T F 18 Vamos a ver el rbol para a*(b+c) 6.3 Generacin de cdigo desde lenguaje intermedio.
6.3.1 Definicin de la mquina objeto. En este apartado vamos a definir una mquina objeto para todos los ejemplos que veamos.
Nuestra mquina dispondr de n registros: R0, R1, ..., Rn. Hay que tener en cuenta que cualquier operacin realizada sobre los registros es mucho ms rpida que si se realiza sobre datos en memoria.
Las operaciones mquina sern de la forma: OP fuente, destino. Vamos a suponer que las palabras son de 4 bytes.
Las intrucciones vlidas para nuestra mquina sern:
MOV fuente, destino Llevar la informacin de la fuente al destino. ADD fuente, destino Hace fuente + destino y el resultado en destino. SUB fuente, destino Hace destino fuente y el resultado en destino. MUL fuente, destino Hace fuente * destino y el resultado en destino. DIV fuente, destino Hace destino / fuente y el resultado en destino. E, T 2
F, T 1 T, a * F, a S, T 2
a ) ( E, T 1
T, c E, b + F, b T, b F, c c b 19 GOTO posicin Saltos en el programa. HALT Parada de programa. CALL Llamada a subrutina. RETURN Retorno de subrutina. CMP fuente, destino Comparar fuente con destino. CJE posicin Salto a posicin si tras CMP fuente = destino. CJL posicin Salto a posicin si tras CMP fuente < destino. CJG posicin Salto a posicin si tras CMP fuente > destino.
Modos de direccionamiento.
MODO FORMA DIRECCIN COSTO Absoluto M M 1 Registro Ri No direccin, valor Ri 0 Indexado C(R) C + Contenido(R) 1 Registro indirecto *R Contenido(R) 0 Indexado indirecto *C(R) Contenido(C + Contenido(R)) 1
Ejemplo.-
MOV R0, M Copia el contenido de R0 a la posicin de memoria M. MOV 4(R0), M Copia el contenido de la direccin (4 + Contenido(R0)) a la direccin M. MOVE *4(R0), M Copia el contenido de la direccin de memoria almacenada en la direccin (4 + Contenido(R0)) a la direccin M. MOVE #325h, M Copia el valor 325h a la direccin de memoria M.
6.3.2 Generacin de cdigo desde RPN. El mecanismo es el siguiente: a) Si llega un smbolo se almacena en la pila. b) Si llega un operador se realiza la operacin, se sacan los operandos de la pila, se genera el cdigo almacenando el resultado de la operacin dentro de la pila.
Veamos el ejemplo con la sentencia A*B+C-D*E, que en RPN es AB*C+DE*-, suponiendo que slo tenemos un registro o acumulador, el R0:
Pila antes Smbolo actual Resto sentencia Accin Cdigo generado $ A B*C+DE*-$ Apilar A $A B C+DE*-$ Apilar B $AB * C+DE*-$ Generar MOV A, R0 MUL B, R0 MOV R0, T 1
$T 1 C +DE*-$ Apilar C $T 1 C + DE*-$ Generar MOV T 1 , R0 ADD C, R0 20 MOV R0, T 2
$T 2 D E*-$ Apilar D $T 2 D E *-$ Apilar E $T 2 DE * -$ Generar MOV D, R0 MUL E, R0 MOV R0, T 3
$T 2 T 3 - $ Generar MOV T 2 , R0 SUB T 3 , R0 $ $ Fin
Como se puede observar muy fcilmente, el cdigo generado no es ptimo pues el registro se vaca y se vuelve a llenar con el mismo valor repetidas veces. Para solucionar este problema vamos a utilizar un smbolo (Acc) que introduciremos en la pila cuando esta est usada; si en algn momento se intenta introducir en la pila dos Acc, significar que tenemos que descargarlo en memoria. Veamos el funcionamiento con el ejemplo anterior.
Pila antes Smbolo actual Resto sentencia Accin Cdigo generado $ A B*C+DE*-$ Apilar A $A B C+DE*-$ Apilar B $AB * C+DE*-$ Generar MOV A, R0 MUL B, R0 $Acc C +DE*-$ Apilar C $AccC + DE*-$ Generar ADD C, R0 $Acc D E*-$ Apilar D $AccD E *-$ Apilar E $AccDE * -$ Generar MOV R0, T 1
MOV D, R0 MUL E, R0 $ T 1 Acc - $ Generar MOV R0, T 2 MOV T 1 , R0 SUB T 2 , R0 $ $ Fin
6.3.3 Generacin de cdigo desde cuartetos. En general, a partir de un cuarteto genrico:
(OP, Op1, Op2, Resultado)
obtendremos el siguiente cdigo genrico:
MOV Op1, R0 OP Op2, R0 MOV R0, Resultado (nota: este Resultado ser un T i )
21 Para manejar el acumulador, y previamente a ejecutar la operacin OP, actuaremos de la siguiente forma, segn las posibilidades:
a) Acumulador vaco: Genera MOV Op1, R0 y luego OP Op2, R0 b) Acumulador lleno con el primer operando: Genera OP Op2, R0 c) Acumulador lleno con el segundo operando: - Si la operacin es conmutativa: Genera OP Op1, R0 - Si la operacin no es conmutativa: Genera MOV R0, Ti MOV Op1, R0 OP Ti, R0 d) Acumulador lleno con algo til, que no es el primero ni el segundo operando: Genera MOV R0, Ti MOV Op1, R0 OP Op2, R0
22
7 OPTIMIZACIN DE CDIGO. En este captulo trataremos de optimizar tanto velocidad de ejecucin como memoria ocupada por el programa. En cualquier caso, los grandes cambios en el hardware que est apareciendo en al actualidad pueden hacer que muchos de los conceptos que veamos cambien.
En este tema trataremos distintos puntos de vista de la optimizacin. Trataremos de analizar el flujo de datos. Por ejemplo, si tenemos las siguientes instrucciones en nuestro programa:
FOR I=1 TO 100 { ... a = b + c ... }
La operacin a = b + c podra realizarse fuera del bucle en el caso de que a no cambie. De esta forma aceleraramos la ejecucin del programa.
Tambin veremos algoritmos para optimizar la ejecucin de operaciones aritmticas.
Para los ejemplos de este captulo utilizaremos cdigo de tres direcciones.
Resumiendo, en este captulo trataremos los temas:
1. Reduccin de operaciones:
Si tenemos la operacin A=4x3+2 dentro de un bucle, entonces lo ms lgico ser realizar la operacin fuera y ese valor ser vlido para todo el bucle.
2. Reacondicionamiento de instrucciones:
La operacin: A = B*C*(D+E), podramos generar el cdigo de las siguientes formas:
Forma 1 Forma 2 MOV B, R0 MOV D, R0 MUL C, R0 ADD E, R0 MOV R0, T1 MUL B, R0 MOV D, R0 MUL C, R0 ADD E, R0 MUL T1, R0
Si lo hacemos de la forma 2 obtenemos un cdigo ms pequeo y que ocupa menos memoria (no utilizamos ningn Ti). 23
En general veremos que reordenando las operaciones se optimiza el cdigo. Esto lo haremos con el algoritmo de Nakata.
3. Eliminacin de redundancias:
Veremos el problema de las redundancias aplicado principalmente a las matrices. Veamos el siguiente ejemplo.-
Definicin de la matriz: a : array [1..4; 1..6; 1..8] of integer
Instruccin dentro de un programa: a[i, j, 5] := a[i ,j ,6] + a[i ,j ,4] + a[i ,j ,3]
Hay que tener en cuenta que las matrices, al almacenarse en memoria, utilizan direcciones de memoria consecutivas. Vamos a suponer que nuestro compilador almacena los datos en memoria en el mismo orden que aparecen los ndices.
Vamos a ver la direccin de memoria de cada uno de los componentes matriz de la instruccin se calcularn sumando a la direccin del primero [1, 1, 1] diferentes valores:
Por lo tanto, vemos que hay una parte comn en el clculo de las direcciones de los elementos de la matriz utilizada en la instruccin (llammosle K), podramos calcularlo primero y luego aadir la parte no comn. Sera de la siguente forma:
K = [1, 1, 1] + (i-1) * 6 * 8 + (j-1) * 8
a[i, j, 5] direccin: K + 5 1 a[i, j, 6] direccin: K + 6 1 a[i, j, 4] direccin: K + 4 1 a[i, j, 6] direccin: K + 3 1
Realizar esto es especialmente importante en el caso de que la instruccin se encontrara en un bucle pues ahorraramos mucho tiempo de ejecucin.
4. Reduccin de potencia.
El clculo a = b 2 puede implementarse de dos formas distintas:
a) a = b ^ 2 b) a = b * b 24
Para la mquina, realizar el clculo es distinto pues normalmente ejecutar la opcin (a) es un trabajo software y la opcin (b) habitualmente se realiza por hardware, y por lo tanto es ms rpido.
7.1 Algoritmo de Nakata. Fue diseado originalmente en el ao 1964 (Anderson) y fue evolucionando hasta 1967 por los trabajos de Nakata. El objetivo es la optimizacin del uso de los registros minimizando el uso de variables temporales.
Dada una expresin aritmtica, primero construmos el rbol (mediante el anlisis sintctico) donde los nodos son los operadores y las hojas son las variables o identificadores. Luego se etiqueta cada arista del rbol con un factor de peso, que inicialmente era el n de acumuladores que necesitaba esa expresin, aunque el que veremos aqu llevar un factor relativo.
Partiendo de la raz se toma aquella rama del rbol o subrbol que quede por analizar con el factor de peso ms grande; en caso de igualdad se toma la rama de la derecha y continuara hasta llegar al final de la expresin.
Algoritmo de etiquetado: IF n es una hoja THEN IF n es el hijo ms a la derecha de su padre THEN etiqueta (n) = 0 ELSE etiqueta (n) = 1 ELSE Sean n 1 , n 2 , ..., n k los hijos de n ordenados por etiqueta, de modo que etiqueta (n 1 ) > etiqueta(n 2 ) > ... > etiqueta (n k ), hacemos: etiqueta(n) = mx 1 s i s k (etiqueta(n i ) + i 1)
NOTA: En caso de tratarse de un rbol binario, si los hijos tienen igual peso, el padre tendr ese mismo peso + 1, si los hijos tienen distinto peso, el padre tendr el peso del mayor.
Ejemplo.- a * (b + c)
El rbol de Nakata es:
Haciendo el recorrido por Nakata, la expresin queda: (b + c) * a * + a b c 1 0 1 1 25
Veamos ahora el cdigo que generara la expresin original y la expresin reordenada por Nakata:
a * (b + c) (b + c) * a
MOV a, R0 MOV b, R0 MOV R0, T1 ADD c, R0 MOV b, R0 MUL a, R0 ADD c, R0 MOV R0, T1 MUL T1, R0 MOV R0, T2
Se puede observar como la operacin generada por Nakata precisa menos cdigo y menos variables temporales para su ejecucin.
Ejemplo.- ( (A + B) / (C + D) + (E - F) * (G + H * I)) * (J * K L * M)
Recorremos el rbol de arriba hacia abajo, siguiendo la rama con mayor peso y obtenemos la siguiente expresin:
((((H * I) + G) * (E F)) + ((C + D) / (A + B))) * ((L * M) (J * K))
Nakata no tiene en cuenta la conmutatividad o no conmutatividad de las operaciones y nos ha cambiado una operacin de divisin y una resta. Sin embargo, si generamos cdigo suponiendo un acumulador, en este caso funciona por la definicin que hemos hecho de nuestra mquina objeto:
MOV H, R0 MOV C, R0 MOV L, R0 MUL I, R0 ADD D, R0 MUL M, R0 ADD G, R0 MOV R0, T3 MOV R0, T5 MOV R0, T1 MOV A, R0 MOV J, R0 MOV E, R0 ADD B, R0 MUL K, R0 SUB F, R0 DIV T3, R0 SUB T5, R0 MUL T1, R0 ADD T2, R0 MUL T4, R0 MOV R0, T2 MOV R0, T4 MOV R0, T6
* + - * * M / - B + + A + L J K 1 0 1 1 1 2 3 1 1 0 1 2 2 0 1 D C 0 1 F E 0 1 * G 1 1 I H 0 1 * 2 26 Ejemplo.- Vamos a hacer una modificacin en la operacin, de la forma siguiente:
((A + (B * X)) / (C + D) + (E - F) * (G + H * I)) * (J * K L * M)
El rbol quedara de la siguiente forma:
Recorremos el rbol de arriba hacia abajo, siguiendo la rama con mayor peso y obtenemos la siguiente expresin:
((((H * I) + G) * (E F)) + (((B * X) + A) /(C + D))) * ((L * M) (J * K))
Vemos que ahora ha realizado la divisin de forma correcta, sin embargo, al generar cdigo con la mquina objeto que hemos definido, tenemos que intercambiar los operandos en la divisin. Veamos el cdigo:
MOV H, R0 MOV B, R0 MOV L, R0 MUL I, R0 MUL X, R0 MUL M, R0 ADD G, R0 ADD A, R0 MOV R0, T6 MOV R0, T1 MOV R0, T3 MOV J, R0 MOV E, R0 MOV C, R0 MUL K, R0 SUB F, R0 ADD D , R0 SUB T6, R0 MUL T1, R0 MOV R0, T4 MUL T5, R0 MOV R0, T2 MOV T3, R0 MOV R0, T7 DIV T4, R0 ADD T2, R0 MOV R0, T5
NOTA: Las operaciones que estn en negrita son las que nos permiten realizar la operacin de divisin de forma correcta. Al hacer el recorrido del rbol sintctico, llegados a / tiene que darse cuenta de que no es conmutativa e invertir los operandos para realizarlo correctamente.
* + - * * M / - * + + A + L J K 1 0 1 1 1 2 3 1 1 1 1 2 2 0 2 D C 0 1 F E 0 1 * G 1 1 I H 0 1 * 2 X B 0 1 27 Ejercicio.- Vamos a optimizar el siguiente cdigo:
FOR I=L0 TO L1 DO BEGIN FOR J=L2 TO L3 DO BEGIN M[I] =X1 * X2 * (X3+X4) N[I,J] = M[I] ** 2 * D END; END;
Vemos que una gran parte del cdigo se puede ejecutar fuera de los bucles, o por lo menos una gran parte. Adems, podemos optimizar por Nakata los clculos y transformar el elevado al cuadrado por una simple multiplicacin. Nos quedara los siguiente:
K1 = (X3 + X4) * X1 * X2 K2 = K1 * K1 * D FOR I=L0 TO L1 DO BEGIN M[I] = K1 FOR J=L2 TO L3 DO BEGIN M[I,J] = K2 END; END;
NOTA: Es preciso hacer notar que no hemos generado el cdigo de forma extricta pues no hemos calculado las direcciones de la matrices. Adems, vemos que, en los bucles con cuartetos, hemos realizado la comprobacin al principio del bucle FOR, con lo que, un FOR I=2 TO 1 nunca se ejecutara. 28 7.2 Un ejemplo de optimizacin manual. --- Aho-Ullman Pag 606-616 --- 7.3 Lazos en los grafos de flujo. Vamos a comenzar con algunas definiciones necesarias para este apartado.
Definicin: decimos que un nodo d de un grafo de flujo domina al nodo n (escribimos d domina n) si todo camino desde el nodo inicial del grafo de flujo a n pasa por d.
NOTA: hay que tener en cuenta que todo nodo se domina a s mismo y que el nodo inicial domina a todos.
Ejemplo.-
Aqu podemos ver como:
El nodo 1 domina a todos los dems nodos. El nodo 2 slo se domina a s mismo. El nodo 3 domina a todos excepto al 1 y el 2. El nodo 4 domina a todos menos al 1, 2 y el 3. El nodo 5 y el nodo 6 slo se dominan a ellos mismos. El nodo 7 domina al 7, 8, 9 y 10 El nodo 8 domina al 8, 9 y 10. 3 1 4 7 8 2 5 9 6 10 29 El nodo 9 y el nodo 10 slo se dominan a s mismo.
Vamos a ver ahora el denominado rbol de dominacin, que es la representacin grfica de la informacin anterior.
Una de las aplicaciones principales de la informacin sobre dominadores es determinar los lazos de un grafo de flujo. Los lazos tienen dos propiedades importantes:
1. Un lazo tendr un solo punto de entrada (encabezamiento). Este punto de entrada domina a todos los nodos dentro del lazo, ser su nica entrada. 2. Debe existir al menos una forma de iterar el lazo, es decir, por lo menos existir un camino hacia el encabezamiento.
Para localizar los lazos en un grafo de flujo, un mtodo es localizar aristas cuyas cabezas dominen a sus colas. Esto es, si tenemos una arista de la forma: a b (b es la cabeza y a es la cola), si sabemos que b domina a entonces significa que ah hay un lazo.
En el grafo anterior podemos ver que: Tenemos una arista de 7 a 4 y 4 domina 7, de la misma forma, hay una arista de 10 a 7 y 7 domina 10. Los otros lazos que nos encontramos aparece con las aristas de 4 a 3, de 8 a 3 y de 9 a 1.
7.4 Anlisis global del flujo de datos.
Para realizar el anlisis del flujo de datos y, por lo tanto, realizar una buena optimizacin, precisamos tener informacin disponible sobre las variables, expresiones, etc, que ser el punto de partida de la optimizacin.
Nos centraremos especialmente en los bucles, un punto en donde las optimizaciones suelen ser importantes. 1 2 3 5 7 6 4 8 9 10 30
Con el anlisis de flujo de datos conseguiremos eliminar cdigo inactivo, obtener subexpresiones comunes, etc.
La informacin del flujo de datos se obtendr resolviendo ecuaciones que relacionan la informacin en varios puntos de un programa. Veamos la siguiente ecuacin:
sal[S] = gen[S] (ent[S] desact[S])
Dependiendo de lo que pretendamos optimizar, variarn las nociones de generar y desactivar.
Es perciso hacer notar que el anlisis se realiza habitualmente a nivel de bloque y no de proposicin, cuando hablamos de sal[S] estamos hablando de un punto final nico, algo que s existe en los bloques.
Hay casos especiales como son los punteros y las matrices, los cuales hacen ms complicado el anlisis. Tambin nos pueden complicar el anlisis las llamadas a procedimientos. Por ejemplo:
x = 5 ... call funcin(x) ... x = 6
Cuando llamamos a la funcin puede haber ocurrido que el valor de x cambie.
En cualquier programa consideraremos que existen puntos entre 2 proposiciones (cada proposicin tiene, al menos, un punto antes y despus).
Un camino de un punto p 1 a un punto p n es una secuencia de puntos p 1 , p 2 ,..., p n-1 , de forma que p i es que punto que precede a una proposicin y p i+1 el que la sigue en el mismo bloque, o bien p i es el final de un bloque y p i+1 es el comienzo del bloque siguiente.
Llamamos definicin de una variable x a una proposicin que asigna un valor a x, o puede asignarlo.
Sern definiciones no ambiguas: x=7, scanf(%d, fpepe), lectura de fichero, etc. Sern ambiguas las llamadas a procedimientos con x como parmetro (por direccin, no por valor) o sin la variable x como parmetro, pero est en su alcance.
Llamamos alcance de una definicin d a un punto p, si existe un camino desde el siguiente punto a d hasta p tal que la definicin no se desactive.
Decimos que una definicin se desactiva si durante el camino a otro punto se realiza otra definicin del mismo elemento. 31 7.4.1 Alcance de definiciones en estructuras de control. Para los ejemplos de este apartado utilizaremos la siguiente sintxis:
S id := E | S ; S | if E then S else S | do S while E E id + id | id
Vamos a ver las definiciones inductivas, o dirigidas por la sintaxis, de los conjuntos ent[S], sal[S] y genera[S] y desact[S] para las diferentes estructuras:
gen[S] = {d} desact [S] = D a {d}
sal[S] = gen[S] (ent[S] desact[S])
Aqu estamos diciendo que lo que se genera en esa proposicin es la definicin que hemos llamado d. Lo que se desactiva son todas la definiciones que haya en el programa de la variable a menos la de la propia proposicin.
Lo que sale de S es lo que generamos dentro (la definicin de a) unido a todo lo que entra menos lo que desactivamos.
Aqu utilizamos una poltica conservadora, asumimos todo lo de S 1 y S 2
(cuando realmente se ejecuta uno de los dos) y en la desactivacin, slo consideramos que se ha desactivado lo que realmente se desactiva en los dos S d: a := b+c S S 1
S 2
S S 1 S 2
32 (pase por el que pase). Esto es correcto si pretendemos ver, por ejemplo, el rango de valores que puede tomar una variable. Si vemos que todas las definiciones en un punto determinado despus del if son siempre x=9 (por las dos ramas), podemos utilizar 9 en lugar de x. Para este tipo de optimizaciones no hacerlo as nos producira optimizaciones incorrectas. Si lo que pretendemos es utilizar el valor de la asignacin hacerlo as no sera correcto pues es posible esa rama no se haya ejecutado.
gen[S] = gen[S 1 ] desact [S] = desact[S 1 ]
ent[S 1 ] = ent[S] sal[S 1 ] sal[S] = sal[S 1 ]
Aqu, la existencia del lazo no afecta a lo que se genera o desactiva en S, as como su salida.
Aqu puede observarse como la ent[S] no es la misma que la ent[S1], y mediante la ecuacin anterior, o sea ent[S 1 ] = ent[S] sal[S 1 ], no se puede calcular la entrada sin tomar en cuenta primero la salida.
7.4.1.1 Recorridos necesarios en el rbol para alcance de definiciones. Veamos como obtener los atributos en el rbol sintctico y su clculo mediante recorridos en el rbol mediante un ejemplo:
1. Calculamos genera y desactiva de forma sintetizada con un primer recorrido ascendente de rbol sintctico.
2. Partimos de que ent[S 0 ] = C, siendo S 0 el programa completo (a la entrada del programa completo no hay ninguna definicin. A partir de ah recorremos el rbol en profundidad de izquierda a derecha y vamos obteniendo la entrada S S 1
; S 1
; S 2
d 1 d 2
d 3
if S 3
; d 4 d 5
e 1
S 0
gen(S 3 ) en funcin de d 4 y d 5 (la ) gen(S 1 ) en funcin de d 1 y d 2
gen(S 2 ) en funcin de gen(S 1 ) y d 3
gen(S 0 ) en funcin de gen(S 2 ) y gen(S 3 ) NOTA: Idem para desactiva, menos en if que es la 33 de forma heredada, y calculando la salida de forma sintetizada escalando en el rbol.
Como ya se ha indicado, es muy importante darse cuenta de que, en el caso de los lazos (do), esta secuencia no es aplicable porque ent[S 1 ] = ent[S] sal[S 1 ], es decir, necesitamos la salida para conseguir la entrada. Luego veremos en este caso como se soluciona con una definicin equivalente.
Para resolver el problema con los lazos, existe una definicin equivalente para ent[S 1 ] que nos va a servir para realizar los clculos normalmente (primero calculamos de forma sintetizada genera y desactiva y luego, heredando la entrada, calculamos la salida), que es:
La salida de la primera iteracin es: X1 1 = G1 (E1 1 D1) = G1 (E D1)
La entrada de la segunda iteracin es: E1 2 = E X1 1 = E G1 (E D1) = E G1
La salida de la segunda iteracin es: X1 2 = G1 (E1 2 D1) = G1 (E G1 D1) = G1 (E - D1)
Si continuamos con las iteraciones veremos que no hay variacin alguna. Por lo tanto, hemos demostrado que:
ent[S 1 ] = ent[S] gen[S 1 ]
7.4.2 Notacin vectorial para representar genera y desactiva. La notacin que utilizaremos para los conjuntos genera y desactiva ser en formato vectorial:
V = (a 1 , a 2 , ..., a n )
n: nmero de definiciones Si a i = 0 significa que se ha generado o desactivado (segn sea uno u otro vector) una definicin, si su valor es 1 es todo lo contrario.
Con esta representacin, los clculos se realizarn de la siguiente forma:
A B se calcula como VA /\ VB (AND lgico con VB negado) A B se calcula como VA \/ VB (OR lgico entre VA y VB) A B se calcula como VA /\ VB (AND lgico entre VA y VB)
Ejemplo.-
/* d 1 */ i := m-1; /* d 2 */ j := n; /* d 3 */ a := u1; do /* d 4 */ i := i+1; /* d 5 */ j := j-1; if e1 then /* d 6 */ a := u2; else /* d 7 */ i := u3; while e2
NOTA: con d 1 , d 2 , ... , d n indicamos las definiciones que se realiza en esa proposicin (ese ser el orden utilizado en los vectores genera y desactiva).
35 Tambin es necesario hacer notar que ambos conjuntos son atributos sintetizados, que se obtienen de forma ascendente.
Veamos el rbol sintctico, con las definiciones en las hojas, y los vectores genera y desactiva: gen[S 1 ] = gen[d 2 ] (gen[d 1 ] desact[d 2 ]) = = (0100000) ( (1000000) (0000100) ) = (1100000)
Vamos a realizar una modificacin en nuestro cdigo, introduciendo una instruccin break dentro del lazo do-while (salta al final del lazo), de la siguiente forma:
/* d 1 */ i := m-1; /* d 2 */ j := n; /* d 3 */ a := u1; do /* d 4 */ i := i+1; /* d 5 */ j := j-1; if e1 then /* d 6 */ a := u2; else begin /* d 7 */ i := u3; break end while e2 Para realizar los clculos, donde esta la instruccin break decimos que no se genera nada (C), en nuestro ejemplo (0000000) y se desactiva todo (U), en nuestro ejemplo (1111111) y realizamos los clculos normalmente. Esta es una ; S 1
; S 2
d 1 ( 1000000) ( 0001001) d 2 ( 0100000) ( 0000100) d 3 ( 0010000) ( 0000010) do S 6
; S 5
if S 4 ; S 3
; d 4 ( 0001000) ( 1000001) d 5 ( 0000100) ( 0100000) d 6 ( 0000010) ( 0010000) d 7 ( 0000001) ( 1001000) e 1
e 2
; break C U 37 postura tambin conservadora porque no se puede llegar al final de una secuencia de proposiciones que finalice con una proposicin break.
7.5 Solucin iterativa de las ecuaciones de flujo de datos.
7.5.1 Anlisis de alcance de definiciones. Vamos a definir bloques (B) bsicos, considerando cada bloque como una proposicin que es cascada de una o varias proposiciones de asignacin. Tambin se definen sal[B], gen[B], desact[B] y ent[B] de la misma forma que en los apartados anteriores.
Algoritmo iterativo para alcance de definiciones:
Partimos de que se ha calculado gen[B] y desact[B] para cada bloque B. Y adems partimos de que:
ent[B] = P sal[P] La unin de los P, bloques predecesores de B. sal[B] = gen[B] (ent[B] desact[B])
Algoritmo:
Entrada: genera y desactiva para cada B i del grafo. Salida: ent[B i ], sal[B i ]
ent[B i ] = C for cada B i do sal[B i ] = gen[B i ]; cambio := true; while cambio do begin cambio := false; for cada bloque B do begin ent[B i ] := P sal[P]; salant := sal[B i ]; sal[B i ] := gen[B i ] (ent[B i ] desact[B i ]); if sal[B i ] = salant then cambio:= true end end
Ejemplo.-
/* d 1 */ i := m-1; /* d 2 */ j := n; /* d 3 */ a := u1; do /* d 4 */ i := i+1; /* d 5 */ j := j-1; if e1 then 38 /* d 6 */ a := u2; else /* d 7 */ i := u3; while e2
Vamos a ver el grafo de flujo:
En formato vectorial, los conjuntos genera y desactiva son:
Inicialmente Iteracin 1 Iteracin 2 BLOQUE ent sal ent sal ent sal B1 C 1110000 C 1110000 C 1110000 B2 C 0001100 1110011 0011110 1111111 0011110 B3 C 0000010 0011110 0001110 0011110 0001110 B4 C 0000001 0011110 0010111 0011110 0010111
d1: i := m-1 d2: j := n d3: a:=u1 d4: i := i+1 d5: j := j-1 d7: i := u3 d6: a := u2 B 1
B 2
B 3 B 4
gen[B 1 ] = {d 1 , d 2 , d 3 } desact[B 1 ] = {d 4 , d 5 , d 6 , d 7 }
gen[B 2 ] = {d 4 , d 5 } desact[B 2 ] = {d 1 , d 2 , d 7 }
gen[B 3 ] = {d 6 } desact[B 3 ] = {d 3 }
gen[B 4 ] = {d 7 } desact[B 4 ] = {d 1 , d 4 } 39 Las casillas en cursiva y negrita, conjuntos salida, tienen el mismo valor, con lo cual paramos el algoritmo y ya tenemos la entrada y la salida para cada uno de los bloques. NOTA: La entrada de la Iteracin 1 en B3 y B4 (cursiva)se puede tomar enrelacin a la salida de B2 de esa misma iteracin o de la anterior. No afecta al resultado, slo al nmero de iteraciones.
Ejemplo.-
/* d 1 */ y := k-1; /* d 2 */ w := l; /* d 3 */ a := C; do /* d 4 */ y := y+1; if condicion1 then /* d 5 */ a := S; /* d 6 */ w := w-1; else /* d 7 */ y := K; while condicion2
El grafo de flujo sera el siguiente:
En formato vectorial, los conjuntos genera y desactiva son:
Vamos a ver como calcular las entradas en los distintos bloques segn sus predecesores: d1: y := k-1 d2: w := l d3: a :=C d4: y := y+1 d7: y := K d5: a := S d6: w := w-1 B 1
B 2
B 3
B 4
gen[B 1 ] = {d 1 , d 2 , d 3 } desact[B 1 ] = {d 4 , d 5 , d 6 , d 7 }
gen[B 2 ] = {d 4 } desact[B 2 ] = {d 1 , d 7 }
gen[B 3 ] = {d 5 , d 6 } desact[B 3 ] = {d 3 , d 2 }
Inicialmente Iteracin 1 Iteracin 2 BLOQUE ent Sal Ent sal ent sal B1 C 1110000 C 1110000 C 1110000 B2 C 0001000 1110111 0111110 1111111 0111110 B3 C 0000110 0111110 0001110 0111110 0001110 B4 C 0000001 0111110 0110111 0111110 0110111
Las casillas en cursiva y negrita, conjuntos salida, tienen el mismo valor, con lo cual paramos el algoritmo y ya tenemos la entrada y la salida para cada uno de los bloques.
7.5.2 Anlisis de expresiones disponibles. Una expresin x+y est disponible en un punto p si todo camino, no necesariamente sin lazos, desde el nodo inicial hasta p evalua x+y y despus de la ltima evaluacin antes de p no hay asignaciones posteriores de x ni de y.
Un bloque B i desactiva una expresin x+y si asigna un valor a x o a y.
Un bloque B i genera una expresin x+y si evalua x+y y no genera una redefinicin posterior de x o de y.
e_gen[B i ] es el conjunto de las expresiones generadas en el bloque B i . e_desact[B i ] es el conjunto de las expresiones desactivadas en el bloque B i .
U es el conjunto universal , todas las expresiones que hay en el programa. C es el complementario de U.
Las ecuaciones a aplicar son:
sal[B i ] = e_gen[B i ] (ent[B i ] e_desact[B i ]) ent[B i ] = p sal[P i ] si Bi no es el bloque inicial ent[B 1 ] = C siendo B 1 el bloque inicial
Aqu utilizamos la de la salida de los predecesores porque para que una expresin est disponible en un bloque tiene que salir de todos sus predecesores (una vez ms, utilizamos una estrategia conservadora).
Ejemplo.- 41
(1) a = b + c -1- Disponible la expresin 1 (2) b = a + d -2- Disponible la expresin 2 (se ha cambiado b y la 1 ya pasa a no estar disponible) (3) c = b + c -3- Disponible la expresin 2 (la 3 ya no es disponible inmediatamente pues hemos cambiado el valor de c) (4) d = a d -4- Ninguna es disponible (se ha cambiado el valor de d y la dos ya no est disponible y la propia expresin 4 ya cambia inmediatamente el valor de d).
Algoritmo iterativo para expresiones disponibles:
ENTRADA: Grafo de flujo G, e_genera[B i ], e_desactiva[B i ] ent[B i ] = C; /* B 1 es el bloque inicial */ sal[B 1 ] = e_gen[B 1 ]; for B = B 1 do sal[B] = U e_desact[B]; cambio = true;
while cambio do begin cambio = false for B = B 1 do begin ent[B] = p sal[P]; salant = sal[B]; sal[B] = e_gen[B] (ent[B] e_desact[B]); if salant = sal[B] then cambio = true; end; end;
Ejemplo.-
/* e1 */ i := m-1; /* e2 */ a := b+c; do /* e3 */ i := i+1; /* e4 */ d := b+c; i f e1 then /* e5 */ b := 2*i; else /* e6 */ c := 3*i; while e2
sal[B i ] = e_gen[B i ] (ent[B i ] e_desact[B i ])
Inicialmente Iteracin 1 Iteracin 2 BLOQUE ent Sal Ent sal ent sal B1 C 110000 C 110000 C 110000 B2 C 110100 100000 100100 100000 100100 B3 C 101011 110100 100010 100100 100010 B4 C 101011 110100 100001 100100 100001
En las dos ltimas iteraciones vemos que sal se mantiene, entonces paramos el algoritmo y ya tenemos las expresiones que entran y salen en cada bloque. e1: i := m-1 e2: a := b+c e3: i := i+1 e4: d := b+c e6: c := 3*i e5: b := 2*i B 1
B 2
B 3 B 4
e_gen[B 1 ] = {e 1 , e 2 } e_desact[B 1 ] = {e 3 , e 5 , e 6 }
e_gen[B 2 ] = {e 4 } e_desact[B 2 ] = {e 3 , e 5 , e 6 }
e_gen[B 3 ] = {e 5 } e_desact[B 3 ] = {e 2 ,e 4 }
e_gen[B 4 ] = {e 6 } e_desact[B 4 ] = {e 2 , e 4 } 43 7.5.3 Anlisis de variables activas. Una variable a es activa en un punto p se utiliza el valor a en algn camino que comience en p, en caso contrario se dir que a est desactiva.
Como se puede deducir, el anlisis en este caso se realiza en direccin opuesta al flujo de ejecucin del programa. Como sabemos que una variable est activa en un punto del programa si se usa en un punto posterior, esa informacin la recogeremos analizando el programa en este sentido.
Entrada es el conjunto de variables activas al comienzo del bloque.
Salida es el conjunto de variables activas a la salida del bloque.
Definidas son el conjunto de variables a las que se les ha asignado definitivamente un valor en en bloque.
Uso son el conjunto de variables cuyos valores se utilizan antes de cualquier definicin de la variable.
Las ecuaciones a aplicar son las siguientes:
ent[B i ] = uso[B i ] (sal[B i ] def[B i ]) sal[B i ] = S ent[S i ]
La primera ecuacin nos indica que una variable est activa al entrar en un bloque si se utiliza antes de una redefinicin en el bloque o si est activa al salir del bloque y dentro no se redefine.
La segunda ecuacin nos indica que una variable est activa al salir de un bloque si, y slo si, est activa al entrar en uno de sus sucesores.
Algoritmo iterativo de anlisis de variables activas:
Entrada: Un grafo de flujo G, def[B i ] y uso[B i ] Salida: sal[Bi], es decir, el conjunto de variables activas a la salida de cada bloque B del grafo de flujo.
for cada bloque B do sal[B] := C; for cada bloque B do ent[B] := uso[B]; while ocurren cambios en los conjuntos ent do for cada bloque B do begin sal[B] := S ent[S]; ent[B] := uso[B] (sal[B] def[B]) end end
44 Ejemplo.-
i := m-1; j := n; a := u1; do i := i+1; j := j-1; if e1 then a := u2; else i := u3; while e2
Vamos a ver el grafo de flujo:
Para la notacin vectorial, vamos a utilizar el orden de aparicin de las variables en el programa, esto es:
( i, m, j, n, a, u1, u2, u3 )
En formato vectorial, los conjuntos definicin y uso son:
Inicialmente Iteracin 1 Iteracin 2 BLOQUE sal Ent sal ent sal ent B1 C 01010100 10100000 01010100 10100011 01010111 B2 C 10100000 00000011 10100011 10100011 10100011 B3 C 00000010 10100000 10100010 10100011 10100011 B4 C 00000001 10100000 00100001 10100011 00100011
Iteracin 3 BLOQUE sal ent B1 10100011 01010111 B2 10100011 10100011 B3 10100011 10100011 B4 10100011 00100011
Las casillas en cursiva y negrita, conjuntos entrada, tienen el mismo valor, con lo cual paramos el algoritmo y ya tenemos la entrada y la salida para cada uno de los bloques.
46
8 ERRORES. En este captulo trataremos de los errores que pueden aparecer durante las tareas de compilacin principalmente, basndonos en criterios ortogrficos, sintcticos y semnticos. 8.1 Tipos de errores. Los distintos tipos de errores que podemos encontrarnos en un programa son los siguientes:
1) Errores lxicos, asociados a las tareas de anlisis lxico. Algunos podran ser la deteccin de un carcter que no pertenece al vocabulario, escritura incorrecta de un identificador, aparicin de ms de un punto decimal en una constante real, etc.
Por ejemplo.- Cuando en el anlisis lxico realizbamos una implementacin mediante un autmata, las casillas en blanco de la tabla de transiciones significaban errores. Estos errores es preciso identificarlos recorriendo el autmata, por ejemplo tenemos errores como la aparicin de dos signos en un nmero, existencia de ms de un punto decimal, etc.
2) Errores sintcticos, se producen cuando el analizador sintctico o parser no es capaz de obtener el rbol de derivacin para la tira de entrada.
Por ejemplo.- La tira A = 3 * I + (J * R tiene un error, pues faltan los parntesis.
3) Errores semnticos, se producen cuando utilizamos variables no declaradas, existen declaraciones mltiples, inconsistencia en parmetros, etc.
4) Errores de compilacin, son los que genera el compilador cuando se exceden lmites marcados por l mismo. Algunos son: - Limitaciones en memoria lo que implica limitaciones en el tamao de la tabla de smbolos, nmero FOR anidados, etc. - Limitaciones en las pilas de anlisis sintcticos y generacin de cdigo, condicionando de esta forma la complejidad del cdigo. - Limitaciones en el nmero de bloques que se pueden definir en el programa.
Todos los lmites del compilador deberan estar claramente definidos.
5) Errores de ejecucin, son los que se producen durante la ejecucin del cdigo objeto generado. Algunos son: - Aritmticos, como por ejemplo una divisin por 0 o una raz cuadrada de un nmero negativo. - Acceso a matrices fuera de rango. 47 - Acceso a ficheros no abiertos. - Problemas en punteros a ficheros (por ejemplo intentar acceder a un dato ms all del fin de fichero). - Problemas de memoria en tiempo de ejecucin al intentar reservar memoria dinmicamente cuando no hay disponibilidad. - Limitaciones de la pila de ejecucin (una de las situaciones ms tpicas es el abuso en la recursividad de partes del cdigo). 8.2 Recuperacin de errores lxico-grficos. En este apartado vamos a centrarnos en la correccin automtica de errores lxico-grficos, producidos por una escritura incorrecta de nombres de palabras reservadas, variables, etc. Veremos errores de intercambio de caracteres, omisin o borrado de caracteres y de inclusin de caracteres. 8.2.1 Correccin de errores de sustitucin. Este tipo de errores se producen cuando se ha introducido en algn elemento lxico cambios en ciertos caracteres.
Vamos a definir el operador de cambio C.
C(a) = T {a}
x e T*
C 1 (x) = { y / y e T* . y resultado de 1 cambio en x} C 2 (x) = { y / y e T* . y resultado de 2 cambios en x} ... C n (x) = { y / y e T* . y resultado de n cambios en x} Nota: Si |x| < n, entonces C n (x) = C
Notacin: Si tenemos que: A o 1 | o 2 1 ... | o m
Entonces escribimos: A = o 1 + o 2 + ... + o m
Si tenemos una G = (N, T, P, S), crearemos una G=(N, T, P, S), en la que las reglas sern: P = P {reglas de los operadores de cambio}. Por lo tanto P:
A o 1 A C 1 (o 1 ) A C 2 (o 1 ) ... A C n (o 1 ) A o 2 A C 1 (o 2 ) A C 2 (o 2 ) ... A C n (o 2 ) ... ... ... ... ... A o m A C 1 (o m ) A C 2 (o m ) ... A C n (o m )
De esta forma tenemos una gramtica G que considera las gramticas bien escritas y las gramticas mal escritas con errores de sustitucin. Esto es:
A = o 1 + o 2 + ... + o m + C 1 (o 1 ) + C 1 (o 2 ) + ... + C 1 (o M ) + ... + C 2 (o 1 ) + C 2 (o 2 ) + ... + C 2 (o m ) + ... + C n (o 1 ) + C n (o 2 ) + ... + C n (o m ) El nuevo lenguaje ser: 48
L(G) = { y / y e T* . y e C n (x) . x e L(G), n > 0}
Ejemplo.- Sea la gramtica:
G = ( {S, B}, {a, b}, P, S}
P: S aB | aSa B b
L(G) = {a n aba n / n > 0}
Vamos a generar una gramtica G = (N, T, P, S). Para ello comenzaremos por calcular los cambios en las partes derechas de las reglas:
C 1 (aB) = C 1 (a)B = bB C 1 (aSa) = C 1 (a)Ss + aSC 1 (a) = bSa + aSb C 1 (b) = a C 2 (aSa) = C 1 (a)SC 1 (a) = bSb
Por lo tanto las nuevas reglas (P) son:
S aB | aSa | bB | bSa | aSb | bSb B b | a
Ahora generamos un esquema de traduccin (EDT) para realizar la correccin:
EDT (N, Te, Ts, P, S), cuyas reglas P son de la siguiente forma:
A o i , o i cuando no existen errores A , o i cuando e C 1 (o i ) A |, o i cuando | e C 2 (o i ) ... ... A o, o i cuando o e C n (o i )
Las reglas P de nuestro EDT son:
S aB, aB S aSa, aSa B b, b Estas tres reglas cuando no hay errores.
S bB, aB S bSa, aSa S aSb, aSa Estas tres reglas cuando hay un error
S bSb, aSa Cuando hay dos errores.
49 8.2.2 Correccin de errores de borrado. Este tipo de errores se producen cuando se ha omitido algn carcter en un componente lxico.
Vamos a definir el operador de borrado B.
B(a) = ( c), a e T
Bn(x) = {C si |x| < n} {y / y e T* . resultado de eliminar n caracteres en x}
Ejemplo.-
G=( {S, B}, {a, b}, P, S )
P: S aB S aSa B b
G= (N, T, P, S)
P: B 1 (aB) = B B 1 (aSa) = B 1 (a)Sa + aSB 1 (a) = Sa + aS B 1 (b) = B 2 (aSa) = B 1 (a)SB 1 (a) = S
Al igual que en el caso de cambio, tambin utilizamos un EDT, de la siguiente forma:
P: S aB, aB S aSa, aSa B b, b Sin errores S B, aB S Sa, aSa S aS, aSa B , b Un error S S, aSa Dos errores
NOTA: Para realizar correciones ocurre lo mismo que en los cdigos Hamming, segn la distancia podremos detectar, detectar y corregir, etc. 8.2.3 Correccin de errores de inclusin. Estos errores aparecen al introducir caracteres no deseados dentro de los elementos lxicos del lenguaje.
Vamos a definir el operador de inclusin I.
50 I(a) = Ta + aT a e T
T = {a 1 , a 2 , ..., a n }
I(a) = aa 1 + aa 2 + ... + aa n + a 1 a + a 2 a + ... a n a
La gramtica Gtambin la construimos con un EDT = (N, Te, Ts, R, S)
G=( {S, B}, {a, b}, P, S )
P: S aB S aSa B b
G= (N, T, P, S)
P: S aaB, aB S baB, aB B bb, b B ab, b B ba, b ...
8.3 Anlisis sintctico en modo pnico . Al realizar el rbol sintctico, cuando no se puede realizar el anlisis, el compilador busca un punto despus del error a partir del cual se puede seguir realizando el anlisis sintctico. Sobre el trozo de cdigo que se ha saltado no muestra todos los errores.
Tpicamente se utilizan las estructuras BEGIN-END para saltar bloques en los que se encuentran errores.
51
9 INTRPRETES.
Los intrpretes, en la primera generacin de ordenadores, han tenido mucho xito por los problemas de memoria existentes. Muchas veces el programa fuente y el intrprete ocupaban memos memoria que el programa fuente y el compilador e incluso que el objeto.
Hoy en da han cado en desuso debido precisamente a la misma causa, hoy no tenemos problemas con la memoria de los ordenadores.
Adems tienen el problema de la deteccin de errores. Puede llegarse a un punto del programa en que exista un error y todo el trabajo anterior no sirve para nada.
Por ello, se opt por una opcin intermedia, se realiza una compilacin previa a un lenguaje intermedio, que adems servir para detectar errores. Luego el intrprete acta sobre este lenguaje intermedio. El esquema es el siguiente:
Es decir, tenemos dos tipos de intrprete:
a) Intrprete puro.- En el que no hay precompilacin. b) Inrprete actual.- Tenemos un cdigo intermedio que har el reconocimiento lxico, sintctico, etc. Posteriormente el mdulo de interpretacin coge lnea a lnea el cdigo intermedio y lo va ejecutando.
9.1 Estructura de un intrprete actual. En un intrprete actual tendremos una primera fase de anlisis que ser similar a la que nos encontramos en los compiladores.
Posteriormente se realiza la fase de interpretacin y ejecucin, que utilizar la TDS generada anteriormente y en la que hemos supuesto la utilizacin de un lenguaje intermedio en RPN.
Veamos el siguiente esquema:
Fuente
Comp.
Interp.
C.Inter.
TDS Precompilacin Ejecucin 52
Ejemplo.-
Un programa en BASIC:
A=9 ; B=8 ; B=B+A ; WRITE B ; GOTO 1000
Se traduce a cdigo intermedio en RPN, en forma compacta, de la siguiente forma:
A 9 := ; ; B 8 := ; B B A + := ; B WRITE ; 9 GOTO ;
El intrprete lo ejecutara utilizando la siguiente programacin (PCP es el puntero a la cabeza de la pila):
case V[pos] of ident, entero: apilar(V[pos]); p := p+1; +: sumar los elementos PCP y PCP-1 y sustituir en la pila; write: representar en pantalla el PCP; :=: evaluar elemento PCP y ponerlo en la direccin del de PCP-1; end
Analizador Lxico Analizador Sintctico TDS
RPN Programa fuente Intrprete Ejecucin/interpretacin
Anlisis
Datos Resultados 53 9.2 Arquitectura neutral de Java El compilador Java compila su cdigo a un fichero objeto de formato independiente de la arquitectura de la mquina en que se ejecutar. Cualquier mquina que tenga el sistema de ejecucin (run-time) puede ejecutar ese cdigo objeto, sin importar en modo alguno la mquina en que ha sido generado. Actualmente existen sistemas run-time para Solaris 2.x, SunOs 4.1.x, Windows 95, Windows NT, Linux, Irix, Aix, Mac, etc.
El cdigo fuente Java se "compila" a un cdigo de bytes de alto nivel independiente de la mquina. Este cdigo (byte-codes) est diseado para ejecutarse en una mquina hipottica que es implementada por un sistema run-time, que s es dependiente de la mquina. En una representacin en que tuvisemos que indicar todos los elementos que forman parte de la arquitectura de Java sobre una plataforma genrica, obtendramos una figura como la siguiente:
En ella podemos ver que lo verdaderamente dependiente del sistema es la Mquina Virtual Java (JVM) y las libreras fundamentales, que tambin nos permitiran acceder directamente al hardware de la mquina. Adems, habr 54 APIs de Java que tambin entren en contacto directo con el hardware y sern dependientes de la mquina, como ejemplo de este tipo de APIs podemos citar: - Java 2D: grficos 2D y manipulacin de imgenes. - Java Media Framework : Elementos crticos en el tiempo: audio, video... - Java 3D: Grficos 3D y su manipulacin. - Etc.
La verdad es que Java para conseguir ser un lenguaje independiente del sistema operativo y del procesador que incorpore la mquina utilizada, es tanto interpretado como compilado. Y esto no es ningn contrasentido, me explico, el cdigo fuente escrito con cualquier editor se compila generando el byte-code. Este cdigo intermedio es de muy bajo nivel, pero sin alcanzar las instrucciones mquina propias de cada plataforma y no tiene nada que ver con el p-code de Visual Basic. El byte-code corresponde al 80% de las instrucciones de la aplicacin. Ese mismo cdigo es el que se puede ejecutar sobre cualquier plataforma. Para ello hace falta el run-time, que s es completamente dependiente de la mquina y del sistema operativo, que interpreta dinmicamente el byte-code y aade el 20% de instrucciones que faltaban para su ejecucin. Con este sistema es fcil crear aplicaciones multiplataforma, pero para ejecutarlas es necesario que exista el run-time correspondiente al sistema operativo utilizado.
Aunque en las versiones iniciales de Java el motor de ejecucin consista de un interprete de cdigos de operacin, actualmente se utiliza la tecnologa de "generacin de cdigo justo en el momento" (Just-in-Time code generation), en dnde las instrucciones que implementan a los mtodos, se convierten en cdigo nativo que se ejecuta directamente en la mquina sobre la que se subyace (siendo de esta forma dependiente de la plataforma). El cdigo nativo se genera nicamente la primera vez que se ejecuta el cdigo de operacin Java, por lo que se logra un aumento considerable en el rendimiento de los programas.