Apuntes t8

31
Enviad sugerencias y/o errores a la dirección de correo [email protected] Procesadores de lenguajes U nidad t emática 8: G eneración de c ódigo i ntermedio V . 11.1 2 !"#$" &’ ()*+’*,&) -. /0&,1)2 ,*+’34’&,)2...................................................................................................... 5 "#"# $%&’( )* +*&+*(*,-./%’,*( %,-*+0*)%.(###########################################################################################################1 !"#$%&’( *"+#,&-$ ................................................................................................................................................................ / 012"3 45 +&(#$6&+ $2+#1$%#$ ............................................................................................................................................ / 7’4&8" 45 #15+ 4&15%%&"(5+ 9%:$1#5#"+; ..................................................................................................................... < 7’4&8" 45 4"+ 4&15%%&"(5+ 9#51%5#"+; ......................................................................................................................... = "#2# 3,(-+4//%’,*( )*5 /6)%7’ )* -+*( )%+*//%’,*(#################################################################################################8 "#9# :*,*+./%6, )* /6)%7’ %,-*+0*)%’ 0*)%.,-* 4,. 7+.0;-%/. )* .-+%<4-’(#########################################= 6. 7((’2) " 4,’4#3)2 &’ 8*" ’2+38(+83" 9 ’$’4’*+)2 &’ 8* "33"9................................... -: 2#"# >//*(’ . 0%*0<+’( )* 4,. *(-+4/-4+.########################################################################################################### "1 2#2# >//*(’ . *5*0*,-’( )* 4, .++.? ####################################################################################################################### "@ >.>.?. @%%5+" $ 3"+ 535A5(#"+ 45 :( $11$B A:3#&4&A5(+&"($3 5( C$+%$3 ................................................... ?< >.>.>. @%%5+" $ 3"+ 535A5(#"+ 45 :( $11$B A:3#&4&A5(+&"($3 5( 7 .............................................................. ?D 5. ;<=3’2,)*’2 $01,("2 ’ ,*2+38((,)*’2 &’ ()*+3)$ &’ >$8?) ................................................ -@ 9#"# A.+%.<5*( ? *B&+*(%’,*( 567%/.( ######################################################################################################################## "C 9#2# D*&+*(*,-./%6, ,40E+%/. ################################################################################################################################### 2F 9#9# D*&+*(*,-./%6, &’+ /’,-+’5 )* G54H’ ############################################################################################################### 21 :. A’>’3’*(,"2 *) 2"+,2>’(B"2. A’$$’*) =)3 3’+3)(’2) ....................................................... 6C 1#"# I5.,-*.0%*,-’ )*5 &+’<5*0.############################################################################################################################## 2@ 1#2# D*55*,’ &’+ +*-+’/*(’############################################################################################################################################ 28 1#9 D*&+*(*,-./%6, ,40E+%/. )* *B&+*(%’,*( 567%/.( J/’, +*55*,’ &’+ +*-+’/*(’K ###########################2L 1#1 D*&+*(*,-./%6, )* *B&+*(%’,*( 567%/.( &’+ /’,-+’5 )* G54H’ J/’, +*55*,’ &’+ +*-+’/*(’K ####### 2= /./.?. 7’4&8" 5( %"1#"%&1%:&#" ..................................................................................................................................... EF /./.>. G$1&$235+ 3’8&%$+ B 15*15+5(#$%&’( *"1 %"(#1"3 45 ,3:-"...................................................................... EF /./.E. H(+#1:%%&"(5+ 45 %"(#1"3 45 ,3:-" ................................................................................................................... E? C. D8#=3)13"4"2 ............................................................................................................. 5: @#"# D*7%(-+’( )* ./-%M./%6, *, I.(/.5# ################################################################################################################### 91 <.?.?. I5%3$1$%&’(............................................................................................................................................................. E/ <.?.>. J3$A$4$ ................................................................................................................................................................... E< @#2# N.+/’( *, O################################################################################################################################################################ 98 <.>.?. I5%3$1$%&’(............................................................................................................................................................. E= <.>.>. J3$A$4$ ................................................................................................................................................................... E= E,#$,)13">F" 3’()4’*&"&" ............................................................................................... 5G ;?’3(,(,)2 ......................................................................................................................... 5H D)$8(,)*’2 " $)2 ’?’3(,(,)2 2’$’((,)*"&)2 ......................................................................... :H

Transcript of Apuntes t8

Enviad sugerencias y/o errores a la dirección de correo [email protected]

Procesadores de lenguajes

U nidad t emática 8:

G eneración de c ódigo i ntermedio

V . 11.1

2

!"#$"%&'%()*+'*,&)%

-.%/0&,1)2%,*+'34'&,)2%......................................................................................................%5!

"#"#!$%&'(!)*!+*&+*(*,-./%',*(!%,-*+0*)%.(!###########################################################################################################!1!

!"#$%&'()*"+#,&-$)................................................................................................................................................................)/!

012"3)45)+&(#$6&+)$2+#1$%#$)............................................................................................................................................)/!

7'4&8")45)#15+)4&15%%&"(5+)9%:$1#5#"+;).....................................................................................................................)<!

7'4&8")45)4"+)4&15%%&"(5+)9#51%5#"+;).........................................................................................................................)=!

"#2#!3,(-+4//%',*(!)*5!/6)%7'!)*!-+*(!)%+*//%',*(!#################################################################################################!8!

"#9#!:*,*+./%6,!)*!/6)%7'!%,-*+0*)%'!0*)%.,-*!4,.!7+.0;-%/.!)*!.-+%<4-'(!#########################################!=!

6.%7(('2)%"%4,'4#3)2%&'%8*"%'2+38(+83"%9%'$'4'*+)2%&'%8*%"33"9%...................................%-:!

2#"#!>//*('!.!0%*0<+'(!)*!4,.!*(-+4/-4+.!###########################################################################################################!"1!

2#2#!>//*('!.!*5*0*,-'(!)*!4,!.++.?!#######################################################################################################################!"@!

>.>.?.)@%%5+")$)3"+)535A5(#"+)45):()$11$B)A:3#&4&A5(+&"($3)5()C$+%$3)...................................................)?<!

>.>.>.)@%%5+")$)3"+)535A5(#"+)45):()$11$B)A:3#&4&A5(+&"($3)5()7)..............................................................)?D!

5.%;<=3'2,)*'2%$01,("2%'%,*2+38((,)*'2%&'%()*+3)$%&'%>$8?)%................................................%-@!

9#"#!A.+%.<5*(!?!*B&+*(%',*(!567%/.(!########################################################################################################################!"C!

9#2#!D*&+*(*,-./%6,!,40E+%/.!###################################################################################################################################!2F!

9#9#!D*&+*(*,-./%6,!&'+!/',-+'5!)*!G54H'!###############################################################################################################!21!

:.%A'>'3'*(,"2%*)%2"+,2>'(B"2.%A'$$'*)%=)3%3'+3)('2)%.......................................................%6C!

1#"#!I5.,-*.0%*,-'!)*5!&+'<5*0.!##############################################################################################################################!2@!

1#2#!D*55*,'!&'+!+*-+'/*('!############################################################################################################################################!28!

1#9!D*&+*(*,-./%6,!,40E+%/.!)*!*B&+*(%',*(!567%/.(!J/',!+*55*,'!&'+!+*-+'/*('K!###########################!2L!

1#1!D*&+*(*,-./%6,!)*!*B&+*(%',*(!567%/.(!&'+!/',-+'5!)*!G54H'!J/',!+*55*,'!&'+!+*-+'/*('K!#######!2=!

/./.?.)7'4&8")5()%"1#"%&1%:&#").....................................................................................................................................)EF!

/./.>.)G$1&$235+)3'8&%$+)B)15*15+5(#$%&'()*"1)%"(#1"3)45),3:-")......................................................................)EF!

/./.E.)H(+#1:%%&"(5+)45)%"(#1"3)45),3:-")...................................................................................................................)E?!

C.%D8#=3)13"4"2%.............................................................................................................%5:!

@#"#!D*7%(-+'(!)*!./-%M./%6,!*,!I.(/.5#!###################################################################################################################!91!

<.?.?.)I5%3$1$%&'().............................................................................................................................................................)E/!

<.?.>.)J3$A$4$)...................................................................................................................................................................)E<!

@#2#!N.+/'(!*,!O!################################################################################################################################################################!98!

<.>.?.)I5%3$1$%&'().............................................................................................................................................................)E=!

<.>.>.)J3$A$4$)...................................................................................................................................................................)E=!

E,#$,)13">F"%3'()4'*&"&"%...............................................................................................%5G!

;?'3(,(,)2%.........................................................................................................................%5H!

D)$8(,)*'2%"%$)2%'?'3(,(,)2%2'$'((,)*"&)2%.........................................................................%:H!

Generación de código intermedio

3

1. Códigos intermedios

Además de analizar el código fuente (fase de análisis) un compilador debe generar código

(fase de síntesis). Esta generación de código se realiza en dos fases. Una primer fase en la que el

“front-ent” del compilador produce como resultado una representación intermedia (“Intermediate

Representation” o IR) del programa fuente y una segunda fase en la que el “back-end” producirá

el código objeto.

La fase de síntesis puede generar directamente como salida un código máquina, pero en ese

caso el compilador será totalmente dependiente de la máquina destino. Además, debido a la gran

disparidad entre el tipo de instrucciones que se emplean en los lenguajes de alto nivel y en los

lenguajes máquina, la construcción del generador de código será larga y el código resultante

difícil de mantener. Otra alternativa consiste en que la fase de síntesis genere código en otro

lenguaje de alto nivel. En este caso se requiere del empleo de otro compilador para la máquina

destino que finalmente genere el código máquina. La construcción del traductor será mucho más

sencilla, pero a cambio el código ejecutable que obtendremos consumirá en general más recursos

(tiempo y memoria).

La utilización de una representación intermedia (IR) aparece como una solución a mitad de

camino entre las dos anteriores. El proceso de generación de código queda dividido en dos etapas

de traducción: una primera fase independiente de la máquina destino en la que se genera una

representación intermedia, y una segunda fase en la que, a partir de la representación intermedia,

se genera el código objeto ejecutable para la máquina destino. En la primera etapa conocida

como generación de código intermedio se genera una representación intermedia del código

fuente que se va a compilar. Esta representación intermedia, independiente de la máquina para la

que se generará código y del lenguaje fuente, será la entrada del módulo generador de código

encargado de realizar la segunda etapa de traducción. Esta tercera opción es la que vamos a

estudiar con más detalle a lo largo de este capítulo.

Un código intermedio es una estructura de código cuya complejidad está entre el código

fuente en el lenguaje de alto nivel y el código máquina. Este código intermedio puede ser

considerado como la interfaz entre el generador de código (“back-end”) y las fases anteriores del

compilador (“front-end”). Su utilización presenta algunas ventajas frente a la generación directa

de un código máquina:

• Se facilita la reutilización de toda la etapa inicial del compilador (“front-end”) para la construcción de compiladores de un mismo lenguaje fuente para distintas máquinas.

(Fig. 2.1), y de toda la etapa final para construcción de compiladores de otros

lenguajes fuente sobre la misma plataforma.

• Se puede aplicar a la representación intermedia un optimizador de código

independiente de la máquina.

Generación de código intermedio

4

• Facilita la división en fases del proyecto: Permite abstraerse de los detalles propios de

la máquina destino en el desarrollo de la etapa inicial del compilador y de los propios

del lenguaje fuente en el desarrollo de la etapa final.

Fortran

Pascal

C

C++

Sparc

PowerPC

x86

ARM

Fortran

Pascal

C

C++

Sparc

PowerPC

x86

ARM

Representación

Intermedia

(a) (b)

Figura 2.1. Compiladores para 4 lenguajes y 4 máquinas sin (a) y con (b) utilización de un código intermedio.

Una buena representación intermedia debe cumplir las siguientes propiedades:

• Debe ser sencilla de generar y manipular.

• Debe ser adecuada para permitir su traducción al código máquina de todas las máquinas-destino deseadas.

• Cada construcción debe tener un significado claro y simple que facilite la

implementación del módulo optimizador.

El código generado por un compilador de Java, conocido como bytecode, puede considerarse

como un ejemplo de código intermedio con la característica peculiar de que que posteriormente

es ejecutado en la máquina virtual.

1.1. Tipos de representaciones intermedias

Hay distintos tipos de representación intermedia. En el capítulo 10 de [Tremblay, 85] puede

encontrarse una explicación más extensa las formas de representación intermedias que se

presentan a continuación.

Notación postfija

Notación sencilla empleada en máquinas de pila donde un operador se coloca a continuación

de sus operandos. Por ejemplo, la instrucción de asignación x := a+b+(c*d) quedaría en notación

postfija como: x ab+cd*+ :=

Este tipo de representación intermedia es poco adecuada para la fase de optimización y es

difícil de emplear para representar estructuras de control de flujo e instrucciones de selección.

Árbol de sintaxis abstracta

Un árbol de sintaxis abstracta (“Abstract Sintax Tree” o AST) no es más que un árbol en el

que un nodo interior representa un operador, y donde sus operandos serán sus descendientes

Generación de código intermedio

5

directos. En la Fig. 2.2 se puede observar como queda un árbol de sintaxis abstracta para la

asignación x:= (a+b*c) / d.

Figura 2.2. Árbol de sintaxis abstracta para la instrucción x:= (a+b*c) / d.

Una variante de los árboles de sintaxis abstracta son los grafos dirigidos acíclicos (GDA ). La

principal diferencia con los árboles de sintaxis abstracta es que en los primeros se permite que un

nodo tenga más de un padre. Este tipo de representación facilita el análisis del código y su

optimización, por lo que es muy empleada en compiladores optimizadores.

Código de tres direcciones (cuartetos)

Representación intermedia en la que las instrucciones constan de un operador y tres

direcciones: dos para los operandos y una tercera para el resultado. Las instrucciones tendrán la

forma genérica

<operador> <op1> <op2> <resultado>

Por ejemplo, la instrucción de asignación x := (a+b*c)/d se podría representar mediante una

secuencia de instrucciones en un código de tres direcciones como

Operador op1 op2 res (1) MULT b c t1

(2) SUMA a t1 t2

(3) DIV t2 d t3

(4) ASIGNA t3 x

Un código de tres direcciones no es más que una representación lineal de un árbol de sintaxis

abstracta o de un GDA en el que se ha empleado una variable temporal para representar los

valores de los nodos interiores.

:=

x /

+ d

a *

b c

Generación de código intermedio

6

Código de dos direcciones (tercetos)

En un código de tres direcciones se suele emplear un gran número de variables auxiliares

(temporales) para almacenar los resultados parciales durante la evaluación de una expresión. Un

