Skip to content

Latest commit

 

History

History
1369 lines (852 loc) · 29 KB

notas.org

File metadata and controls

1369 lines (852 loc) · 29 KB

Compiladores ============

Bajar: dcc.fceia.unr.edu.ar/lcc/T-521-Compiladores/Public/entrega1 && /test.tgz

[2013-08-21 Wed]

Pasemos a discribir la gramática de tiger. Usaremos gramáticas líbres de contexto.

  • Un conjunto finito de símbolos terminales T (Tokens).
  • Un conjunto finito de símbolos no terminales NT ; T ∩ NT = ∅
  • Un símbolo start ∈ NT.
  • Un conjunto finito de reglas de producción.

    α : secuencia de 0 o mas β_i, β_i \inc NT \union T

Ejemplo 1: Una gramatica que balancee paréntesis.

Gramática:

T = { ( , ) } NT = { α } start = α

Reglas de producción:

  • α : e //* vacío *//
  • α : ( α )

Funciona?

((())) →^1 (((α))) →^2 ((α)) →^2 (α) →^2 α(start)

Ejemplo 2: Una calculadora.

T = { +, *, NRO, (, ) } NT = { expr }

start = expr

Reglas de produción:

  • expr : NRO
    expr + expr
    expr * expr
    ( expr )

Acá aparecen problemas:

2 + 3 + 4 NRO + NRO + NRO expr + expr + expr ----------- ----

+— expr + expr -> expr

2 + 3 + 4 NRO + NRO + NRO expr + expr + expr ---- ------------

+— expr + expr -> expr

Tenemos dos formas de reducir.

       +             +
      / \           / \
     +   4    ó    2   +
    / \               / \
   2   3             3   4


Asociativo:
Izquierda           Derecha

Shift Reduce

Este conflicto se conoce como “problema shift-reduce” y no es grave. Se puede eliminar de vairas maneras.

a) Reescribir la grámatica agregando más NT, para forzar un shift ó un reduce. b) Agregar directivas para indicar cómo resolver las ambigüedad.

En particular usaremos: %left (asocia a izquierda) %right (asocia a derecha) %noasoc (no asociativo)

Ahora, nuestra glc es:

%left + %left *

El orden establece precedencia, de menor a mayor.

- expr : NRO
  | expr + expr
  | expr * expr
  | ( expr )

Cuándo usamos %right en C?

a = b = c == (a = (b = c))

— a == (-(-(-a)))

Operaciones no asociativas en C?

a < b < c –> a < b && b < c

No asociativo

De hecho bison tiene uan directiva

%expect n

Espere n conflictos shift-reduce, para tranquilizar al programador. Uno de estos conflictos se conoce como el dangling else, y aparece en los lenguajes derivados de ALGOL, que introdujo nocion de sentencia compuesta.

Usado en C y Pascal

sent: if ( cond ) sent sent: { sentencias } * sentencias compuestas *

Otra opcion es usar una construccion (Fortran, Ada, etc) donde una construccion se cierra por otra palabra clave:

Ej. (ada)

sent: if conf then sentencia end if

El dangling else:

en C

if (cond_1) if (cond_2) sent_1 else | sent_1 | -> a que if pertenece?

Por convención el else paretence al if mas próximo. En ADA no ocurre.

if cond_1 then if cond_2 then sent_1 else sent_2 end if end if

Hay problemas mas graves (de hecho fatales) llamados reduce-reduce. Estos aparecen cuando tenemos reglas de produccion:

α : sentencia α : sentencia

La glc para C, que aparece en un apendice del K&R tiene un conflicto reduce-reduce.

decl : id_tipo -> id_var id_var : id id_tipo : id

a) Agregar cosas para que el scanner lo diferencie. Haskell resuelve este problema diferenciando los tipos de las vars, utilizando mayusculas y minusculas respectivamente.

b) LLevar la cuenta de las definiciones concretas.

c) El algortimo del avestruz. Patear el problema y resolverlo más adelante.

LALR(n)-> Look ahead left recursive - nivel n

El formato de la especificación de la GLC es

%{ Código ML

%}

Directivas y definiciones de tokens.

%%

Reglas de prod y acciones semanticas.

%%

Más codigo ML

Gramáticas libres de contexto.

