La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

1 Agregando estructuras de datos. 2 Introducción Ahora extenderemos a la máquina G para poder manipular estructuras de datos. En esta versión el objetivo.

Presentaciones similares


Presentación del tema: "1 Agregando estructuras de datos. 2 Introducción Ahora extenderemos a la máquina G para poder manipular estructuras de datos. En esta versión el objetivo."— Transcripción de la presentación:

1 1 Agregando estructuras de datos

2 2 Introducción Ahora extenderemos a la máquina G para poder manipular estructuras de datos. En esta versión el objetivo será poder compilar código para programas que contengan constructores (EConstr) y expresiones case (ECase). Una expresión case es utilizada para inspeccionar los valores contenidos en un constructor. Un ejemplo: length xs = case xs of -> 0; y ys -> 1 + length ys Veamos ahora como se ejecuta esta expresión:

3 3 Introducción (2) i) Primero evaluamos xs a WHNF. ii) Ahora podemos decidir cuál de las alternativas tomar. El tag de la expresión evaluada, que debe ser un objeto estructurado, determina cuál alternativa tomar. En el ejemplo de length, por ejemplo, tenemos que 1) si el tag del constructor para xs es 1, entonces la lista es vacía y por lo tanto tomamos la primer rama. Entonces devolveremos el valor 0. 2) si el tag es 2, entonces la lista es no-vacía. En este caso tenemos dos componentes del constructor ( y e ys ). El largo de la lista es entonces 1 más el largo de ys.

4 4 Introducción (3) Asumiremos que cuando sea que intentemos “desmantelar” un constructor éste ha sido aplicado al número correcto de argumentos. Un constructor en este estado se dice que está saturado. Ahora podemos notar también que un programa puede retornar un resultado que es un objeto estructurado. La máquina entonces debe ser capaz de imprimir esta clase de objetos en forma lazy. Consideraremos primero las modificaciones a los componentes de la máquina.

5 5 Estado y Heap type GmState = (GmOutput, GmCode, GmStack, GmDump, GmHeap, GmGlobals) Para poder representar nodos de constructores en el heap extendemos el tipo Node con un nuevo constructor, NConstr. Este toma dos argumentos: un número no negativo, que representa el tag y una lista de componentes, que serán representadas como una lista de direcciones de nodos en el heap : data Node = NNum Int | NAp Addr Addr | NGlobal Int GmCode | NInd Addr | NConstr Int [Addr]

6 6 Instrucciones Ahora definiremos el nuevo conjunto de instrucciones. Lo que hacemos es simplemente extender el tipo Instruction de Mark 4 con cuatro nuevas instrucciones: data Instruction = Slide Int | … | Add | Sub | … | Pack Int Int | Casejump [(Int, GmCode)] | Split Int | Print

7 7 Pack y Casejump La instrucción Pack es muy simple. Asume que hay suficientes argumentos en el stack para construír un constructor saturado, si no los hay entonces la instrucción queda indefinida: o Pack t n : i a 1 :... a n : s d h m => o i a : s d h[a : NConstr t [a 1,…,a n ] m La regla de transición para la instrucción Casejump espera: i) el nodo en el tope del stack está en WHNF ii) y que el nodo es además un objeto estructurado. Usando el tag del objeto seleccionamos una de las secuencias de ins- trucciones alternativas, la que es agregada (prefijamente) al stream corriente de instrucciones:

8 8 Casejump o Casejump […, t -> i’,…]: i a : s d h[a : NConstr t ss] m => o i’ ++ i a : s d h m Esta es una forma muy simple de especificar un salto multicamino y luego un “join”. Es decir, al prefijar el código corriente i con el código i’ logramos el efecto de primero ejecutar el código para la alternativa y luego continuar con el resto. El código para cada una dela alternativas comienza con una instruc- ción Split n y termina con una instrucción Slide n. El número n es de- terminado por la aridad del constructor. La instrucción Split es usada para poder acceder a los componentes del constructor.

9 9 Ejemplo Consideremos la secuencia de código generada para la función length : [Push 0, Eval, Casejump [ 1 -> [Pushint 0] 2 -> [Split 2, Push 1, Pushglobal “length”, Mkap, Eval, Pushint 1, Add, Slide 2]], Update 1, Pop 1, Unwind] Asumiendo que la función es aplicada a un nodo Cons, la ejecución de este caso ilustra como las instrucciones Slide y Split son usadas temporalmente para extender el conjunto de ligaduras locales:

10 10 Ejemplo (2) 2 tail head 2 tail head (a) Al entrar a la alternativa(b) Después de Split 2 (c) Después del código para el cuerpo(d) Después de Slide 2 tail head

