1 Agregando primitivas 2 Introducción Ahora presentaremos cómo agregar operaciones primitivas a la máquina G. Entenderemos como operaciones primitivas.

Slides:



Advertisements
Presentaciones similares
IBD Clase 14.
Advertisements

Curso de java básico (scjp)
DATSI, FI, UPM José M. Peña Programación en C DATSI, FI, UPM José M. Peña Programación en C.
INTELIGENCIA ARTIFICIAL
Compiladores e intérpretes Análisis Sintáctico VI
IBD Clase 7.
CLASE 4 EL ENSAMBLADOR.
CLASE 3 SOFTWARE DEL MICROPROCESADOR
Los elementos invertibles de Z6 son 1 y 5
UPC Tema: ESPACIO VECTORIAL Rn
Clases Extendidas La clase extendida hereda los campos y métodos de la clase de la cual extiende. La clase original se conoce como superclase y la clase.
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.
TEORÍA DE CONJUNTOS.
Interpretando objetos
Direcciones, Arreglos y Argumentos de Funciones
Técnico en programación de Software
Ecuaciones y Resolución de Ecuaciones Lineales
Expresiones Racionales
DIAGRAMAS DE FLUJO Y PSEUDOCÓDIGO
1 Parsing Un parser podría ser definido como un programa que analiza una porción de texto para determinar su estructura lógica: la fase de parsing en un.
Reducción de grafos Qué es reducción normal de grafos? Ejemplo:
Chequeo e inferencia de tipos
Combinadores SK.
Informática II Prof. Dr. Gustavo Patiño MJ
5.3 Funciones Especiales Ecuación de Bessel de orden v (1) donde v  0, y x = 0 es un punto singular regular de (1). Las soluciones de (1) se.
3. INTRODUCCIÓN A LA PROGRAMACIÓN
Números enteros.
Programación Orientada a Objetos en Java
Ingeniero Anyelo Quintero
27/07/14 Catedra de Programacion I 1 Programación I Operadores y Expresiones.
Tema 3. Optimización de Código
¿Qué es un PUNTERO?: Un puntero es un objeto que apunta a otro objeto. Es decir, una variable cuyo valor es la dirección de memoria de otra variable. No.
Tema 7: Polimorfismo Antonio J. Sierra. Índice Introducción. Sobrecarga de métodos. Objetos como parámetros. Paso de argumentos. Devolución de objetos.
CI TEORIA semana 8 Subprogramas o funciones Definición de funciones.
Programación I Teoría VI: Recursividad
Métodos Algoritmos y Desarrollo de Programas I. Cuando utilizar métodos  Los métodos se utilizan para romper un problema en pedazos de forma que este.
POO Java Módulo 3 Elementos de programas Identificadores
ANALISIS SINTACTICO El análisis gramatical es la tarea de determinar la sintaxis, o estructura, de un programa. Por esta razón también se le conoce como.
Slide 7-1 Copyright © 2003 Pearson Education, Inc. Figure: Estructuras de datos.
APLICACIONES DE PILAS Estructuras de Datos.
TRAMPAS EN EL DISEÑO DE LENGUAJES CON RELACIÓN A LOS NOMBRES - REGLAS DE ALCANCE - COMPILACIÓN POR SEPARADO CONTROL DE FLUJO - EVALUACIÓN DE EXPRESIONES.
Clases y objetos La unidad fundamental de programación OO son las clases. Conjunto de métodos y semántica Qué se va a hacer POO Clase: que define la implementación.
Tema 6: Clases Antonio J. Sierra.
Arboles Binarios de expresiones
1 Herencia en Java Agustín J. González Diseño y Programación Orientados a Objetos.
UNIVERSIDAD DE MANAGUA I CUATRIMESTRE INTRODUCCION A LA PROGRAMACION
Práctica 7 AOC. Gestión de la pila Una pila es una estructura de datos caracterizada por que el último dato que se almacena es el primero que se obtiene.
Semana 5 Subprogramas..
Índice. Revisando conceptos acerca de la memoria.
Estructura de Datos y Algoritmos
Clasificación de Gramáticas y Manejo de Errores
(Organización y Manejo de Archivos)
Pila1UVM Pilas. Pila2UVM Pila3UVM 2.1 Objetivos El estudiante manejará el tad Pila contigua.
Programación Básica con NQC Patricio A. Castillo Pizarro 25/08/2007.
Material de apoyo Unidad 4 Estructura de datos
Tipos de Datos. Entrada-Salida.. La entrada-salida (I/O) le permite a un programa comunicarse con el mundo exterior. Esta comunicación puede realizarse.
1 Compilación, pereza y expresiones let(rec) 2 Compilando un programa Describiremos un compilador para la máquina minimal usando un conjunto de esquemas.
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.
Términos algoritmo diseñar algoritmo implementar algoritmo
Patricio A. Castillo José M. Galarce Agosto 23 de 2008 Segunda Clase.
1 Las máquinas TIM y Spineless-Tagless G. 2 TIM: La máquina de tres instrucciones En principio TIM parece ser una máquina de reducción de grafos muy diferente.
Programación Básica con NQC Patricio A. Castillo 12/04/2008.
INTRODUCCION A LA PROGRAMACION
Organización de la Computadora
PROGRAMACION DE Pilas o Stacks
 Las funciones son un conjunto de instrucciones que realizan una tarea específica. En general toman unos valores de entrada, llamados parámetros y proporcionan.
