Vous êtes sur la page 1sur 213

CURSO: PROGRAMACION DE SISTEMAS

Dr. Ramn Zatarain Cabada

ELEMENTOS DEL CURSO


    

Programa del Curso..... PDF Datos del Profesor . RZC Contenido del Curso. Diaps Proyectos . Archs Software y Herramientas

Unidad I Introduccin a la Programacin de Sistemas

1.1 Qu es y que estudia la P. de S. ?

Son los programas que residen en un sistema de computacin. Su funcin es proporcionar al usuario o programador una interfase mas eficiente y practica con relacin al hardware de la maquina. La P. de S. estudia como estn implementados cada uno de los programas de un Sistema (ver notas).

1.2 Herramientas desarrolladas con la P de S


Ejemplos:
      

Compiladores (javac) Ensambladores (Masm) Interpretes (Visual Basic) Ligadores (Link) Cargadores Sistema Operativo (Windows) Utileras de Sistemas (Debugger)

1.3 Lenguajes


Naturales (Traductores de Ingles-Espaol, Ingles-Ruso, etc) Artificiales (Compiladores de LP como Java, C++, Ada, etc.)

1.4 Traductor y su Estructura




Ensambladores . Compiladores. Intrpretes......

(notas) (notas) (notas)

1.5 Generador de Cdigo para Compiladores (Compilador de Compiladores)




Definicin: Un compilador de compiladores o generador de Parsers es una utilera para generar el cdigo fuente de un parser, intrprete o compilador a partir de una descripcin de lenguaje anotado en la forma de gramtica (usualmente BNF) mas cdigo que es asociado con cada una de las reglas de la gramtica que debe ser ejecutada cundo esas reglas sean aplicadas por el parser. Esas piezas de cdigo son algunas veces llamadas rutinas de acciones semnticas ya que ellas definen la semntica de las estructura sintctica y que es analizada por el parser. Dependiendo del tipo de parser que ser generado, las rutinas pueden construir un rbol de parsing (o AST) o generar cdigo ejecutable directamente (notas).

Unidad II Introduccin al Diseo de Lenguajes de Programacin

  

 

Visin del Problema. Consideraciones Preliminares. Objetivos y filosofas del diseo de los lenguajes de programacin. Diseo detallado. Caso de estudio.

Unidad III Anlisis de Lxico

3.1 Introduccin a los Autmatas Finitos y Expresiones Regulares

(ver apuntes de Lenguajes y Autmatas).

3.2 Analizador de Lxico.




Descompone la entrada en palabras individuales llamadas tokens. El analizador de lxico (scanner) toma una cadena de caracteres que forman la entrada y produce una cadena de nombres o identificadores, palabras claves o reservadas (PC) signos o marcas de puntuacin, operadores aritmticos y lgicos, constantes (nmeros, cadenas, etc.) y otros. Tambin el scanner tiene como funcin desechar espacios en blanco y comentarios entre los tokens, otra funcin podra ser crear la tabla de smbolos. Ejemplo:
TIPO ID NUM REAL IF COMMA NOTEQ != LPAREN EJEMPLOS foo n14 last 73 0 00 515 66.1 .5 10. 1e67 if , (

Analizador de Lxico (cont.)


Tokens de puntuacin como IF, VOID y RETURN son llamadas palabras reservadas y en la mayora de los lenguajes no pueden usarse como identificadores.

3.3 Manejo de Buffers




El analizador de lxico (scanner) y el analizador de sintxis (parser) forman un duo productorconsumidor. El scanner produce tokens y el parser los consume. La implementacin de la lectura de los caracteres de la entrada (disco) es usualmente hecha por un buffer (memoria) que contendr una buena parte de la entrada, desde donde el scanner ir formando los tokens. Esto agiliza la entrada que de otra forma leera carcter por carcter desde el disco.

3.4 Creacin de la Tabla de Smbolos.




Checar notas La tabla de smbolos es una estructura de datos muy importante en casi todo el proceso de compilacin. En ella se guarda durante las primeras fases de compilacin los nombres de los identificadores (smbolos) usados en el programa fuente, adems de los atributos de cada uno de estos identificadores. Estos identificadores y smbolos junto con sus atributos sern usados posteriormente para realizar funciones como el chequeo de tipos, la asignacin de memoria, generacin de cdigo objeto etc.

Ejemplo
Ejemplo.Programa X1; Var X, Y: Integer; Z: Real; Arreglo: Array [1100] of int Procedure compara (a, b: Integer) Var n, m: integer; Begin ------End Begin ------End

3.5 Manejo de Errores de Lxico




Errores posibles detectados en el anlisis de lxico son:




Patrones de tokens que no coincidan con algn patrn vlido. Por ejemplo el token #### sera invlido en algunos L.P. Caracteres invlidos para el alfabeto del el lenguaje. Longitud de ciertos tokens demasiado larga.

3.6 Generadores de Cdigo Lxico




La construccin de un scanner puede automatizarse por medio de un generador de analizadores de lxico. El primer programa de este tipo que se hizo popular fue Lex (Lesk, 1975) que generaba un scanner en lenguaje C. Hoy en da existen muchos programas de este tipo: Flex, Zlex, YooLex, JavaCC, SableCC, etc.

Ejemplo: Javacc


Javacc (Java Compiler Compiler) es un generador de scanners y parsers (https://javacc.dev.java.net/). Toma como entrada especificaciones de lxico (expresiones regulares) y sintxis (gramtica de contexto libre) y produce como salida un analizador de lxico y un parser recursivo decendente. Se puede usar como pre-procesador de Javacc, a jjtree y a JTB para la construccin del rbol sintctico.

(cont.)
USO DE JAVACC Miparser.jj javacc Miparser.jj Miparser.java ParseException.java TokenMgrError.java otros archivos java

javac Miparser.java Miparser.class java Miparser <inputfile mensajes

(cont.)
EJEMPLO DE ESPECIFICACION PARA GENERAR UN SCANNER
PARSER_BEGIN(PS1) class PS1 {} PARSER_END(PS1) /* Para la expresin regular de la derecha lo de la izquierda ser retornado */ TOKEN: { <IF: "if"> |<#DIGIT: ["0"-"9"]> |<ID: ["a"-"z"] (["a"-"z"]|<DIGIT>)*> |<NUM: (<DIGIT>)+> |<REAL: ((<DIGIT>)+ "." (<DIGIT>)*) | ((<DIGIT>)* "." (<DIGIT>)+)> } SKIP: { <"--" (["a" - "z"])* ("\n" | "\r" | "\r\n")> |" " |"\t" |"\n" |"\r" } void Start(): {} { (<IF> | <ID> | <NUM> | <REAL>)* }

Unidad IV Anlisis de Sintxis

4.1 Introduccin a las gramticas Libres de Contexto y rboles de derivacin




El tipo de gramticas usadas en los LP son llamadas gramticas de contexto libre, las cules, junto con rboles de derivacin, fueron estudiadas en el curso de Lenguajes y Autmatas. Un ejemplo de una gramtica para un LP simple es la siguiente: 1) S S;S 4) E id 8) L E 2) S id := E 3) S print (L) 5) E num 6) E E + E 7) E (S,E) 9) L L , E

 

(ver ejemplo de derivaciones y rboles de parsing) A continuacin veremos un ejemplo de una gramtica para Java en formato BNF (liga).

(cont.)


Gramtica Ambigua. Una gramtica es ambigua si puede derivar una oracin (cadena) con dos diferentes rboles de parsing. La sig. Gramtica es ambiga: E id E E+E E num E E*E E E-E E E/E E (E)

ya que tiene dos rboles de parsing para la misma oracin (rboles para id:=id+id+id con primera gramtica y rboles para 1-2-3 con segunda en sig slice).

(cont.)
S Id := E E Id + S E + E id E id Id := E id E + E id E + E id

(cont.)
E E E 1 E 2 E 3 E 1 E E 2 E E 3

4.2 Diagramas de Sintaxis




Un mtodo alternativo al BNF para desplegar las producciones de ciertas gramticas es el diagrama de sintaxis. sta es una imagen de la producciones que permite al usuario ver las sustituciones en forma dinmica, es decir, verlas como un movimiento a travs del diagrama. Ejemplo en Java (liga)

4.3 Precedencia de Operadores




En los dos ejemplos vistos en seccin 4.1, los dos rboles de parsing para 1-2-3 significaba diferentes cosas: (1-2)-3=-4 versus 1-(2-3)=2. Similarmente, (1+2)x3 no es lo mismo que 1+(2x3). Es por eso que gramticas ambiguas son problemticas para un compilador. Afortunadamente, una gramtica ambigua puede convertirse en una no ambigua. Por ejemplo, si queremos encontrar una gramtica no ambigua para el lenguaje de la segunda gramtica de seccin 4.1, lo podemos hacer por medio de dos operaciones: primero, aplicar orden de precedencia a los operadores y segundo aplicar asociatividad por la izquierda. Con esto, tenemos la sig. gramtica:

(cont.)
S E$ E E+T T-->T*F F id nota: $ es EOF- marker E E-T T T/F F num E T T F F (E)

Esta gramtica acepta el mismo lenguaje que la de seccin 4.1 pero solo produce un rbol de parsing por oracin. Esta gramtica no podra producir rboles como los siguientes: X + ?Y + ?V + ?U *

4.4 Analizador Sintctico


 

 

En esta fase se analiza la estructura de la frase del programa. El parser es el programa que funciona como ncleo del compilador. Alrededor del parser funcionan los dems programas como el scanner, el analizador semntico y el generador de cdigo intermedio. De hecho se podra decir que el parser comienza el proceso de compilacin y su primer tarea es pedir al escner que enve los tokens necesarios para llevar a cabo el anlisis sintctico, del estatuto, expresin o declaracin dentro de un programa. Tambin el parser llama rutinas del anlisis semntico para revisar si el significado del programa es el correcto. Por ultimo el parser genera cdigo intermedio para los estatutos y expresiones si no se encontraron errores en ellos.

(cont.)
Existen diferentes tcnicas o mtodos para realizar un anlisis sintctico Parsing. Estas tcnicas se dividen en dos tipos:
 

Descendentes Ascendentes

Las tcnicas descendentes realizan su anlisis partiendo desde el smbolo inicial de la gramtica y realizando derivaciones hasta llegar a producir las hojas o tokens. Por otra parte las tcnicas ascendentes realizan su anlisis partiendo de las hojas o tokens y mediante una serie de operaciones llamadas reducciones terminan en la raz o smbolo inicial de la gramtica. Por lo general las tcnicas descendentes son mas sencillas de implementar que las ascendentes, pero por otra parte son menos eficientes

4.4.1 Analizador descendente (LL).


Parsing Predictivo  Algunas gramticas son sencillas de analizarse sintcticamente usando un algoritmo o mtodo cuyo nombre es recursivo descendente. En esta tcnica cada produccin de la gramtica se convierte en una clusula de una funcin recursiva.  Un parser Predictivo es aquel que predice que camino tomar al estar realizando el anlisis sintctico. Esto quiere decir que el parser nunca regresara a tomar otra decisin ( back tracking ) para irse por otro camino, como sucede en las derivaciones. Para poder contar con un parser predictivo la gramtica debe de tener ciertas caractersticas, entre ellas la mas importante es que el primer smbolo de la parte derecha de cada produccin le proporcione suficiente informacin al parser para que este escoja cual produccin usar. Normalmente el primer smbolo mencionado es un Terminal o token.

(cont.)


Esta tcnica se utiliz o populariz en los aos 70 a partir del primer compilador de pascal implementado con ella. A continuacin ilustraremos esto escribiendo un parser recursivo descendente para la siguiente gramtica:
if E then S else S begin S L print E end ;SL num = num

S S S L L E

Nuestro Parser tendra 3 mtodos (uno por cada produccin)

Programa del Parser