código de dos direcciones evita este problema permitiendo que los operandos que aparecen en

una instrucción sean apuntadores a las instrucciones cuyo resultado se desea emplear.

La forma general de una instrucción de dos direcciones es

<op> <operando1> <operando2>

El mismo ejemplo de instrucción de asignación que hemos vistos representada mediante un

código de tres direcciones, x := (a+b*c)/d se podría representar mediante un código de dos

direcciones como

Operador op1 op2 (1) MULT b c

(2) SUMA a (1)

(3) DIV (2) d

(4) ASIGNA (3) x

Donde (1), (2) y (3) representan punteros a las instrucciones de código dos direcciones 1, 2 3

respectivamente.

La principal ventaja del código de dos direcciones frente al de tres es el ahorro de variables

temporales.

1.2. Instrucciones del código de tres direcciones

Para este capítulo hemos escogido como representación intermedia un código de tres

direcciones. Se muestran a continuación los tipos de instrucciones de este código y la forma

general que tendrán. En la mayoría de los casos, los operadores podrán ser constantes,

direcciones de memoria donde se encuentra el valor a usar, o directamente direcciones de

memoria.

Asignación

x := y op z

Donde op es una operador aritmético.

x := op z

Siendo op un operador unario.

x := z

Salto incondicional

Generación de código intermedio

7

goto E

Donde E es la etiqueta (representada por un número) de la instrucción de código

intermedio a la que se saltará.

Salto condicional

if x op y goto E

Siendo op un operador lógico binario. Si se cumple la relación se saltará a la instrucción

etiquetada E.

Pila de activación

push x

Apila x en la pila de activación.

pop

Devuelve el valor que se desapila.

Llamada y retorno de subprograma

call E

Salta a la instrucción etiquetada E.

ret

Finaliza el subprograma y salta a la dirección que se encuentra en la cima de la pila de

activación.

Asignaciones relativas

a [i] := x

x quedará almacenada i posiciones más allá de la dirección base a.

x := a[i]

Asigna a x el valor de la posición que se encuentra i unidades más allá de la posición base

a.

Acceso a la pila de activación

Acceso en las instrucciones de asignación al “frame pointer” (FP) y al tope de la pila (TOP).

Generación de código intermedio

8

Generación de etiquetas

Los destinos de los saltos se indican mediante etiquetas. Al definir el código intermedio

podemos elegir entre dos formas de generar estas etiquetas:

Generación explícita de etiquetas: Cada vez que se desee poner una etiqueta a una

instrucción de código intermedio se debe crear y emitir una etiqueta que tendrá la forma:

<nombre_etiqueta> :

Esta forma es la usada habitualmente por los lenguajes ensambladores. En el ejercicio

8.20 se puede ver un ejemplo de utilización de este tipo de etiquetas.

Generación implícita de etiquetas: Podemos suponer que cada instrucción de código

intermedio generada tendrá implícitamente una etiqueta que se corresponderá con un

número entero. Este número se corresponde con el número de orden de la instrucción

dentro de programa. Esta forma es empleada en algunos lenguajes de alto nivel como

Basic, que comienza cada línea con un número que representa la etiqueta de la

instrucción.

El código intermedio que se emplea en este capítulo utiliza etiquetas implícitas.

1.3. Generación de código intermedio mediante una gramática de atributos

En temas anteriores se ha mostrado la utilización de las gramáticas atribuidas como

formalismo para la especificación semántica. En este tema se seguirán empleando las gramáticas

atribuidas, pero sus acciones generan código intermedio de tres direcciones. Para ello vamos a

emplear un procedimiento llamado emite que recibirá como parámetros una instrucción de

código intermedio y sus argumentos. Este procedimiento se encargará de almacenar

secuencialmente las instrucciones en una estructura intermedia (que podría ser un fichero o un

vector de instrucciones de código intermedio). De esta forma, como resultado colateral de la

evaluación de la gramática, en la estructura intermedia empleada por emite, tendremos toda la

secuencia de instrucciones de código intermedio generadas, las cuales forman el programa en

código intermedio equivalente al programa fuente. Este programa en código intermedio tendrá

todas sus instrucciones etiquetadas con un número entero: el número de instrucción que será

generado automáticamente por el procedimiento emite.

Las expresiones que aparezcan entrecomilladas en la llamada al procedimiento emite, se

tomarán literalmente, mientras que el resto de expresiones se evaluarán antes de generar la

instrucción de código intermedio.

Generación de código intermedio

9

Ejemplo 2.1.-

emite (x ‘:=’ 5 ‘+’ 9)

emite (x ‘:=’ 5 + 9)

La primera llamada al procedimiento emite generará la instrucción de código tres direcciones

correspondiente a la asignación en la posición x del resultado de sumar las constantes 5 y 9 (se

emite una instrucción de suma, es decir, la suma se realizará en tiempo de ejecución). En cambio

la segunda llamada al procedimiento emite generará una instrucción que almacenará en la

posición x el valor 14 (se emite una instrucción de asignación, la suma se ha realizado en tiempo

de compilación).

En las gramáticas atribuidas que construyamos para generar código intermedio utilizaremos

la siguiente notación:

id.nom: Atributo asociado el símbolo léxico identificador que representa su lexema (nombre).

Su valor es devuelto por el analizador léxico. Este nombre nos permitirá buscar la posición en la

tabla de símbolos (TDS) donde se encuentra la información del identificador.

id.pos: Atributo del símbolo léxico que representa la posición de memoria que contendrá

durante la ejecución el valor de la variable representada por el identificador. Se le asigna a la

variable cuando se declara, se almacena en la TDS y puede ser consultado mediante la función

BuscaPos(...). En un lenguaje con estructura de bloques esta posición suele estar representada

por el par (nivel, desplazamiento). Cuando otros símbolos de la gramática tengan un atributo con

el mismo (.pos) nombre tendrán el mismo significado.

id.tipo: Atributo que representará la expresión de tipo asignada al identificador. Está

almacenado en la TDS y se puede consultar mediante la función BuscaTipo(...). Cuando otros

símbolos de la gramática tengan un atributo con el mismo nombre tendrán el mismo significado.

num.val: Atributo asociado al símbolo léxico constante numérica (num) que representa su

valor. Es devuelto por el analizador léxico. Cuando otros símbolos de la gramática tengan un

atributo con el mismo nombre tendrán el mismo significado.

Para simplificar los esquemas de traducción dirigidos por la sintaxis, nos vamos a permitir

emplear en las acciones semánticas variables globales. Estas variables globales podrán ser

accedidas desde cualquier acción semántica, es decir, se corresponderán con variables globales

del código fuente del compilador desarrollado. Para distinguirlas, aparecerán siempre en

mayúsculas. Si en alguna acción semántica aparece una variable en minúsculas se tratará de una

variable local asociada al símbolo no-terminal del lado izquierdo de la producción en la que

aparece.

Generación de código intermedio

10

SIGINST: Variable global del compilador que estamos construyendo que representa el

número de la SIGuiente INSTrucción que se va a generar. Esta variable es inicializada a 0 y el

procedimiento emite se encarga de incrementarla automáticamente cada vez que se emite una

instrucción.

Dada la siguiente gramática que representa una instrucción de asignación de un lenguaje de

alto nivel similar a C

Asig ! id = E

E ! E + E

E ! id

podemos construir un esquema de traducción dirigido por la sintaxis (ETDS) que genere

código de tres direcciones para instrucciones de asignación usando el procedimiento emite:

Asig ! id = E { Asig.pos := BuscaPos ( id.nom ) ; emite ( Asig.pos ‘:=’ E.pos ); }

E ! E1 + E2 { E.pos := CrearVarTemp() ;

emite ( E.pos ‘:=’ E1.pos ‘+’ E2.pos ) }

E ! id { E.pos := BuscaPos (id.nom ) }

Veamos cada una de las acciones semánticas asociadas a estas producciones.

Asig ! id = E { Asig.pos := BuscaPos ( id.nom )

;

emite ( Asig.pos ‘:=’ E.pos ) }

En el ETDS (esquema de traducción dirigido por la sintaxis) anterior se puede observar que

no aparecen acciones semánticas necesarias para realizar las comprobaciones semánticas. Esto es

debido a que este capítulo se centra únicamente en la generación de código intermedio.

Se puede observar como en la primera acción semántica se realiza una llamada a la función

BuscaPos. La función BuscaPos recibe como argumento el lexema (nombre) del identificador,

busca la entrada del identificador en la TDS y devuelve la posición de memoria que se ha

asignado a dicho identificador. Esta posición de memoria en un lenguaje con estructura de

bloques será habitualmente el nivel de anidamiento (o contexto) del identificador y su

desplazamiento relativo.

La siguiente acción semántica es una llamada al procedimiento emite. Éste emitirá una

instrucción en código de tres direcciones que asignará en la posición del identificador id el

contenido de la posición de memoria representada por E.pos. También se puede observar que la

acción semántica devuelve en el atributo sintetizado Asig.pos la posición de memoria donde

Generación de código intermedio

11

estará en tiempo de ejecución el valor de la variable id.nom. Esto se hace porque en C una

instrucción de asignación debe devolver el valor asignado a la variable del lado izquierdo de la

asignación. La utilidad de este atributo sintetizado se puede observar si añadimos a la gramática

anterior la producción

E ! Asig { E.pos :=Asig.pos }

donde se puede observar que una expresión puede ser una asignación.

La siguiente producción del ETDS es:

E ! E1 + E2 { E.pos := CrearVarTemp() ;

emite ( E.pos ‘:=’ E1.pos ‘+’ E2.pos ) }

La primera acción de esta producción realiza una llamada a la función CrearVarTemp(). Está

función devolverá la dirección de la primera posición de memoria libre (no asignada) dentro del

área de datos del bloque que se está compilando. En esa posición de memoria se almacenará el

valor de una variable temporal (de ahí el nombre de la función).

La segunda acción emite una instrucción de código de tres direcciones. Esta instrucción se

almacenará en la estructura intermedia empleada por emite para guardar el código intermedio.

Cuando en tiempo de ejecución se ejecute la instrucción, se asignará a la posición de memoria

representada por E.pos (la posición que devolvió CrearVarTemp) el resultado de sumar el

contenido de las posiciones de memoria representadas por E1.pos y E2.pos. Podemos observar

que E.pos es un atributo sintetizado que toma valor en esta producción mientras que E1.pos y

E2.pos son dos atributos sintetizados cuyo valor se usa en esta producción.

E ! id { E.pos := BuscaPos (id.nom ) }

La única acción semántica asociada a esta producción se encarga de dar al atributo E.pos la

posición de memoria asignada al identificador id. Para ello se realiza un llamada a la función

BuscaPos que recibe como argumento el lexema del identificador (id.nom), lo busca en la tabla

de símbolos y devuelve la posición de memoria que se le adjudicó en la fase de asignación de

memoria.

Ejemplo:

Veamos el comportamiento de este ETDS mediante la construcción del árbol anotado

correspondiente a la cadena a = b + c (Fig. 2.3). Suponemos que en la TDS tenemos la

información asociada a cada identificador que aparece en la Fig. 2.3.

Generación de código intermedio

12

TDS

Nom tipo posición [nivel, desp]

a tentero [0,24]

b tentero [0,26]

c tentero [0,28]

Figura 2.3. Árbol anotado y código intermedio generado para la cadena

a=b+c.

Para que resulte más cómodo de leer el código intermedio, en lugar de poner las posiciones

de memoria, en el resto de este capítulo se utilizará el nombre de la variable a la que

corresponde. En el caso de las variables temporales, su nombre comenzará por la letra minúscula

t seguida de un número entero que indicará el orden en el que ha sido generada: t1, t2, … En la

Fig. 2.3 se puede observar como al llamar a la función CrearVarTemp(), ésta ha devuelto como

posición de memoria la primera posición libre dentro del área de datos del bloque para el que se

estaba generando código (nivel 0, desplazamiento 30). Como ésta es la primera variable temporal

que se crea se le ha dado el nombre t1. De esta forma el código generado anteriormente puede

representarse para mayor claridad como:

(1) t1 := b + c

(2) a := t1

Veamos a continuación como se puede extender el ETDS anterior para generar código

también para otras expresiones aritméticas sencillas.

Instruc

id.pos=[0,24] = E.pos = [0,30]

+ E.pos = [0,28]E.pos = [0,26]

id.pos=[0,26] id.pos=[0,28]

Código tres direcciones generado (1) [0,30] := [0,26] + [0,28]

(2) [0,24] := [0,30]

Generación de código intermedio

13

E ! E1 * E2 { E.pos := CrearVarTemp() ;

emite ( E.pos ‘:=’ E1.pos ‘*’ E2.pos ) }

E ! num { E.pos := CrearVarTemp() ;

emite ( E.pos ‘:=’ num.val ) }

E ! ( E1 ) { E.pos := E1.pos }

E ! - E1 { E.pos := CrearVarTemp() ;

emite ( E.pos ‘:= -‘ E1.pos) }

Las acciones semánticas de la primera producción son semejantes a las que aparecían en la

producción que representaba la suma de expresiones, pero en este caso se genera una instrucción

de código intermedio de multiplicación. En la segunda producción podemos observar que cuando

la expresión es una constante numérica se ha decidido crear una variable temporal y generar una

instrucción de código intermedio que asigne en la posición de la variable temporal el valor de la

constante numérica (num.val) que proporciona el analizador léxico. Una opción más adecuada

habría sido que el no-terminal E devolviese el valor de la constante numérica, lo que habría

evitado la creación de una variable temporal. Sin embargo, esta segunda opción habría obligado

a que el no-terminal E, además del atributo .pos tuviese al menos otro atributo para devolver

valores constantes, lo que obligaría a añadir más acciones semánticas a todas las producciones

del no-terminal E. Para simplificar los esquemas de traducción hemos elegido la primera opción.

Hay que tener en cuenta que esta elección no afectará a la calidad del código finalmente

generado porque este tipo de variables temporales son fácilmente eliminadas durante la fase de

optimización de código intermedio.

En la tercera producción, una expresión (E) se reescribe como una expresión entre paréntesis

(E1). Puesto que el valor de una expresión entre paréntesis es el mismo de la expresión sin

paréntesis, basta con hacer que el atributo E.pos tome el mismo valor del atributo E1.pos. De esta

manera los dos atributos representan la misma posición de memoria (y por lo tanto el mismo

valor de la expresión).

Finalmente, en la última expresión observamos que E se reescribe como una expresión

cambiada de signo. Este caso es diferente del anterior, puesto que la expresión representada por

E no tomará el mismo valor que E1, por lo tanto es necesario utilizar una nueva posición de

memoria para almacenar el nuevo valor.

Ejercicio