11 11 Split y Print La transición para Split es la siguiente: o Split n : i a : s d h[a : NConstr t [a 1,…,a n ]] m => o i a 1 :… a n : s d h m Finalmente, describimos las reglas para Print. Hay dos transiciones, una para números y la otra para constructores : o Print : i a : s d h[a : NConstr t [a 1,…,a n ]] m => o i’ ++ i a 1 :… a n : s d h m o Print : i a : s d h[a : NNum n] m => o ++ [n] i s d h m donde i’ es [Eval, Print, …, Eval, Print] |------------- n -----------------|

12 12 Esquemas de compilación A continuación mostraremos las extensiones a los esquemas E y C. Estos a su vez requerirán esquemas auxiliares, D y A, para poder manejar las alternativas que pueden ser seleccionadas en una expresión case: E [ case e of alts ]  = E [ e ]  ++ [Casejump D [ alts ]  ] E [ Pack{t,a} e 1 … e a ]  = C [ e a ]  +0 ++ … ++ C [ e 1 ]  +(a-1) ++ [Pack t a] C [ Pack{t,a} e 1 … e a ]  = C [ e a ]  +0 ++ … ++ C [ e 1 ]  +(a-1) ++ [Pack t a] D [ alts ]  compila el código para las alternativas de una expresión case D [ alt 1 … alt n ]  = [ A [alt 1 ] , …, A [ alt n ]  ] A [alt ]  compila código para una alternativa de una expresión case A [ x 1 … x n -> body ]  = t -> [Split n ] ++ E [body]  ’ ++ [Slide n] donde  ’ =  +n [x 1 |-> 0 … x n |-> n - 1]

13 13 Operadores relacionales y expresiones booleanas Ahora mostraremos como la máquina Mark 6 construída puede ser modificada para usar una nueva representación de booleanos. Primeros observamos que podemos implementar a los booleans como objetos estructurados, con True y False representados como constructores de aridad cero y tags 2 y 1 respectivamente. Para implementar condicionales agregaremos una nueva definición para el programa if: if c t f = case c of -> f; -> t

14 14 Operadores relacionales y expresiones booleanas (2) El primer cambio a hacer es en las operaciones de comparación, las que tienen la siguiente regla de transición genérica: o op : i a 0 : a 1 : s d h[a 0 : NNum n 0, a 1 : NNum n 1 ] m => o i a : s d h[a: NConstr (n 0 op n 1 ) [] ] m Por ejemplo, en la instrucción Eq, reemplazaremos op con una función que retorna 2 (el tag para True) si los números n 0 y n 1 son iguales y 1 (el tag para False) en el otro caso.

15 15 Extendiendo el lenguaje Hay algunas expresiones legales construídas usando Ecase y Econstr para las cuales nuestro compilador fallará. Estas pueden ser clasificadas en dos clases: i) Ocurrencias de Ecase en un contexto no estricto, o sea, en expresiones compiladas con el esquema C. ii) Ocurrencias de Econstr no saturadas. Los dos problemas pueden ser resueltos usando técnicas de transformación de programas. La solución para Ecase es transformar las expresiones inválidas en SC, que luego serán aplicados a sus variables libres.

16 16 Extendiendo el lenguaje (2) Por ejemplo, el programa f x = Pack{2,2} (case x of -> 1; -> 2) Pack{1,0} puede ser transformado en el programa equivalente f x = Pack{2,2} (g x) Pack{1,0} g x = case x of -> 1; -> 2 La solución para el caso Econstr es crear un SC por cada constructor, este será generado con un número suficiente de variables libres para saturarlo: prefix p xs = map (Pack{2,2} p) xs será transformado a prefix p xs = map (f p) xs f p x = Pack{2,2} p x

17 17 Extendiendo el lenguaje (3) Otra forma de resolver este último problema es modificando la instrucción Pushglobal de tal forma que funcione para “nombres” de la forma Pack{t,a}. Podemos entonces buscar por funciones constructoras como f en el componente global del estado. Si la función no está presente podemos crear un nuevo nodo global a ser asociado con la función de la siguiente forma: NGlobal a [Pack t a, Update 0, Unwind] Las nuevas transiciones son, primero, si la función ya existe o Pushglobal Pack{t,n} : i s d h m[Pack{t,n} : a] => o i a : s d h m