Final int if = 1, then = 2, else = 3, begin = 4, end = 5, print = 6, semi = 7, num = 8, EQ = 9 int tok = get token ( ); void advance ( ) { tok = get token ( ); } void eat ( int t) { if ( tok == 1) advance ( ); else error ( ); } void S ( ) { switch ( tok ) { case If: eat ( if ); E ( ); eat ( then ); S ( ); eat ( else ); S ( ); break; case begin: eat ( begin ); S ( ); L ( ); break; case print: eat ( print ); E ( ); break; default: error; }} void L ( ) { switch ( tok ) { case end: eat ( end ); break; case semi: eat ( semi ); S ( ); L ( ); break; default: error ( ); }} void E ( ) { eat ( num ); eat ( EQ ); eat ( num ); }

(cont.)


Un parser predictivo que examina la entrada de izquierda a derecha (left-to-right) en un paso y realiza su derivacin por la izquierda es llamado Parser LL. Cuando el parser solo necesita mirar el siguiente token para hacer su funcin (llokahead(1)), recibe el nombre de Parser LL(1). Un parser podra necesitar mirar K tokens por adelantado para tomar desiciones. En este caso recibe el nombre de parser LL(K). Ejemplo: (parte de una gramtica) IF-STM if EXP then STM else STM | if EXP then STM

Eliminacin de Recursin por la izquierda




Suponer que queremos construr un Parser predictivo para la gramtica de seccin 4.3.
S E$ E E+T T-->T*F F id T T/F F num E E-T T F F (E) E T

Producciones como E E + T contienen recursin por la izquierda. Parsers descendentes no pueden manejar recursin por la izquierda en una gramtica. Para eliminar este tipo de recursin utilizamos la siguiente transformacin:

(cont.)
En general, si tenemos producciones X X K y X Edonde Eno comience con X podemos aplicar la siguiente transformacin: X X X X XK XK E E X EX X EX X KX X KX X P

(cont.)


Aplicando la transformacin a la gramtica anterior, obtenemos la nueva equivalente gramtica (sin recursin por la izquierda):
S E$ E - T E T / F T E P T P E T E E + T E T F T T * F T F idF num F (E)

Factorizacin por la izquierda




Otro problema en una gramtica ocurre cuando dos producciones para el mismo no terminal comienza con el mismo smbolo. Por ejemplo:
IF-STM IF-STM if EXP then STM else STM if EXP then STM

En dicho caso podemos factorizar por la izquierda la gramtica. El resultado sera el sig:
IF-STM if EXP then STM X X P X else IF-STM

las producciones anteriores facilitarn el trabajo del parser predictivo.

4.4.2 Analizador ascendente(LR y LALR).




La debilidad de las tcnicas descendentes LL(k) es que deben predecir que producciones usar, despus de haber mirado los primeros k tokens de la cadena de entrada. Una tcnica ascendente mas poderosa es la tcnica LR(K), la cual pospone la decisin hasta que ha visto los tokens de la entrada correspondientes a la parte derecha de la produccin (adems de k mas tokens tambin). LR(K) quiere decir parser de izquierda a derecha, derivacin por la derecha y lookahead(k). Esta tcnica fue introducida por primera vez en 1965 por Knuth.

(cont.)


Un Parser LR(K) est compuesto de:


  

La cadena de entrada Una pila Una tabla de Parsing LR

El parser obtiene los tokens de la entrada y dependiendo de el token actual y de lo que est en el tope de la pila, ejecuta una de dos acciones (shift-reduce) con la ayuda de la tabla de parsing

Algoritmo de Parsing LR (aho,Sethi y Ullman)


Tomar el primer token de w$ /* w es la cadena */ Repeat forever begin Sea s el estado en el tope de la pila y a el token actual; if accin[s,a] = shift s then begin push a primero y sedpus s al tope de la pila; obten el siguiente token de la cadena de entrada else if accin[s,a] = reduce A->B then begin pop 2*|B| smbolos fuera de la pila; Sea s ahora el estado en el tope de la pila; Push A y despus goto[s,A] al tope de la pila; Imprime la produccin A->B end else if accin[s,a] = accept then return else error() end

Ejemplo: Parser LR(K)


TABLA DE PARSING
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 id S4 S4 num print s7 s7 s6 r1 S20 S4 S20 s10 r5 r2 s3 r3 r5 r2 s18 r3 s19 r8 r6 r5 s16 r5 r5 r2 r3 s13 r8 s8 r6 S20 S20 s10 s10 r4 r7 r4 r7 r9 r4 r7 s16 s16 s8 s8 r4 s22 r7 r9 r4 r7 r6 r6 g21 g23 g17 s10 s7 r1 s8 s9 g12 g15 g14 r1 g11 ; s3 , + := ( ) $ a g5 S g2 E L

S20

s10

Ejemplo (cont.):
PILA 1 1 id4 1 id4 := 6 1 id4 := 6 num10 1 id4 := 6 E11 . . . . . . . 1 S2 ENTRADA a:=7;B:=c+(d:=5+6,d)$ := 7;B:=c+(d:=5+6,d)$ 7;B:=c+(d:=5+6,d)$ ;B:=c+(d:=5+6,d)$ ;B:=c+(d:=5+6,d)$ . . . . . . . $ ACCION shift shift shift reduce E->num reduce S->id:=E . . . . . . . accept

Parsing LALR


 

La tcnica de parsing LALR (LookAhead-LR) evita el uso de tablas muy grandes, como las manejadas en la tcnica de parsing LR. Esta tcnica fue inventada por DeRemer en 1971. Casi cualquier construccin sinctica de un LP puede ser expresado de manera conveniente por una gramtica LALR. Generadores de Parsers famosos como YACC (Yet Another Compiler-Compiler-Johnson) producen un parser LALR. Para una gramtica de un LP como Pascal una tabla de parsing LALR ocupara varios cientos de estados, mientras que una tabla LR seran miles de estados.

4.5 Administracin de tablas de smbolos.




 

Como se estudi en la unidad anterior, durante el anlisis de lxico se inicia la construccin de la tabla de smbolos. Esto ocurre al detectarse las declaraciones en el programa fuente. Sin embargo tambin el parser ayuda a realizar esta tarea pues el es quien llama a las respectivas rutinas semnticas para que realicen funciones relacionada con la tabla de smbolos (ver figura).

4.6 Manejo de errores sintcticos y su recuperacin.




Dentro del cdigo del parser predictivo estudiado en clase se puede observar que el parser llama un metodo error para el manejo de errores sintcticos, que es cuando el parser obtiene un token no esperado. Cmo se maneja el error para esta clase de parsers? Una forma simple es ejecutar una excepcin y parar la compilacin. Esto como que no es muy amigable par el usuario. La mejor tcnica es desplegar un mensaje de error y recuperarse de este, para que otros posibles errores sintcticos puedan ser encontrados en la misma compilacin.

(cont.)


 

Un error sintctico ocurre cuando la cadena de tokens de entrada no es una oracin en el lenguaje. La recuperacin del error es una forma de encontrar alguna oracin correcta, similar a la cadena de tokens. Esto se puede realizar por medio de borrar, reemplazar o insertar tokens. Por ejemplo la recuperacin de un error en S, podra ser al insertar un token if,begin o print (o pretender que existe en la cadena), desplegar el mensaje del error y continuar la compilacin.

Ejemplo:
void S ( ) { switch ( tok ) { case If: eat ( if ); E ( ); eat ( then ); S ( ); eat ( else ); S ( ); break; case begin: eat ( begin ); S ( ); L ( ); break; case print: eat ( print ); E ( ); break; default: print(se esperaba if, begin o print); }} Un problema que puede ocurrir al insertar un token faltante es que el programa caiga en un ciclo infinito, por eso a veces es preferible y mas seguro borrar el token, ya que el ciclo terminar cuando el EOF sea encontrado. Esta tcnica trabaja muy cercanamente con la tabla de parsing (cuando el parser se implementa con tablas). En un parser del tipo LR o LALR, la tabla de parsing tiene 4 acciones: shift, reduce, accept y error (entrada nula). Cuando el parser encuentra una accin error, se para el proceso de anlisis y se reporta la falla.

4.7 Generadores de cdigo para analizadores sintcticos


 

 

