La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

1 La máquina G. 2 Introducción G: Una implementación de lenguajes funcionales basada en compilación (Augustsson y Johnsson, Chalmers, 1987). Instanciación.

Presentaciones similares


Presentación del tema: "1 La máquina G. 2 Introducción G: Una implementación de lenguajes funcionales basada en compilación (Augustsson y Johnsson, Chalmers, 1987). Instanciación."— Transcripción de la presentación:

1 1 La máquina G

2 2 Introducción G: Una implementación de lenguajes funcionales basada en compilación (Augustsson y Johnsson, Chalmers, 1987). Instanciación como es implementada por la función instantiate es bastante lenta: debe recursivamente recorrer el cuerpo del SC (template) cada vez que una instanciación es efectuada. La gran idea de G: Antes de ejecutar un programa, traducir el cuerpo de cada supercombinador a una secuencia de instrucciones que al ser ejecutadas construyan una instancia del mismo.

3 3 Introducción (2) La ejecución del código generado es necesariamente más rápida que invocar una función de instanciación, ya que todas las instrucciones están dedicadas a construir una instancia. No existen instrucciones dedicadas a recorrer el template, esto es resuelto cuando se efectúa la traducción. La ejecución de un programa se divide en dos partes: i) un compilador produce un código intermedio del programa (tiempo de compilación) ii) en la segunda etapa el código intermedio es ejecutado (tiempo de ejecución) El SC original puede ser descartado una vez compilado.

4 4 Introducción (3) Usamos un compilador a código-G para para transformar un programa fuente en una secuencia de instrucciones de lenguaje de máquina (abstracta). Una buena máquina abstracta cumple las siguientes propiedades: i) debe poder ser fácilmente traducida a código de cualquier máquina concreta ii) el código de la máquina debe ser fácilmente generado a partir del lenguaje fuente Una máquina abstracta es una brecha muy importante entre el lenguaje fuente y el código de una máquina concreta particular.

5 5 Ejemplo Analizaremos un ejemplo muy simple para ilustrar a G. Consideremos la función f g x = K g x La secuencia de instrucciones de código-G a la que será compilada es la siguiente: Push 1 Mkap Pushglobal K Mkap Slide 3 Unwind

6 6 x f g Estado de la máquina antes de ejecutar la secuencia de ins trucciones para f. La espina fue desplegada y punteros denotan los argumentos a ser ligados por g x f g Push 1 La instrucción Push usa direccionamiento relativo al tope del stack, no f, x f g Push 1 Notar que después de este Push tnemos un puntero a g en el tope del stack. Aunque el offset sea el mismo (1) esto funciona porque le Push anterior incremento el tope.

7 7 x f Este diagrama muestra que pasa luego de efectuar la ins- trucción Mkap. Toma dos punteros del stack y construye un nodo de plicación a partir de ellos, dejando un puntero al resultado en el tope del x f K Pushglobal K El efecto de ejecutar Pushglobal K es el de pushear un puntero al SC K en el tope del stack.

8 8 x f Mkap Esta última aplicación de Mkap completa la instancia- ción del cuerpo de f. Ahora podemos reemplazar la expresión original, f g x, con el cuerpo recientemente g x En la primer versión de G que veremos, que no es lazy, simplemente se desliza el cuerpo tres lugares en el stack, descartando los tres punteros que habia. Esta acción es la efectuada por la instrucción Slide 3. Slide 3 La instrucción final Unwind fuerza a la máquina a seguir evaluando.

9 9 Optimizaciones Compilación posibilita otro tipo de optimizaciones, además de la ya remarcada de no tener que atravesar templates. Considere la siguiente definición f x = x + x Las máquinas de reducción de grafos descriptas anteriormente intentarán evaluar x dos veces. La segunda, sin embargo, se encontrarán con que x ya fue evaluado. Una implementación compilada pude darse cuenta que x ya estará evaluada y directamente omitirá el paso de evaluación.

10 10 Secuencia de código para crear templates Recordemos la definición de la función instantiate: instantiate body var value = case body of Var x -> if x == var then value else body App f e -> App (instantiate f var value) (instantiate e var value) Lam bvs e -> if var bvs then body else Lam bvs (instantiate e var value) Definición recursiva! En vez, trataremos de compilar una secuencia lineal de instrucciones que permita efectuar la instanciación de una expresión.

11 11 Sistemas de transición de estados Describiremos la implementación de reducción de grafos usansdo un sistema de transición de estados (STE). Un STE es un formalismo que permite describir el comportamiento de una máquina secuencial. En cualquier momento la máquina está en algún estado, habiendo comenzado en un cierto estado inicial. Si el estado de la máquina machea (es una premisa válida de ) una de las reglas de transición de estado, entonces esta regla es aplicada (un paso de inferencia es efectuado) especificando así un nuevo estado para la máquina. Cuando no hay matching le ejecución para (no es posible seguir construyendo una derivación). Nuestra máquina además será determinística.

