Sei sulla pagina 1di 57

Compiladores

Ingeniera Informtica, 4 curso











Segundo cuatrimestre:

- 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).

Aho, A.V.; Ullman J.D.
"Principles of compiler design"
Addison-Wesley, Reading, Massachussetts.

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:

char
freturns (char)
pointer (freturns(char))
array (pointer(freturns(char)))

Si codificamos los constructores de la siguiente forma:

CONSTRUCTOR CODIFICACIN
pointer 01
array 10
freturns 11

La codificacin de las expresiones anteriores ser:

CONSTRUCTOR CODIFICACIN
char ...000000 0001
freturns (char) ...000011 0001
pointer (freturns(char)) ...000111 0001
array (pointer(freturns(char))) ...100111 0001

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.

1. (:=, 0, s)
2. (:=, 0, i)
16
3. (READ, , n)
4. (+, i, 1)
5. (:=, [4], i)
6. (READ, , a)
7. (sqr, , a)
8. (+, s, (7))
9. (:=, (8), s)
10. (-, i, n)
11. (JLZ, 4, [10])
12. (WRITE, , s)

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:

a[i, j, 5] direccin: [1, 1, 1] + (i-1) * 6 * 8 + (j-1) * 8 + 5 1
a[i, j, 6] direccin: [1, 1, 1] + (i-1) * 6 * 8 + (j-1) * 8 + 6 1
a[i, j, 4] direccin: [1, 1, 1] + (i-1) * 6 * 8 + (j-1) * 8 + 4 1
a[i, j, 3] direccin: [1, 1, 1] + (i-1) * 6 * 8 + (j-1) * 8 + 3 1

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;

Vamos a generar cdigo en cuartetos:

1. (+, X3, X4, T1)
2. (*, T1, X1, T2)
3. (*, T2, X2, T3)
4. (=, T3, , K1)
5. (*, K1, K1, T4)
6. (*, T4, D, T5)
7. (=, T5, , K2)
8. (=, L0, , I)
9. (JL, 20, L1, I)
10. (=, K1, , M[I])
11. (=, L2, , J)
12. (JL, 17, L3, J)
13. (=, K2, , M[I,J])
14. (+, J, 1, T7)
15. (=, T7, , J)
16. (JP, 12, , )
17. (+, i, 1, T8)
18. (=, T8, , I)
19. (JP, 9, , )
20. (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.



gen [S] = gen[S
2
] (gen[S
1
] desact[S
2
])
desact[S] = desact[S
2
] (desact[S
1
] gen[S
2
])

ent[S
1
] = ent[S]
ent[S
2
] = sal[S
1
]
sal[S] = sal[S
2
]



Lo que se genera en S
2
ya es lo que genera S, adems de lo que genere S
1
,
siempre y cuando no sea desactivado por S
2
. Idem con la desactivacin.

gen [S] = gen[S
1
] gen[S
2
]
desact[S] = desact[S
1
] desact[S
2
]

ent[S
1
] = ent[S]
ent[S
2
] = ent[S]
sal[S] = sal[S
1
] sal[S
2
]

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:

ent[S
1
] = ent[S] gen[S
1
]

Vamos a demostrarlo. Partimos de que:

ent[S
1
] = ent[S] sal[S
1
]
sal[S
1
] = gen[S
1
] (ent[S
1
] desact[S
1
])

vamos a escribirlo como:

E1 = E X1
X1 = G1 (E1 D1)

E1 y X1 son variables, las otras tres son constantes.

Al comenzar la primera iteracin, X1 = 0 y como entrada de la primera
iteracin tenemos:

E1
1
=E
; S
1

; S
2

d
1
d
2

d
3

if S
3

;
d
4
d
5

e
1

S
0

ent(d
4
) =ent(S
3
)
ent(S
2
) =
= ent(S0) = C
Arrancamos aqu
ent(S
0
) = C
ent(S
2
) =
= ent(S
0
) = C
ent(d
1
) =
= ent(S
2
) = C
ent(d
2
) =
= sal(d
1
)
sal(S
1
) =
= sal(d
2
)
etc
ent(S
3
) =
= sal(S
2
)
ent(d
5
) =ent(S
3
)
sal(S
0
) =
= sal(S
3
)
NOTA: aplicamos las ecuaciones
en donde no se indique nada
sal(S
3
) =
= sal(d
4
)
sal(d
5
)
34

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)