18 18 Extendiendo el lenguaje (4) Segundo, si la función no existe o Pushglobal Pack{t,n} : i s d h m => o i a : s d h[a:gNode t,n ] m[Pack{t,n}:a Donde gNode t,n es Nglobal n [Pack t n, Update 0, Unwind] Nuestro compilador puede entonces generar código para expresiones con nodos de constructor no saturados directamente: C [ Pack{t,a} ]  = [Pushglobal “Pack{t,a}”]

19 19 Más optimizaciones Consideremos de nuevo el programa ejemplo a partir del cual desarrollamos la Mark 5: main = 3 + 4 * 5 Este programa, como hemos visto, genera el siguiente código: [Pushint 5, Pushint 4, Mul, Pushint 3, Add] Cuando se ejecuta este código usará 5 nodos de heap. Es posible reducir esto más aún? Se puede reducir el número de accesos al heap por aritmética si usamos un stack de números para representar valores intermedios en la computación. En la Mark 7 estos valores serán mantenidos en un nuevo componente del estado, llamado V-stack

20 20 Más optimizaciones (2) El problema es que guardar números en el heap o accederlos es una operación muy costosa en una máquina real. Es mucho más eficiente usar el stack o los registros de la misma. En Mark 7 se usará un stack, no hay que preocuparse por la falta de registros. El nuevo código para el programa main = 3 + 4 * 5 será muy similar al anterior: [Pushbasic 5, Pushbasic 4, Mul, Pushbasic 3, Add, Mkint] La primera instrucción guarda 5 en el tope del V-stack, al igual que Pushbasic 4. La instrucción Mul ahora espera encontrar sus argumentos en el V-stack, donde también guardará su resultado. La instrucción final Mkint toma el valor en el tope del stack y lo aloja como un nodo de número en el heap, dejando un puntero a él en el tope del S-stack.

21 21 Estado e instrucciones El uso del V-stack requiere que el estado de la máquina G ahora tenga un nuevo componente: type GmState = (GmOutput, GmCode, GmStack, GmDump, GmVstack, GmHeap, GmGlobals) data Instruction = Slide Int | … | Add | Sub | … | Pack Int Int |... | Pushbasic Int | Mkbool | MkInt | Get

22 22 Nuevas reglas de transición Un requerimiento obvio es que cada una de las transiciones aritméticas ahora tendrá que tomar sus argumentos de, y retornar sus resultados en, el V-stack en vez del stack ordinario de la máquina. La transición genérica para operadores binarios es entonces: o op : i s d n 0 : n 1 : v h m => o i s d (n 0 op n 1 ) : v h m La instrucción Neg ahora tiene esta simple transición: o Neg : i s d n : v h m => o i s d (- n) : v h m

23 23 Nuevas reglas de transición (2) También necesitamos instrucciones para mover valores entre el heap y el V-stack. Comenzamos con Pushbasic, que pushea un número n en el tope del V-stack: o Pushbasic n : i s d v h m => o i s d n : v h m Para mover un valor dersde el V-stack al heap usaremos dos instrucciones: Mkbool y Mkint. Ambas trtan al entero que está en el tope del stack como booleanos y enteros respectivamente. o Mkbool : i s d t : v h m => o i a : s d v h[a : NConstr t [] ] m

24 24 Nuevas reglas de transición (3) La transición para Mkint es similar, excepto que crea un nuevo nodo entero en el heap: o Mkint : i s d n : v h m => o i a : s d v h[a : NNum n] m Para efectuar la operación inversa (mover del heap al V-stack) usaremos la instrucción Get. Esta es especificada por las dos siguientes transiciones: o Get : i a : s d v h[a : NConstr t [] ] m => o i s d t : v h m

25 25 Nuevas reglas de transición (4) Finalmente para usar booleanos en el V-stack usaremos un Casejump simplificado que inspecciona el V-stack para determinar que stream de instrucciones usar. Esta nueva instrucción es llamada Cond. o Cond t f : i s d 2 : v h m => o t ++ i s d v h m o Get : i a : s d v h[a : NNum n] m => o i s d n : v h m o Cond t f : i s d 1 : v h m => o f ++ i s d v h m

26 26 El compilador Debido al componente extra, la función compile debe ahora inicializar el V-stack como vacío: compile :: CoreProgram -> GmState compile program = ([], initialcode, [], [], [], heap, globals) where (heap, globals) = buildInitialHeap program Debido al cambio de las transiciones para las instrucciones primitivas, debemos ahora cambiar el código para cada primitiva compilada. primitives :: [(Name, [Name], CoreExpr)] primitives = [ (“+”, [“x”, “y”], (Eap (Eap (Evar “+”) (Evar “x”)) (Evar “y”))),... (“True”, [], (Econstr 2 0))]

27 27 El esquema B El esquema B constituye otro tipo distinto de contexto. Para ser compilada con el esquema B, una expresión no sólo debe ser requerida en WHNF, debe ser también una expresión de tipo entero o booleano. Las siguientes expresiones propagarán el contexto estricto B (CEB): i) si let(rec) D in e ocurre en un CEB también lo hará e. ii) si la expresión if e 0 e 1 e 2 ocurre en un CEB entonces también lo haran e 0, e 1 y e 2. iii) si e 0 op e 1 ocurre en un CEB, con op un operador aritmético o relacional, entonces las expresiones e 0 y e 1 también lo harán. iv) si negate e ocurre en un CEB también lo hará la expresión e.