Construye una gramática no-ambigua equivalente a la anterior. A continuación asocia a

cada producción las acciones semánticas necesarias para generar el código intermedio

correspondiente.

Generación de código intermedio

14

2. Acceso a miembros de una estructura y elementos de un

array

2.1. Acceso a miembros de una estructura

El acceso a los miembros de un estructura en C depende de la información que se almacene

en la TDS sobre la posición de los miembros. La forma habitual consiste en almacenar, junto a

cada miembro, su desplazamiento con respecto al comienzo del área de memoria asignada a esa

estructura (Fig. 2.4).

Ejemplo 2.2.- Dada la definición de las variables a y b de tipo estructura en el lenguaje C

struct ej {

int c1 ;

float c2 ;

}a, b;

La TDS quedaría:

TDS nom tipo posición … ref

a testructura 0 , 124

b testructura 0 , 130

Figura 2.4. Asignación de memoria en la TDS para los miembros de una estructura.

De esta forma, en la entrada en la TDS de cada miembro de un tipo de estructura concreta se

almacenará el mismo desplazamiento independientemente de que hayan declaradas más de una

variable de ese tipo. Lo único que cambiará será la dirección base de cada variable de ese tipo de

estructura. Evidentemente antes de generar código intermedio para acceder a un miembro de una

estructura será necesario sumar a la dirección base de la variable estructura, el desplazamiento

asignado al miembro. En la Fig. 2.5 se muestra como quedaría un ETDS para acceder a los

miembros de una estructura de esta manera.

Se ha usado la función EsMiembro(id1.nom, id2.nom) que devolverá el valor TRUE si el

nombre id2.nom es un miembro de la estructura de nombre id1.nom. La función

BuscaPos(id1.nom) devuelve la posición de memoria base a partir de la cual se encontrarán en

tiempo de ejecución los miembros de la estructura id1.nom. La llamada a

BuscaPosMiembro(id1.nom, id2.nom) devolverá el desplazamiento relativo del miembro a partir

de la posición de memoria base de la estructura.

TALLA_REAL = 4

TALLA_ENTERO = 2

Tabla de miembros

nom tipo posición

c1 tinteger 0

c2 treal 2

Generación de código intermedio

15

Inst_simple ! id1 . id2 = E

{ si BuscaTipo (id1.nom) <> testructura ent MemError();

sino base := BuscaPos (id1.nom) ;

si no EsMiembro (id1.nom, id2.nom) ent MemError()

sino desp_miembro:=BuscaPosMiembro(id1.nom, id2.nom);

pos := base + desp_miembro ;

emite ( pos ‘:=’ E.pos ) }

Figura 2.5. ETDS Para el acceso a los miembros de una estructura.

2.2. Acceso a elementos de un array

En los lenguajes de alto nivel podemos ver que frecuentemente se permite definir arrays de

varias dimensiones (como en Pascal o C), con un límite inferior (Pascal) y superior (Pascal y C)

definido para cada una de las dimensiones. En cambio, en el código intermedio definido solo

disponemos de instrucciones de asignación con un modo de direccionamiento relativo: de

z:=a[x] (en la posición z se almacena el valor de la posición resultante de sumar a la

dirección base a el contenido de la posición x) y a[x]:=z. Esto obliga a realizar proceso de

traducción que requiere el cálculo de la posición de memoria que ocupará en tiempo de ejecución

cada elemento del array definido en el programa fuente.

2.2.1. Acceso a los elementos de un array multidimensional en Pascal

Una forma bastante frecuente y cómoda de almacenar las matrices consiste en almacenar todos sus elementos en posiciones consecutivas de memoria. Con esta representación, cada vez

que nos encontremos en el lenguaje de alto nivel con una expresión donde aparezca un elemento

de una matriz, será necesario calcular el desplazamiento que hay que sumar a la dirección base

de la matriz para acceder al elemento indicado. Veamos como se realiza este cálculo en el caso

en el que se pueden definir límites inferiores y superiores (Pascal).

Si suponemos la declaración de una variable A de tipo vector (matriz de una dimensión) en

Pascal:

A: array [inf..sup] of integer ;

y se desea acceder al elemento A[i], éste se encontrará en la posición

base + (i - inf) * Talla

donde

base es la dirección base donde se encuentra el primer elemento del vector,

inf es el límite inferior de vector, y

Generación de código intermedio

16

Talla es el número deposiciones de memoria que ocupa cada elemento del vector

(dependiente del tipo de elementos que contiene el vector).

De igual forma, si se define una matriz de dos dimensiones:

A: array [inf1..sup1, inf2..sup2] of integer ;

la fórmula para acceder a un elemento A[i1,i2] sería:

base + ((i1 – inf1) * n2 + (i2-inf2) ) *Talla

donde n2 es el número de elementos de la segunda dimensión de la matriz (n2 = sup2–

inf2+1)

que puede quedar como:

base + ( (i1*n2 +i2) – (inf1*n2 + inf2) )*Talla

(inf1* n2+inf2) puede ser calculado en tiempo de compilación, y recibe el nombre de

desplazamiento inicial de la matriz. Este valor, puesto que no depende del elemento de la matriz

al que se quiera acceder, puede ser almacenado en la TDS. En cambio, (i1*n2+i2) tendrá que ser

calculado en tiempo de ejecución y por lo tanto habrá que generar código para realizar este

cálculo.

En general, dada la declaración de una matriz n-dimensional:

A: array [inf1..sup1, inf2..sup2, ... infn..supn] of integer ;

la fórmula para acceder a un elemento A[i1,i2,...in ] quedará como:

base + ( (((i1*n2+i2)*n3+i3 …)*nn+in) – (((inf1*n2+inf2)*n3+inf3…)*nn+infn) ) * Talla

donde (((inf1*n2+inf2)*n3+inf3…)*nn+infn) es el desplazamiento inicial de la matriz.

Puesto que el acceso a un elemento de una matriz puede aparecer en el lado derecho o

izquierdo de una asignación, las producciones que definen la sintaxis de este acceso aparecerán

en dos lugares distintos de la gramática. Como el código generado para los dos casos es

prácticamente el mismo, se muestra únicamente uno de los casos (cuando el acceso al elemento

de la matriz aparece en el lado izquierdo de la asignación):

Inst_simple ! id [ Lista_indices ] := E

Lista_indices ! E Resto_LI

Resto_LI ! , E Resto_LI

| "

Generación de código intermedio

17

En la Fig. 2.6 se muestra un ETDS que genera código intermedio para el acceso a los

elementos de una matriz n-dimensional en Pascal.

Inst_simple -> id { Lista_indices.nom := id.nom }

[ Lista_indices ] := { temp := Lista_indices.pos ;

emite (temp := temp ‘–‘ Desp_inicial(id.nom)) ;

emite (temp := temp ‘*’ Talla (id.nom)) }

E { pos := BuscaPos (id.nom) ;

emite ( pos ‘[‘ temp ‘]’ := E.pos }

Lista_indices ! E { temp := CrearVarTemp()

emite (temp := E.pos); Resto_LI.ndim := 1;

Resto_LI.temp := temp ; Resto_LI.nom := Lista_indices.nom }

Resto_LI { Lista_indices.pos := Resto_LI.pos }

Resto_LI ! , E { Resto_LI.ndim := Resto_LI.ndim + 1;

emite (Resto_LI.temp :=

Resto_LI.temp‘*’Num_elementos(Resto_LI.nom, Resto_LI.ndim));

emite (Resto_LI.temp := Resto_LI.temp ‘+’ E.pos)

Resto_LI1.ndim := Resto_LI.ndim ;

Resto_LI1.temp := Resto_LI.temp ;

Resto_LI1.nom := Resto_LI.nom ; }

Resto_LI1 Resto_LI1 { Resto_LI.pos:= Resto_LI1 pos }

Resto_LI ! " { Resto_LI.pos:= Resto_LI.temp }

Figura 2.6. ETDS para el acceso a los elementos de un array en Pascal.

La función Desp_inicial(R_Inst_simple.nom) devuelve el desplazamiento inicial de la matriz

de nombre R_Inst_simple.nom que se guardó en la TDS cuando se declaró la matriz. De igual

forma, la función Talla(R_Inst_simple.nom), devuelve el tamaño de cada elemento de la matriz,

y Num_elemen(Lista_indices.nom,n_dim) devuelve el número de elementos de la dimensión

n_dim de la matriz Lista_indices.nom.

Ejemplo 2.3.- Veamos qué código generará este ETDS para el acceso a un elemento de una

matriz bidimensional que se define en el siguiente trozo de código fuente en Pascal:

var ...

A: array [2..11,1..20] of real ;

x,y: integer ; z: real;

begin

...

A[x,y] := z ...

Generación de código intermedio

18

TDS

nom tipo posición tipo_elem … desp_ini ref

A tarray 0, 140 treal 41

x tentero 0, 940 -

y tentero 0, 942 -

z treal 0, 944 -

Figura 2.7. Contenido de la TDS para el Ejemplo 2.3.

Suponiendo que el contenido en la TDS es el de la Fig. 2.7 se generará el siguiente código

intermedio:

(100) t1 := x

(101) t1 := t1 * 20

(102) t1 := t1 + y

(103) t1 := t1 – 41

(104) t1 := t1 * 4

(105) A [t1] := z

2.2.2. Acceso a los elementos de un array multidimensional en C

La generación de código intermedio para acceder a arrays multidimensionales en C es más sencilla que la del caso de Pascal, ya que en C todos los arrays comienzan en la posición 0. Se

trata por lo tanto de un caso particular del visto para Pascal.

En general, dada la declaración de un array n-dimensional en C

int A[n1][n2]... [nn];

la fórmula para acceder al elemento A[i1][i2]...[in] quedaría como:

base + (((i1*n2+i2)*n3+i3 …)*nn+in) * Talla

donde ni es el número de elementos de la i-ésima dimensión.

Se puede construir un ETDS que genere código para el acceso a los elementos de una matriz

n-dimensional en Pascal tal y como aparece en la Fig. 2.8.

Tabla de arrays

n_dim lim_inf lim-sup

1 2 11

2 1 20

TALLA_REAL = 4 TALLA_ENTERO = 2 desp_ini = (inf1 * n2 + inf2) = 2*20+1 =

41

Generación de código intermedio

19

Inst_simple -> id { LI.nom := id.nom }

LI = E { emite (LI.pos := LI.pos ‘*’ Talla (id.nom)) ;

pos := BuscaPos (id.nom) ;

emite ( pos ‘[‘ LI.pos ‘]’ := E.pos ; }

LI ! [ E ] { LI.pos := CrearVarTemp() ;

emite (LI.pos := E.pos); LI.ndim := 1; }

LI ! { LI1.nom := LI.nom ; }

LI1 [ E ] Resto_LI1 { LI.ndim := LI1.ndim + 1; LI.pos := LI1.pos ;

emite (LI.pos := LI.pos ‘*’ Num_elementos(LI.nom, LI.ndim)) ;

emite (LI.pos := LI.pos ‘+’ E.pos) ; }

Figura 2.6. ETDS para el acceso a los elementos de un array en C.

3. Expresiones lógicas e instrucciones de control de flujo

En un lenguaje de alto nivel las expresiones de tipo lógico se emplean principalmente como

expresiones condicionales en instrucciones de control de flujo. Debido a esto hay una

dependencia elevada entre la forma de representar los valores de las expresiones lógicas y el

código que se debe generar para traducir las instrucciones de control de flujo. Por esta razón

vamos a ver en la misma sección la representación de los valores lógicos y la traducción de

instrucciones de control de flujo.

3.1. Variables y expresiones lógicas

Cualquier variable necesita una posición de memoria en la que, durante la ejecución del

código generado, se almacene su valor. Cuando se trata de una variable de tipo lógico o boolena,

para representar su valor también se necesita una posición de memoria (¡bastaría un bit!) para

almacenar su valor. Como el código intermedio definido en este capítulo no dispone del tipo

lógico ni el tipo bit, en la posición de memoria de una variable lógica se almacenará un entero

que representará su valor. Para esta representación numérica se empleará el mismo criterio

seguido en el lenguaje C1 de que un cero representa el valor de verdad “falso”, mientras que

cualquier valor distinto de cero representa el valor de verdad “cierto”.

Pero además de variables lógicas, en los lenguajes de alto nivel aparecen otro tipo de

expresiones lógicas, conteniendo comparaciones y/o conectivas lógicas. Para representar el valor

de una expresión lógica se pueden emplear dos métodos: la representación numérica y la

representación por control de flujo.

1 El tipo bool (lógico) no aparece en el lenguaje C hasta el estándar C99 (a través de <stdbool.h>) y no todos los

compiladores lo soportan. Por esta razón, las instrucciones de control de flujo en C utilizan expresiones enteras (0 es

falso y distinto de cero es cierto) para decidir el flujo.

Generación de código intermedio

20

En la representación numérica de una expresión lógica toda expresión lógica tendrá asignada

una posición de memoria donde se almacenará su valor de verdad: un entero igual a 0 para

“falso” o distinto de cero para “cierto”. Por el contrario, en la representación por control de flujo

no se emplea una posición de memoria para representar el valor de verdad de la expresión. Su

valor vendrá determinado implícitamente por la instrucción de código intermedio que se ejecute.

Tal y como se afirma en [AHOA90], ninguno de los dos métodos es siempre mejor que el otro.

En este apartado se muestran las dos formas de representación.

3.2. Representación numérica

En la representación numérica a cada expresión lógica se le asigna una posición de memoria

donde se almacena un valor numérico que indica el valor de verdad de la expresión. Si el código

intermedio permite emplear valores y operadores binarios bastaría con usar un 0 para representar

el valor falso y un 1 para el valor cierto. Si, como es nuestro caso, el código intermedio no

permite el empleo de valores binarios, se puede usar un número entero con un valor de cero para

representar el valor falso y un número distinto de cero para representar el valor cierto. Con esta

forma de representación, la evaluación de una expresión lógica se convierte en la evaluación de

una expresión aritmética.

En los siguientes ETDS se va a presentar una generación de código intermedio para el caso

en el que el lenguaje fuente permite la utilización de expresiones booleanas (como en Pascal) en

lugar de utilizar valores enteros (como en C). Como se ha comentado anteriormente, esto

obligará a realizar una traducción entre la representación del lenguaje fuente (booleanos) y la del

lenguaje intermedio (enteros).

Podemos escribir un ETDS para una sencilla gramática que genere expresiones lógicas

similares a las de Pascal tal y como aparece en el Fig. 2.8.

E ! true { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ 1 ) }

E ! false { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ 0 ) }

E ! ( E1 ) { E.pos := E1.pos }

E ! id { E.pos := BuscaPos(id.nom) }

Figura 2.8. ETDS representación numérica de expresiones lógicas.