Gramatica de tiger

%token EOF %token ARRAY OF VAR FUNCTION %token LET IN END IF THEN ELSE WHILE DO FOR TO BREAK %token PTO DOSP DOSPIG COMA PCOMA IGUAL PI PD CI CD LI LD %token AMPER PIPE MENOR MENIG MATOR MAGIG DIST %token MAS MENOS POR DIV NIL

Appel por simplicidad reconoces los numeros negativos en el parser con una regla como

expr MENOS NEG

Pero hay un problema no podemos escribir el entero minimo va de -2^w a 2^w-1

Pero lo podemos arreglar así: En el scanner reconocemos el |entero mínimo| 2^32 {ABS MIN INT}

%token ABS MININT

Hasta ahora hemos tratado con tokens generados de manera única.

entradatoken

----------------+

“while”WHILE

Ahora debemos tratar con tokes no únicos, nro, Strings, ids, etc.

%token<int> NRO %token<string> LITERAL ID

Dejamos esto y veamos las reglas de producción:

%start prog

%% prog: expr EOF expr: NRO \| MENOS ABSMININT \| PI PD * unit * \| NIL \| LITERAL \| BREAK \| L\_value * valor izquierdo * \| L\_value DOSPIG expr \| PI expr PCOMA explist PD \| expr PIPE expr \| expr AMPR expr \| expr IGUAL expr \| expr DIST expr \| \| expr DIST expr \| expr MAS expr \| \| expr DIV expr \|

%left PIPE %left AMPER %nonasoc IGUAL MENOR … DIST * comparaciones * %left MAS MENOS %left POR DIV

Y ahora tenemos el problema del uso del menos. Para indicar que el opuesto tiene mas presedencia que la resta No podemos hacer esto:

%left MAS MENOS | … |— Mismo token %right MENOS |

Para esto generamos un token trucho (nunca sera generador por el scanner) lo usamos para la asociatividad y precedencia del opuesto.

%right UMENOS

y en las reglas de produccion

expr: MENOS expr %prec UMENOS

Seguimos \| PI expr PD \| id PI args PD * llamada a función * \| IF expr THEN expr \| IF expr THEN expr ELSE \| WHILE expr DO expr \| FOR id DOSPIG expr TO expr DO expr \| LET decs IN END * let unit * \| LET desc IN expr END \| LET desc IN expr PCOMA exprlist END \| L_value CI expr CD \| id LI rec_field LD

Seguimos con los NT Usados.

exprlist : expr PCOMA explist \| expr ;

rec_fields : id IGUAL expr COMA rec_fields \| id IGUAL expr \| * nada * ;

decs : dec decs

;

dec : TYPE id IGUAL ty \| vardec \| fundec

ty: id \| LI tyfld LD \| ARRAY of id

id: ID

tyflds : tyfields COMMA tyflds

;

tyfield: id DOSP id [fn:1]

vardec: VAR id DOSPIG expr \| VAR id DOSP id DOSPIG expr

fundec: FUNCTION id PI args PD IGUAL expr \| FUNCTION id PI args PD DOSP id \| IGUAL expr

args : expr COMA args \| expr \| expr ;

L_value : id \| L_value PTO id \| L_value CI expr CD

[2013-08-22 Tue]

Los constructores de AST

structure tigaerabs =

struct
   type symbol = string
   type pos = int
   datatype var = SimpleVar of symbol
                | FieldVar of var *symbol
                | SuscriptVar of var *exp
   and
            exp = VarExp of var *pos
                | UnitExp of pos
                | NilExp of pos
                | IntExp of int *pos
                | StringExp of string *pos
                | CallExp of {func: symcol, args:explist}*pos
                | OpExp of {left:exp, oper:oper, right:exp}*pos
                | RecordExp of { fields: symbol*exp list, ytp:symbol}*pos
                | SeqExpOf of explist*pos
                | AssignExpOf { var:var, exp:exp}*pos
                | IfExpOf { test:exp, then':exp, else':exp option }*pos
                | WhileExpOf { test:exp, body: exp}*pos

Viene un problema fuera de fase: el for. Un for DEFINE implícitamente su índice (Como ada). Así que podemos hacer:

\| ForExp of { var:symbol, escapa: bool ref, lo:exp, hi: exp, body: exp}