desact[S
1
] = desact [d
2
] (desact[d
1
] gen[d
2
]) =
= (0000100) ( (0001001) (0100000) ) = (0001101)

gen[S
2
] = gen[d
3
] (gen[S
1
] desact[d
3
]) =
= (0010000) ( (1100000) (0000010) ) = (1110000)

desact[S
2
] = desact [d
3
] (desact[S
1
] gen[d
3
]) =
= (0000010) ( (0001101) (0010000) ) = (0001111)

gen[S
3
] = gen[d
5
] (gen[d
4
] desact[d
5
]) =
= (0000100) ( (0001000) (0100000) ) = (0001100)

desact[S
3
] = desact[d
5
] (desact[d
4
] gen[d
5
]) =
= (0100000) ( ( 1000001) (0000100) ) = (1100001)

gen [S
4
] = gen[d
6
] gen[d
7
] = (0000010) (0000001) = (0000011)
desact[S
4
] = desact[d
6
] desact[d
7
] = (0000000)

gen[S
5
] = gen[S
4
] (gen[S
3
] desact[S
4
]) =
= (0000011) ( (0001100) (0000000) ) = (0001111)

; 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

36
desact(S
5
) = desact [S
4
] (desact[S
3
] gen[S
4
]) =
= (0000000) ( (1100001) (0000011) ) = (1100000)

gen[S
6
] = gen[S
5
] = (0001111)
desact[S
6
] = desact[S
5
] = (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:

BLOQUE genera desactiva
B1 1110000 0001111
B2 0001100 1100001
B3 0000010 0010000
B4 0000001 1001000

Vamos a ver como calcular las entradas en los distintos bloques segn sus
predecesores:

ent[B
1
] = C
ent[B
2
] = sal[B
1
] sal[B
3
] sal[B
4
]
ent[B
3
] = sal[B
2
]
ent[B
4
] = sal[B
2
]

Si aplicamos el algoritmo:

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:

BLOQUE genera desactiva
B1 1110000 0001111
B2 0001000 1000001
B3 0000110 0110000
B4 0000001 1001000

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
}


gen[B
4
] = {d
7
}
desact[B
4
] = {d
1
, d
4
}
40

ent[B
1
] = C
ent[B
2
] = sal[B
1
] sal[B
3
] sal[B
4
]
ent[B
3
] = sal[B
2
]
ent[B
4
] = sal[B
2
]

Si aplicamos el algoritmo:

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

42

En formato vectorial, e_genera y e_desactiva son:

BLOQUE e_genera e_desactiva
B1 110000 001011
B2 000100 001011
B3 000010 010100
B4 000001 010100


Vamos a ver como calcular las entradas en los distintos bloques segn sus
predecesores:

ent[B
1
] = C
ent[B
2
] = sal[B
1
] sal[B
3
] sal[B
4
]
ent[B
3
] = sal[B
2
]
ent[B
4
] = sal[B
2
]

Para las salidas aplicamos:

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:

BLOQUE definicin uso
B1 10101000 01010100
B2 10100000 10100000
B3 00001000 00000010
B4 10000000 00000001

Vamos a ver como calcular las salidas en los distintos bloques segn sus
sucesores:

i := m-1
j := n
a:=u1
i := i+1
j := j-1
i := u3 a := u2
B
1

B
2

B
3
B
4

def[B
1
] = {i, j, a}
uso[B
1
] = {m, n, u1}


def[B
2
] = {i, j}
uso[B
2
] = {i, j}


def[B
3
] = {a}
uso[B
3
] = {u2}


def[B
4
] = {i}
uso[B
4
] = {u3}
45
sal[B
1
] = ent[B
2
]
sal[B
2
] = ent[B
3
] ent[B
4
]
sal[B
3
] = ent[B
2
]
sal[B
4
] = ent[B
2
]

Si aplicamos el algoritmo:

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.

Potrebbero piacerti anche