12 12 Evaluación postfija de expresiones aritméticas El objetivo de construír una secuencia lineal de instrucciones para instanciar expresiones se asemeja al proceso de evaluar expresiones aritméticas en forma postfija. Exploraremos esta analogía antes de proseguir con la máquina G. El lenguaje de expresiones aritméticas consiste de números, suma y multiplicación: data AExpr = Num Int | Plus AExpr AExpr | Mult AExpr AExpr Se supone que este lenguaje tiene un significado asociado, el que se puede describir con la siguiente función

13 13 Evaluación postfija de expresiones aritméticas (2) aInterpret :: Aexpr -> Int aInterpret (Num n) = n aInterpret (Plus e 1 e 2 ) = aInterpret e 1 + aInterpret e 2 aInterpret (Mult e 1 e 2 ) = aInterpret e 1 * aInterpret e 2 Alternativamente se podría compilar la expresión a una secuencia fija de operadores (o instrucciones). Por ejemplo, la expresión * 4 se representaría con la secuencia: [INum 2, INum 3, INum 4, IMult, IPlus] Definimos las instrucciones para nuestra máquina postfija con el siguiente tipo: data AInstruction = INum Int | IPlus | IMult

14 14 Evaluación postfija de expresiones aritméticas (3) El estado del evaluador es un par, formado por una secuencia de operadores y un stack de números. El significado de una secuencia de código es definido por las siguientes reglas de transición. [] [n] => n INum n : i ns => i n : ns IPlus : i n0:n1: ns => i (n0 + n1) : ns IMult : i n0:n1: ns => i (n0 * n1) : ns

15 15 Evaluación postfija de expresiones aritméticas (4) Para generar la secuencia de código postfijo para una expresión debemos definir un compilador. Este toma una expresión y retorna una secuencia de instrucciones, las que al ser ejecutadas computarán el valor de la expresión. aCompile :: AExpr -> [Ainstruction] aCompile (Num n) = [INum n] aCompile (Plus e 1 e 2 ) = aCompile e 1 ++ aCompile e 2 ++ [IPlus] aCompile (Mult e 1 e 2 ) = aCompile e 1 ++ aCompile e 2 ++ [IMult]

16 16 Usando código postfijo para construír grafos Para crear una instancia del cuerpo de un SC podemos usar la misma técnica. En este caso los valores en el stack serán direcciones de partes de las expresión que está siendo instanciada. Habrá una diferencia, sin embargo, ya que las instrucciones tendrán generalmente el efecto lateral de alojar nodos en un heap, Mkap es un buen ejemplo. Un punto importante a recordar al hacer uso del stack: El mapeo de valores del stack que correponden a nombres de variables cambiará a medida que agreguemos o saquemos objetos del stack. Esto tendrá que ser tenido en cuenta cuando compilemos una expresión.

17 17 Qué ocurre luego de haber efectuado una instanciación? Una vez que hemos generado una instancia del cuerpo de un SC, debemos arreglar el stack y continuar con el proceso de evaluación. Al completar la evaluación de un secuencia postfija de un SC de n argumentos el stack se encontrará en el siguiente estado: i) en el tope estará la dirección en el heap del cuerpo recien instanciado, e digamos ii) luego habrá n+1 punteros. Usándolos podremos acceder a los argumentos usados en el proceso de instanciación. iii) el último de estos punteros apunta a la raíz de la expresión recien instanciada.

18 18 Qué ocurre luego de haber efectuado una e n e f e 1 e Debemos reemplazar el redex con la nueva instancia y luego sacar n items del stack usando la instrucción Slide. Para encontrar el próximo SC debemos comenzar a desplegar la espina nuevamente, usando la instrucción Unwind. El código para la función f x 1... xn = e es : Slide n+1 Unwind

19 19 Una máquina G minimal Presentaremos a partir de ahora el código para una máquina G y su compilador. Comenzaremos con una versión muy simple de la máquina (no perezosa), donde no se efectúan actualizaciones de raíces de redexes y tampoco se efectúa el tratamiento de expresiones aritméticas. Gradualmente iremos construyendo versiones más sofisticadas donde se incorporarán todos los elemntos de pereza y cálculo que conforman la máquina G completa.

20 20 Estado El estado de la máquina minimal se representará por la siguiente cuádrupla: (Code, Stack, Heap, Globals) Code: es una lista de instrucciones. Notación: (i : is) Stack: es un stack de direcciones, cada una identifica un nodo en el Heap. Estos nodos forman la espina de la expresion que esta siendo evaluada. Notación: (a 1 : s) Heap: es una colección de nodos etiquetados. Notación: h[a:nodo] Globals: dirección de cada SC ( y luego de primitivas)