Debemos saber si el índice es por una función definida dentro del for.

for i:=1 to 10 do ( let function f():int=i in f() + 1 end; () ) \| LetExp of { desc: des list, body: exp } * pos \| BreakExp of pos \| ArrayExp of { type: symbol, size: exp, init: exp} * pos \| and dec = FunctionDec of ( name:symbol, parms: field list, result: symbol option, body: exp} * pos ) list \| VarDec of { name: symbol, escape: bool ref, typ: symbol option, init: exp} * pos and ty = NameTy of symbol \| RecordTy of field list \| ArrayTy of symbol

and oper = PlusOp | MinusOp | TimesOp | DivideOp \| EqOp | Neqop | LtOp | LeOp \| GtOp | GeOp withtype field = { name: symbol, escape: bool ref, typ: ty }

Acciones semanticas y atributos de terminal y no terminales.

En 1969 D.E.Knuth definió las gramáticas con atributos, es decir, GLC donde los terminales y no terminales podrían “arrastrar” valores de un cierto tipo (atributos) de uno de ellos en base a los atributos existentes.

expr = expr + expr { $$ = $1 + $3; } ← $$ $1 $3

Usemos esto. Declaramos los tipos de los atributos en las directivas.

%type<tigerabs.exp> prog expr %type<tigerabs.ty> ty

%type<string> id

id : ID { $1}

%type<tigerabs.var> L\_value

Y con esto rellenamos las acciones semanticas.

Necesitamos tambien la posicion para esto, declaramos en tigernlin.sm

var nlin = ref 1

En el codigo ml del parser

fun P() = !tigerlin.nlin