Tal y como se puede apreciar en el ETDS anterior, cuando una expresión lógica sea la

constante TRUE o FALSE, basta con crear una nueva variable temporal y asignar en la posición

de memoria correspondiente un 1 o un 0 respectivamente. En el caso de que la expresión lógica

sea una variable (id), al igual que ocurría en el caso de las expresiones aritméticas, basta con que

el atributo E.pos tome el valor de la posición de memoria donde se almacena el valor de verdad

del identificador (devuelto por la función BuscaPos).

Generación de código intermedio

21

Para evaluar una expresión relacional, como a<5 hay que tener en cuenta que su valor de

verdad, en general, no se puede conocer en tiempo de compilación. Por esta razón es necesario

generar código de tres direcciones cuya ejecución produzca como resultado la evaluación de la

expresión relacional, asignando en la posición de memoria reservada para la expresión un valor 0

o distinto de cero según sea falsa o cierta la expresión, respectivamente.

Ejemplo

Para evaluar la expresión a<5, se puede generar la siguiente secuencia de código de tres

direcciones:

(1) t1 := 1

(2) if a < 5 goto 4

(3) t1 := 0

(4) ...

Tal y como fácilmente se puede comprobar, la ejecución del fragmento anterior de código de

tres direcciones produce como resultado que si a < 5 se almacenará un 1 en la posición de

memoria reservada para almacenar el valor de verdad de la expresión (en el ejemplo t1). En caso

contrario se almacenará un 0. El ETDS para generar la secuencia de código intermedio anterior

es

E ! E1 oprel E2 { E.pos := CrearVarTemp() ;

emite( E.pos ‘:=’ 1 ) ;

emite( ‘if’ E1.pos oprel.op E2.pos ‘goto’ SIGINST+ 2 ) ;

emite( E.pos ‘:=’ 0 ) }

En este ETDS puede verse la utilización del atributo oprel.op. Este atributo represente al

valor del operador relacional que aparece en el código fuente que se está compilando: “=”, “>”,

”>=”, “<”,… También se puede observar la aparición de una nueva variable global: SIGINST.

En el código intermedio de este tipo de expresiones se debe generar una instrucción de salto

condicional que se salte la instrucción que le sigue. Para generar esta instrucción se debe conocer

cuál es el número de la instrucción que se está generando. Con este fin, en el ETDS (y por lo

tanto en el código fuente del compilador desarrollado) existe un contador del número de

instrucciones de código intermedio generadas: SIGINST. Esta variable global del compilador es

actualizada de forma automática por el procedimiento emite cada vez que se genera una

instrucción. De esta forma, si en el momento de llamar al procedimiento emite para generar la

instrucción de salto condicional ‘if’ E1.pos oprel.op E2.pos ‘goto’ SIGINST+ 2, la variable

SIGINST vale 15, se generará la instrucción (15) if pos1 oprel pos2 goto 17.

Cuando la expresión lógica incluye operadores lógicos “and” y “or”, si el código intermedio

no dispone de estos operadores, como es nuestro caso, será necesario buscar una equivalencia

con los operadores disponibles en el código intermedio. En nuestro caso, podemos observar que

el operador lógico “and” puede implementarse como una operación de multiplicación entre

Generación de código intermedio

22

valores de verdad, y el operador “or” como una suma. De esta manera, podemos obtener el

ETDS:

E ! E1 or E2 { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ E1.pos ‘+’ E2.pos ) }

! E1 and E2 { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ E1.pos ‘*’ E2.pos ) }

Al generar código intermedio de esta manera hay que tener en cuenta que, cuando se realice

una operación “or” entre dos expresiones con el valor de verdad “cierto”, el resultado será otra

expresión con el valor “cierto” pero representado con un valor entero distinto de “1”. Esto

introduce un nuevo problema: En algún momento podría darse el caso de que el valor entero

sobrepase el rango válido de los enteros, apareciendo enteros negativos. Esto a su vez puede

provocar que una operación “or” entre dos expresiones ciertas representadas por valores enteros

iguales pero con signos distintos den como resultado el valor falso. Así por ejemplo, si

consideramos dos expresiones booleanas con el valor “cierto” pero representadas

respectivamente por los valores enteros “-5” y “5”, la operación “or” entre ellas (su suma) dará

como resultado “0” (valor “falso”). Esto es un error provocado por la forma en la que generamos

el código y por lo tanto debe corregirse. Una posible forma de corregir este problema consiste en

asegurarse de que un valor “cierto” siempre se representa por el entero “1”. Para ello

generaremos una instrucción que compruebe el valor entero después de la operación “or”. De

esta manera, el ETDS anterior quedaría:

E ! E1 or E2 { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ E1.pos ‘+’ E2.pos )

emite( ‘if’ E.pos ‘<= 1’ goto’ SIGINST+2 ) ;

emite( E.pos ‘:= 1’)}

Para generar el código correspondiente al operador lógico “not”, si no está disponible en el

código intermedio, tal y como es nuestro caso, será necesario generar una secuencia de

instrucciones cuya ejecución produzcan el resultado equivalente. Así por ejemplo, para obtener

en la posición de memoria t1 el valor numérico de verdad de la expresión “not a” se puede

generar la secuencia de instrucciones de código intermedio:

Generación de código intermedio

23

(1) t1 := 0

(2) if a <> 0 goto 4

(3) t1 := 1

(4) ...

El ETDS correspondiente quedará como aparece en la Fig. 2.9.

E ! E1 or E2 { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ E1.pos ‘+’ E2.pos )

emite( ‘if’ E.pos ‘<= 1’ goto’ SIGINST+2 ) ;

emite( E.pos ‘:= 1’)}

! E1 and E2 { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ E1.pos ‘*’ E2.pos ) }

! not E1 { E.pos:= CrearVarTemp() ;

emite( E.pos ‘:=’ 0 ) ;

emite( ‘if’ E1.pos ‘<> 0 goto’ SIGINST+2 ) ;

emite( E.pos ‘:=’ 1) }

Figura 2.9. ETDS operadores lógicos con representación numérica.

Para ver un ejemplo de la utilización de las expresiones lógicas en las instrucciones de

control de flujo, se va a construir un ETDS que genere código de tres direcciones para la

instrucción de do-while de C. Su sintaxis es

I ! do I1 while ( E )

El esquema del código a generar se puede representar tal y como aparece en la Fig. 2.10. El

bloque I1 representa todas las instrucciones que se han generado en las producciones del no-

terminal I para el cuerpo del bucle. De igual forma, el bloque E representa todo el código

intermedio emitido para la evaluación de la expresión E.

I1

E

if E.pos = 0 goto

Figura 2.10: Esquema de código para un do-while con representación numérica.

La ejecución del código intermedio generado para la evaluación de la expresión lógica dejará,

tal y como se ha visto, en una posición de memoria (E.pos) un valor 0 o distinto de 0 (1) según

sea falsa o cierta la expresión. El código que se genera para la instrucción do-while, saltará al

inicio del bucle si en esa posición hay un número igual a 0. El ETDS quedará por lo tanto

Generación de código intermedio

24

I ! do

I1

while ( E )

{ I.inicio := SIGINST }

{ emite( ‘if’ E.pos ‘<> 0 goto’ I.inicio) }

Se puede observar que se ha utilizado el atributo I.inicio para guardar el valor de la variable

global SIGINST justo antes de I1. Este atributo almacenará el número de la primera instrucción

de código intermedio generada en I1. Esta instrucción será el destino del salto condicional

generado para ejecutar una nueva iteración del bucle. Este ETDS emite únicamente una

instrucción, el salto que aparece al final del bucle, ya que las instrucciones del cuerpo del bucle

serán emitidas por las acciones semánticas de las producciones I y las correspondientes a la

evaluación de la expresión lógica por las del no-terminal E.

De forma semejante se puede intentar construir una gramática atribuida que genere código de

tres direcciones para una instrucción “while” de C:

I! while

( E )

I1

{ I.inicio:= SIGINST }

{ emite( ‘if’ E.pos ‘=0 goto’ I.fin ) }

{ emite( ‘goto’ I.inicio ) ;

I.fin := SIGINST }

Sin embargo, se puede observar que esta gramática no es L-atribuida. Esto es debido a que en

la acción semántica de generación de la instrucción de salto condicional { emite( ‘if’ E.pos‘=0

goto’ I.fin ) } aparece el atributo I.fin que toma su valor en una acción que aparece a la derecha

de la acción semántica del emite. Esto significa que si se realiza una traducción dirigida por la

sintaxis, en el momento en que se realiza la llamada a emite para generar la instrucción de salto

condicional, no se conoce todavía el valor del atributo I.fin que debería contener el número de la

primera instrucción de código intermedio generada después del bucle (se trata de un argumento

de la llamada a emite cuyo valor no se conoce). Este problema de tener que generar una

instrucción antes de conocer todos sus argumentos se resolverá en la sección dedicada a las

referencias no satisfechas.

3.3. Representación por control de flujo

En la representación por control de flujo se considera la representación de expresiones

lógicas dentro del contexto en el que más frecuentemente aparecen: las instrucciones de control

del flujo. En una instrucción de control de flujo el valor de la expresión lógica se utiliza para

decidir a qué bloque de código se debe saltar. El código generado para una expresión lógica

consistirá en la generación de una instrucción de salto para el caso en el que la expresión sea

cierta y otra para el caso en el que la instrucción sea falsa. Veamos un ejemplo de utilización de

esta representación en el contexto de un lenguaje de programación de alto nivel similar a Pascal.

El código generado para las constantes lógicas TRUE y FALSE quedaría:

Generación de código intermedio

25

E ! true { emite( ‘goto’ E.verdadero) }

E ! false { emite( ‘goto’ E.falso) }

donde E.verdadero y E.falso son etiquetas que representan el número de la instrucción a la

que hay que saltar cuando la expresión es cierta o falsa respectivamente.

Considerando una representación de los valores lógicos por control de flujo, un ETDS para la

instrucción repeat-until de Pascal podría ser:

I ! repeat

I1

until E

{ E.falso:= SIGINST }

{ E.verdadero := SIGINST) }

Se puede observar que en este ETDS no se ha generado ninguna instrucción. Únicamente ha

sido necesario dar un valor a los atributos E.falso y E.verdadero. L instrucción de salto que

provocará que se vuelva al principio del bucle si la expresión E se avalúa a falso se ha generado

en las producciones para el no-terminal E.

Desgraciadamente en el momento de generar el código para la expresión lógica no siempre se

conoce el valor de estas etiquetas. Este problema de tener que generar una instrucción antes de

conocer todos sus argumentos apareció también en la representación numérica y se resolverá en

la sección dedicada a las referencias no satisfechas.

4. Referencias no satisfechas. Relleno por retroceso

4.1. Planteamiento del problema

En la sección anterior se ha visto que frecuentemente se debe generar una instrucción de

código de la que no se conoce el valor de todos sus argumentos. Una primera aproximación para

solucionar este problema es realizar dos pasadas: En una primera pasada se generan las

instrucciones aunque algunas de ellas contengan referencias cuyo valor no se conoce todavía.

Tras esta pasada, en la que se han generado todas las instrucciones, se realiza una segunda

pasada en la que se van sustituyendo las referencias por sus valores.

Una alternativa que permite solucionar el problema de las referencias no-satisfechas sin tener

que realizar una segunda pasada es la conocida como relleno por retroceso. Básicamente esta

técnica consiste en la generación de cada instrucción aunque alguno de sus argumentos quede

incompleto. Cada vez que se genera una instrucción incompleta se guarda el número de la

instrucción incompleta en un atributo relacionado con al argumento cuyo valor no se conoce.

Cuando posteriormente, al seguir generando el resto de las instrucciones, se conozca el valor se

incorpora directamente a la instrucción que quedó incompleta, quedando de esta manera

completada.

Generación de código intermedio

26

4.2. Relleno por retroceso

Para realizar la implementación de esta técnica se pueden definir tres funciones con el

siguiente perfil:

ptro CreaLans(E): Crea una lista que solo contiene un número de instrucción E a rellenar

posteriormente. Devuelve un puntero a dicha lista.

ptro CompletaLans( ptro, E ): Rellena todas las instrucciones incompletas, cuyo número está

contenido en la lista apuntada por ptro, con el valor de argumento E.

ptro FusionaLans (ptro, ptro): Concatena las listas apuntadas por sus dos argumentos y

devuelve un puntero a la nueva lista.

Veamos mediante un ejemplo cómo pueden utilizarse estas funciones para implementar la

técnica de relleno por retroceso. Supongamos que en el momento de generar la instrucción de

salto incondicional de la línea 100 no se conoce cuál será el número de la instrucción destino de

este salto.

100 goto –

Se puede anotar en una lista, relacionada con el destino del salto, el número de la instrucción

generada que contiene este argumento no-satisfecho. Esto se hace mediante una llamada a la

función CreaLans(100). La lista ahora contendrá el número de la instrucción 100: lista={100}

Se continúa generando código y, si se genera otra instrucción de salto incompleta (por

ejemplo la número 103) cuyo destino sea el mismo, se añade a la lista asociada a ese destino el

número de la nueva instrucción incompleta: lista={100, 103}.

101 ...

102 ...

103 if a>0 goto ---

Continúa la generación de código intermedio y, en el momento en el que se conozca el valor

del argumento (por ejemplo el destino de los dos saltos podría ser la instrucción número 105), se

llama a la función CompletaLans(lista ,105) que se encargará de completar las instrucciones

100 y 103 con el valor 105. El código del ejemplo quedará por tanto:

100 goto 105

101 ...

102 ...

103 if a>0 goto 105

104 ...

105 ...

En la sección 3.1 se construyó un ETDS para la instrucción while de C suponiendo que se

utilizaba una representación numérica para las expresiones lógicas:

Generación de código intermedio

27

I! while

( E )

I1

{ I.inicio:= SIGINST }

{ emite( ‘if’ E.pos ‘=0 goto’ I.fin )

}

{ emite( ‘goto’ I.inicio ) ;

I.fin := SIGINST }

En este ETDS ya aparece el problema de tener que generar una instrucción de salto

condicional en la que no se conoce el número de la instrucción destino. Ahora se puede resolver

este problema usando relleno por retroceso. Justo antes de generar la instrucción de salto se crea

una lista en la que se guarda el número de la instrucción que contiene la referencia no-satisfecha:

CreaLans(SIGINST). Cuando finalmente se conozca el destino del salto (la instrucción que se

genere justo después de la última instrucción del bucle), se llamará a la función

CompletaLans(final, SIGINST) para completar la instrucción de salto condicional. El ETDS

quedará por tanto como aparece en la Fig. 2.11.

I! while

( E )

I1

{ Iinicio:= SIGINST }

