1 / 30

La m áquina G

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

doris
Download Presentation

La m áquina G

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  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 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. 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. 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. 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 • Push 1 • Mkap • Pushglobal K • Mkap • Slide 3 • Unwind

  6. Ejemplo (2) Estado de la máquina antes de ejecutar la secuencia de ins trucciones para f. La espina fue desplegada y punteros a @ denotan los argumentos a ser ligados por g y x @ @ x f g @ @ x f g La instrucción Push usa direccionamiento relativo al tope del stack, no f, 0, 1, ... Push 1 @ @ x f g 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. Push 1

  7. Ejemplo (3) Mkap 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 stack. @ @ x f g @ @ @ x f g El efecto de ejecutar Pushglobal K es el de pushear un puntero al SC K en el tope del stack. @ K Pushglobal K

  8. Ejemplo (4) 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 instanciado. @ @ x f g @ K @ 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. @ K @ g x Slide 3 La instrucción final Unwind fuerza a la máquina a seguir evaluando.

  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. 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. 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. 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. Evaluación postfija de expresiones aritméticas (2) • aInterpret :: Aexpr -> Int • aInterpret (Num n) = n • aInterpret (Plus e1 e2) = aInterpret e1 + aInterpret e2 • aInterpret (Mult e1 e2) = aInterpret e1 * aInterpret e2 • Alternativamente se podría compilar la expresión a una secuencia fija de operadores (o instrucciones). Por ejemplo, la expresión • 2 + 3 * 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. 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. 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 e1 e2) = aCompile e1 ++ aCompile e2 ++ [IPlus] • aCompile (Mult e1 e2) = aCompile e1 ++ aCompile e2 ++ [IMult]

  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. 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. Qué ocurre luego de haber efectuado una instanciación? (2) @ @ en en-1 @ f e1 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 x1 ... xn = e es: <código para construír una instancia de e> Slide n+1 Unwind

  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. 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 expresi’on que est’a siendo evaluada. Notación: (a1 : 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. 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. 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. 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. 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. 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 despachala 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. 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 sh m[f:a] => i a:s h m La siguientes transiciones implementan la construcción del cuerpo de un SC.

  27. Pushint y Mkap • La transición para Pushint aloja un nodo entero en el heap: Pushint n : i sh 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 a1 : a2 : sh m => i a : s h[a:Nap a1 a2] m

  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 a0 : ... : an+1 : sh[an+1:NAp an a’n] m => i a’n : a0 : ... : an+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 a0 : ... : an : sh m => i a0 : s h m

  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 : sh[a:Nnum n] m => [] a: s h m

  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 : sh[a:Nap a1 a2] m => [] a1 : 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] a0 : ... : an : s h[a0:Nglobal n c] m => c a0 : ... : an : s h m

More Related