expr: NRO { IntExp($1, P()) } \| MENOS ABSMININT { IntExp( valOf Int.minInt, P()) } \| PI PD { UnitExp(P()) } \| NIL { NilExp(P()) } \| LITERAL { StringExp($1, P()) \| L\_value { VarExp( $1, PC ) } \| L\_value DOSPIG expr { AssignExp({ var=$1, exp=$3 }, P())}

Habrán notado que no tenamos and, or o not, en el AST. De hecho, Tiger no tiene booleanos. Tiger hace como C: Cualquier expresión entera que evalúe a cero es falase, distinto de cero true.

Así:

\| expr PIPE expr \| expr AMPER expr

a | b == if a then 1 else b a & b == if a then b else 0

\| expr PIPE expr { IfExp({test=$1, then’=IntVal(1, P()), else’=Some($3)} P()) } \| expr AMPER expr { IfExp({test=$1, then’=$3 else’=IntVal(1, P()) } P()) }

… \| expr MENOR expr { OpExp({left = $1, oper = LtOp, right= $3}, P()) } \| MENOS expr %prec UMENOS { OpExp({left = IntExp(0, P()), oper = MinusOp, right= $2}, P()) }

\| PI expr PD { $2 }

\| IF expr THEN expr { IfExp({test=$2, then’=$4, else’=NONE } P()) } \| IF expr THEN expr ELSE expr { IfExp({test=$2, then’=$4, else’=Some($6)} P()) }

Para evitar el dangling else usamos

* menor precedencia *

%nonassoc THEN %left ELSE %nonassoc DOSPIG

[2013-08-26 Mon]

Empecemos a unir los pedazos.

El main de TIGER, en tigermain tendremos

<include tigermain.ml>

Ya es claro que tener un compilador operativo implica unas cuantas operaciones (generción del parser etc) Para facilitar esto usaremos make.

<include makefile de tiger>

Variables escapadas

Tiger permite funciones anidadas. Las reglas de scope establecen que las variables definidas en las funciones y lets anidantes son visibles en las anidadas. En el caso de las funciones esto es un problema. Las variables se definen en:

  • Lets, que no crean marcos de activación
let var x := let var y := let var z := 0 in z end in y end in x end //TODO: revisar

x,y,z comaprten el marco de activación.

  • For, su indice tambien comparte el marco de activacion.
  • Definicion de función, los argumentos crean un nuevo marco.

Una variable es escapada si se crea un nuevo marco de activación, pero es accedida desde otro.

let var x:=10
  function f(y:int)=
     (for i:=1 to 10 do
       let function g():int x + y + i
       in end)
in
  x end

/* x, y, i son escapadas. */

Supongamos una función recursiva con una anidada

let function f(i:int) =
  let function g():int = i
  in
    if i > 1 then f(i-1)
    else g()
  end
in f(1000);0 end

Necesitaremos (más adelante) un mecanismo para que g acceda a la última i de f. Esto se facilita si las escapadas se alojan en, memoria dentro del marco de activación de su definición.

Pero para esto, debemos delimtar las escapadas, recorriendo el AST. Ir llevando cuenta de qué variables fueron definidas y en que marcos de activación. Para esto viene comodo una tabla hash, en general las tablas se pueden implementar de dos maneras.

Ej.

  let var x:=1
       function f(i:int)
          let var z := 2
       in end
  in x end

/*
{x}     dentro del let mas grande
{x,i,z} dentro de la definicion de funcion.
*/

Las tablas crecen al entrar a un scope y se achican al salir de él.

  • Una forma de hacer esto es la manera imperativa: Se lleva la cuenta de qué variables se han definido el último scope, y al abandonarlo se sacan esas variables. Hay que llevarla cuenta de qué variables fueron ocultadas y despues restaurarlo.
  • manera funcional: Cada vez que se ingrasa a un scope se crea una nueva tabla a parir de las vigente, y se descarta al abandonar ese scope.

Nota: los compiladores se pueden clasificar en:

  • Narrow: apenas tiene una porción de AST suficiente, aplica todas las etapas hasta la emisión de código. Usa mejor la memoria, la sincronización es una pesadilla
  • Borad: procesa todo el AST en cada operación. Usa mal la memoria, pero es mas sencillo.

Tiger será broad, excepto en la etapa semantica, y de generción de código intermedio que será narrow.

Es conveniente definir un modulo para diccionario que oculte la implementación. Para propósitos de debug, tendrá bastante ventajas.

incluir<tigertab.sig>

Una posible implementacion es usar el modulo Hash (Standard) o, cin mosml, Polyhash (no standard).

[2013-08-28 Wed]

Tablas y cálculo de escapes.

Un poco de la implementación de las tablas hash.

insert <tigertb.sml>

structure tigertab :> tigertab =
struct
  open Polyhash
  type (''a, 'b) Tabla = (''a, 'b) hash_table

  fun name x = x
insert <tigerescp.sml>

El cálculo de escapes se hace luego del parsing.

Pretty-printing: está para ver el AST.

Basado en unos combiadores hechos por J.Hughes,

insert <tigerpp.sig>

insert <tigerpp.sml>

[2013-08-29 Thu]

Terminado el cálculo de escapes viene la etapa de análisis semantico que incluye verificar el tipado del AST y que genera código intermedio. Es lo suficientemente dura como para perder la calma. El modulo debe tomar un AST con los escapes y devoler un nuevo record

{exp, ty}

  • exp: código intermedio
  • ty: tipo del programa

Para el momento, exp será un tipo trucho para no mezclar las dos cosas

datatype exp = SCAF (* de scafolding *)

Para ty definiremos los tipos que manejamos.

En tigertips.sml definimos

*include tigertips*

Es más,

let
  type A = array of int
  var a := A[10] of 0
  type b = array of int
  var b := B[10] of 1
end

Si a y b TIENEN distinto tipo, para poder discrinar estos dos tipos se agrega a la signatura unique

Cuantos espacios de nombres tiene Tiger? Tres Tipos de espacios:

  1. Espacios de nombre de tipos.
  2. Espacios de nombre de funciones y variables.
  3. Los nombres de miembros de records. (uno por cada record)
let type i = {i:int}
    var i : i:= {i=1}
in
    i.i
end

Debido a esto usaremos dos diccionarios: 1 (tenv) y para 2 (venv)

datatype EnvEntry =
   VIntro (* int RO *) of {access: tigertrans.access, level: int}
 | Var of {access: tigertrans.access, level: int}
 | Fuct {ty: Tipo:, level: tigertemp.label, formals: Tipo list, extern:bool}

El módulo tigertrans es el que produce el código intermedio. Acá lo definimos.

<include tigertrans.sml>

Por supuesto, en tigertemp definimos

<include tigertemp.sig>

La implementación

<include tigertemp.sml>

Ahora sí modulo tigerseman

signature tiegersman =
sig
    type venv = (string, EnvEntry) tigertab.Tabla
    type tenv = (string Tipo) tigertab.Tabla

    type expty (* codigo intermedio)

    val tab_vars: venv
    val tab_tenv: tenv
    val transExp: venv * tenv * tigerabs.exp -> expty
    val transDec: venv * tenv * tigerabs.dec -> (tenv*tenv)
    val transTy: tenv * tigerabs.ty -> Tipo
    val transProg: tigerabs.exp -> tigertrans.frag list
end

[2013-09-02 Mon]

Volvamos a los tipos internos de Tiger.

tigerty.sml?

Asuntos a tener en cuenta.

Inicialmente tendremos

(“int”, TInteger, RW) (“string”, TString)

Durante todo el análisis semántico necesitaremos comprar los tipos. Esto no es elemental, y lo haremos con una función checkTipos, que tendrá esta pinta.

fun checkTipos t1 t2 =
  case (t1, t2) of
  (TInteger _, TInteger _) => true
  // Tstring, Tunit => true
  | TRecord(_, u1), TRecord(_, u2) => u1 = u2
  | TRecord _, TNil => true
  | TNil, TRecord _ => true
  | TNil, TNil => false (* nil = nil *)
  | TArray(_, u1), TArray(_, u2) => u1 = u2
  | etc..

Lo mejor es que estas declaraciones den origen a estos tipos (asumimos que tenemos int/string).

("I", TTipo("int", SOME(TInteger RW)))
("S", TTipo("string", SOME(String)))

Dos tipos iguales

type A = B
type B = A /* Ciclo */

Por qué razón son ilegales? Los dos dependen mutuamente, y pueden dar lugar a estos tipos internos:

("A", TTipo("B", NONE))
("B", TTipo("A", NONE))

Que pasa acá

type A = int
var x := 1
type A = B
type B = A

a) Podemos tomar que sea el primer A. (Esto es legal) b) Podemos tomar el último A (Estos es ilegal)