28 28 El esquema B (2) Si no podemos reconocer ninguna de estas expresiones, entonces el código compilado evaluará la expresión usando el esquema E, y luego efectuará una instrucción Get. Esta instrucción “unboxea” el valor en el tope del stack y lo mueve al tope del V-stack. Lo único que queda por especificar es cuando sabemos que una expresión está inicialmente en un CEB. La situación usual es que generaremos un CEB desde el CE usual del esquema E, con el conocimiento adicional de que el valor es de tipo integer o boolean.

29 29 El esquema B (3) B [ e ]  compila código que evalúa una expresión e a WHNF en el environment , dejando el resultado en el tope del V-stack. B [ i ]  = [Pushbasic i] B [ let x 1 = e 1 ;... ; x n = e n in e ]  = C [ e 1 ]    ++... ++ C [ e n ]   n  ++ B [ e ]  ’ ++ [Pop n]  ’ =   n [ x 1 |-> n-1,..., x n |-> 0] E [ let rec x 1 = e 1 ;... ; x n = e n in e ]  = [Alloc n] ++ C [ e 1 ]  ’ ++ [Update n-1] ++... ++ C [ e n ]  ’ ++ [Update 0] B [ e ]  ’ ++ [Pop n]  ’ =   n [ x 1 |-> n-1,..., x n |-> 0]

30 30 El esquema B (4) B [ e 0 + e 1 ]  = B [ e 1 ]  ++ B [ e 0 ]  ++ [Add] y similarmente para los restantes operadores aritméticos B [ e 0 = = e 1 ]  = B [ e 1 ]  ++ B [ e 0 ]  ++ [Eq] y similarmente para los restantes operadores relacionales B [ negate e]  = B [ e ]  ++ [Neg] B [ if e 0 e 1 e 2 ]  = B [ e 0 ]  ++ [Cond (B [ e 1 ]  ) (B [ e 2 ]  )] B [ e ]  = E [ e ]  ++ [Get] otherwise

31 31 El esquema E final El nuevo esquema E especifica que se le debe dar tratamiento especial al llamado de funciones aritméticas y relacionales. La diferencia con la versión para la Mark 6 en que se hace uso del esquema B para efectuar las operaciones aritméticas y de comparación. Nuevamente si no ocurren casos especiales, se deberá usar un método por defecto para compilar la expresión. Esto es simplemente construír el grafo usando el esquema C y luego una instrucción Eval. Esto asegura que la expresión sea evaluada a WHNF.

32 32 El esquema E final (2) E [ e ]  compila código que evalúa una expresión e a WHNF en el environment , dejando un puntero a la expresión en el tope del stack. E [ i ]  = [Pushint i] E [ let x 1 = e 1 ;... ; x n = e n in e ]  = C [ e 1 ]    ++... ++ C [ e n ]   n  ++ E [ e ]  ’ ++ [Slide n]  ’ =   n [ x 1 |-> n-1,..., x n |-> 0] E [ let rec x 1 = e 1 ;... ; x n = e n in e ]  = [Alloc n] ++ C [ e 1 ]  ’ ++ [Update n-1] ++... ++ C [ e n ]  ’ ++ [Update 0] E [ e ]  ’ ++ [Slide n]  ’ =   n [ x 1 |-> n-1,..., x n |-> 0]

33 33 El esquema E final (3) E [ case e of alts ]  = E [ e ]  ++ [Casejump D [ alts ]  ] E [ Pack{t,a} e 1 … e a ]  = C [ e a ]  +0 ++ … ++ C [ e 1 ]  +(a-1) ++ [Pack t a] E [ e 0 + e 1 ]  = B [e 0 + e 1 ]  ++ [Mkint] y similarmente para los restantes operadores aritméticos. E [ e 0 == e 1 ]  = B [e 0 + e 1 ]  ++ [Mkbool] y similarmente para los restantes operadores relacionales. E [ negate e]  = B [ e ]  ++ [Neg] E [ if e 0 e 1 e 2 ]  = B [ e 0 ]  ++ [Cond (E [ e 1 ]  ) (E [ e 2 ]  )] E [ e ]  = C [ e ]  ++ [ Eval ] otherwise


Descargar ppt "1 Agregando estructuras de datos. 2 Introducción Ahora extenderemos a la máquina G para poder manipular estructuras de datos. En esta versión el objetivo."

Presentaciones similares


Anuncios Google