Como se vio al final del captulo 3, Javacc es un generador de scanners y de parsers (https://javacc.dev.java.net/). Javacc produce un parser del tipo descendente (recursivo) o LL(k) donde k (lookahead) puede ser cualquier nmero entero positivo. El parser producido puede correr en cualquier plataforma que cuente con el compilador de Java. En el ejemplo siguiente del uso de Javacc, utilizamos de nuevo la gramtica presentada anteriormente (seccin 4.4.1) para un parser predictivo. Ejecutamos Javacc con la anterior gramtica y obtendremos un parser recursivo descendente para dicha gramtica.

Ejemplo: gramtica para estatutos begin, if y print


PARSER_BEGIN(MiniParser) public class MiniParser { public static void main(String[] args) { MiniParser parser; try { // RGC: added line if( args.length == 0 ) parser = new MiniParser(System.in); // RGC: added lines else parser= new MiniParser ( new java.io.FileInputStream( args[0] ) ); } // RGC: End parser.Program(); } catch (ParseException e) { System.out.println(e.getMessage()); } //RGC: added lines catch( Exception e ) { System.out.println(e.getMessage()); } //RGC :End } } PARSER_END(MiniParser) SKIP : { | | | } "" "\t" "\n" "\r"

(cont. Ejemplo)

TOKEN : { | | | | | | | | | } <INT: "INT"> <IF: "if"> <THEN: "then"> <ELSE: "else"> <BEGIN: "begin"> <PRINT: "print"> <END: "end"> <SEMI: ";"> <EQUAL: "="> <ID: (["a"-"z"]|["A"-"Z"]) (["a"-"z"]|["A"-"Z"]|["0"-"9"])* >

void Program() : {} { S() <EOF> } void S() : {} { <BEGIN> S() L() | <PRINT> E() | LOOKAHEAD(12) "if" E() "then" S() "else" S() | "if" E() "then" S() } void L() : {} { <END> | <SEMI> S() L() } void E() : {} { <ID> <EQUAL> <ID> }

Unidad V Anlisis Semntico

5.1 Analizador semntico


 

Un compilador no solo tiene que revisar la sintaxis de cdigo fuente, si no tambin la semntica de este. Al igual que en los lenguajes naturales (espaol, ingles, etc.) en los lenguajes de programacin existen reglas semnticas para definir el significado de los programas, estatutos, expresiones, etc. Por ejemplo un error semntico es usar (en pascal java) un identificador que no fue anteriormente declarado. Otro ejemplo de error semntico en un programa es cuando este es compilado y y no se detectan errores pero el momento de ser ejecutado este programa no funciona correctamente.

5.2 Verificacin de tipos en expresiones.




Cuando mezclamos diferentes tipos en una misma expresin o que llamamos una rutina que no existe existe un error semntico. Una de las funciones del analizador smntico es verificar que los tipos de una expresin sean compatibles entre si. Para hacer lo anterior el compilador cuenta con informacin de los atributos (tipos, tamao, nmero de argumento, etc.) de los identificadores en una tabla de smbolos.

5.3 Conversin de tipos.


 

Algunas veces los tipos de una expresin o estatuto son diferente. Por ejemplo en la asignacin, a = b * c; el tipo del resultado de evaluar b*c es diferente al de el identificador a. El compilador algunas veces con ciertos diferentes tipos puede hacer una conversin interna en forma implcita para solucionar el problema. Otras veces el programador explcitamente es el que hace la conversin (casting). Ejemplo: float dinero; int cambio; dinero = (float) cambio;

5.4 Acciones agregadas en un Analizador sintctico descendente (top-down).




En un parser recursivo-descendente, el cdigo de las acciones semnticas es mezclado dentro del flujo de control de las acciones del parser. En un parser especificado en javaCC, las acciones semnticas son fragmentos de cdigo de programa en java unido a las producciones gramticales. Cada smbolo terminal y noterminal puede asociarse con su propio tipo de valor semntico. Por ejemplo en la siguiente gramtica para YACC de una calculadora simple, el tipo asociado con exp e INT podra ser int: %token INT PLUS MINUS TIMES UMINUS %start exp %left PLUS MINUS %left TIMES %left UMINIS exp: INT | exp PLUS exp | exp MINUS exp | exp TIMES exp 1 MINUS exp %prec UMINUS

(cont.)
 

Los otros tokens no necesitaran tener un valor. Por otra parte el tipo asociado a un token debe por supuesto coincidir con el tipo de token que el scanner retorne. Para una regla A BCD, la accin semntica debe retornar un valor cuyo tipo es el asociado al noterminal A. Pero puede construr este valor de los valores asociados a los terminales y noterminales B, C, D.

Recursivo-descendente


En un parser recursivo-descendente, las acciones semnticas son los valores retornados por las funciones de parsing, o los efectos laterales de esas funciones o ambos. Por cada smbolo terminal y noterminal, asociamos un tipo (desde el lenguaje de implementacin del LP del compilador) de valor semntico representando frases derivadas desde ese smbolo. El siguiente programa es un intrprete recursivo descendente para una parte de la gramtica en la cual eliminamos la recursin por la izquierda (por conveniencia la volvemos a mostrar):

(cont.)
S E$ T F T F id E T E T * F T F num F E + T E T / F T (E) E - T E T P E P

Los tokens ID y NUM deben ahora acarrear valores de tipo string e int, respectivamente. Asumiremos que existe una tabla lookup que mapea identificadores a enteros. El tipo asociado con E, T, F, etc., es int, y la accin semntica es fcil de implementar.

Interprete

Acciones semnticas

class Token2 { int kind; Object val; Token2(int k, Object v) { kind=k; val=v; } } final int EOF=0, ID=1, NUM=2, PLUS=3, MINUS=4,LPAREN=5, RPAREN=6, TIMES=7; int lookup(String id) { . } int F_follow[] = {PLUS,TIMES,RPAREN,EOF}; int F() {switch(tok.kind) { case ID: int i=lookup((String)(tok.val));advance()return i; case NUM: int i=((integer)(tok.val)).intVal(); advance();return i; case LPAREN: eat(LPAREN); int i=E(); eatOrSkipTo(RPAREN,F_follow); return i; case EOF: default: print("1 esperaba ID,NUM, o parent izq"); //skipto(F_follow); return 0; }} int T_follow[]= {PLUS,RPAREN,EOF};

int T() {switch(tok.kind) { case ID: case NUM: case LPAREN: return Tprime(F()); default: print("2 esperaba ID, NUM o parent izq"); //skipto(T_follow); return 0; }} int Tprime(int a) {switch (tok.kind) { case TIMES: eat(TIMES); return Tprime(a*F()); case PLUS: case RPAREN: case EOF: return a; default: print("3 esperaba ID, NUM o parent izq"); //skipto(T_follow); return 0; }} void eatOrSkipTo(int expected, int[] stop) { if (tok.kind==expected) eat(expected); else {print("4 esperaba ID, NUM o parent izq"); //skipto(stop);} }

Parser Automticamente generado




Una especificacin del parser para javaCC consistira de un conjunto de reglas gramaticales, cada una anotada o agregada con una accin semntica el cual sera un estatuto java. Ejemplo:
void Start(): { int i; } { i=Exp() <EOF> {System.Out.println(i);} } Int Exp(): { int a,i; } { a=Term() ( + i=Term() {a=a+i;} | - i=Term() {a=a-i;} )* { return a; } } Int Term(): { int a,i; } { a=factor() ( * i=Factor() { a=a*i;} | / i=Factor() {a=a/i;} )* { return a; } } Int Factor(): { Token t; int i; } { t=<IDENTIFIER> { return lookup(t.image); } | t=<INTEGER_LITERAL> {return Integer.parseInt(t.image); } | ( i=Exp() ) {return i; } }

rboles de Parsing Abstractos




Para mejorar la modularidad del compilador, es recomendable separar detalles de la sintaxis con detalles de la semntica (chequeo de tipos y traduccin a cdigo mquina). Una forma de hacerlo es producir un rbol de sintaxis abstracta (una forma condensada de rbol de parsing til para representar construcciones del LP). Por ejemplo la produccin S if B then S1 else S2 pudiera aparecer en un arbol sintctico como:
rbol sintctico

If-then-else

rbol De parsing

Est-if

If B S1 S2

Exp ( Exp ) Est

En un rbol sintctico, los operadores y las palabras claves (reservadas) no aparecen como hojas, sino que estn asociadas con el nodo interior que sera el padre de esas hojas en el arbol de parsing.

(cont.)


Otro ejemplo es en el rbol de parsing:


L E T F 2 * + F 4 T F 8 + * 2 4 8

Cuyo rbol sintctico abstracto sera:

Ejemplo:


La gramtica siguiente nos muestra una sintaxis abstracta de un lenguaje para expresiones: E E+E E E-E E E*E E E/E E id E num Esta gramtica es imprctica para un parser ya que es ambigua pues no tiene precedencia de operadores. Sin embargo, esta gramtica no es para el parser. El analizador semntico podra usarla el cual no se molesta por la ambiguedad puesto que ya tiene su arbol.

rboles de Sintaxis en Java




En Java las estructuras de datos para el rbol de sintaxis contienen una clase abstracta para cada noterminal y una subclase para cada produccin. As, las clases de el programa siguiente son las clases de la sintaxis abstracta para la gramtica de la diapositiva anterior.

Programa de clases para Exp


public abstract class ExpCh4 { public abstract int eval(); } class PlusExp extends ExpCh4 { private ExpCh4 e1,e2; public PlusExp(ExpCh4 a1, ExpCh4 a2) {e1=a1; e2=a2;} public int eval() { return e1.eval()+e2.eval(); } } class MinusExp extends ExpCh4 { private ExpCh4 e1,e2; public MinusExp(ExpCh4 a1, ExpCh4 a2) {e1=a1; e2=a2;} public int eval() { return e1.eval()-e2.eval(); } } class TimesExp extends ExpCh4 { private ExpCh4 e1,e2; public TimesExp(ExpCh4 a1, ExpCh4 a2) {e1=a1; e2=a2;} public int eval() { return e1.eval()*e2.eval(); } } class DivideExp extends ExpCh4 { private ExpCh4 e1,e2; public DivideExp(ExpCh4 a1, ExpCh4 a2) {e1=a1; e2=a2;} public int eval() { return e1.eval()/e2.eval(); } } class Identifier extends ExpCh4 { private String f0; public Identifier(String n0) {f0=n0;} public int eval() { return (7); //return lookup(f0); } } class IntegerLiteral extends ExpCh4 { private String f0; public IntegerLiteral(String n0) {f0=n0;} public int eval() { return (4); //return Integer.parseInt(f0); } }

(cont.)


Ahora veamos un intrprete para el lenguaje de expresiones de la gramtica de seccin 4.1.1. Por conveniencia la mostramos de nuevo.
S E$ E TE T F T F id E + T E T * F T F num F E - T E T / F T (E) E P T P

Nuestro intrprete primero construye rboles sintcticos y despus los interpreta. El siguiente cdigo es el de la gramtica JavaCC con acciones semnticas para interpretar (evaluar) y producir (construir) rboles sintcticos. Cada clase de nodos de rbol sintctico contiene una funcin eval que cuando es llamada retorna el valor de la expresin representada.


Gramtica con acciones semnticas para rboles sintcticos

PARSER_BEGIN(InterSinTree) class InterSinTree {} PARSER_END(InterSinTree) TOKEN: { <#DIGIT: ["0"-"9"]> |<ID: ["a"-"z"] (["a"-"z"]|<DIGIT>)*> |<INTEGER_LITERAL: (<DIGIT>)+> } SKIP: { <"--" (["a" - "z"])* ("\n" | "\r" | "\r\n")> |" " |"\t" |"\n" |"\r" } ExpCh4 Start(): { ExpCh4 e; } { e=Exp() <EOF> {System.out.println(e.eval()); return e; } } ExpCh4 Term(): { ExpCh4 e1,e2; } { e1=Factor() ("*" e2=Factor() { e1=new TimesExp(e1,e2);} |"/" e2=Factor() { e1=new DivideExp(e1,e2);} )* { return e1;} } ExpCh4 Factor() : { Token t; ExpCh4 e; } { (t=<ID> {return new Identifier(t.image); } | t=<INTEGER_LITERAL> {return new IntegerLiteral(t.image); } | "(" e=Exp() ")" {return e; }) } ExpCh4 Exp(): { ExpCh4 e1,e2; } { e1=Term() ("+" e2=Term() { e1=new PlusExp(e1,e2);} |"-" e2=Term() { e1=new MinusExp(e1,e2);} )* { return e1;} }

VISITADORES


Es una tcnica de patrones (opuesta a la orientada a objetos) que se puede usar para implementar el rbol sintctico del compilador o intrprete. Un visitador es un objeto que contiene un mtodo visit por cada clase de rbol sintctico. Cada clase de rbol sintctico debe contener un mtodo accept. Cada mtodo accept sirve como enganche para cada diferente tipo de operacin sobre el rbol y es llamado por un visitador donde tiene una tarea: pasar el control de ida y venida (back and forth) entre el visitador y las clases del rbol sintctico.

(cont.)


A continuacin veremos un ejemplo del intrprete de expresiones anterior pero ahora implementado con visitadores. Cada visitador implementa la interfase Visitor. Cada mtodo accept toma un visitador como argumento y cada mtodo visit toma un objeto de un nodo del rbol sintctico como argumento.

Sintxis Abstracta para MiniJava




En la siguiente figura (siguiente diapositiva) mostramos las clases de la sintaxis abstracta para minijava. Solo los constructores son mostrados en la figura. Cada una de las clases de listas se implementa en la misma forma. Por ejemplo:
public class ExpList { private Vector list; public ExpList() { list=new vector(); } Public void addElement (Exp n) { list.addElement(n); } public Exp elementAt(int i) { return (exp)list.elementAt(i); } public int size() { return list.size(); } }

Package syntaxtree; Program(MainClass m, ClassDeclList cl) MainClass(Identifier i1, Identifier i2, Statement s) Abstract class ClassDecl ClassDeclSimple(Identifier i, VarDeclList vl, MethodDeclList ml) ClassDeclExtends(Identifier i, identifier j, VarDeclList vl, MethodDeclList ml) VarDecl(Type t, Identifier i) MethodDecl(Type t, Identifier i, FormalList fl, VarDeclList vl, StatementList, Exp e) Formal(Type t, Identifier i) Abstract class Type IntArrayType() BooleanType() IntegerType() IdentifierType(String s)

Abstract class Statement Block(StatementList sl) If(Exp e, Statement s1, Statement s2) While(Exp e, Statement s) Print(Exp e) Assign(Identifier i, Exp e) ArrayAssign(Identifier i, Exp e1, Exp e2)

(cont. figura)
Abstract class Exp And(Exp e1, Exp e2) LessThan(Exp e1, Exp e2) Plus(Exp e1, Exp e2) Minus(Exp e1, Exp e2) ArrayLoockup(Exp e1, Exp e2) ArrayLength(Exp e) Call(Exp e, Identifier i, ExpList el) IntegerLiteral(int i) True() False() IdentifierExp(String s) This() NewArray(Exp e) NewObject(Identifier i) Not(Exp e) Identifier(String s) List classes ClassDeclList() ExpList() FormalList() StatementList() MethodDeclList() VarDeclList()

Times(Exp e1, Exp e2)

Arbol Sintctico


Cada una de las clases que no son listas tiene un mtodo accept para usarse con el patrn visitador. La interface Visitador se muestra en la siguiente diapositiva.

Visitador MiniJava

public interface Visitor { public void visit(Program n); public void visit(MainClass n); public void visit(ClassDeclSimple n); public void visit(ClassDeclextends n); public void visit(VarDecl n); public void visit(MethodDecl n); public void visit(Formal n); public void visit(IntArrayType n); public void visit(BooleanType n); public void visit(IntegerType n); public void visit(IdentifierType n); public void visit(Block n); public void visit(If n); public void visit(While n); public void visit(Print n); public void visit(Assign n); public void visit(ArrayAssign n); public void visit(And n); public void visit(LessThan n); public void visit(Pluss n); public void visit(Minus n); public void visit(Times n); public void visit(ArrayLoockup n); public void visit(ArrayLength n); public void visit(Call n); public void visit(IntegerLiteral n); public void visit(True n); public void visit(False n); public void visit(IdentifierExp n); public void visit(This n); public void visit(NewArray n); public void visit(NewObject n); public void visit(Not n); public void visit(Identifier n); }

(cont. Arbol Sintctico)




Podemos construir un rbol sintctico usando expresiones new anidadas. Por ejemplo el rbol sintctico para el estatuto MiniJava: x = y.m(1,4+5); usara el siguiente cdigo: ExpList el= new ExpList(); el.addElement(new IntegerLiteral(1)); el.addelement(new Plus(new IntegerLiteral(4), new IntegerLiteral(5))); Statement s = new Assign(new Identifier x), new Call(new identifierExp(y), new Identifier(m), el));

5.5 Pila semntica en un analizador sintctico ascendente (bottom-up).




Como fue visto en el capitulo anterior (4), un parser ascendente utiliza durante el anlisis una pila. En esta va guardando datos que le permiten ir haciendo las operaciones de reduccin que necesita. Para incorporar acciones semnticas como lo es construir el rbol sintctico, es necesario incorporar a la pila del parser otra columna que guarde los atributos de los smbolos que se van analizando. Estos atributos estaran ligados a la correspondiente produccin en la tabla de parsing (consultar seccin 5.3 del libro de Aho, Ullman, Sethi para ver mas detalles de la implementacin).

5.6 Administracin de la tabla de smbolos




El anlisis semntico conecta las definiciones de las variables con sus usos, checa que cada expresin tenga un tipo correcto y traduce la sintaxis abstracta a una representacin mas simple para generar cdigo mquina. Esta fase es caracterizada por el mantener la tabla de smbolos (tambin llamada environment) la cual mapea identificadores con sus tipos y localidades. Cada variable local en un programa tiene un mbito (scope) dentro del cual es visible. Por ejemplo, en un mtodo MiniJava m, todos los parmetros formales y variables locales declarados en m son visibles solo hasta que finalice m.

(cont.)


Un ambiente es un conjunto de atados (bindings) denotados por . Por ejemplo, podemos decir que el ambiente z0 contiene los atados {g string,a int}, que significa que el identificador a es una variable entero y g es una variable string.

Ejemplo:
1 2 3 4 5 6 7 8 9 10 11 Class C { int a, int b; int c; public void m() { System.out.println(a+c); int j=a+b; String a=hello; System.out.println(a); System.out.println(j); System.out.println(b); } }

Suponer que compilamos esta clase en el ambiente z0. Las declaraciones de campo en lnea 2 nos da la tabla z1 igual a z0 + {a int,b int,c int}. Los identificadores en lnea 4 pueden encontrarse (look up) en ambiente z1. En lnea 5, la tabla o ambiente z2=z1+{j int} es creada; y en lnea 6, z3=z2+{a string} es creada.

Implementacin de la Tabla


Existen dos opciones: El estilo funcional donde cuando z1 existe y z2 es creado, z1 sigue existiendo. Y el imperativo en donde z1 es destruido al crearse z2. Mientras z2 existe no podemos mirar z1. Pero al morir z2, z1 de nuevo existe.

Mltiple Tablas de Smbolos




En algunos LP pueden existir varios ambientes a la vez: Cada mdulo, o clase o registro en el programa tiene una tabla de smbolos z propia. Ejemplos (ML y Java).
Package M; class E { static int a=5; } Class N { static int b=10; static int a=E.a+b; } Class D { static int d=E.a+N.a; }

Structure M = struct structure E = struct val a= 5; end structure N = struct val b=10 val a=E.a+b end structure D = struct val d=E.a+N.a end end

(cont.)


Al anlizar los 2 programas anteriores, sea z0 el ambiente base conteniendo funciones predefinidas, y sea z1={a int} z2={E z1} z3={b int,a int} z4={N z3} z5={d int} z6={D z5} z7=z2+z4+z6

(cont.)


En ML, N es compilado usando el ambiente z0+z2 para buscar los identificadores en la tabla;D es compilado usando z0+z2+z4 y el resultado del anlisis es {M z7}. En Java, referencias adelantads son permitidas (dentro de N la expresin D.d sera legal), asi E,N y D son compilados en el ambiente z7.

TABLAS DE SIMBOLOS EN LENGUAJES IMPERATIVOS




Un programa grande puede contener miles de distintos identificadores. Esto hace que la bsqueda en la tabla (loock up) tenga que ser eficiente. En ambientes imperativos usualmente se usan tablas de dispersin. La operacin z=z+{a t} es implementada insertando t en la tabla de dispersin usando la llave a. Una tabla de dispersin con encadenamiento externo funciona bien y soporta eliminacin fcil de a t para recuperar z al final del ambito de a. El siguiente programa implementa una tabla de dispersin. El bucket i es una lista ligada de todos los elementos cuya llave genere i mod SIZE.

(cont.)


Considere z+{a t2} cuando z ya contiene a t1. La funcin insert deja a t1 en el bucket y pone a t2 antes en la lista. Entonces, cuando pop se realiza despus del ambito de a, z es restaurado.

SIMBOLOS


Para evitar comparaciones innecesarias de cadenas podemos convertir cada cadena a un smbolo, y as todas las diferentes ocurrencias de cualquier cadena se conviertan a un mismo objeto smbolo. El mdulo smbolo implementa los smbolos y tiene estas propiedades:
 

Comparar smbolos por igualdad o por mayor es rpido (comparacin por apuntador o por entero). Extraer una llave hash de tipo entero es rpido

(cont.)
 

 

Los ambientes son implementados en la clase Symbol.Table como Tables mapeando Symbols a ligados (bindings). Para eso se manejan ligaduras para diferentes propsitos en el compilador ligadura para tipos, para variables, para funciones, etc. Entonces, una ligadura es un Objeto. Para implementar la clase Symbol, usaremos el mtodo intern() (java.lang.String), para darnos un objeto nico a partir de una cadena de caracteres. Para el uso de la tabla de smbolos usaremos java.util.Hashtable. La funcin beginScope recuerda el estado actual de la tabla y endScope restaura la tabla a donde estaba en el mas reciente beginScope que no ha terminado.

(cont.)


Cuando la atadura x b es metido a la tabla (table.put(x,b)), x es dispersado a un ndice i, y un objeto binder x b es puesto en la cabeza de la lista ligada para el bucket i. Si la tabla ya tiene una ligadura x b, esto permanecera en el bucket, pero escondido por x b. Esto es importante ya que soportara la implementacin de undo (beginScope y endScope). Tambin deber existir una pila auxiliar, que muestre en que orden los smbolos son metidos (pushed) a la tabla de smbolos. Cuando x b es encontrado, entonces x es metido a la pila (beginScope). Entonces, para implementar endScope, los smbolos deben sacarse de la pila.

Chequeo de Tipos en MiniJava


 

Con que se llena una tabla de smbolos? Esto es, Qu es la ligadura o binding? Para realizar el chequeo de tipos de programas MiniJava, la tabla de smbolos debe contener toda la informacin declarada:
   

Cada nombre de variable y nombre de parmetro formal debe ser ligado a su tipo. Cada nombre de mtodo debe ser ligado a sus parmetros, tipo de resultado y variables locales. Cada nombre de clase debe ser ligado a su variable y declaraciones de mtodos. Liga a pgina con mas informacin.

(cont.)


Por ejemplo, considere la siguiente figura, que muestra un programa y su tabla de smbolos. PARAMS
p q
FIELDS f j q C int [ ] int

Class B { C f; int [ ] j; int q; public int start(int p, int q) { int ret; int a; /* */ return ret; } public boolean stop(int p) { /* .. */ return false; } } Class C { /* .*/ }

int int

B C

LOCALS ret int a int

METHODS start int stop bool

PARAMS p int LOCALS

(cont.)


Los tipos primitivos en MiniJava son int y boolean; todos los otros tipos son arreglo de enteros o nombres de clases. Por simplicidad todos los tipos son string, en lugar de smbolos; esto nos permite checar igualdad de tipos por medio de comparacin de strings. El chequeo de tipos de un programa MiniJava ocurre en dos fases. Primero, construimos la tabla de smbolos y despus checamos los tipos de los estatutos y las expresiones. Lo hacemos en dos fases porque en MiniJava (igual que en Java) las clases son mutuamente recursivas.

(cont.)


La primera fase de el checador de tipos se puede implementarse por medio de un visitador que visite los nodos del rbol sintctico de MiniJava y construya la tabla de smbolos. Por ejemplo el mtodo visitador en el siguiente programa maneja declaraciones de variables. Este agrega el nombre de la variable y el tipo a la estructura de datos para la clase actual que mas tarde ser agregada a la tabla de smbolos. El mtodo visitador checa si la variable ya fue declarada anteriormente.

Mtodo Visitador
Class ErrorMsg { boolean anyErrors; void complain (String msg) { anyErrors = true; System.out.println(msg); } } // Type t; // Identifier i; Public void visit(VarDecl n) { Type t = n.t.accept(this); String id= n.i.toString(); if (currMethod ==null) { if (!currClass.addVar(id,t)) error.complain(id + is already defined in + currClass.getId()); } else if (!currentMethod.addVar(id,t)) error.Complain(id + is already defined in + currClass.getId( ) + . + currMethod.getId( ));

(cont.)


  

La segunda fase del checador de tipos puede ser implementada por un visitador que cheque tipos de todas los estatutos y expresiones. El tipo del resultado de cada mtodo visitador es String, que representa los tipos de MiniJava. La idea es que cuando el visitador visita una expresin, entonces retorna el tipo de esa expresin. El siguiente mtodo (siguiente diapositiva) es el visitador que implementa la adicin (plus) e1 + e2. En MiniJava ambos operandos deben ser de tipo entero (el checador revisa esto) y el resultado debe ser entero (el checador retorna este tipo).

Mtodo Visitador para expresiones Plus


// Exp e1, e2; Public Type visit(Plus n) { if (! (n.e1.accept(this) instanceOf IntegerType) ) error.complain(Left side of LessThan must be of type integer); if (! (n.e2.accept(this) instanceOf IntegerType) ) error.complain(Right side of LessThan must be of type integer); return new IntegerType( ); }

5.7 Manejo de errores semnticos.




 

Cuando el checador de tipos detecta un error de tipos o un identificador no declarado, debe imprimir el mensaje de error y continuar. Esto debido a que normalmente el programador prefiere que le describan todos los errores posibles del programa fuente. Esto quiere decir, que si un error de tipos es encontrado, no debe producirse un programa objeto por parte del compilador. As, las siguientes fases no deben ejecutarse. Hasta esta etapa (chequeo de tipos), la parte del compilador se conoce con el nombre de front End.

REGISTROS DE ACTIVACION


En casi cualquier LP, una funcin (mtodo) puede tener variables locales que son creadas cuando se llama la funcin (al entrar a esta). Diferentes invocaciones a la funcin pueden existir a la vez, y cada invocacin tiene su propia instanciacin de variables.

(cont.)


En el siguiente mtodo de Java


Int f(int x) { int y= x+x; if (y<10) return f(y); else return y-1;

Una nueva instancia de x es creada (e inicializada por el llamador de f) cada vez que f es llamada. Debido a que existen llamadas recursivas, muchas de esas x existen simultneamente. Similarmente, una nueva instancia de y es creada cada vez que el cuerpo f es iniciado.

(cont.)


En muchos LP (incluyendo Pascal, C y java), las variables locales son destruidas cuando una funcin retorna. Ya que las variables locales son creadas y destruidas en una forma LIFO, podemos usar una pila para manejarlas.

MARCOS DE PILA
 

Debido a que se trabaja con bloques de datos por funcin un push y pop no funciona. Entonces la pila es tratada como si fuera un gran arreglo, con un registro especial- el stack pointer que apunta a una localidad. Todas las localidades despus del apuntador son basura y todas las que estn antes estn asignadas. El rea en la pila dedicada a las variables locales, parmetros, direccin de retorno y otras variables temporales para una funcin es llamada el registro de activacin o marco de pila de la funcin.

(cont.)


El diseo de la estructura de los marcos es de acuerdo con la arquitectura y el LP que se compila. Aunque normalmente el constructor de la arquitectura define un diseo de marco standard para todos los compiladores para esa arquitectura.

Ejemplo: Un marco de pila


Direcciones de Memoria mas altas
Argumentos de entrada Apuntador Del marco Argumento n Argumento 1 Liga esttica

Marco anterior

Variables locales

Direccin retorno Temporales Registros salvados

Marco actual

Argumentos De salida Apuntador De pila

Argumento m Argumento 1 Liga esttica

Marco siguiente

Marco de Pila


  

Los argumentos de entrada son los pasados por el llamador (tcnicamente son parte del marco anterior pero pueden accesarse usando un desplazamiento del apuntador de marco). Cuando la funcin actual llama otras funciones, puede usar el espacio de los argumentos de salida para pasar parmetros. La direccin de retorno es creada por la instruccin CALL. Las variables locales tambin tienen su espacio. Las variables mantenidas en registros algunas veces son salvadas a memoria.

El Apuntador de Marco (FP)




  

Suponer que una funcin g() llama la funcin f(a1,an). Diremos que g es el llamador (caller) y f el llamado (callee). Al entrar a f, el apuntador de la pila (SP) apunta al primer argumento que g pasa a f. Al entrar, f coloca un marco solo con restar el tamao del marco de el SP. El viejo SP se convierte en el actual FP y el viejo FP es salvado en el marco. Cuando FP termina, solo copia FP de regreso a SP y regresa el valor viejo salvado de FP. Si los marcos son siempre del mismo tamao entonces no es necesario contar con FP y todo se simplifica sumando o restando la constante framesize a SP.

Registros


Por eficiencia, es importante mantener las variables locales, resultados intermedios y otros valores en registros en lugar de la pila de marcos. Si funcin f llama a g y ambas hacen uso de registro r, entonces r debe ser salvado (dentro de la pila de marcos) antes de que lo use g y restaurado (desde la pila) despus de que termine g. de quien es responsabilidad de salvar r? de f o g? si lo salva f se dice que r es un registro callersave; si lo salva g se llama callee-save.

Pase de Parmetros


Estudios actuales han mostrado que raramente una funcin pasa mas de 4 parmetros. Debido a esto, la mayora de las mquinas definen que los primeros k argumentos (con k=4) se pasan en registros y el resto en memoria.

Direcciones de Retorno


Si g llama a f, entonces si la instruccin call dentro de g est en direccin a, el lugar de retorno en g es a+1, la siguiente instruccin del call. En mquinas modernas la direccin de retorno es pasada a un registro en lugar de la memoria. En funciones hoja la direccin no necesita ponerse en la pila.

Registros vs. Memoria




Registros siempre deben usarse en asignacin a menos que:


 

 

La variable sea pasada por referencia La variable es accesada por una funcin anidada dentro de la funcin actual. El valor es demasiado grande para un registro. La variable es un arreglo, donde es necesario realizar aritmtica de direcciones. El registro que contiene la variable es necesitado para otros propsitos. Existen demasiadas variables locales y valores temporales

Ligas Estticas (Static Links)




En LP que admiten funciones anidadas (Pascal,ML y Java) las funciones de mas adentro pueden usar variables declaradas en funciones de mas afuera (Estructuras de Bloque). En el siguiente programa (sig. Diapositiva) la funcin write hace referencia a la variable de afuera output e indent hace referencia a n y output. Para cumplir con esto, indent debe tener acceso no solo a su propio marco (para i y s) sino tambin a los marcos de show (por n) y prettyprint (por output).

Programa de funciones Anidadas


Type tree= {key: string, left: tree, right: tree} Function prettyprint(tree:tree): string= let var output := function write(s:string) = output :=concat(output,s) function show(n:int, t:tree) = let function indent(s:string)= (for i:= 1 to n do write( ); output:=concat(output,s);write( ); in if t=nil then indent(.) else (indent(t.key); show(n+1,t.left); show(n+1,t.right)) end in show(0,tree); output end

Ligas Estticas (cont.)




Existen varios mtodos para solucionar lo anterior:




Siempre que una funcin f sea llamada, puede pasarse un apuntador a el marco de la funcin que estticamente encierra a f; este apuntador es la liga esttica. Un arreglo global puede mantenerse, conteniendo -en posicin i - un apuntador a el marco del procedimiento mas recientemente activado cuyo profundidad de anidamiento esttico es i. Este arreglo es llamado un display.

A continuacin describimos el mtodo de liga esttica para el ejemplo de la diapositiva anterior.

(cont.)
    

Lnea 21: prettyprint llama show, pasando el apuntador del marco del propio prettyprint como una liga esttica de show. Lnea 10: show guarda su liga esttica (la direccin del marco de prettyprint) dentro de su propio marco. Lnea 15: show llama indent, pasando su propio apuntador de marco como liga esttica de indent. Lnea 17: show llama a show,pasando su propia liga esttica (no su propio apuntador de marco) como la liga esttica. Lnea 12: indent usa el valor n del marco de show. Para hacer esto, trae un desplazamiento apropiado de la liga esttica de indent (que apunta al marco de show). Lnea 13: indent llama a write. Debe pasar el apuntador de marco de prettyprinter como la liga esttica. Para obtener esto, primero trae un desplazamiento de su propia liga esttica (desde el marco de show), la liga esttica que haba sido pasada a show. Lea 14: indent usa la variable output del marco de prettyprint. Para hacer esto, comienza con su propia liga esttica, entonces trae a show, y luego trae a output.

Unidad VI Generacin de Cdigo Intermedio

6.1 Lenguajes intermedios.




El cdigo intermedio en una estructura de cdigo cuya complejidad est entre un cdigo fuente en un lenguaje de alto nivel y el cdigo mquina.
Cdigo fuente Cdigo intermedio Cdigo Objeto

Un compilador produce primero un cdigo intermedio, pues producir directamente el cdigo objeto resulta sumamente complicado e ineficiente.

(cont.)
Ventajas de producir cdigo intermedio:  Ms fcil de producir cdigo objeto despus, si se parte de un cdigo intermedio.  Facilita y hace eficiente la optimizacin de cdigo (algunos tipos de optimizacin).  El cdigo intermedio es transportable (puede usarse en diferentes arquitecturas) ya que no hace referencia a componentes de la mquina.

6.2 Notaciones.


Infija. Es la notacin habitual. El orden es primer operando, operador, segundo operando. Ejemplo: a/b/c La notacin infija tiene el problema de que en expresiones con ms de un operador existe ambiguedad sobre cual es el orden de evaluacin. Por ejemplo, la expresin 8/4/2 se puede interpretar como (8/4)/2 o bien como 8/(4/2). Las otras notaciones (prefija y postfija) no sufren este problema.

(cont.)


Postfija. El orden es primer operando, segundo operando, operador. Ejemplo: La expresin X+Y-X*Y en notacin Postfija es XY+XY*Por lo general la notacin postfija se emplea en mquinas de pilas ya que la pila facilita la ejecucin. Al analizar una notacin postfija de izquierda a derecha, cada vez que se detecta un operando se mete a la pila. La ocurrencia de un operador con m' operandos significa que el ensimo operando estar m-n posiciones por debajo del tope de la pila. Despus se sacan los operandos de la pila y se mete el resultado de la operacin. Por ejemplo, suponer que X=1 y Y=2. Las operaciones seran: push 1 (meter 1) push 2 ' + ' requiere de 2 operandos, se sacan, se suman y se mete el resultado (3) push 1 push 2 ' * ' se mete el resultado(2) ' - ' se mete el resultado (1)

Notacin prefija: El orden es operador, primer operando, segundo operando.

6.3 Representacin de cdigo intermedio.




Existen muchas clases de representaciones intermedias. Algunas de las mas comunes son:
    

Notacin polaca. Cdigo P (Pascal). Triples y Cuadruples. Bytecodes (Java) MSIL (C#)

Notacin Polaca


Tambin conocida como notacin postfija. Se utiliza como se dijo anteriormente en mquinas de Pila. Ejemplos: Pascal Notacin Polaca a+b-c ab+ca+b*c abc*+ a+b*c+d abc*+d+

Cdigo P


Se us como cdigo intermedio y objeto en las primeras implementaciones de Pascal. El cdigo P era interpretado en una mquina abstracta. Algunas implementaciones de Basic y Pascal usan cdigo P el cual despus es traducido a cdigo nativo por un compilador Just-in-Time. La mquina P est orientada a usarse en una pila (stack-oriented).

Ejemplo:
Insn. Stack before
adi Adr dvi ldci mov not i1 i2 r1 r2 i1 i2 i1 i1 a1 a2 b1

Stack after
i1+i2 r1+r2 i1/i2

Description
add two integers add two reals integer division load integer constant move boolean negation

~b1

Triples y Cuadruplos (Cdigo de 3 Direcciones)




Ha sido uno de los mas populares. Sus instrucciones constan de 3 direcciones o registros para 2 argumentos u operandos y el resultado. Su formato es:
resultado:= argumento1 operador argumento2

Donde resultado, argumento1 y argumento2 pueden ser constantes, identificadores y variables temporales definidos por el compilador mientras que operador representa una operacin arbitraria. Esta forma se denomina Cudruplo.

(cont.)
EJEMPLO: ADD MUL SUB STORE Z:= X + Y X * Y X Y X Y VAR1 VAR2 VAR3 VAR1 VAR2 VAR3 Z

(cont.)


EJEMPLO: (Estructure de Control): If (a==b) a=0; else a=1; En cuadruplos tendramos: 1 A


2 3 4 5 6 JnZ = JP = t1 0 1

t1
5 A 6 A

(cont.)


En el cdigo de 2 direcciones se evita el uso se variables temporales. El formato es: Operador argumento1 argumento2 EJEMPLO: Z = X + Y - X * Y
1. 2. 3. 4.

ADD MUL SUB STORE

X X (1) (3)

Y Y (2) (Z)

Donde los nmeros entre parntesis representan apuntadores a la secuencia de operaciones de 2 direcciones.

Java Bytecodes


El bytecode es un cdigo intermedio ms abstracto que el cdigo mquina. Habitualmente viene a ser un archivo binario que contiene un programa ejecutable similar a un mdulo objeto o cdigo mquina producido por el compilador. El bytecode recibe su nombre porque generalmente cada cdigo de operacin tiene una longitud de un byte, si bien la longitud del cdigo de las instrucciones vara. Cada instruccin tiene un cdigo de operacin entre 0 y 255 seguido de parmetros tales como los registros o las direcciones de memoria. Esta sera la descripcin de un caso tpico, si bien la especificacin del bytecode depende ampliamente del lenguaje.

(cont.)


Como cdigo intermedio, se trata de una forma de salida utilizada por los implementadores de lenguajes para reducir la dependencia respecto del hardware especfico y facilitar la interpretacin. Menos frecuentemente se utiliza el bytecode como cdigo intermedio en un compilador. Algunos sistemas, llamados traductores dinmicos o compiladores just-in-time (JIT) traducen el bytecode a cdigo mquina inmediatamente antes de su ejecucin para mejorar la velocidad de ejecucin. Los programas en bytecode suelen ser interpretados por un intrprete de bytecode (en general llamado mquina virtual, dado que es anlogo a un ordenador).

(cont.)


Su ventaja es su portabilidad: el mismo cdigo binario puede ser ejecutado en diferentes plataformas y arquitecturas. Es la misma ventaja que presentan los lenguajes interpretados. Sin embargo, como el bytecode es en general menos abstracto, ms compacto y ms orientado a la mquina que un programa pensado para su modificacin por humanos, su rendimiento suele ser mejor que el de los lenguajes interpretados. A causa de esa mejora en el rendimiento, muchos lenguajes interpretados, de hecho, se compilan para convertirlos en bytecode y despus son ejecutados por un intrprete de bytecode. Entre esos lenguajes se encuentran Perl, PHP y Python. El cdigo Java se suele trasmitir como bytecode a la mquina receptora, que utiliza un compilador just-in-time para traducir el bytecode en cdigo mquina antes de su ejecucin.

La Mquina Virtual de Java (JVM)




La siguiente liga nos lleva a la informacin de lo que es la JVM y mas sobre Java bytecodes.

rboles de Ensamblador
  

Un paso antes de generar Ensamblador Real Permite mas fcil optimizacin de cdigo Primero vamos a estudiar la implementacin de asignacin de memoria en el cdigo generado (stack y heap).

Implementacin de variables
  

En Java las variables locales son almacenadas en la pila y en registros. Las variables de arreglos y de clases son almacenadas en el heap. Registros de Activacin (RA).


Son los segmentos de la pila que contienen las variables para una funcin. Tambin se le llama stack frame. Tambin almacenan valores de registros (saved registers), retornos, parmetros, etc.

Por ejemplo, la funcin:


Int foo() { int a; int b; /* body of foo */ }

Tendra el registro de activacin Existen dos apuntadores. FP que apunta al inicio del RA actual y SP que apunta a la primera localidad vaca. El valor retornado de una funcin es puesto en un registro especial en lugar de la pila. Cuando una funcin es llamada los valores de los parmetros de entrada son puestos en la pila, y cuando la funcin retorna un valor, el valor es almacenado en el registro de resultado.

a b Registros salvados

FP

SP

(cont.)


Como el FP siempre apunta al comienzo del RA actual, podemos acceder a las variables locales usando un offset desde el FP. Los parmetros de funciones son almacenados en RA de la funcin que llama, no la que es llamada. Los parmetros de entrada a una funcin pueden ser accesados por medio del FP, usando un offset en la direccin opuesta a las variables locales. El formato completo de un RA es mostrado en la siguiente diapositiva.

Formato completo de RA
Input Parameter n

Previous Activation Record

Input Parameter 2 Input Parameter 1

FP
Local Variables

Current Activation Record


Saved Registers

SP

Considere el siguiente programa:


void foo(int a, int b); void foo(int c, int d); void main() { int u; int v; /* Label A */ bar(1,2); } void bar(int a, int b) { int w; int x; foo(3,4); } void foo(int c, int d) { int y; int z; /* Label B */ }

En label A de el programa, la pila de RA se mirara de la forma siguiente

u v Saved Registers

FP Activation Record for main SP

La variable local u puede accesarse examinando la localidad de memoria apuntada por FP. La variable v puede accesarse examinando (FP-wordsize). Algo para aclarar es que la pila crece de direcciones altas a bajas.

En etiqueta B los RA se miraran as:


Activation Record for main

u v Saved Registers b a w
Activation Record for bar

x Saved Registers d c y

Activation Record for foo

FP

z Saved Registers

SP

(cont.)


La variable y en funcin foo puede accesarse al examinar la localidad de memoria apuntada por FP. La variable z en foo puede accesarse examinando la localidad (FP-wordsize). El parmetro de entrada c en funcin foo puede accesarse examinando la localidad (FP+wordsize), mientras que el parmetro d puede accesarse con (FP+2*wordsize). Algo importante es que la funcin llamadora es responsable de poner los parmetros actuales y de quitarlos cuando acabe la funcin llamada.

Ensamblador Abstracto


El ensamblador abstracto es dividido en rboles de estatutos y rboles de expresiones. rboles de estatutos no tienen valores pero los de expresiones si lo tienen. Existen cinco diferentes tipos de rboles de expresiones:
  

Constante. Consiste de un valor de la constante (solo enteros). Registro. Una cadena que especifica el registro. Operador. Dos rboles de expresin para los operandos, y el operador: +,-,*,/,<,>,<=,>=,=,&&,||,!.

(cont.)


CallExpression. Contiene una etiqueta de lenguaje ensamblador y un rbol de expresin por cada uno de los parmetros en la llamada de la funcin (function call). La etiqueta es la direccin del inicio de la funcin llamada. Por ejemplo la funcin foo(3,4,5) se representara por el rbol de expresin:
CallExpresion(foo)

Constant(3)

Constant(4)

Constant(5)

El ensamblador abstracto en este caso no contiene instrucciones explcitas para asignar los parmetros actuales a la pila. Cuando se tradusca el ensamblador abstracto de una callExpression a ensamblador real, se incluye el cdigo para hacer la copia de los parmetros actuales a la pila.

Memoria. Solo se denota la direccin de memoria. Ejemplo:


Memory

Constant(1006)

Casi nunca se manejan direcciones absolutas sino que estas son relativas al FP. Por ejemplo para referirse a la localidad de memoria de una variable local con un desplazamiento (oofset) de 4 del FP es representado con el rbol:
Memory

Operator(-)

Register(FP)

Constant(4)

Existen 8 tipos de rboles de estatutos:




  

 

 

Move. Tienen dos subrboles elsubrbol izquierdo es el destino del move, y el derecho es el valor a mover. El izquierdo necesita ser un rbol de expresin de registro (mover un dato a un registro) o un rbol de expresin de memoria (mover un dato a memoria). La parte derecha puede ser una expresin arbitraria. Label (Etiqueta). Se usan como destinos de jumps y calls. Jump. Saltos incondicionales contienen una etiqueta. Conditional Jump. Contienen un subrbol de expresin y una etiqueta de lenguaje ensamblador. Si la expresin es verdadera se hace una transferencia de control a una etiqueta. Si es falsa, entonces habr un noop. Sequential. Tiene dos subrboles. Representa la ejecucin del izquierdo seguido del derecho. CallStatement. Contienen una rbol de etiqueta, y un rbol expresin de cada uno de los parmetros actuales. Calls representan void function calls. Empty. Son no-ops y son removidos cuando se traduce a ensamblador real. Return. No retorna un valor (como Java). Solo cambia el flujo de control. Un return de Java se implementa incluyendo cdigo que retorne el valor de la funcin (en el registro resultado) adems del ensamblador abstracto del return.

Ejemplos: (asumiremos que wordsize=4 y que un entero se guarda en una palabra)


void foo(int a, int b) { int x; int y; boolean z; x = 1; y = a * b; y++; bar(y, x + 1, a); x = function(y+1, 3); if (x > 2) z = true; else z = false; }

X=1;

Move

Memory Constant(1)

Register(FP)

y = a * b;
Move

Memory Memory Operator (-) Operator (+) Register(FP) Constant(4) Register(FP)

Operator(*) Memory

Operator (+)

Constant(4)

Register(FP)

Constant(8)

Y++;
Move

Memory Memory Operator (-) Operator (-) Register(FP) Constant(4) Register(FP)

Operator(+) Constant(1)

Constant(4)

bar(y,x+1,a);

CallStatement(bar)

Memory Operator (-)

Operator (+)

Memory Operator (+)

Memory

Constant(1)

Register(FP) Constant(4)

Register(FP)

Register(FP) Constant(4)

x=function(y+1,3);
Move Callexpression(function) Operator(+) Memory Operator(-) Register(FP) Constant(4) Constant(3) Constant(1)

Memory Register(FP)

if (x > 2) z = true; else z = false;


Sequential ConditionalJump(iftrue) Operator(>) Memory Constant(2) Memory Register(FP) Operator(-) Register(FP) Constant(8) Move Sequential Sequential Jump(ifend) Sequential Sequential Label(iftrue) Constant(0) Move Memory Operator(-) Register(FP) Constant(8) Constant(1) Label(ifend)

Creacin de Ensamblador Abstracto




Variables


Variables base
void foo() { int x; int y; /* Body of foo */ }

La variable x y y se representaran con los rboles: Memory


Memory Register(FP) Operator(-) Register(FP) Constant(4)

Variables Arreglo: Son almacenadas en el heap, y usando un apuntador a la base del arreglo el cual se almacena en la pila. La siguiente funcin o mtodo
void foo arrayallocation() { int x; int A[]; int B[]; A = new int [5]; B = new int [5]; /* Body of function */ }

La variable local x es almacenada en la pila, igual que la direccin base del arreglo A y del arreglo B.
Stack FP x A B Heap A[0] A[1] A[2] A[3]
Saved Registers

A[4] B[0] B[1] B[2] B[3] B[4]

SP

Cmo debemos representar el arreglo A[3]


Memory Operator(-) Memory Operator(-) Operator(*) Constant(WORDSIZE) Constant(3)

Register(FP)

Constant(WORDSIZE)

Y A[x] ?
Memory Operator(-) Memory Operator(-) Operator(*) Constant(WORDSIZE) Memory Register(FP)

Register(FP)

Constant(WORDSIZE)

Y para A[B[2]] ?
Memory Operator(-) Memory Operator(-) Operator(*) Constant(WORDSIZE) Memory Operator(-) Register(FP) Constant(WORDSIZE) Memory Operator(-) Operator(*) Constant(WORDSIZE) Constant(2)

Register(FP)

Constant(2*WORDSIZE)

Arreglos Multidimensionales se manejan de manera similar. Ejemplo:


void twoDarray { int i; int c [ ] [ ] ; C = new int [3] [ ] ; for (i=0; i<3; i++) C[i] = new int[2] ; /* Body of function */ }

El RA y el Heap se ven as:


Stack FP i C
Saved Registers

Heap C[0] C[1] C[2] C[0] [0] C[0] [1] C[1] [0] C[1] [1] C[2] [0] C[2] [1]

SP

La variable C sera representada as:

Memory Operator(-)

Register(FP)

Constant(WORDSIZE)

La variable C[2] as:


Memory Operator(-) Memory Operator(-) Operator(*) Constant(WORDSIZE) Constant(2)

Register(FP)

Constant(WORDSIZE)

La variable c[2] [1]


Memory Operator(-) Memory Operator(-) Memory Operator(-) Operator(*) Constant(WORDSIZE) Constant(1)

Operator(*) Constant(WORDSIZE) Constant(2)

Register(FP) Constant(WORDSIZE)

Variables Instanciadas
Son muy similares a las variables arreglo. La nica diferencia es que en las variables arreglo, el desplazamiento para el ndice necesita ser calculado, mientras que en las variables instanciadas, el desplazamiento es conocido en tiempo de compilacin. Ejemplo:
class simpleClass { int x; int y; int A[ ]; } void main() { simpleClass(); s = new simpleClass(); s.A = new int[3] /* Body of main */ }

Cul es el rbol de ensamblador para s.y ?


l rbol de s: Memory

Register(FP)

Para obtener y de s: Memory Operator(-) Memory Constant(WORDSIZE)

Register(FP)

El rbol de la variable s.x:

Memory Memory

Register(FP)

El rbol para .A[3]:

Memory Operator(-)

Memory Operator(-)

Operator(*) Constant(WORDS Constant(3) IZE)

Memory Register(FP)

Constant(2*WORD SIZE)

Estatutos
Estatutos de Asignacin <variable> = <expresin> es representada por el rbol de ensamblador: Move <variable> <expression>

Ejemplo: Void main() { int x; int y; y = x + 4; } Register(FP) Constant(4) Memory Operator(-) Operator(+) Memory Register(FP) Constant(4) Move

Estatuto If if (<test>) <estatuto1> else <estatuto2> se puede traducir en If (<test>) goto IFTRUE <cdigo para estatuto2> goto IFEND IFTRUE: <cdigo para estatuto1> IFEND:

Y se representa con el rbol:

Sequential ConditionalJump(iftrue) <test> Sequential <statement2> Sequential Sequential Sequential Label(iftrue) Label(ifend) <statement1>

Jump(ifend)

Estatuto while: while (<test>) <estatuto> se puede traducir en WHILESTART: if (not <test>) goto WHILEEND <cdigo de estatuto> goto WHILESTART WHILEEND: aunque es mas eficiente goto WHILETEST WHILESTART: <cdigo de estatuto> WHILETEST: if (<test>) goto WHILESTART WHILEEND:

Estatuto for:
for (<initialize>;<test>;<increment>) <estatement> es equivalent a:

<initialize> while (<test>) { <statement> <increment> }

Entonces queda como:

<initialize> goto FORTEST FORSTART: <statement> FORTEST: <if (<test>) goto FORSTART

Estatuto Return: Cuando un estatuto return es ejecutado, el valor que es retornado necesita salvarse en el registro resultado, el registro de activacin (RA) actual necesita ser sacado de la pila (POP OFF) y el control necesita ser retornado a la funcin llamadora. En lugar de duplicar todo el cdigo de ensamblador abstracto para limpiar la pila por cada estatuto return, podemos copiar el valor a ser retornado al registro resultado y entonces saltar al final de la funcin, donde una sola copia del cdigo que limpia la pila existe.

Definiciones de Funcin o Mtodos:


El ensamblador abstracto debe: Guardar el viejo SP, FP y registros de direccin de retorno en la pila. Apuntar el FP al inicio del RA actual. Apuntar el SP al final del RA actual. Incluir el ensamblador para el cuerpo de la funcin. Restaurar el viejo SP, FP y registros de direccin de retorno (el FP al ltimo si se usa para referenciar los datos en la pila). Retornar del mtodo, con una instruccin de ensamblador abstracto return. Incluir etiquetas despus del cuerpo para instrucciones return desde adentro del cuerpo El cdigo ensamblador queda as: <Etiqueta de inicio de el mtodo> <Cdigo para asignar o meter el registro de activacin>

<cuerpo de la funcin>

<Etiqueta del fin de mtodo> <Cdigo para sacar el registro de activacin> <return>

Creacin de rboles de Ensamblador Abstracto (AAT) en Java




Para construir AAT en Java, primero definimos una interfase para construir AATs para estatutos y para expresiones. Entonces, se agregan en el anlisis semntico llamadas a las funciones definidas en la interfase. De esta forma, el analizador semntico construir los AATs al igual que checar por los errores semnticos.

Etiquetas Se necesitan tanto para las llamadas a mtodos como para los if y los ciclos while y for. La siguiente clase nos genera etiquetas nicas (ifEnd001,ifEnd02,etc.) y etiquetas especficas (llamadas a mtodos).
import java.util.Hashtable; class Label { protected String label; private static HashTable labelhash; Integer last; Int next; public Label(String s) { if (labelhash == null) { labelhash = new HashTable(199); } last = (Integer) labelhash.find(s); If (last == null) { next = 1; labelhash.insert(s, new Integer(1)); label = s + 1; } else { next = last.intValue() + 1; labelhash.delete(s); labelhash.insert(s, new Integer(next)); label = s + next; } } public static Label AbsLabel(String s) { Label abslab = new Label(); abslab.label = s; return abslab; } public String toString() { return label; }

Interfase para Construir rboles de Ensamblador Abstracto


import java.util.Vector; public interface AATBuildTree { public AATSatement functionDefinition(AATStatemen body, int framesize, Label start, Label end); public AATSatement ifStatement(AATExpression test, AATStatement ifbody, AATStatement elsebody); public AATEexpression allocate(AATExpression size); public AATStatement whileStatement(AATExpression test, AATStatement increment, AATStatemen body); public AATStatement emptyStatement( ) ; public AATStatement callStatement(Vector actuals, Label name); public AATStatement assignmentStatement(AATExpression lhs, AATExpressionrhs); public AATStatement sequentialStatement(AATStatement first, AATStatement second); public AATExpression baseVariable(int offset); public AATExpression arrayVariable(AATExpresion base, AATExpression index, int elementSiza); public AATExpression classVariable(AATExpression(AATExpression base, int, offset); public AATExpression constantExpression(int, value); public AATExpression operatorExpression(AATExpression left, AATExpression rigth, int operator); public AATExpression callExpression(Vector actuals, Label name); public AATStatement returnStatement(AATExpression value, Label functioned); }

EJEMPLOS:
void foo(int a) { int b; int c; if (a > 2) b = 2; else c = a+ 1; }

Sea bt una instancia de una clase que implementa la interfase AATBuildTree, y sea 4 el tamao de la palabra de la mquina. El rbol de ensamblador abstracto para la expresin (a > 2) en la funcin foo podra crearse con: AATExpression e1; e1 = bt.operatorExpression(bt.baseVariable(4), bt.constantExpression/(2), AATOperator.GREATER_THAN);

Igual, el ensamblador abstracto para el estatuto b = 2; y c = a + 1, puede crearse con:

AATEstatement s1, s2; s1 = bt.assignmentStatemen(bt.baseVariable(0), bt.constantExpression(2)); s2 = bt.assignment(bt.BaseVariable(-4), bt.operatorExpression(bt.baseVariable(4), bt.constantExpression(1), AATOperator.PLUS));

Finalmente, dadas las definiciones anteriores, el ensamblador abstracto para el estatuto if (a > 2) b = 2 else c = a + 1; puede crearse con:

AATstatement s3; s3 = bt.ifStatement(e1,s1,s2);

Algunas de las funciones para construr rboles de ensamblador abstracto pueden explicarse mejor:
public AATSatement functionDefinition(AATStatemen body, int framesize, Label start, Label end); El rbol de ensamblador para definicin de funciones comienza con la etiqueta para la funcin, seguido de una serie de instrucciones para salvar los valores viejos de la direccin de retorno, los registros FP y SP, seguido del cdigo para poner nuevos valores del FP y SP, seguido del cuerpo de la funcin, seguido por una etiqueta para el final de la funcin, seguido del cdigo para restaurar los valores viejos de la direccin de retorno, registros FP y SP, seguido de un estatuto de ensamblador abstracto return, para regresar el flujo de control a la funcin llamadora. public AATStatement returnStatement(AATExpression value, Label functioned); El rbol de ensamblador para un estatuto return copia el valor del estatuto return dentro de el registro resultado y despus salta a la etiqueta del final de la funcin. public AATEexpression allocate(AATExpression size); Esta funcin es llamada por medio de una expresin new, para asignar espacio en el heap. Es implementada por una llamada a la funcin intrnseca (build-in) que asigna espacio- igual que una funcin input/output son implementadas por medio de llamadas a funciones intrnsecas. La funcin allocate toma un solo parmetro el tamao (en bytes) del espacio a asignar y retorna un apuntador al bloque nuevo asignado.

Modificaciones al Analizador Semntico




En lugar de crear un mdulo separado que recorra el AST de nuevo para construir un AAT, se modifica el analizador semntico para que mientras checa la semntica vaya construyendo el AAT. Cada uno de los mtodos para visitar expresiones y estatutos en el analizador semntico retornar dos piezas de informacin en lugar de una el tipo del subrbol, como antes, mas un AAT que representa la expresin o el estatuto.

Ejemplo:
Para analizar un AST: Integer_Literal(3) El analizador semntico actualmente retorna IntegerType. Para analizar el AST: + Integer_Literal(3) Integer_Literal(4)

El analizador semntico llama al mtodo accept para cada subrbol constant(3) y constant(4), se asegura que ambos sean enteros, y retorne un entero. Despus de modificar el analizador semntico para producir AATs, cunado se analiza el AST: Integer_Literal(3) El analizador retornar dos valores: Integer_Type y bt.constantExpression(3).

Cuando se analiza el AST: + Integer_Literal(3) Integer_Literal(4)

Primero se llama el mtodo accept de los subrboles, se asegura que los tipos sean enteros y entonces construye una expresin operador basado en los valores de los subrboles (usando una llamada al mtodo operatorExpression en la interfase AATBuildTree).

VIII Generacin de Cdigo




 

La funcin de un generador de cdigo es bsicamente la traduccin de la salida del anlisis sintctico y semantico (el cdigo intermedio) a una secuencia equivalente de instrucciones que pueden ejecutarse en la maquina destino. El cdigo debe ser correcto y optimo. El generador de cdigo toma las desiciones bsicas:
 

Asignar Registros.- Registros generales, Prop. Especficos, pares, impares, registros ndices, etc. Seleccin de Instrucciones.- La mejor secuencia de instrucciones (la optima). Por ejemplo elegir INC en lugar de MOV ADD.

En el caso de nuestro compilador, una vez que se ha creado un rbol de ensamblador, el prximo paso es crear cdigo ensamblador para una mquina especfica. Generaremos cdigo ensamblador por medio de usar tiles para rellenar huecos, con el rbol de ensamblador abstracto. Vamos a crear un conjunto de tiles o rellenos cada uno de los cuales pueden cubrir una porcin pequea de un rbol de ensamblador abstracto. Para generar el ensamblador real para un rbol de ensamblador especfico, primero cubriremos el rbol con tiles, asegurndose que todas las partes del rbol estn cubiertas y que no existan tiles traslapados. Despus, se producir el ensamblador asociado con cada tile.

Cdigo Ensamblador Objeto Usaremos un subconjunto del ensamblador para MIPS


Instruction lw rt, <offset> (base) sw rt, <offset> (base) add rd, rs, rt sub rd, rs, rt addi rt, rs, <val> mult rs, rt div rs, rt mflo rd j <target> jal <target> jr rs slt rd, rs, rt beq rs, rt, <target> bne rs, rt, <target> bltz rs, <target> Bgez rs, <target> Description Add the constant value <offset> to the register base to get an address. Load the contents of this address into the register rt. Rt = M[base + <offset>] Add the constant value <offset> to the register base to get an address. Store the contents of rt into this address. M[base + <offset>] = rt Add contents of register rs and rt, put result in register rd. Subtract contents of register rt from rs, put result in register rd. Add the constant value <val> to register rs, put result in register rt. Multiply contents of register rs by register rt, put the low order bits in register LO and the high bits in register HI Dive contents of register rs by register rt, put the quotient in register LO and the remainder in register HI Move contents of the special register LOW into the register rd. Jump to the assembly label <target>. jump and link. Put the address of the next instruction in the return register, and then jump to the address <target>. Used for function and procedure calls. Jump to the address stored in register rs. Used in conjunction with jal to return from function and procedure calls if rs < rt, rd = 1, else rd = 0 if rs == rt, jump to the label <target> if rs rt, jump to the label <target> if rs < 0, jump to the label <target> if rs 0, jump to the label <target>

Register that we will use for code generation


Mnemonic Name $FP $SP $ESP SPIM Name $fp $sp $esp Description

Frame pointer points to the top of the current activation record Stack Pointer Used for the activation record stack Expression Stack Pointer The next expression stack holds temporary values for expression avaluations Result Register Holds the return value for functions Result Register Holds the return address for the current function Zero register This register always has the value 0 Accumulator Register Used for expressioncalculation General Purpose Register General Purpose Register General Purpose Register

$result $return $zero $ACC $t1 $t2 $t3

$v0 $ra $zero $t0 $t1 $t2 $t3

Tiling Simple
      

Usaremos una pila para guardar valores temporales en expresiones. Esta pila estar adems de la pila normal de RA. Primero describiremos una forma de producir cdigo ineficiente. Despus veremos como producir cdigo mas eficiente. Tiling simple est basado en un recorrido post-order del rbol de ensamblador abstracto. Esto es, despus de cubrir el rbol con tiles, emitiremos cdigo para cada tile en una forma post-order. Primero emitiremos recursivamente tiles para cada uno de los subrboles del rbol de ensamblador, de izquierda a derecha. Despus, emitiremos el cdigo para el tile de la raz del rbol.

Ejemplos:
Tiling Simple para rboles de Expresiones.
El cdigo asociado con el tile colocar el valor de la expresin en el tope de la pila de expresiones. Expresiones de Constantes Considere el siguiente rbol: Constant(5) El cdigo asociado con este tile necesita poner un 5 en el tope de la pila de expresiones.
addi $t1, $zero, 5 sw $t1, 0($ESP) addi $ESP, $ESP, -4 % Load the constant value 5 into the register $t1 % Store $t1 on the top of the expression stack % Update the expression stack pointer

En general, el tile para cualquier valor constante x es: addi $t1, $zero, x sw $t1, 0($ESP) addi $ESP, $ESP, -4 % Load the constant value into x the register $t1 % Store $t1 on the top of the expression stack % Update the expression stack pointer

Operaciones de Aritmtica Binaria +


Constant(3) Constant(4)

En lugar de tratar de cubrir todo el rbol con un tile, usaremos tres. La constante 4 y 5 pueden cubrirse cada una con un tile de constante, y usaremos un tile + para cubrir la suma.

+
Constant(3) Constant(4)

Cmo debe ser el cdigo para +? Recordemos que emitimos cdigo en post-order (subrbol izquierdo, subrbol derecho y luego raz). Podemos asumir que los valores de los operandos de la izquierda y la derecha de el + ya estn en la pila cuando el cdigo de el tile + es emitido. Ntonces, todo lo que necesitamos es hacer es sacar (pop off) esos valores , hacer la suma y meter (push) el resultado de regreso a la pila. El cdigo para el tile + es:

lw $t1, 8(ESP) lw $t2, 4(ESP) add $t1, $t1, $t2 sw $t1, 8(ESP) add $ESP, $ESP, 4

% Load the first operand into temporary $t1 % Load the second operand into temporary $t2 % Do the addition. Storing result in $t1 % Store the result on the expression stack % Update the expression stack pointer

y el cdigo para toda la expresin es:


addi $t1, $zero, 3 sw $t1, 0($ESP) addi $ESP, $ESP, -4 addi $t1, $zero, 4 sw lw lw sw $t1, 0($ESP) $t1, 8($ESP) $t2, 4($ESP) $t1, 8(ESP) addi $ESP, $ESP, -4 % Load the constant value 3 into the register $t1 % Store $t1 on the top of the expression stack % Update the expression stack pointer % Load the constant value into 4 the register $t1 % Store $t1 on the top of the expression stack %Update the expression stack pointer % Load the first operand into temporary $t1 % Load the second operand into temporary $t2 % Do the addtion, storing result in $t1 % Update the expression stack pointer

add $t1, $t1, $t2

Registros
Considere el rbol de expresin Register(FP) Esto ocupa un tile. sw $FP, 0($ESP) addi $ESP, ESP, -4 Combinando otros tiles discutidos antes, el rbol

Register(FP) Puede ser los tiles: Constant(8)

Register(FP) Constant(8)

Produciendo el rbol
sw $FP, 0($ESP) % Store frame pointer on the top of the expression stack % Update the expression stack pointer % Load the constant value 8 into the register $t1 % Store $t1 on the top of the expression stack % Update the expression stack pointer % Load the first operand into temporary $t1 % Load the second operand into temporary $t2 % Do the subtraction, storing result in $t1 % Store the result on the expression stack % Update the expression stack pointer

addi $ESP, $ESP, -4 addi $t1, $zero, 8 sw lw lw sw $t1, 0($esp) $t1, 8($ESP) $t2, 4($ESP) $t1, 8($ESP) addi $ESP, $ESP, -4

sub $t1, $t1, $t2 add $ESP, $ESP, 4

Operaciones relacionales
ARBOL ABSTRACTO:

>
Constant(3) Constant(4)

TILES:

>
Constant(3) Constant(4)

El cdigo para > es: lw lw slt sw addi $t1, 8($ESP) $t2, 4($ESP) $t1, $t2, $t1 $t1, 8($ESP) $ESP, $ESP, 4

Thus, one posibility for the code for the == tile is: lw lw slt slt add sub addi sw addi $t1, 8($ESP) $t2, 4($ESP) $t3, $t2, $t1 $t2, $t1, $t2 $t1, $t1, $t2 $t1, $zero, $t1 $t1, $t1, 1 $t1, 8($ESP) $ESP, $ESP, 4

% $t3 = (x < y) % $t2 = (y < x) % $t1 = (x < y) I I (y <x) % %

Acceso a Memoria Un nodo en memoria es solo un dereferencia a memoria (indexamiento). Entonces, el cdigo de un nodo en memoria es: Lw $t1, 4 ($ESP) lw $t1, 0($t1) sw $t1, 4($ESP) % Pop the address to dereference off the top of the % expression stack % Dereference the pointer % Push the result back on the expression stack

Ejemplo: El rbol de expresin Para una variable simple es.

Memory

Register(FP) Constant(12) Los tiles son: Memory

Register(FP) Constant(12)

Que resulta en el siguiente ensamblador: sw $FP, 0($ESP) % Store frame pointer on the top of the expression stack % Update the expression stack pointer % Load the constant value 12 into the register $t1 % Store $t1 on the top of the expression stack % Update the expression stack pointer % Load the first operand into temporary $t1 % Load the second operand into temporary $t2 % Do the subtraction, storing result in $t1 % Store the result on the expression stack % Update the expression stack pointer % Pop the address to dereference off the top of % the expression stack lw sw $t1, $t1, $0($t1) 4($ESP) % Dereference the pointer % Push the result back on the expression stack

addi $ESP, $ESP, -4 addi $t1, sw addi lw lw sub sw add lw $t1, $zero, 12 0($ESP)

$ESP, $ESP, -4 $t1, $t2, $t1, $t1, 4($ESP) 8($ESP) $t1, $t2

8($ESP)

$ESP, $ESP, 4 $t1, $4($ESP)

El resultado de este cdigo es: El valor que estaba en memoria (FP 12) es empujado (pushed onto) en el tope de la pila de expresiones.

Llamadas a Funciones
Para implementar una llamada a funcin, necesitamos primero copiar todos los parmetros de la funcin en el tope de la pila de llamadas (opuesta a la pila de expresiones), y despus saltar al inicio de la funcin. Cuando la funcin regresa, necesitamos sacar los argumentos del tope de la pila de llamadas, y meter el valor de retorno de la funcin al tope de la pila de expresiones. Ejemplo: Considere el siguiente rbol de expresin, el cual resulta de la llamada a funcin foo(9,3). Call(Foo)

Constant(9) Constant(3)

Call(Foo)

Constant(9) Constant(3)

El lenguaje ensamblador asociado con el tile Call es: lw sw lw sw addi addi jal addi sw Addi $t1 $t1 $t1 $t1 $SP, $ESP, foo $SP, $SP, 8 $result, 0($ESP) $ESP, 4($ESP) 0($Sp) 8($ESP) -4($SP) $SP, -8 $ESP, 8 % Pop the first argument off the expression stack % Store first argument on the call stack % Pop second argument off the expression stack % Store second argument on the call stack % Update call stack pointer % Update expression stack pointer % Make the finction call % Update call stack pointer % Store result of function on expression stack

$ESP, -4 % Update expression stack pointer

As, el ensamblador para todo el rbol de expresin es: addi $t1 sw addi addi $t1 sw addi lw stack sw lw stack sw addi addi jal addi sw addi $t1, $t1, $ESP, $t1, $t1, $ESP, $t1 $t1 $t1 $t1 $SP, $ESP, foo $SP, $result, $ESP, $zero, 9 % Load the constant value 9 into the register

0($ESP) % Store $t1 on the top of the expression stack $ESP, -4 % Update the expression stack pointer $zero, 3 % Load the constant value into 3 the register 0($esp) % Store $t1 on the top of the expression stack $ESP, -4 % Update the expression stack pointer 4($ESP) % Pop the first argument off the expression 0($SP) 8($ESP) -4($ESP) $SP, -8 $ESP, 8 % Store first argument on the call stack % Pop second argument off the expression

% Store second argument on the call stack % Update call stack pointer % Update expression stac pointer % Make the function call $SP, 8 % Update call stack pointer 0($ESP) % Store result of function on expression stack $ESP, -4 % Update expression stack pointer

Tiles de rboles de Estatutos


Mientras que el cdigo de ensamlador para rboles de expresiones mete (push) el valor de la expresin en el tope de la pila de expresiones, el cdigo ensamblador para rboles de estatutos implementa el estatuto descrito por el rbol. rboles de Etiquetas El subrbol Label(label1) puede ser cubierto con solo un tile. El cdigo para ese tile es: Label1: rboles para Move El lado izquierdo de un MOVE necesita que sea un registro o una localidad de memoria. Ejemplo: Move Register(r1) Constant(8)

Ser dividido en los tiles: Move Register(r1) Constant(8)

Considere el tile del MOVE registro: Move Register(r1)

Cuando el cdigo para este tile es ejecutado, el valor que es cargado al registro est ya en el tope de la pila de expresiones. Entonces, todo lo que necesitamos hacer es sacar el valor de la pila de expresiones y meterlo al registro.

El tile del MOVE tiene el siguiente cdigo asociado: lw addi $r1, $ESP, 4($ESP) $ESP, 4 % load the rhs of the move into a register % update expression stack pointer

Todo el cdigo para el rbol es: addi sw addi lw addi $zero, 8 0($ESP) 4($ESP) $ESP, 4 % Store the constant 8 in a register % Push the value on the expression stack % load the rhs of the move into a register % update expression stack pointer

$t1, $t1, $ESP, $r1, $ESP,

$ESP, -4 % Update the expression stack pointer

Veamos ahora un MOVE a memoria. Consideremos el siguiente rbol:

Move Memory Register(FP) El rbol puede tener los tiles: Move Mem Register(FP) El tile MOVE a memoria es: Move Constatnt(4) Constant(4)

Necesita sacar dos valores del tope de la pila el destino del MOVE y el valor a almacenarse.
lw lw sw addi $t1, $t2, $t2, $ESP, 8($ESP) 4($ESP) 0($t1) $ESP, 8

Todo el rbol se puede implementar con el sig. Cdigo: sw addi addi sw addi lw lw sw Addi $FP $ESP, $t1, $t1, $ESP, $t1, $t2, $t2, $ESP, 0($ESP) $ESP, -4 $ZERO, 4 0($ESP) $ESP, -4 8($ESP) 4($ESP) 0($t1) $ESP, 8

rboles para Estatutos Jump Estatutos rbol como Jump(label) se pueden realizar con un solo tile: j label rboles para Jump condicional Considere el siguiente rbol de estatuto: CondJump(jumplab) < Constant(3) Podemos crear los siguientes tiles: Constant(4) Tile 4 CondJump(jumplab) Tile 3 Tile 1 Constant(3) < Tile 2 Constant(4)

El cdigo que emitimos para el subrbol de la expresin jump condicional pone el valor de la expresin en el tope de la pila de expresiones. Asi, necesitamos sacar la expresin del tope de la pila de expresiones, y saltar a la etiqueta jumplab si la expresin es verdadera. Todo el estatuto se implementa con el cdigo: addi sw, addi addi sw addi lw lw slt sw addi bgtz

$t1, $t1, $ESP, $t1, $t1, $ESP, $t1, $t2, $t1, $t1, $ESP, $t1,

$zero, 3 0($ESP) $ESP, -4 $zero, 4 0($ESP) $ESP -4 8($ESP) 4($ESP) $t1, $t2 8($ESP) $ESP, 4 jumplab

% salta a jumplab si $t1 es mayor a cero

rboles de Secuencias Qu hacemos despus que creamos el cdigo del rbol de la parte izquierda y el rbol de la parte derecha? nada! No existe cdigo asociado pues los dos rboles son ya traducidos. Estatutos Call Son muy similares a las llamadas a funciones. La nica diferencia es que en el call no existe resultado en el return. Esto significa que despus que termina el procedimiento, no necesitamos copiar el resultado a la pila de expresiones.

Unidad VII Optimizacin

7.1 Tipos de Optimizacin


   

 

Idealmente, los compiladores deben producir cdigo objeto tan bueno como el producido a mano por un programador experto. En la realidad, este objetivo raramente es alcanzado. Sin embargo, el cdigo puede ser mejorado en tiempo y espacio usando algoritmos eficientes. Mejorar el cdigo por un compilador es llamado optimizacin del cdigo, aunque el trmino optimizar no es el adecuado (mas bien mejorar). Los tipos de optimizacin de un compilador pueden ser: Locales, Globales y de Bucles. Tambin los tipos de optimizacin se pueden dividir en independientes y dependientes de la mquina.

Introduccin Objetivo: Mejorar cd. objeto final, preservando significado del programa Factores a optimizar : velocidad de ejecucin tamao del programa necesidades de memoria Se sigue una aproximacion conservadora ! No se aplican todas las posibles optimizaciones, solo las seguras

Clasificacin de las optimizaciones 1. En funcin de la dependencia de la arquitectura Dependientes de la mquina: Aprovechan caractersticas especficas de la mquina objetivo asignacin de registros, uso de modos de direccionamiento uso instrucciones especiales (IDIOMS) relleno de pipelines, prediccin de saltos, aprovechamiento estrategias de mem. cach, etc.. Independientes de la mquina: Aplicables en cualquier tipo de mquina objetivo ejecucin en tiempo de compilacion eliminacin de redundancias cambios de rden de ejecucin, etc..