Universidad Simón Bolívar Departamento de Computación y Tecnología de la Información CI4722 – Lenguajes de Programación III Abril-Julio 2010 Carnet: Nombre: Examen I (35 puntos) Antes de empezar, revise bien el examen, el cual consta de cuatro preguntas. Pregunta 0 Pregunta 1 Pregunta 2 Pregunta 3 Total 8 puntos 7 puntos 10 puntos 10 puntos 35 puntos Pregunta 0 — 8 puntos Siguiendo los esquemas de generación de código utilizados en clase (basados tanto en el texto de Scott, “Programming Language Pragmatics”, con gramáticas de árboles abstractos, como en el texto de Aho et al., “Compilers”, con herencia de etiquetas para expresiones booleanas), deseamos manejar una variante de las típicas instrucciones break y continue. En sus versiones básicas estándar, la instrucción break finaliza la iteración actual, mientras la instrucción continue reinicia la iteración actual, correspondiendo esto a reevaluar la expresión booleana que controla la terminación de una iteración-while y decidir según el resultado si se continúa iterando o no. En la variante que queremos implementar, tanto break como continue reciben como parámetro un entero positivo que determina a cuál de muchas iteraciones anidadas se refiere la instrucción. Así, en un punto del programa que se encuentre bajo al menos n iteraciones anidadas, break n finaliza la n-ésima iteración actual contando desde el punto del programa donde aparece el break hacia el exterior, mientras continue n reinicia la n-ésima iteración actual contando de la misma manera. El parámetro debe ser estrictamente positivo. Note que break 1 y continue 1 corresponden a la versión básica estándar de estas instrucciones. Construya entonces esquemas de generación de código para estas nuevas instrucciones, utilizando las mismas convenciones manejadas en clase (tomadas, como se señaló anteriormente, de los textos de Scott y de Aho et al.). Puede agregar nuevos atributos sobre la gramática de árboles abstractos si Ud. lo considera necesario. Considere sólo las estructuras de control de la gramática de árboles abstractos que se presenta a continuación, y considere que el esquema de generación de código para expresiones booleanas se mantiene tal como fue presentado en clase (tomado del texto de Aho et al.). En el árbol del programa principal se omite adrede el componente de tabla de símbolos, por ser irrelevante para el tema de esta pregunta. La gramática es: PRINC : AProg → AInstr SEC : AInstr → AInstr AInstr COND1 : AInstr → AEBool AInstr COND2 : AInstr → AEBool AInstr AInstr ITER : AInstr → AEBool AInstr BRK : AInstr → int (n) CNT : AInstr → int (n) , donde PRINC se refiere al programa principal (ignorando su tabla de símbolos), SEC es secuenciación, COND1 es un condicional con una sola rama (esto es, sin else, o con un skip en el else, según Ud. prefiera verlo), COND2 es un condicional con dos ramas (típico condicional con else), ITER es una iteración estilo while, BRK se refiere a la instrucción break, y CNT se refiere a la instrucción continue. En las últimas dos producciones puede suponerse que los enteros son estrictamente positivos y válidos según su ubicación en el programa, ya que, de lo contrario, el programa habría sido rechazado por las fases anteriores del front end. Nota: Puede ignorar el atributo correspondiente al próximo registro a ser utilizado en evaluación de resultados temporales. Pregunta 1 — 7 puntos Considere el pasaje de parámetros por valor-resultado a un subprograma, utilizando la pila para enviar y recibir parámetros. Se desea que, dado un árbol de expresión con l-value correspondiente a un parámetro real, Ud. indique el código que se debe generar para empilar y desempilar a éste. Ahora bien, sabemos que el l-value puede cambiar entre el inicio y el final del subprograma, por lo cual es importante conocer en qué momento debe ser evaluado éste según la definición del lenguaje. Suponga entonces que la definición del lenguaje señala que el l-value debe ser utilizado como se indica a continuación: (i) al inicio, su valor antes de entrar al subprograma, usando éste para pasar el parámetro real al formal; y (ii) al final, su valor luego de salir del subprograma, usando éste para pasar el parámetro formal al real. Construya entonces funciones empilar y desempilar que reciban los siguientes dos parámetros: pr : ALVal , el árbol de expresión con l-value del parámetro real a pasar; y px : int , el típico atributo “próximo registro” (tomado de los esquemas de generación de código presentados por el texto de Scott, “Programming Language Pragmatics”); y que devuelvan un string correspondiente al código deseado. Suponga que el parámetro para el que se está manejando el empilamiento/desempilamiento es de tipo entero, que un entero ocupa 4 bytes, y que sp es el nombre del registro que se usa como stack pointer. Suponga también que cuenta con un subprograma genCod (in lv : ALVal ; in px : int ; out cd : string ) , el cual, dado el árbol lv y el “próximo registro” px , devuelve el código cd que evalúa a lv . Por último, puede suponer, como de costumbre, que el registro de bajo nivel correspondiente a “próximo registro” está disponible. Si necesitase más registros, debe considerar la posibilidad de que no haya más disponibilidad y manejar este hecho apropiadamente. Pregunta 2 — 10 puntos Suponga que se tiene el siguiente fragmento de código fuente x := x + a[i] * b[i]; y := y + a[i] * b[i]; x := x * y en un contexto con las siguientes variables globales: int x, y; int[10] a, b; int i; Suponga también que estamos utilizando el mismo esquema de generación de código intermedio que fue utilizado en clase (basado en el texto de Scott, “Programming Language Pragmatics”), en el que: se asume que se dispone de una cantidad infinita de registros virtuales v0, v1, v2, etcétera, para almacenar resultados temporales; en el código generado no se incluye verificación de errores de indexación inadecuada de arreglos; las variables globales son referenciadas por su nombre; etcétera. Responda entonces lo siguiente: (a) Muestre el código de nivel intermedio que se genera para el fragmento de código fuente dado arriba. (b) Tomando al código generado en (a) como un bloque básico, aplique a éste la técnica de eliminación local de redundancia vista en clase (presentada en el texto de Scott bajo el nombre de “numeración local de valores”). Muestre el código resultante y los contenidos finales de los diccionarios utilizados. Nota: La suma y la multiplicación tienen la precedencia usual. Pregunta 3 — 10 puntos Utilizando el mismo lenguaje y las mismas convenciones del texto de Nielson et al., “Principles of Program Analysis”, se tiene el siguiente programa: [ j := 0 ]0 ; [ k := j ]1 ; while [ k < 10 ]2 do ( [ j := j + 1 ]3 ; [ k := k + 1 ]4 ); [ n := k ]5 . Considere el problema LV de variables vivas (“live variables”) presentado en el texto de Nielson et al., y suponga que a la salida del programa la única variable viva es n (lo cual es equivalente a suponer que estamos procesando el cuerpo de una subrutina que sólo maneja variables locales y cuyo único parámetro de salida es n). Considere ahora una variante LV’ de este problema bajo la cual una variable a la que se le hace una asignación está “realmente viva” si el valor que se le está asignando realmente contribuirá al resultado final del programa. Esta variante del problema refina el análisis para aumentar las posibilidades de detección de “código muerto” (asignaciones innecesarias que pueden ser eliminadas). Responda entonces lo siguiente: (a) Muestre el sistema de ecuaciones del problema LV’ correspondiente al programa dado. (b) Resuelva el sistema de ecuaciones de LV’ dado por Ud. en (a), utilizando alguno de los algoritmos de punto fijo analizados en clase (tomados de los textos de Nielson et al., “Principles of Program Analysis”, y de Aho et al., “Compilers”). Muestre los resultados temporales de la aplicación de su algoritmo. (c) Indique qué asignaciones pueden ser eliminadas por ser consideradas “código muerto” según los resultados obtenidos para LV’ en (b). Justifique su respuesta.