Elegimos el segundo por el ppio de mínimo asombro.

Lo mismo podemos hacer con arreglos

type A = array of B
type B = array of A

Con los records no tenemos este problema gracias al nil. Por eso tiger pide que se detecten clicos en un batch de types, y se marquen como errores. Si esto no se hace, no ocurre nada terriblemente grave (último ejercicio del libro de appel) La razon es que no podemos construir ningun valor de ese tipo (conseguimos un tipo sin valores)

De hecho, en ML es correcto:

datatype Void = Void of Void

Como detectar ciclos en forma útil (que podemos reemplazar todos los NONE en TTipo, por SOME)?

Depende de qué dependencias tengamos entre los tipos del batch. Notemos estas dependencias de esta manera

(pred, succ)
ConstruccionGenera
type A = B(B,A)
type A = Array of B(B,A)

Una vez conseguidos los pares pred/succ de batch, los ordenamos mediante un sort topológico.

tigerseman.ml (<tab_vars>)

Otro problema fuera de fase:

Si emitimos assembler y ensablador debemos linkear nuestro código con el runtime, la libc y el startup (arranca crt*.o). Este último se ocupa de disponer el escenario para la ejecución (argumentos, enviroment, señales, etc) y llama a la función main. Debemos ver como enganchar nuestro código Tiger con el startup. La manera que elegimos es:

  • Un main en el runtime y desde allí llamar a una función tigermain (el ’’ está para que no haya colisión con otros símbolos Tiger)
  • Embeber nuestro código tiger en una función trucha llamada _tigermain.
tigerseman.ml (<transProg>)

[2013-09-04 Wed] (* Falta *)

[2013-09-05 Thu]

Seguimos con el frame estático.

<tigerframe.sig>

Dos implementaciones de sort topológico.

Problema: Un conjunto A, a; ∈ A. Un conjunto de restricciones de tipo pred/succ (a_i, a_j) a_i; precede a a_j

Se busca una ssecuencia de olos a_i quee no viole estas restricciones. Normalmente esta secuencia, si existe no es única. Si hay ciclos es imposible. Una implementación (scada de Haskel)

---

[2013-09-09 Mon]

Vemos cómo tipar funciones