21 21 Instrucciones El stream de instrucciones es un objeto de tipo GmCode y es simplemente una lista de instrucciones type GmCode = [Instruction] Inicialmente tendremos sólo 6 instrucciones: data Instruction = Unwind | Pushglobal Name | Pushint Int | Push Int | Mkap | Slide Int

22 22 Stack y Heap El stack de la máquina es una lista de direcciones de nodos en el Heap data GmStack = [Addr] Usaremos una estructura abstracta para definir el Heap de la máquina data GmHeap = Heap Node En la máquina G minimal habrá sólo tres tipos de nodos: números, aplicaciones y globales. data Node = NNum Int | NAp Addr Addr | NGlobal Int GmCode

23 23 Globales Como luego transformaremos la máquina para que evalúe perezosamente es importante que en principio haya sólo un nodo para cada función global. La dirección de una global puede ser determinada haciendo un lookup de su valor (definición) en una lista de asociación type GmGlobals = Assoc Name Addr

24 24 El evaluador El evaluador de G, eval, está definido de forma de producir una lista de estados. El primero de ellos es aquél construído por el compilador. Si existe un estado final entonces el resultado de la evaluación se encontrará en el tope del stack de este último estado. eval :: GmState -> [GmState] eval state = state : restStates where restStates | gmFinal state = [] | otherwise = eval nextState nextStates = step state gmFinal s = case (getCode s) of [] -> True ; _ -> False El intérprete de G termina cuando la secuencia de código a ejecutar es vacía.

25 25 Ejecutando un paso La función step se define de forma tal que efectúa una transición de estado basada en la instrucción que está ejecutando: step :: GmState -> GmState step state = dispatch i (putCode is state) where (i:is) = getCode state La función despacha la instrucción corriente i y reemplaza la secuencia de código corriente con la secuencia is. Esto corresponde a avanzar el program counter en una máquina real. La función dispatch simplemente selecciona una transición de estado a ejecutar. Ahora presentaremos las reglas de transición, una por cada objeto en Instruction.

26 26 Pushglobal Comenzamos con la instrucción Pushglobal, que usa el componente GmGlobals del estado para encontrar el único nodo Nglobal en el heap que contiene la función global f. Si no lo encuentra un mensaje de error tendría que ser desplegado: Pushglobal f : i s h m[f:a] => i a:s h m La siguientes transiciones implementan la construcción del cuerpo de un SC.

27 27 Pushint y Mkap La transición para Pushint aloja un nodo entero en el heap: Pushint n : i s h m => i a : s h[a:Nnum n] m La instrucción Mkap usa las dos direcciones que están en el tope del stack para construír un nodo aplicación en el heap: Mkap : i a 1 : a 2 : s h m => i a : s h[a:Nap a 1 a 2 ] m

28 28 Push La instrucción Push efectúa una copia de un argumento que ha sido pasado a una función. Para esto, debe mirar a través del nodo de aplicación que es apuntado desde el stack. También se debe saltear el nodo que apunta al SC que está en el stack. Push n : i a 0 :... : a n+1 : s h[a n+1 :NAp a n a n ] m => i a n : a 0 :... : a n+1 : s h m El rearreglo del stack, que ocurre luego que un SC ha sido instanciado es efectuado por la instrucción Slide Slide n : i a 0 :... : a n : s h m => i a 0 : s h m

29 29 Unwind Unwind es la instrucción más compleja porque reemplaza el loop externo de la función de instanciación. Esta es siempre la última instrucción de una secuencia. El nuevo estado a construír depende del item que se encuentre en el tope del stack, que a su vez determina la regla de transición a aplicar. Primero consideramos el caso en el que el tope es un número. En este caso la evaluación debe terminar: [Unwind] a : s h[a:Nnum n] m => [] a : s h m

30 30 Unwind (2) Si lo que hay es un nodo de aplicación en el tope entonces debemos continuar unwinding desde el próximo nodo: [Unwind] a : s h[a:Nap a 1 a 2 ] m => [] a 1 : a : s h m La regla más complicada ocurre cuando hay un nodo global en el tope Puede suceder que esté aplicada a suficientes argumentos o no. En el primer caso hay un error de tipos. En el segundo saltaremos al código del SC. En la regla esto se expresa moviendo el código al componente de código de la máquina [Unwind] a 0 :... : a n : s h[a 0 :Nglobal n c] m => c a 0 :... : a n : s h m


Descargar ppt "1 La máquina G. 2 Introducción G: Una implementación de lenguajes funcionales basada en compilación (Augustsson y Johnsson, Chalmers, 1987). Instanciación."

Presentaciones similares


Anuncios Google