{ I.final:= CreaLans(SIGINST) ;

emite( ‘if’ E.pos ‘=0 goto’ --- ) }

{ emite( ‘goto’ I.inicio ) ;

CompletaLans(I.final, SIGINST) }

Figura 2.11. ETDS while con relleno por retroceso (representación numérica de expresiones lógicas).

4.3 Representación numérica de expresiones lógicas (con relleno por retroceso)

Usando relleno por retroceso se puede resolver también el problema de argumentos no

satisfechos que aparece al intentar generar código para otras instrucciones de control de flujo en

C. Suponiendo que se emplea una representación numérica de las expresiones lógicas, se puede

generar código intermedio para una instrucción if-else de C mediante el siguiente ETDS:

I ! if E

I1 else

I2

{ I.falso:= CreaLans(SIGINST);

emite( ‘if’ E.pos ‘=0 goto’ --- ); }

{ I.fin := CreaLans( SIGINST) ;

emite( ‘goto’ --- ) ;

CompletaLans(I.falso, SIGINST) }

{ CompletaLans (I.fin, SIGINST) }

En este ETDS, además del salto al código del “else” para el caso en que la expresión sea falsa,

se emite un salto “emite( ‘goto’ --- )” que garantiza que, tras la ejecución del código de I1 no se

Generación de código intermedio

28

ejecuta siempre el del I2. Las dos instrucciones de salto se generan de forma incompleta (falta el

destino de los saltos) y se completan posteriormente con las llamadas a “CompletaLans(I.falso,

SIGINST)” para añadir el destino del salto a I2 y con “CompletaLans (I.fin, SIGINST)” para

completar el salto emitido después del código de I1.

De forma similar, podemos construir un ETDS que genere código intermedio para un bucle

for parecido al del lenguaje C:

I ! for ( I1 ;

E ;

I2 )

I

{ I.cond := SIGINST; }

{ I.fin := CreaLans (SIGINST);

emite( ‘if’ E.pos ‘=0 goto’ --- );

I.cuerpo := CreaLans (SIGINST);

emite( ‘goto’ --- ) ;

I. incr := SIGINST ; }

{ emite(‘goto’ I.cond);

CompletaLans(I.cuerpo, SIGINST) ; }

{ emite(‘goto’ I.incr);

CompletaLans(I.fin, SIGINST) ;

}

4.4 Representación de expresiones lógicas por control de flujo (con relleno por retroceso)

Usando relleno por retroceso podemos resolver el problema que apareció en el ejemplo de la representación de expresiones lógicas por control de flujo en Pascal. El ETDS para la evaluación

de la expresión lógica (solo para el caso de las constantes TRUE y FALSE) quedaría:

E ! True { E.listaverdad := CreaLans( SIGINST) ;

E.listafalso := nil }

emite( ‘goto’ --- ) }

E ! False { E.listafalso := CreaLans( SIGINST) ;

E.listaverdad := nil ;

emite( ‘goto’ --- ) }

Como se puede apreciar, en cada producción se emite una instrucción de salto que queda

incompleta, pero se guarda una referencia a la instrucción en la lista E.listaverdad (para TRUE) o

E.listafalso (para FALSE). Estas instrucciones de salto incompletas se completarán cuando se

conozca el destino del salto. Por ejemplo, el ETDS para un repeat-until de Pascal quedaría tal y

como aparece en la Fig. 2.12.

Generación de código intermedio

29

I! repeat

I1

until E

{ I.inicio:= SIGINST }

{ CompletaLans(E.listafalso, I.inicio);

CompletaLans(E.listaverdad, SIGINST) }

Figura 2.12. ETDS repeat-until con relleno por retroceso (representación expresiones lógicas por control de flujo).

El ETDS para un do-while de C sería muy parecido, ya que bastaría con intercambiar los

valores con los que se completan las listas de referencias no satisfechas .listaverdad y .listafalso:

I! do

I1

while (E)

{ inicio:= SIGINST }

{ CompletaLans(E.listaverdad, inicio);

CompletaLans(E.listafalso, SIGINST) }

Figura 2.13. ETDS do-while con relleno por retroceso (representación expresiones lógicas por control de flujo).

Siguiendo esta técnica se puede construir un ETDS que genere código intermedio para la

evaluación de expresiones lógicas e instrucciones de control de flujo empleando una

representación de los valores lógicos mediante control de flujo, solucionando el problema de las

referencias no-satisfechas mediante relleno por retroceso.

Hemos visto que en la representación de los valores lógicos mediante control de flujo se

generan instrucciones de salto de las que no se conoce a donde se debe saltar. Para solucionar

este problema, se genera la instrucción de salto sin especificar la dirección destino. Se anota el

número de la instrucción incompleta en una lista que se corresponde con el valor de verdad de la

expresión: E.lv para el valor verdadero y E.lf para el valor falso. De esta forma, E.lv (E.lf) será

una lista que contendrá los números de las instrucciones de código intermedio donde se ha

generado un salto a la dirección destino para el caso de que la expresión sea cierta (falsa).

Cuando se conozca la dirección destino correspondiente, se completarán las instrucciones

incompletas con el valor de la dirección. El ETDS para las expresiones lógicas empleando esta

técnica quedará tal y como se muestra en la Fig. 2.14.

E ! E1 or

E2

{ CompletaLans( E1.lf,SIGINST) ;

E.lv := FusionaLans( E1.lv, E2.lv ) ;

E.lf := E2.lf }

E ! E1 and

E2

{ CompletaLans( E1.lv, SIGINST) ;

E.lv := E2.lv ;

E.lf := FusionaLans( E1.lf,E2.lf ) }

E ! not E1 { E.lv := E1.lf ;

E.lf := E1.lv }

E ! ( E1 ) { E.lv := E1.lv ;

Generación de código intermedio

30

E.lf := E1.lf }

E ! E1 oprel E2 { E.lv := CreaLans( SIGINST) ;

emite( ‘if’ E1.pos oprel.op E2.pos ‘goto’ --- ) ;

E.lf := CreaLans( SIGINST ) ;

emite( ‘goto’ --- ) }

E ! True { E.lf := nil ;

E.lv := CreaLans( SIGINST) ;

emite( ‘goto’ --- ) }

E ! False { E.lv := nil ;

E.lf := CreaLans( SIGINST) ;

emite( ‘goto’ --- ) }

E ! id { si BuscaTipo( id.nom ) = tlógico

ent E.lf := CreaLans( SIGINST)

emite( ‘if’ id.pos ‘= 0 goto’ --- ) ;

E.lv := CreaLans( SIGINST) ;

emite( ‘goto’ --- ) }

Figura 2.14. ETDS expresiones lógicas con representación por control de flujo y relleno por retroceso.

4.4.1. Código en cortocircuito

Del ETDS de la Fig. 2.14 se puede destacar el tratamiento que han recibido los operadores lógicos “and” y “or”. Para estos dos operadores se ha decidido generar código en la forma

conocida como “cortocircuito”. Esto supone que no siempre se avaluarán todos los componentes

de la expresión lógica: se finalizará la evaluación de la expresión tan pronto como se conozca su

valor de verdad. Así por ejemplo la evaluación de la expresión lógica

TRUE or (a<b) or (a=5)

no llegará hasta las dos últimas subexpresiones, puesto que al evaluar la primera

subexpresión (la constante TRUE) ya se conoce el valor lógico de toda la expresión.

El código intermedio que se genera realiza esta evaluación “en cortocircuito” porque las acciones

semánticas de la producción E ! E1 or E2 fusionan las listas de los atributos E1.lv y E2.lv. Lo

mismo se puede decir del operador “and”, solo que en este caso la evaluación de la expresión

lógica se interrumpirá en cuanto se evalúe una subexpresión cuyo valor sea FALSE.

4.4.2. Variables lógicas y representación por control de flujo

Teniendo en cuenta que una variable siempre debe tener almacenado su valor en una posición

de memoria, parece evidente que esto será también cierto para las variables lógicas. Por esta

razón es necesario utilizar una representación numérica para almacenar el valor de una variable

lógica. Si para representar los valores de expresiones lógicas también se utiliza una

representación numérica no habrá ningún problema, ya que en ambos casos se utiliza el mismo

tipo de representación. En cambio, si se desea realizar una asignación de una expresión lógica

Generación de código intermedio

31

cuya representación es por control de flujo a una variable lógica (cuya representación es

numérica), será necesario realizar una conversión. Para realizar esta conversión es necesario

generar código intermedio tal y como se muestra en el ETDS de la Fig. 2.15.

I ! id := E { id.pos := BuscaPos(id.nom) ;

si BuscaTipo( id.nom ) = tlógico

ent

CompletaLans( E.lf , SIGINST) ;

emite( id.pos ‘:= 0’ ) ;

emite( ‘goto’ SIGINST+ 2 ) ;

CompletaLans( E.lv, SIGINST) ;

emite ( id.pos ‘:= 1’ ) ;

sino

emite ( id.pos ‘:=’ E.pos ) }

Figura 2.15. ETDS asignación de valor a variable lógica.

4.4.3. Instrucciones de control de flujo

En el ETDS de la Fig. 2.15 puede apreciarse que el código emitido para evaluar las

expresiones lógicas contiene instrucciones de salto cuyo destino no se conoce en el momento de

emitirla (referencias no satisfechas). Por lo tanto, en las instrucciones de control de flujo será

necesario realizar el relleno de los argumentos no satisfechos. En el ETDS de la Fig. 2.16 se

emite código intermedio para tres instrucciones típicas de los lenguajes de programación de alto

nivel (en este caso Pascal) suponiendo que las expresiones lógicas se representan mediante

control de flujo. Obsérvese que en el ETDS se asume que el código intermedio generado para

evaluar las expresiones lógicas realiza la evaluación de éstas mediante “cortocircuito”.

I ! if E then

I1 else

I2

{ CompletaLans( E.lv, SIGINST) }

{ I. listaSig := CreaLans( SIGINST) ;

emite( ‘goto’ --- ) ;

CompletaLans( E.lf, SIGINST) }

{ CompletaLans (I. listaSig, SIGINST) }

I ! while

E

do I1

{ I. inicio := SIGINST }

{ CompletaLans( E.lv, SIGINST) }

{ emite( ‘goto’ I.inicio ) ;

CompletaLans( E.lf, SIGINST) }

Generación de código intermedio

32

I ! for id := E1

to E2

do I1

{ id.pos := BuscaPos (id.nom) ;

emite( id.pos ‘:=’ E1.pos ) }

{ I. inicio := SIGINST ;

condic := CreaLans( SIGINST) ;

emite( ‘if’ id.pos ‘>’ E2.pos ‘goto’ -- ) }

{ emite( id.pos ‘:=’ id.pos ‘+’ 1 ) ;

emite( ‘goto’ I. inicio ) ;

CompletaLans (condic, SIGINST) }

Figura 2.16a. ETDS instrucciones de control de flujo del lenguaje Pascal con relleno por retroceso (expresiones lógicas con representación por control de flujo).

Ejemplo 2.4.- Para entender un poco mejor como funciona el ETDS anterior, se puede

comparar el código que se generaría para la asignación x:= a<b or (c<d and e<f)

suponiendo que se utilice una representación por control de flujo o numérica para las expresiones

lógicas. En la Fig. 2.17. se muestra el árbol anotado para la instrucción cuando se emplea una

representación por control de flujo.

Instruc

id.pos

=X := E

orE

oprel.op

=<(

E

id.pos

=a id.pos

=b E)

E Eand

oprel.op

=<id.pos

=c id.pos

=doprel.

op=<id.

pos=e

id.pos

=f

.lv

= {100,104}

.lf = {103,105}

.lv

= {100}

.lf = {101}

.lv

= {104}

.lf =

{103,105}

.lv

= {102}

.lf = {103}

.lv

= {104}

.lf = {105}

.lv

= {104}

.lf = {103,105}

Figura 2.17: Árbol anotado para la cadena x:=a<b or (c<d and e<f).

Generación de código intermedio

33

Repr. Numérica Repr. por control de flujo 100 t1 := 1 100 if a<b goto 108

101 if a<b goto 103 101 goto 102

102 t1 := 0 102 if c<d goto 104

103 t2 := 1 103 goto 106

104 if c<d goto 106 104 if e<f goto 108

105 t2 := 0 105 goto 106

106 t3 := 1 106 x := 0

107 if e<f goto 109 107 goto 109

108 t3 := 0 108 x := 1

109 t4 := t2 * t3 109

110 t5 := t1 + t4

111 x := t5

Ejemplo 2.5.- Veamos cuál sería el código intermedio generado para un bucle while-do de

Pascal, utilizando una representación por control de flujo para las expresiones lógicas

while a<b or (c<d and e<f) do a:=a+1

100 if a<b goto 106 101 goto 102 102 if c<d goto 104 E w 103 goto 110 h 104 if e<f goto 106 i 105 goto 110 l 106 t1 := 1 e 107 t2 := a + t1 I 108 a := t2 109 goto 100 110

El siguiente ETDS genera código para varias instrucciones de control del flujo en C

suponiendo una representación del valor de las expresiones lógicas por control de flujo y usando

relleno por retroceso para resolver el problema de los argumentos no satisfechos:

Generación de código intermedio

34

I ! if E

I1 else

I2

{ CompletaLans( E.lv, SIGINST) }

{ I. listaSig := CreaLans( SIGINST) ;

emite( ‘goto’ --- ) ;

CompletaLans( E.lf, SIGINST) }

{ CompletaLans (I.listaSig, SIGINST) }

I ! while

( E )

I1

{ I.inicio := SIGINST }

{ CompletaLans( E.lv, SIGINST) }

{ emite( ‘goto’ I.inicio ) ;

CompletaLans( E.lf, SIGINST) }

I ! do

I1

while ( E )

{ I.inicio:= SIGINST }

{ CompletaLans(E.listaverdad, I.inicio);

CompletaLans(E.listafalso, SIGINST) }

Figura 2.16b. ETDS instrucciones de control de flujo del lenguaje C con relleno por retroceso (expresiones lógicas con representación por control de flujo).

En [Aho 90] pueden encontrarse ETDS similares a los presentados en esta sección, en los que

se utiliza un atributo .siguiente asociado a un auxiliar S, para representar el número (o etiqueta)

de la instrucción siguiente a las generadas por el auxiliar S. Además, puede encontrarse en ese

tema cómo se genera código cuando el código intermedio permite la utilización de etiquetas

explícitas (similar a uno de los ejercicios que aparecen al final de este tema).

5. Subprogramas

En el capítulo anterior ya se vio con bastante detalle cuál sería la secuencia de carga y

descarga de un registro de activación para la llamada y finalización de un subprograma. En esta sección se presentan los ETDS que especifican la generación del código intermedio que se