Décimo Curso de Programación Básica con NQC “Segunda Clase” Cristián Arenas Ulloa Agosto 29 de 2009.
PROF. RAFAEL MONTENEGRO B. UNELLEZ-APURE Introducci Ó n a los Arreglos (“arrays”) en C++
Lenguaje ensamblador Resumen en diapositivas
Omar Herrera Caamal Rigoberto Lizárraga Luis Cetina Luna.
Transcripción de la presentación:

1 Agregando primitivas

2 Introducción Ahora presentaremos cómo agregar operaciones primitivas a la máquina G. Entenderemos como operaciones primitivas a las aritméticas y a los operadores relacionales. Usaremos la operación de suma como ejemplo paradigmático. Introduciremos entonces una instrucción Add, la que suma dos números que residen en el heap, y a su vez retorna el resultado en un nuevo nodo del mismo.

3 La instrucción Add Las direcciones de los dos argumentos se encontrarán en el tope del stack, y es a su vez en el tope donde quedará almacenada la dirección del nodo resultante. La instrucción tiene la siguiente regla de transición: Add : i a 0 : a 1 : s h[a 0 : NNum n 0, a 1 : NNum n 1 ] m => i a : s h[a : NNum (n 0 + n 1 )] m Es correcta esta especificación de Add ? La regla es aplicable solamente cuando los elementos en el tope del stack se encuentran en WHNF.

4 La instrucción Add (2) Como estamos implementando una máquina perezosa no es correcto asumir que los argumentos de una operación primitiva están en WHNF. En el algoritmo de reducción especificado anteriormente el proceso efectuaba este chequeo antes de evaluar un operador, y en el caso de que los argumentos no estuvieran normalizados se invocaba recursivamente al evaluador. En G la idea es mantener las instrucciones lo más simples posibles, por lo tanto usaremos la instrucción Add sólo cuando estemos seguros que los argumentos están efectivamente en WHNF.

5 Eval Lo que haremos es aumentar el conjunto de instrucciones de la máquina con la instrucción Eval. Esta instrucción satisface los siguiente requerimientos: Asumamos que nos encontramos en el estado Eval : i a : s h m Cuando sea que comience la ejecución de la secuencia de instrucciones i, el estado será i a : s h´ m y el item en el tope del stack se encontrará en WHNF. Será posible también que Eval no termine, ésto podrá suceder si el nodo en el heap apuntado desde el tope del stack no tiene WHNF.

6 Eval (2) Si el nodo cuya dirección está en el tope del stack ya está en WHNF, entonces Eval no hace nada. Si es posible efectuar una reducción, entonces la acción de Eval es efectuar una reducción a WHNF. Si esta llamada a Eval termina, entonces la ejecución continúa con el heap modificado como único resultado. Esto es muy similar a la estructura de llamado a subrutinas y retorno que se usa tradicionalmente en la implementación de lenguajes de programación. Recordamos que la solución clásica para implementar este comportamiento es usar un stack. En el stack se salvará la información necesaria para poder continuar la ejecución una vez que la subrutina invocada termina de ejecutarse.

7 Dump En la versión Mark 4 de la máquina G el stack previamente mencionado será llamado Dump, y es un stack de pares, cuyo primer componente es una secuencia de instrucciones, y el segundo componente un stack (de G). type GmState = (GmCode, GmStack, GmDump, GmHeap, GmGlobals) type GmDump = [GmDumpItem] type GmDumpItem = (GmCode, GmStack)