Procedemos por partes:

  1. Introducimos el tipo de función, en EnvV. esto es necesario si queremos que la función puede ser recursiva.

Antes del examen de su body.

fun decFunc( {name, params, body, result}, nl ) (tenv, venv) =
 let
    val formals = map (fn x => transTy(tenv v1#2 x)) params
    val tipfun => case
val name = case name of
     | "_tigermain" => "tigermain"
     | name => "." ^ name ^ makestring(nl) ^ "_" ^ newlabel()

Tenemos est problema: Como acceder desde un marco de activacion a otro marco de activacion.

  1. solución lambda lifting (Functional programming, de harrison)
 \x.\y.\z = ( x + y + z )
(completar el ejemplo)

La ventaja de esta solucion, e que funciona y es conceptualmente sencilla. Desventajas: puede consumir mucho stack

  1. solución: tabla display ( Dijkstra )

Toda función construye un arreglo con todos los frames pointers de los ultimos marcos de activación de las funciones anidantes.

  1. Volvamos al esquema de -lifting. El sigt programa Oberon.
< Ejemplo de lambda lifting en C >

  1. Static link

Una mejora para el -lifting es juntar las variables escapadas en estructura y pasar sólo el puntero a esas estructuras.

La otra cosa que se puede hacer es pasar sólo un puntero, y rescatar los recorriendo los m. de a. como difernecias una lista enlazada.

Ventajas: creación muy rapida y constante. Desventaja: Acceso lineal.

La expriencia indica que SL es la mejor alternativa. Ahora hay que buscar cómo gestionar el SL en llamados a funciones.

Llamamos P_llamante y P_llamada a las profundidades de las anidantes de las funciones llamadas y llamantes.

Resp

1^er caso

P_llamante = P_llamda

f()
{

}

g()
{

}

2^do caso

P_llamante < P_llamada

Por scope, debe ser

P_llamada = P_llamante + 1

3^er caso

P_llamante > P_llamada

No hay restricciones. #

R \letfarrow SL_llamante R ← *R

P_llamante - P_llamada } veces

R ← *R

SL_llamante ← R

Lenguaje intermedio (IL o IR representacion)

Este leguaje se defini para acercarse al assembler. Su diseño varía según qué optimizaciones se quieran hacer y existen lenguajes intermedios de alto, medio y bajo nivel. El lenguaje intermedio para tiger se llama Tree.

<TigerTree.sml>

[2013-09-11 Wed]

Empesemos a esbozar la traducción de código intermedio. Es una etpa angosta: se tipa el programa al mismo tiempo que generamos código.

     | --- exp -->  |
sema |              |  translate
     | <-- tree --  |

Se puede ver como una especie de semántica denotacional. Presenta algunos problemas en el sentido que nos faltará contexto para saber cómo deberá usarse un código Ej:

a > b

Si se usa en :

if (a > b) then ... deberá manejar un salto

ó

c: = a > b ... deberá devolver 0 ó 1

Otro problemas es empaquetar y desepmaquetar el código:

 tigertrans exp Ex, Nx, Cx

Number, Condition, Expr.

fun seq()

fun unEx()


Ahora, desempaquetaremos una sentencia

fun unNx

El tercer desempaquetador

fun unCx

Veamos cómo se pueden integrar seman y translate.

En seman:

  trexp(BreakExp, nl) = { exp = SCAF, ty = TUnit }

Cambiamos esto a

  trexp(BreakExp, nl) = { exp = breakExp(), ty = TUnit }

fun breakExp() =
  Nx(JUMP(hd (!salida), [hd (!salida)]))

Salida es una pila de labels que se marcan dónde terminan la iteración. Son generadas y sacadas por funciones en translate.

def preForWhile() =
   salida := newLabel() :: (!salida)

def postForWhile() =
   salida := tt(!salida)

[2013-09-12 Thu] (* Falta *)

[2013-09-16 Mon]

Generacion de records

Corresponde a las expresiones del tipo:

R{ m_1 = exp_1 ... m_n = exp_n}

Cosas a tener en cuenta:

  • A diferencia de las estrucutras de C, tiger no pide que la posición de los miembros siga la secuencia de declaración.

Vamos a tomar la filosofía ML, que reordena (lexico-gráficamente los nombres). Así:

    {c = 10, b = 9, a = 8}

| 8 | 9 | 10 |
| a | b |  c |

Ademas, la ordenacion permite ver si hay nombres repetidos. Tambien permite que no se deba seguir la secuencia en la declaracion:

let type R = { next:R, i:int, j:string }

var r:R = R{ j = "hola", next = nil, i = 15 }

in 0 end

Por esto, cuando se analiza una declaracion de un record, se ordenan los nombres y se le agrega un entero que indica que orden tiene la secuencia (empezando por 0)

Al igual que los areglos, los records son dinámicos y la creacion la hacemos en el runtime, usando stdarg.h por razones obvias.

En el rutime:

#include <stdlib.h>
#include <stdarg.h>

long *makeRecord(long cts, ...)
{
  long *p, *q;
  va_list lst;
  va_start(lst, ctos);
  p = q = malloc(ctos * sizeof(long))

  while(ctos--)
    *q++ = va_arg(lst, long);

  va_end(lst)
  return p;

}

Veamos el codigo intermedio para la creacion, debe evaluar las expreciones para los miembros y luego llamar a makeRecord.

insert <fun recordExp> ver foto (2013-09-16 15:31 + 2013-06-16 15:46)

Código para invocar funciones

El principal problema es ocuparse del STATIC LINK (SL) que va como primer argumento.

fun callExp() { Mucho codigo }

Pasemos una decisión importante:

Qué debe hacer el compilador con expresiones como estas?

2 + 3

Puede generar código para hacer esta suma en tiempo de ejcucion ó puede calcularla en tiempo de compilacición. Supongamos que elegimos esta última. Debemos tener cuidad con cosas como:

2 + x + 3

Tenemos que reconvertir, para evaluar en :

1 + 2 + x == 3 + x

Por supuesto, estos reordenamientos valen si la operación es commutativa. Podemos volver conmutativas la diferencia y el cociente, usando:

  a - b = a + (- b)

y

  a/b = a * b^-1

Esto y las leyes de distribución

a ( b + c ) == a b + a c

Nos puede permitir ir desplazando todas las conastantes y evaluarlar en compilacion.

Pero queda en cuestión de qué hacer si hay overflow.

De hecho, hay que cuidar al reordenar :

(INT_MAX - INT_MAX) + 1 /=
(INT MAX - (INT_MAX + 1))

Tambien está el problema de que Tiger hace caso omiso de overflows (como C), pero ML sí los detecta. pero ML sí los detecta. Una posible solucion es, si hay overflow, el compilador no evalua, sino que genera código para el cálculo en ejecución.

[2013-09-18 Wed]

Declaraciones de Variables

Ver fotos de las 2013-09-18 - 16:06

[2013-09-23 Mon] (* Falta *)

[2013-09-26 Thu]

Canonización

El codigo intermedio, escrito en TREE, sigue siendo un árbol, y tenemos que tranformarlo en una lista. Haremos esto usando un sistema de reescritura de términos (Term-Rewriting System). Nuestro TRS termina

Def: Un árbol que está canonizado si:

  • No Tiene SEQ ni ESEQ.
  • El padre de todo CALL, ó EXP ó MOVE(TEMP T, ..)

Reglas

  • Página 176 de Libro

Una vez canonizado el árbol (esto es convertido en una lista), se divide en BLOQUES BASICOS.

Un BB es una secuencia de instrucciones que cumple estas reglas:

  • Tiene un solo LABEL, y es la primera instrucción
  • Tiene un solo CJUMP ó JUMP al final

Sabemos que todas las instrucciones de un bloque basico son ejecutadas. Ademas un BB se puede cambiar de lugar sin prlboemas. Ayuda a identificar código muerto.

tigercanon.sig :

 basicBlocks
 traceSchedule

[2013-10-03 Thu]

let
  function f(iF:int, j:string):int =
     function g(iG:int):int =
        function h(iH:int):int =
           ( if (ih = 2) then f(10,"hola") else 0;
           iF + iG + iH + size(j)
        in h(iG + 1 ) end
        var x := z(iF)
     in g(x+1) end
   function z(i:int) = 10 * i;
  in f(10, "chau") end

Footnotes

[fn:1] El segundo id debe ser un nombre de tipo, pero no podemos saberlo, lo chequeamos mas adelante.