encarga de realizar la carga y descarga de un registro de activación (Pascal) y marco (C).

5.1. Registros de activación en Pascal.

5.1.1. Declaración

Cuando se genera código para la declaración de un subprograma es necesario generar el código que realiza la parte de la secuencia del carga y descarga del registro de activación que

lleva a cabo el subprograma llamado. Se muestra a continuación como quedaría esta parte del

ETDS correspondiente a la declaración a una función en Pascal. Obsérvese como es necesario

utilizar relleno por retroceso para completar la instrucción de salto incondicional encargada de

saltar el posible código de subprogramas locales al subprograma que se está compilando.

Generación de código intermedio

35

La instrucción PUSH permite apilar el valor del display del nivel de anidamiento

correspondiente al subprograma que estamos compilando (PUSH Display[NIVEL]). Así mismo,

suponemos que el tope de la pila se encuentra almacenado en la posición (o registro) TOP.

Decl_Subprg ! function id (Param_Form):

Tipo;

Bloque ;

Decl_Subprg

{emite(‘push Display[‘NIVEL’]’);

emite(‘display[‘NIVEL’] := top’);

area_datos := CreaLans(SIGINST);

emite(‘incTop’ ---) ; }

{ CompletaLans (area_datos, DESP)

emite(‘top := Display[‘NIVEL’]’);

emite(‘pop Display[‘NIVEL’]’);

emite(‘ret’) }

Bloque ! Decl_Var

Decl_Subprg begin

Instrucciones end

{ inst := CreaLans(SIGINST);

emite(‘goto’ ----); }

{ CompletaLans(inst,SIGINST); }

Figura 2.18. ETDS carga/descarga de un registro de activación por el procedimiento llamado.

5.1.2. Llamada

Cuando se genera código para la llamada a un subprograma es necesario generar el código que realiza la parte de la secuencia de carga y descarga de un registro de activación que lleva a

cabo el subprograma llamador. Se muestra a continuación como quedaría el ETDS

correspondiente al lenguaje Pascal. Obsérvese como se utiliza la instrucción INCTOP para

reservar espacio para el valor devuelto por la función, y la instrucción PUSH para apilar los

parámetros actuales. Posteriormente, durante la secuencia de descarga del registro de activación,

se utiliza una instrucción DECTOP para decrementar el valor del tope de la pila y de esta forma

desapilar los parámetros que se cargaron al realizar la llamada.

E ! id

( Param_Act )

{ E.pos := CreaVarTemp();

emite(‘incTop Talla_Tipo(id.nom)) }

{ emite(‘push’ <estado máquina>);

emite(‘call’ BuscaPos(id.nom)) ;

emite(‘pop’ <estado máquina>);

emite(‘decTop’ TallaParam(id.nom)) ;

emite(‘pop’ E.pos) }

Param_Act ! E { emite(‘push’ E.pos) }

Param_Act ! Param_Act , E { emite(‘push’ E.pos) }

Figura 2.19. ETDS carga/descarga de un registro de activación por el procedimiento llamador.

Generación de código intermedio

36

5.2. Marcos en C

5.2.1. Declaración

Se muestra a continuación como quedaría un ETDS correspondiente a la declaración a una

función en C. La instrucción “push FP” que se emite, permitirá en tiempo de ejecución apilar el

valor del “frame pointer” correspondiente al subprograma que se estará cargando.

Decl_Subprg ! Tipo id ( Param_Form )

Bloque ;

{ emite(‘push FP’);

emite(‘FP := TOP’);

area_datos := CreaLans(SIGINST);

emite(‘TOP := TOP + ---‘) ; }

{ CompletaLans (area_datos, DESP)

emite(‘TOP := FP’);

emite(‘FP := pop);

emite(‘ret’) }

Figura 2.18. ETDS carga/descarga de un frame en C realizado por la función llamada.

5.2.2. Llamada

Al igual que en Pascal, en C cuando se genera código para la llamada a una función es necesario generar el código que realiza la parte de la secuencia de carga y descarga del frame de

la función llamada. La instrucción “top := top + Talla_Tipo(id.nom)” se emite para que al

ejecutarse reserve espacio para el valor devuelto por la función. De igual forma, la instrucción

“push” se emite para que en tiempo de ejecución apile los parámetros actuales. Durante la

secuencia de descarga del frame se utiliza decrementa el valor del tope de la pila para, de esta

forma, desapilar los parámetros que se cargaron al realizar la llamada.

E ! id

( Param_Act )

{ E.pos := CreaVarTemp();

emite(‘TOP := TOP +’ Talla_Tipo(id.nom)) }

{ emite(‘push’ <estado máquina>);

emite(‘call’ BuscaPos(id.nom)) ;

emite(‘pop’ <estado máquina>);

emite(‘TOP := TOP -’ TallaParam(id.nom)) ;

emite(‘pop’ E.pos) }

Param_Act ! E { emite(‘push’ E.pos) }

Param_Act ! Param_Act , E { emite(‘push’ E.pos) }

Figura 2.19. ETDS carga/descarga de un frame realizado por la función llamadora.

Generación de código intermedio

37

Bibliografía recomendada

En [Tremblay 85] se puede encontrar un tema dedicado a formas de representación de código

intermedio. El tema de generación de código intermedio que más coincide con el contenido de

este tema es el que aparece en [Aho 90].

[Aho 08] Aho, Alfred V.; Lam, Monica S.; Sethi, Ravi; Ullman, Jeffrey D. Compiladores:

Principios, técnicas y herramientas. Segunda edición Pearson/Addison-Wesley

Iberoamericana, 2008

[Tremblay 85] Tremblay, Jean-Paul; Sorenson, Paul G. The Theory and Practice of

Compiler Writing. McGraw-Hill, 1985

Generación de código intermedio

38

Ejercicios

*8.1.- La siguiente gramática genera bucles “for” muy parecidos a los de Pascal estándar pero

con algunos matices:

I ! for id := E to E S do I

| begin LI end

S ! step E

| "

LI ! I ; LI

| I

...

donde E es una expresión de tipo entero.

La opción “step E” que puede aparecer en la cabecera del bucle indica que la variable

contador (id) en vez de incrementarse en una unidad (como ocurre cuando no aparece la opción

(“step”), se incrementará en “E” unidades al final de cada iteración.

Construir un ETDS que incluya las acciones semánticas necesarias para la comprobación de

tipos y la generación de código intermedio. Deben emplearse listas de referencias no satisfechas

(relleno por retroceso) para el control de los saltos.

*8.2.- Dado el siguiente fragmento de una gramática que genera instrucciones de asignación con

sumas y multiplicaciones, donde las llaves “{ }” representan la clausura reflexivo-transitiva

(0 o más ocurrencias):

IS ! id := E

E ! T { + T }

T ! F { * F }

F ! num

| id

| ( E )

...

a) Construir un ETDS para la generación de código intermedio.

b) Partiendo del ETDS anterior, incluir en el analizador descendente recursivo asociado a la

gramática, las acciones necesarias para la generación de código intermedio.

NOTAS:

• No incluir comprobaciones de tipo.

Generación de código intermedio

39

• Emplear el juego de instrucciones de tres direcciones. (Ejemplo: “id.pos:=id.pos*5”)

• El analizador léxico devuelve para cada identificador su entrada en la TDS en la variable

“ind”, y para una constante numérica su valor en la variable global “num”.

• Se suponen definidas las siguientes funciones:

• CrearVarTemp(): Crea una variable temporal y devuelve su posición.

• BuscaPos(id.nom): Devuelve la posición de memoria asignada al identificador cuya entrada en la TDS es id.nom.

• En la declaración de cada parámetro formal, deberá indicarse si el uso es por valor o

referencia.

*8.3.- Dada la siguiente gramática (inspirada en la proposición “switch” del C):

I ! switch ( E ) { L }

L ! case num : P ; L

L ! default : P ;

L ! " ...

P ! P ; P

P ! break

P ! I

P ! " ...

Donde:

a) Tanto “E” como la constante “num” deben ser enteras.

b) Si la constante de un “case” coincide con el valor de la expresión E, se deberá

comenzar a ejecutar todas las instrucciones asociadas a ese “case” y a los que le siguen.

La proposición “break” provoca una salida inmediata del “switch”. Las instrucciones

asociadas al “default” se ejecutarán siempre (excepto si se encontró antes una

instrucción “break”).

Construir un ETDS que realice las acciones semánticas necesarias para la generación de

código intermedio de tres direcciones.

*8.4.- Construir un Esquema de Traducción Dirigido por la Sintaxis que realice las

comprobaciones semánticas y genere código de tres direcciones para una gramática con las

siguientes reglas de producción.

Generación de código intermedio

40

S ! case E of L end

L ! num : S ; L

L ! "

...

Tanto las constantes de selección, como la expresión, deben ser de tipo entero.

*8.5.- Dada la siguiente gramática:

P ! D ; I .

D ! id : T # D ; D # "

T ! pila ( num ) de TS # TS

TS ! entero # real

I ! apilar ( id , E ) # id := E # I ; I # "

E ! desapilar ( id ) # cima ( id ) # id

Donde:

pila (num) de TS, representa la definición del tipo pila, con un máximo de num

elementos de tipo entero o real.

apilar (id, E), indica que se apila el valor de E en la pila id

desapilar (id), devuelve el elemento que desapila de la pila id

cima (id) devuelve el elemento que hay en la cima de la pila id (no lo desapila).

Construir un ETDS que realice las acciones semánticas necesarias para la comprobación de tipos, la asignación de memoria y la generación de código tres direcciones teniendo en cuenta que:

a) La talla de los enteros y reales es 1.

b) No se debe permitir la asignación de pilas.

c) No hay coerción de entero a real.

Se deben realizar las comprobaciones dinámicas para los controles de desborde de pila y pila

vacía.

Generación de código intermedio

41

*8.6.- Dada la siguiente gramática:

P ! D ; I .

D ! id : T | D ; D | "

T ! cadena ( num ) | TS

TS ! entero | real | caracter ...

I ! concatena ( id , id ) | id := E | I ; I | "

E ! longitud ( id ) | id

Donde:

cadena(num) representa la definición de un objeto de tipo “cadena de caracteres”

cuyo tamaño máximo viene dado por num.

concatena(id1, id2) concatena al final de la cadena id1 la cadena id2.

longitud(id) devuelve el tamaño de la cadena id.

Construir un ETDS que realice las acciones semánticas necesarias para la comprobación de

tipos, la asignación de memoria y la generación de código de tres direcciones teniendo en cuenta

que:

a) La talla de los enteros, reales y caracteres es 1, y no hay coerción entre tipos.

b) Se deben realizar las comprobaciones dinámicas para controlar que no se sobrepasa el

tamaño máximo permitido de una cadena.

c) No se deben permitir las asignaciones entre cadenas.

NOTA: Se recomienda implementar una cadena declarada de tamaño máximo num, como un

vector de num+1 elementos, donde el elemento 0 se puede usar para almacenar la longitud actual

de la cadena.

*8.7.- Incorporar a la gramática siguiente, las acciones semánticas necesarias para la

comprobación de tipos, la asignación de memoria y la generación de código intermedio (tres

direcciones):

S ! D S

D ! D ; D | id : T

T ! char | integer | string(num)

S ! S ; S | id := E

E ! F + F | id (num : num)

F ! id | /id/

NOTAS:

Generación de código intermedio

42

1) string(num) representa una cadena de caracteres de talla máxima num.

2) id(a:b) representa la subcadena de id comprendida entre a y b.

3) /id/ es la longitud de la cadena id.

4) Será necesario considerar explícitamente la talla actual de las cadenas.

*8.8.- Dado el siguiente fragmento de gramática.

I ! while E do I Rw

Rw ! except E do I

Construye un ETDS para la comprobación de tipos y emisión de código intermedio. La sentencia “while-do” tiene el comportamiento estándar de Pascal, pero con un pequeño matiz: La parte “except” permite indicar una instrucción alternativa a ejecutar en las iteraciones en las que se cumple la condición booleana introducida por la palabra “except”. La expresión del “except” se evalúa antes de entrar en el cuerpo del bucle.

Ejemplo: a := 9 ;

while a < 15 do

begin a:= a +1; write(a) end => 10 11 12 13 15

except a=13 do a:= a+1;

*8.9.- Escribe un ETDS que genere código intermedio para el siguiente fragmento de una gramática independiente del contexto, correspondiente a un lenguaje de programación. La semántica de la instrucción loop es la siguiente: Si la expresión “E” que sigue a loop es cierta se ejecutan las instrucciones que siguen al then y preceden a exitloop, y la instrucción loop finaliza, en caso contrario, se irán evaluando sucesivamente las expresiones que siguen al elseif hasta que una sea cierta, en cuyo caso, se ejecutan las instrucciones que siguen a ese then y el flujo del programa vuelve al principio de la instrucción loop. Si ninguna expresión es cierta se ejecutan las instrucciones que siguen al else y se vuelve al principio de la instrucción loop.

I ! loop E then I exitloop O

O ! elseif E then I O

O ! else I endloop

I ! I ; I

E ! E oprel E

El diagrama de flujo de la instrucción sería el de la figura.

Generación de código intermedio

43

*8.10.- Definir un ETDS que

realice la generación de código intermedio para la instrucción WHILE-DO, en la que se

permita la posibilidad de una instrucción EXITLOOP. La instrucción Exitloop supone la

salida inmediata del bucle. Para ello utilizar la sintaxis propia de Pascal.

*8.11.- Dada la siguiente gramática (inspirada en la proposición “for” del C), construir un ETDS

que realice las acciones semánticas necesarias para la comprobación de tipos y la generación

de código intermedio de tres direcciones usando una representación por control de flujo de

las expresiones boolenas.

I ! for ( I1 ; E ; I2 ) { P }

...

P ! I ; P

P ! I

...

Donde:

a) Tanto I1 como I2 son instrucciones, típicamente de asignación o llamadas a función. I1 se

ejecutará antes del bucle e I2 dentro del bucle después de las instrucciones asociadas a la

proposición P.

b) E debe ser una expresión booleana que determine la condición de finalización del bucle.

*8.12.- Construir un ETDS que realice la declaración, la comprobación estática de tipos y la generación de código intermedio para la siguiente gramática:

E

E

E

E

I

I

I

I

True

False

False

False

False

True

True

True

I

...

salida

Generación de código intermedio

44

P ! D ; I .

D ! id : T | D ; D | !

T ! integer | set of [ num ]

E ! member ( E , id ) | num | id

...

I ! for-each id in id do I

Siendo:

set of [ num ] un tipo de datos que representa un conjunto cuyos elementos pueden ser {0, 1,

2, 3, . . . num}. Por ejemplo: “a: set of [5]” declara la variable a cuyo valor puede ser cualquier