8 Instrucciones de Mark 4 data Instruction = Slide Int | Alloc Int | Update Int | Pop Int | Unwind | Pushglobal Name | Pushint Int | Push Int | Mkap | Eval | Add | Sub | Mul | Div | Neg | Eq | Ne | Lt | Le | Gt | Ge | Cond GmCode GmCode

9 Nuevas transiciones Instrucciones del evaluador. Las instrucciones que manipulan el dump son pocas. Por un lado Eval, que crea un nuevo item del dump cada vez que el nodo que está en el tope del stack no está WHNF. También tendremos que introducir una modificación para la instrucción Unwind, la que tendrá que popear un item del dump cuando la evaluación es terminada. Cuando la expresión al tope del stack está en WHNF, Unwind puede recomponer el viejo contexto usando el dump, alojando la dirección al tope del stack en el tope del stack previo, que es recuperado.

10 Instrucciones del evaluador Esto último se ve claramente en la siguiente regla de transición: [Unwind] a : s : d h[a : NNum n] m => i a : s d h m La expresión con dirección a está en WHNF porque es un entero, en- tonces la vieja secuencia i´ de instrucciones es retomada y el stack pa- sa a ser el viejo stack pero con a en el tope. Esta regla se aplica sólo cuando el dump no es vacío, si no la máquina ha terminado de evaluar. Todas las restantes transiciones para Unwind quedan igual a como fueron definidas en Mark 3 (excepto que ahora también tendrán el com- ponente dump). Ahora podemos especificar la regla de transición para Eval.

11 Instrucciones del evaluador (2) La instrucción Eval salva el resto del stack ( s ) y el resto de las instrucciones ( i ) como un item del dump. La nueva secuencia de instrucciones es un unwinding y el nuevo stack contiene solamente la dirección a. Eval : i a : s d h m => [Unwind] [a] : d h m

12 Instrucciones aritméticas La ejecución de cualquiera de los operadores binarios aritméticos puede especificarse por medio de una regla de transición de la siguiente forma: op : i a 0 : a 1 : s d h[a 0 : NNum n 0, a 1 : NNum n 1 ] m => i a : s d h[a : NNum (n 0 op n 1 )] m La instrucción Neg, por otro lado, niega el número que se encuentra en el tope del stack, por lo que tiene la siguiente regla de transición: Neg : i a : s d h[a : NNum n] m => i a : s d h[a : NNum (- n)] m

13 Comparación Los operadores de comparación también pueden especificarse con una regla de transición genérica: op : i a 0 : a 1 : s d h[a 0 : NNum n 0, a 1 : NNum n 1 ] m => i a : s d h[a : NNum (n 0 op n 1 )] m Notar que esta regla es similar a la de los operadores aritméticos. La diferencia está en que la operación ==, por ejemplo, retorna un booleano y no un entero. Para arreglar esto asignaremos valores enteros a las constantes True (1) y False (0).

14 La instrucción Cond Finalmente, para compilar la instrucción if implementaremos la instrucción Cond, con las siguientes reglas de transición Cond i 1 i 2 : i a : s d h[a : NNum 1] m => i 1 ++ i s d h m Cond i 1 i 2 : i a : s d h[a : NNum 0] m => i 2 ++ i s d h m

15 El compilador Introduciremos los menores cambios posibles al compilador para poder usar las instrucciones aritméticas, relacionales y condicionales que hemos agregado. La forma más simple de extender el compilador es agregar código-G para cada una de las nuevas funciones primitivas: compiledPrimitives :: [GmCompiledSC] compiledPrimitives = [(+, 2, [Push 1, Eval, Push 1, Eval, Add, Update 2, Pop 2, Unwind]),... (negate, 1, Push 0, Eval, Neg, Update 1, Pop 1, Unwind]),... (= =, 2, [Push 1, Eval, Push 1, Eval, Eq, Update 2, Pop 2, Unwind]),... (if, 3, [Push 0, Eval, Cond [Push 1] [Push 2], Update 2, Pop 2, Unwind])]

16 Un mejor manejo de aritmética La forma en que G es implementada fuerza a que cada operador primitivo es invocado usando una de las primitivas compiladas. Podemos mejorar esta situación, teniendo en cuenta que a menudo podemos invocar al operador primitivo directamente. Consideremos por ejempo el siguiente programa: main = * 5 Al ser compilado genera el siguiente código [Pushint 5, Pushint 4, Pushglobal *, Mkap, Mkap, Pushint 3, Pushglobal +, Mkap, Mkap, Eval] (33 pasos, 11 nodos) Uno podría pensar en usar directamente las instrucciones Add y Mul en lugar de invocar a la funciones + y *, generando el siguiente código: [Pushint 5, Pushint 4, Mul, Pushint 3, Add] (5 pasos, 5 nodos)

17 Un problema Un posible problema surge al considerar el siguiente ejemplo: main = K 1 (1/0) que genera el siguiente código: [Pushint 0, Pushint 1, Pushglobal /, Mkap, Mkap, Pushint 1, Pushglobal K, Mkap, Mkap, Eval] Si seguimos el pattern del ejemplo anterior podríamos tratar de generar el código: [Pushint 0, Pushint 1, Div, Pushint 1, Pushglobal K, Mkap, Mkap, Eval] El problema es que división por 0 es aplicada antes que K, un compilador correcto debiera generar código sin este tipo de errores. Lo que pasa es que nuestro código es demasiado estricto. Un problema similar ocurre con expresiones que no terminan y son inadvertidamente evaluadas.

18 La solución La solución al problema anterior es controlar el contexto en que una expresión ocurre. Distinguiremos dos contextos: Estricto: el valor de la expresión es requerido en WHNF. Perezoso: el valor de la expresión podría o no ser requerido en WHNF. Para cada contexto tendremos un esquema de compilación correspondiente. En el contexto estricto el esquema de compilación será el esquema E. En el contexto perezoso usaremos el esquema C. El objetivo es tratar de encontrar tantos contextos estrictos como sea posible, ya que el código generado es más eficiente.

19 La solución (2) Observación: cuando sea que se quiera instanciar un supercombinador es porque interesa reducir su valor a WHNF. Entonces un SC puede ser siempre evaluado en un contexto estricto. Hay también algunas expresiones en las que sabemos que algunas sub-expresiones serán evaluadas a WHNF si la expresión lo es. La clase de expresiones en contextos estrictos (CE) puede ser descripta recursivamente:

20 Expresiones en contextos estrictos La expresión en el cuerpo de un SC está en un CE. If e 0 op e 1 ocurre en un CE, donde op es un operador aritmético o de comparación, entonces las expresiones e 0 y e 1 están también en un CE. Si negate e ocurre en un CE, también lo hace e. Si la expresión if e 0 e 1 e 2 ocurre en un CE, también lo hacen las expresiones e 0, e 1 y e 2. Si let(rec) D in e ocurre en un CE entonces la expresión e también lo está Veamos un ejemplo:

21 Expresiones en contextos estrictos (2) f x y = (x + y) + g (x * y) Las expresiones (x + y) y g (x * y) serán evaluadas en CE (porque el cuerpo de f lo es). El operador + propaga el CE a x e y. En la segunda expresión la presencia de un SC definido por el usuario implica que la sub-expresión x * y será compilada asumiendo que la expresión podría no ser evaluada.. El compilador E será definido recursivamente. Como el cuerpo de cada SC es evaluado en un CE necesitamos invocar al esquema E desde el esquema R. Esto satisface el primer requerimiento. Para propagar la información de contexto E será recursivamente invocado.

22 El esquema de compilación E 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]

23 El esquema de compilación E (2) E [ e 0 + e 1 ] = E [ e 1 ] ++ E [ e 0 ] [Add] y similarmente para todos los operadores primitivos binarios E [ negate e] = E [ e ] ++ [Neg] E [ if e 0 e 1 e 2 ] = E [ e 0 ] ++ [Cond (E [ e 1 ] ) (E [ e 2 ] )] E [ e ] = C [ e ] ++ [ Eval ] otherwise El esquema R ahora se define como sigue: R [ e ] n = E [ e ] ++ [Update n, Pop n, Unwind]

24 Modificación de Unwind La máquina podría fallar al usar el nuevo compilador. Es entonces necesario introducir una nueva regla de transición para la instrucción Unwind : [Unwind] [a 0,..., a k ] : d h[a 0 : NGlobal n c] m => i a k : s d h m si k < m Esta nueva definición nos permite usar Eval para evaluar cualquier objeto a WHNF y no solamente números.

25 Nota La forma en que se implementó el contexto estricto en el compilador es simplemente una aplicación de la noción de herencia de atributos tratada en teoría de la compilación. No es posible en general determinar en tiempo de compilación si una expresión debe ser compilada en un contexto estricto o no. Acá se asumió un compromiso, tratando un conjunto limitado de expresiones - expresiones aritméticas - en forma especial.