subconjunto de {0, 1, 2, 3, 4, 5}.

member(E, id) una función que devuelve TRUE (ó 1) si el elemento E está en el conjunto id

(FALSE ó 0, en caso contrario).

for-each id1 in id2 do I una instrucción donde la variable entera “id1” toma todos los valores

que corresponden a la variable conjunto id2 . Además, la instrucción I se ejecutará para cada uno

de los valores de id1 cuando este valor pertenezca al conjunto id2. Por ejemplo:

a: set of [5];

.....

a := {1, 3, 5}

for-each n in a do print (n);

producirá como resultado: 1, 3, 5.

Nota.- Se recomienda representar las variables de tipo conjunto “set of [num]” como un “num + 1” posiciones consecutivas de memoria. Donde cada posición de memoria representará la pertenencia (1 ó 0) de un elemento al conjunto. Por ejemplo, la variable “a”, de"nida anteriormente, se representaría por 6 posiciones de consecutivas memoria, que si contuviesen los valores (1, 0, 1, 0, 0, 1) indicarían que el conjunto a contiene los elementos {0, 2, 5}.

8.13.- Dada la siguiente gramática, construir un ETDS que realice: las comprobaciones semánticas y de tipo usuales en la instrucción FOR y la generación de código intermedio. Utilizar listas de referencias no satisfechas para la gestión de las expresiones booleanas.

S ! for id := E R E do S

R ! to | downto

8.14.- Construir un ETDS que genere código intermedio para comprobar en tiempo de ejecución que los índices de una matriz están dentro de los límites definidos en su declaración. Para la generación de este código, se puede suponer la existencia de otros atributos necesarios, cuya evaluación se realiza en otras producciones, siempre que se indique su carácter sintetizado o heredado.

Generación de código intermedio

45

8.15.- Considérese la siguiente instrucción cuya semántica viene dada por el diagrama de flujo que se adjunta. Diseñar el ETDS para la generación de código tres direcciones. Utilizar listas de referencias no satisfechas (relleno con retroceso) para el control de los saltos.

S ! pseudowhile id , E’ until E” do S’ then S”

8.16.- Dada la siguiente gramática que representa registros en Pascal, construir un ETDS que realice:

P ! D ; I

D ! D ; D | id : T

T ! integer | real | record LC end

LC ! id : T RLC

RLC ! ; LC | " I ! L := E

L ! L . id | id

E ! E + E | L

a) Las comprobaciones semánticas de: Compatibilidad de tipos, los tipos de los campos de un RECORD solo pueden ser de tipo simple y no se permite la asignación de registros.

b) El cálculo de las posiciones relativas de los objetos en memoria.

c) La generación de código intermedio para las expresiones e instrucciones.

d) Extender el ETDS para generar código intermedio para la asignación de registros.

e) Extender el ETDS para permitir registros con parte variante.

8.17.- La siguiente gramática representa expresiones en las que un operador (suma o producto) actúa sobre una lista de operandos que se encuentran entre paréntesis junto al operador. Además, alguno de estos operandos puede ser a su vez toda una expresión entre paréntesis.

S ! ( + P L ) | ( * P L )

P ! id | S

L ! P L | "

a) Escribir en PASCAL las rutinas del Análisis Sintáctico por Descenso Recursivo LL(1)

para esta gramática.

b) Introducir en el esquema anterior las acciones semánticas necesarias para la generación

de código tres direcciones.

id > E'

id > E''

S' S''

No

NoSi

Si

Generación de código intermedio

46

8.18.- Suponiendo que una matriz A[a1..b1,a2..b2,...an..bn] se representa en memoria por el método de división en subtablas (tal y como se ilustra en la figura). En las subtablas de los niveles 1..n-1 únicamente aparecen referencias (de talla 1) a los orígenes de las subtablas de los niveles inmediatamente siguientes. Solo en las subtablas de nivel n se almacenarán los elementos de la matriz.

a) Determinar la función de acceso para el elemento genérico A[i1,i2,...in].

b) Diseñar el ETDS para la generación del código intermedio correspondiente, para la

siguiente gramática.

S ! L := E

E ! L

L ! id

L ! I ]

I ! I , E

I ! id [ E

8.19.- Dada la siguiente gramática:

P! var D_V I

D_V ! D_V D_V | Lista_id : T ;

Lista_id ! Lista_id , id | id

T ! record ListaCampos Cvar end | T_simple

T_simple ! T_disc | integer | real

ListaCampos ! ListaCampos ; ListaCampos | id : T_simple

T_disc ! num..num | boolean

Cvar ! case id : T_disc of LCV | "

LCV ! LCV ; LCV | Val_sel : ( ListaCampos )

Val_sel ! num | id

I ! id := E | id . id := E | if E then I else I

E ! E oprel E | id . id | id

a) Dar un ETDS que realice el cálculo de las posiciones relativas de las variables en

memoria para esta gramática.

a1

b1

i1 i2

a2

b2

.

.

2

.

.

in-1in

.

.

.

.

n-1

.

.

n

.

.

an

bn

A[i1,i2,..in]

1

Generación de código intermedio

47

b) Ampliar el anterior ETDS para que realice la comprobación de tipos y genere el código

intermedio necesario para evaluar las expresiones y realizar las asignaciones adecuadas en

dicha gramática.

NOTA: Diseñar la estructura de la tabla de símbolos que permita la realización de los

apartados anteriores. Igualmente, describir brevemente los atributos, así como los perfiles y

acciones realizadas por las funciones y/o procedimientos que utilicéis.

*8.20.- El código intermedio definido en este capítulo se modifica para permitir generar etiquetas

y utilizarlas como destino de los saltos (similares a las utilizadas en los lenguajes

ensambladores). Por ejemplo, podríamos generar secuencias de código intermedio similares a:

if e<f goto L1

goto L2

L2: a:= a-1

L1: a := a + 1

a) Construye un ETDS que genere este código intermedio para la evaluación de expresiones

lógicas (mediante representación por control de flujo) para la siguiente gramática:

E ! E or E | E and E | not E

| ( E ) | E oprel E

| true | false | id

b) Construye otro ETDS que genere código intermedio para las instrucciones representadas

por la siguiente gramática, que sea coherente con el ETDS del apartado anterior.

Instruc ! if E then Instruc else Instruc

| while E do Instruc

| for id := E to E do Instruc

| id := E /* de tipo lógico */

c) ¿Qué código generará tu ETDS para las siguientes cadenas?

x := a<b or (c<d and e<f)

while a<b or (c<d and e<f) do a:=a+1

while (not a) or (x < b and true) do x := x + 1

Generación de código intermedio

48

Soluciones a los ejercicios seleccionados

8.1.-

E 1

id .p o s := E 1.p o s

E 2

if id > E 2 g o to

g o to

(s te p ) E

g o to

I

g o to

id := id + E

I ! for id := E1

to E2

S

do I

{ si E1.tipo <> tentero ent MemError()

id.pos:=BuscaPos(id.nom);

emite(id.pos ‘:=’ E1.pos) }

{ si E2.tipo <> tentero ent MemError()

fin:=CreaLans(SIGINST); comi:=SIGINST;

emite(‘if’ id.pos ‘>’ E2.pos ‘goto’ ---)

cont:=CreaLans(SIGINST);

emite(‘goto’ -- ); inc:=SIGINST; S.pos:=id.pos; }

{ emite(‘goto’ comi); CompletaLans(cont, SIGINST) }

{ emite(‘goto’ inc);

CompletaLans(fin, SIGINST) }

S ! step E { si E.tipo <> tentero ent MemError()

emite(S.pos ‘:=’ S.pos ‘+’ E.pos) }

S ! " { emite(S.pos ‘:=’ S.pos ‘+’ 1) }

8.2.-

a) E ! T1

{ +

T2

}

{ E.pos:=CreaVarTemp(); emite (E.pos ’:=’ T1.pos) }

{ emite(E.pos ‘:=’ E.pos ‘+’ T2.pos ) }

T ! F1

{ *

F2

{ T.pos:=CreaVarTemp(); emite (T.pos ‘:=’ F1.pos) }

{ emite(T.pos ‘:=’ T.pos ‘*’ F2.pos ) }

Generación de código intermedio

49

}

F ! num { F.pos:=CrearVarTemp();

emite(F.pos ‘:=’ num.val) }

F ! id { F.pos:=BuscaPos(id.nom) }

F ! ( E ) { F.pos:=E.pos }

IS ! id := E { id.pos:=BuscaPos(id.nom);

emite(id.pos ‘:=’ E.pos) }

b)

Procedure E ( var E.pos );

var

T1.pos, T2.pos

begin

T(T1.pos); E.pos:=CrearVarTemp(); emite (E.pos`:=´T1.pos);

while sim=mas do

begin

obtsym;

T(T2.pos); emite(E.pos ‘:=’ E.pos ‘+’ T2pos);

end;

end; { procedure E }

Procedure T ( var T.pos );

var

F1.pos, F2.pos

begin

F(F1.pos); T.pos:=CrearVarTemp(); emite(T.pos `:=´F1.pos);

while sim=multi do

begin

obtsym;

F(F2.pos); emite(T.pos ‘:=’ T.pos ‘*’ F2.pos);

end;

end; { procedure T }

Procedure F ( var F.pos );

var

E.pos

begin

case sim of

num:

F.pos:=CrearVarTemp(); emite(F.pos:=num.val);

obtsym;

identificador:

F.pos:=BuscaPos(id.nom);

obtsym;

abrepar:

Generación de código intermedio

50

obtsym;

E(E.pos); F.pos:=E.pos;

empareja(cierrapar);

end; { case }

end; { prcedure F }

Procedure IS;

var

E.pos, id.pos

begin

id.pos:=BuscaPos(id.nom);

empareja(identificador);

empareja(asignación);

E(E.pos); emite(id.pos:=E.pos);

end; { procedure IS }

8.3.-

E

if E<>cte1 goto

P1

goto

P2

goto (break)

if E<>cte2 goto

goto

P (default)

...

En la solución propuesta se puede observar que se genera una instrucción “goto” inmediatamente a continuación del cuerpo de un “case” cuya finalidad es saltar al cuerpo del siguiente “case”. Con esto se consigue que si se cumple la igualdad de la constante de un “case”, tras ejecutarse su cuerpo se ejecuten las instrucciones del cuerpo de todos los “case” que le siguen (hasta que se encuentre un salto generado para un break).

I ! switch ( E )

{ L }

{ L.pos:=E.pos; L.anterior := nil; L.fin:=nil}

L ! case num :

P ;

{ sigcond:=CreaLans(SIGINST) ;

emite(‘if’ num.val ‘<>’ L.pos ‘goto’ --);

CompletaLans(L.anterior, SIGINST) }

{ L1.anterior:=CreaLans(SIGINST) ; emite (‘goto’ --);

Generación de código intermedio

51

L1

L1.pos:=L.pos; CompletaLans(sigcond, SIGINST);

L1.fin:=FusionaLans(P.fin, L.fin) }

L ! default :

P ;

{ CompletaLans(L.anterior, SIGINST) }

{ CompletaLans(L.fin, SIGINST);

CompletaLans(P.fin, SIGINST) }

L ! " { CompletaLans(L.anterior, SIGINST)

CompletaLans(L.fin, SIGINST);}

P ! P1 ; P2 { P.fin:=FusionaLans(P1.fin,P2.fin) }

P ! break { P.fin:=CreaLans(SIGINST); emite(‘goto’ --) }

P ! I { P.fin:=I.fin }

P ! " { P.fin:=nil }

8.4.-

E

if E .p o s< > c te .va l g o to

S

g o to

S

if E .p o s < > ... g o to

g o to

...

L ! num :

S ;

L1

{ si num.tipo $ tentero ent MemError() ;

sig:=CreaLans(SIGINST);

emite(‘if’ num.val ‘<>’ L.pos ‘goto’ --) }

{ falsa:=CreaLans(SIGINST);

emite(‘goto’ --);

CompletaLans(sig, SIGINST);

L1.pos:=L.pos }

{ L.fin:=FusionaLans(falsa, L1.fin) }

L ! " { L.fin:=nil }

S ! case E

of L

{ si E.tipo $ tentero ent MemError();

L.pos:=E.pos }

{ CompletaLans(L.fin, SIGINST) }

8.5.-

Generación de código intermedio

52

Es importante observar que en tiempo de compilación no conocemos el tamaño de la pila, solo conocemos el tamaño máximo de la pila. Por lo tanto, será necesario reservar una posición de memoria para que en tiempo de ejecución se almacene allí el tope de la pila. Podemos implementar la pila de ‘num’ elementos como un vector (array) de ‘num+1’ elementos, donde el elemento 0 se puede usar para almacenar el tamaño actual (tope) de la pila. Cada vez que apilemos un nuevo elemento, lo haremos sobre la posición indicada en pila[0] (dirección base del vector). A continuación incrementaremos el valor de pila[0]. De esta forma, las acciones semánticas para generar código para apilar, desapilar, y obtener la cima podrían ser:

I ! apilar ( id ,

E )

{ pos := BuscaPos (id.nom) ;

emite ( pos ‘:=’ pos ‘+’ 1 );

emite (‘if’ pos ‘<=’ limite ‘goto’ SIGINST +2 )

emite (‘fin’) }

{ emite ( pos ’[‘ pos ’] :=’ E.pos) }

E ! desapilar (id) { pos := BuscaPos (id.nom) ;

emite (‘if’ pos ‘>’ 0 ‘goto’ SIGINST + 2 ) ;

emite (‘fin’) ;

E.pos := CrearVarTemp() ;

emite ( E.pos ‘:=’ pos ’[‘ pos ’]’ );

emite (pos‘:=‘ pos ‘-‘ 1) }

E ! cima (id) { pos := BuscaPos (id.nom) ;

emite (‘if’ pos ‘> 0 goto’ SIGINST +2 );

emite (‘fin’)

E.pos := CrearVarTemp();

emite (E.pos ‘:=’ pos ’[‘ pos ’]’ ) }

En el ETDS anterior hemos se puede ver que pos[0] y pos hacen referencia al mismo valor, ya que es lo mismo tomar el contenido de la dirección base del vector (pos) o el

contenido de la dirección resultante de sumar a la base del vector un desplazamiento cero (pos[0]) .

El ETDS, incluyendo algunas comprobaciones de tipo quedará finalmente: P !

D ; I .

{ DESP:=0 }

D ! id : T { InsertarTDS (id.nom, T.tipo, DESP);

DESP := DESP + T.talla ;

si T.tipo = Tpila(limite,tipo) ent emite (DESP ‘:=’ 0) }

D ! D ; D

D ! "

T ! pila ( num ) de { si num.val <=0 ent T.tipo := terror

Generación de código intermedio

53

TS sino T.tipo:= tpila (num.val, TS.tipo) ;

T.talla := TS.Talla * (num.val +1) }

T ! TS { T.talla := TS.talla ; T.tipo := TS.tipo }

TS ! entero { T.talla := 1 ; T.tipo := tentero }

TS ! real { T.talla := 1 ; T.tipo := treal }

I ! apilar ( id , E )

{ si BuscaTipo (id.nom) <> tpila (limite,tipo) ent I.tipo := terror

sino pos := BuscaPos (id.nom) ;

emite ( pos ‘:=’ pos ‘+’ 1 );

emite (‘if’ pos ‘<=’ limite ‘goto’ SIGINST + 2 )

emite (‘fin’)

si E.tipo <> tipo ent I.tipo := terror

sino emite ( pos’[‘ pos ‘] :=’ E.pos)

I.tipo := tvacío }

I ! id := E { id.tipo := BuscaTipo (id.nom) ;

si id.tipo = tpila (limite, tipo) ent I.tipo := terror

sino id.pos = BuscaPos (id.nom) ;

si id.tipo <> E.tipo ent I.tipo := terror

sino emite (id.pos ‘:=’ E.pos) ;

I.tipo := tvacio }

I ! I ; I

I ! "

E ! desapilar ( id ) { si BuscaTipo (id.nom) <> tpila (limite, tipo ) ent E.tipo := terror

sino pos := BuscaPos (id.nom) ;

emite (‘if’ pos ‘> 0 goto’ SIGINST + 2 ) ;

emite (‘fin’) ;

E.pos := CrearVarTemp() ;

emite ( E.pos ‘:=’ pos ’[‘ pos ’]’ );

emite ( pos ‘:=’ pos ‘-‘ 1 ) ;

E.tipo := tipo }

E ! cima ( id ) { si BuscaTipo (id.nom) <> tpila (limite, tipo) ent E.tipo := terror

sino pos := BuscaPos (id.nom) ;

E.pos := CrearVarTemp();

emite (E.pos ‘:=’ pos ’[‘ pos ’]’ );

E.tipo := tipo }

E ! id { E.tipo := BuscaTipo (id.nom) ; E.pos := BuscaPos (id.nom) }

Generación de código intermedio

54

8.6.-

P ! { DESP:=0 } D ; I .

D ! id : T

| D ; D

| "

{ InsertaTDS(id.nom, T.tipo, DESP) ; DESP:=DESP+T.talla }

T ! cadena ( num )

| TS

{ T.tipo:=tcadena(num.val) ; T.talla:=num.val }

{ T.tipo:=TS.tipo ; T.talla:=TS.talla }

TS ! entero

| real

| caracter

{ TS.tipo:=tentero ; TS.talla:=1 }

{ TS.tipo:=treal ; TS.talla:=1 }

{ TS.tipo:=char ; TS.talla:=1 }

I ! concatena ( id1 , id2)

| id := E

| I ; I

| "

{ id1.pos:=BuscaPos(id1.nom); id2.pos:=BuscaPos(id2.nom);

si BuscaTipo(id1.nom)=BuscaTipo(id2.nom)=tcadena

ent

I.tipo:=tvacío;

t1:=CrearVarTemp();

emite ( t1 ‘:=’ 1);

inicio:=SIGINST;

listaFin:=CreaLans(SIGINST);

emite ( ‘if’ t1 ‘>’ id2.pos ‘goto’ -- );

emite ( id1.pos ‘:=’ id1.pos ‘+’ 1 );

emite (‘if’ id1.pos’<=’Talla(id1.nom) ‘goto’ SIGINST+2 );

emite ( ‘fin’ );

emite ( id1.pos ’[‘ id1.pos ’]:=‘ id2.pos ’[‘ t1 ’]’ );

emite ( t1 ’:=’ t1 ’+’ 1 );

emite ( ‘goto’ inicio );

CompletaLans ( listaFin, SIGINST );

sino I.tipo:=terror; }

{ si (BuscaTipo(id.nom):=E.tipo) $ tcadena $terror

ent emite( BuscaPos(id.nom) ‘:=’ E.pos );

I.tipo:=tvacío;

sino I.tipo:=terror; }

E ! longitud ( id )

| id

{ si BuscaTipo(id.nom) $ tcadena ent E.tipo:=terror;

sino E.tipo:=tentero;

E.pos:=BuscaPos(id.nom); }

{ E.tipo:=BuscaTipo(id.nom); E.pos:=BuscaPos(id.nom); }

Se puede observar que en lugar de usar la técnica de relleno por retroceso con:

listaFin:=CreaLans(SIGINST);

Generación de código intermedio

55

emite ( ‘if’ t1 ‘>’ id2.pos ‘goto’ -- );

en este caso podíamos haber contado las instrucciones que separaban la instrucción de salto y su

destino, y haber emitido directamente la instrucción

emite ( ‘if’ t1 ‘>’ id2.pos ‘goto’ SIGINST +7 ) */

8.7.-

Las comprobaciones de tipo serán las típicas del bloque de declaraciones. Para asignar memoria basta con emplear una variable global que se incremente (según la talla del elemento definido) cada vez que se declare una variable. En el caso de una cadena de caracteres (tcadena), la talla nos viene determinada por el valor de la constante num (más uno por la razón que se explica más adelante).

Respecto a la generación de código intermedio, se trata de un ejercicio muy parecido a los dos anteriores. De forma parecida a como ocurría en ellos, en tiempo de compilación solo conocemos el tamaño máximo de la cadena, pero no su tamaño real en un instante dado de la ejecución. Una solución consistiría en reservar la primera posición de la cadena para que en tiempo de ejecución se almacene su tamaño. En este caso, cada vez que realicemos una asignación de cadenas, será necesario calcular el tamaño de la cadena resultado. Con este fin, se puede observar que la instrucción para obtener una subcadena: id(num1:num2) nos devuelve una cadena de tamaño num2 .val - num1.val + 1.

8.8.-

El esquema del código que queremos generar será:

E

I1

goto

goto

.v

.f

E (RW).v

.f

I (RW)

El ETDS pedido quedaría:

I ! while

E do

I1

RW

{ RW.inicio := SIGINST }

{ si E.tipo <> tlógico ent MemError() ;

RW.cuerpo := SIGINST }

{ emite (‘goto’ RW.inicio) ;

CompletaLans (E.lv , SIGINST) }

{ CompletaLans (E.lf, SIGINST) }

Generación de código intermedio

56

RW ! except E

do I

{ si E.tipo <> tlógico ent MemError() ;

CompletaLans (E.lv, SIGINST) ;

CompletaLans (E.lf, RW.cuerpo) }

{ emite (‘goto’ RW.inicio) }

8.9.-

Solución usando representación por control de flujo para las expresiones lógicas.

I ! loop

E then

I exitloop

O

{ O.inicio := SI; }

{ CompletaLans(E.lv, SI); }

{ fin:= CreaLans(SI); emite (‘goto’ -); CompletaLans(E.lf, SI);}

{ CompletaLans(fin, SI); }

O ! elseif E

then I

O1

{ CompletaLans(E.lv, SI); }

{ emite(‘goto’ O.inicio) ; CompletaLans(E.lf, SI);

O1. inicio := O.inicio ;}

O ! else I endloop { emite(‘goto’ O.inicio) ;}

I ! I ; I

E ! E oprel E { E.lv := CreaLans( SI) ;

emite( ‘if’ E1.pos oprel.op E2.pos ‘goto’ --- ) ;

E.lf := CreaLans( SI ) ;

emite( ‘goto’ --- ) }

8.10.-

Vamos a usar la gramática que se muestra a continuación, donde I representa una instrucción, y E una expresión. El no-terminal I tendrá más producciones, pero las que nos interesan son las relacionadas directamente con la generación de código intermedio para las instrucciones ‘while-do’ y ‘Exitloop’.

I ! while E do I

I ! Exitloop

I ! I ; I

I ! …

Usaremos listas de referencias no satisfechas (relleno por retroceso) para el control de los saltos. El no-terminal E, que representa la expresión lógica, tendrá asociados dos atributos: E.lv y E.lf que representan respectivamente las listas de referencias no satisfechas de saltos para cuando la expresión es cierta (E.lv) y para cuando es falsa (E.lf).

Generación de código intermedio

57

SIGINST, es una variable global que contiene la etiqueta de la siguiente instrucción de código intermedio que se va a generar.

I ! while

E

do I1

{ inicio := SIGINST }

{ CompletaLans (E.lv, SIGINST) }

{ emite (‘goto’ inicio)

CompletaLans (I1.salida, SIGINST) ;

CompletaLans (E.lf, SIGINST) ;

I.salida := nil }

I ! Exitloop { I.salida := CreaLans (SIGINST) ;

emite (‘goto’ ---) }

I ! I1 ; I2 { I.salida:= FusionaLans (I1.salida, I2.salida) ; }

8.11.-

El esquema del código intermedio que queremos generar es el siguiente:

I1

E

I2

goto

P

goto

.v

.f

El ETDS por tanto quedaría:

I ! for ( I1 ;

E ;

I2 )

{ P }

{ inicio:=SIGINST; }

{ si E.t $ tlógico ent MenError()

sino incr:=SIGINST; }

{ emite(‘goto’ inicio);

CompletaLans(E.lv, SIGINST) }

{ emite(‘goto’ incr);

CompletaLans(E.lf, SIGINST) }

Generación de código intermedio

58

8.12.-

P ! D ; I .

D ! id : T { insertaTDS( id.nom, T.tipo) }

| D ; D

| "

T ! set of [ num ] { T.tipo := conjunto(num.val) }

| integer { T.tipo := entero }

E ! member ( E1 , id ) { si (E1.tipo <> entero) or (BuscaTipo(id.nom) <> conjunto(n))

ent error;

E.tipo := lógico;

E.pos := CreaVarTemp;

emite( E.pos := id.pos[E1.pos]) }

| num { E.tipo := entero;

E.pos:= CreaVarTemp;

emite(E.pos:= num.val) }

| id { E.tipo := BuscaTipo(id.nom)

E.pos := BuscaPos(id.nom) }

I! for_each id1 in id2

do I for_each id1 in id2

{ si (BuscaTipo(id1.nom)<> entero)

or (BuscaTipo(id2.nom) <> conjunto(n))

ent error;

t1 := CreaVarTemp;

emite (id1.pos := 0);

fin := Crealans(si);

inicio := si;

emite( if id1.pos > buscalimite(id2.nom) goto __);

emite (t1 := id2.pos[id1.pos]);

salta := Crealans(si);

emite( if t1 = 0 goto __); }

{ completalans(salta, si);

emite( id1.pos := id1.pos + 1);

emite( goto (inicio));

completalans(fin, si); }

Generación de código intermedio

59

8.20.-

a) Usaremos la función CreaEtiqueta par crear una etiqueta. Para generar mediante el procedimiento emite una etiqueta bastará con realizar una llamada del tipo:

emite (<NombreEtiqueta> ‘:’) Se puede observar la similitud de este ETDS con el construido usando relleno por retroceso para la misma gramática:

- Las instrucciones generadas, excepto los destinos de los saltos que en este ETDS son etiquetas, son las mismas. - Donde se emite una etiqueta que anteriormente se usó como destino de un salto, en el ETDS mediante relleno por retroceso aparecía un completaLans.

El ETDS para las expresiones lógicas quedaría:

E !

E1 or

E2

{ E1.f := CreaEtiqueta(); E1.v := E.v }

{ emite( E1.f ‘:’);

E2.v := E.v; E2.f := E.f }

|

E1 and

E2

{ E1.v := CreaEtiqueta(); E1.f := E.f }

{ emite( E1.v ‘:’);

E2.v := E.v; E2.f := E.f }

| not E1 { E1.v := E.f ;

E1.f := E.v }

| ( E1 ) { E1.v := E.v ;

E1.f := E.f }

| E1 oprel E2 { emite( ‘if’ E1.pos oprel.op E2.pos ‘goto’ E.v );

emite( ‘goto’ E.f ) }

| True { emite( ‘goto’ E.v ) }

| False { emite( ‘goto’ E.f ) }

| id { emite( ‘if’ id.pos ‘= 0 goto’ E.f ) ;

emite( ‘goto’ E.v ) }

Instruc ! id :=

E

{ E.v := CreaEtiqueta(); E.f := CreaEtiqueta()

fin := CreaEtiqueta() }

{ emite( E.f ‘:’) ;

emite( id.pos ‘:= 0’ ) ,

emite( ‘goto’ SIGINST + 2 ) ,

emite( E.v ‘:’);

emite ( id.pos ‘:= 1’ ) ; emite( fin ‘:’ ) }

Generación de código intermedio

60

b)

Instruc !

if E then

Instruc1 else

Instruc2

{ E.v:= CreaEtiqueta(); E.f:= CreaEtiqueta(); fin:= CreaEtiqueta() }

{ emite( E.v ‘:’ ) }

{ emite( ‘goto’ fin ) ; emite( E.f ‘:’) }

{ emite( fin ‘:’) }

| while

E

do Instruc1

{ Inicio := CreaEtiqueta() ; E.t := CreaEtiqueta() ;

E.f := CreaEtiqueta() ; emite(inicio ‘:’) ’:’ }

{ emite( E.v ‘:’) }

{ emite( ‘goto’ inicio ) ;

emite( E.f ‘:’) }

| for id := E1 to E2 do Instruc1

{ id.pos := BuscaPos (id.pos) ;

emite( id.pos := E1.pos ) }

{ inicio := CreaEtiqueta() ;

fin := CreaEtiqueta() ;

emite( inicio ‘:’) ;

emite( ‘if’ id.pos ‘>’ E2.pos ‘goto’ fin ) }

{ emite( id.pos ‘:=’ id.pos ‘+’ 1 ) ;

emite( ‘goto’ inicio ) ;

emite( fin ‘:’) }

c) x := a<b or (c<d and e<f)

if a<b goto L1

goto L3

L3: if c<d goto L4

goto L2

L4: if e<f goto L1

goto L2

L2: x := 0

goto L5

L1: x := 1

L5:

while a<b or (c<d and e<f) do a:=a+1

L0:

if a<b goto L1

goto L3

L3: if c<d goto L4 E w

goto L2 h

L4: if e<f goto L1 i

goto L2 l

L1: t1 := 1 I e

t2 := a + t1

a := t3

goto L0

L2:

Generación de código intermedio

61

while (not a) or (x < b and true) do x := x + 1

L0:

if a = 0 goto L1

goto L3

L3: if c<d goto L4 E w

goto L2 h

L4: goto L1 i

L1: t1 := 1 I l

t2 := a + t1 e

a := t3

goto L0

L2: