Lab 10 (CodeGen II)
Last updated
Was this helpful?
Last updated
Was this helpful?
Para este laboratorio van a completar la generación de código para el lenguaje Viper
Los archivos necesarios para este laboratorio los pueden encontrar en el siguiente enlace:
Por favor revisen el siguiente enlace para recordarse cómo está estructurado el Activation Record en Viper:
Si no se recuerden de la firma del método code
aquí está nuevamente:
Los parámetros tienen el siguiente significado:
locals: un contador que nos ayuda a saber cual es el siguiente espacio disponible en el área de variables locales dentro del activation record en relación al frame pointer.
O: Es nuestra tabla de símbolos, esta nos sirve para guardar en que posición del activation record se encuentran los símbolos (variables locales y parámetros) en relación al frame pointer.
p: Nos sirve para imprimir el código generado a un archivo.Activation Record
Cuando ustedes implementaron el nodo Function se dieron cuenta que fue necesario llamar al método locals()
para preparar parte del prólogo de la función (reservar espacio). Esta función solo tiene una tarea en específico y es decir la cantidad de espacio necesario a reservar dentro del activation record para todas las variables locales (sin contar parámetros formales). Esta función lo que hace es visitar cada nodo dentro de los statements de una función (recursivamente) y verificar si son una instancia del nodo Declaration e ir incrementando la cantidad de espacio con +1
cada vez que esto suceda. Si ustedes revisan la implementación de este método realmente hace más cosas, por ejemplo en el caso de un If:
locals()
devolvería 2 en vez de 1, porque en tiempo de compilación no se sabe si se va a tomar la consecuencia o la alternativa (a menos que se hiciera una optimización que por el momento está fuera del objetivo de este laboratorio), por lo que es necesario devolver el máximo entre los dos bloques de statements del If. Una implementación un poco más ineficiente en términos de memoria sería que locals()
devolviera 3 para x
, y
y z
, esto es ineficiente porque el espacio que se reserva para x
y y
se puede reutilizar para z
porque el scope de la consecuencia no es el mismo ni se comparte con el de la alternativa. Ustedes son libres de implementar esto en su proyecto como ustedes crean que les permita terminarlo al 100%.
Otra cosa importante que tienen que tener claro, es que el único nodo que puede aumentar la cantidad de espacio a reservar en Viper es Declaration y los lugares donde puede aparecer este nodo es dentro de una función, dentro de los bloques de un If, y dentro del bloque de un While. En COOL ustedes van a tener que considerar a Let y a Typecase como los responsables de incrementar la cantidad de espacio.
Bien, ahora que ya está un poco más claro como funciona locals()
es hora de hablar un poco de como utilizar ese espacio (lo que necesitan realmente para este laboratorio). Como el espacio se reserva en el activation record de la función, para referenciar una posición de ese espacio de manera fácil es utilizando el frame pointer (el registrofp
). Volvamos al ejemplo del laboratorio pasado:
Si se quisiera acceder al valor de la variable var
bastaría con hacer lo siguiente en el ensamblador:
Como COOL es orientado a objetos y pueden haber atributos en cada clase, cuando se hace referencia a un identificador, este podría ser un atributo o variable local por lo que para hacer referencia a su espacio de memoria podría ser utilizando el frame pointer o el registro s0
que guarda el self object.
Se les provee como en el laboratorio pasado un compilador completo de Viper:
pueden probar esto para tener una referencia de como es que se tiene que generar código para los diferentes nodos de este laboratorio (pueden hacer lo mismo en el proyecto con coolc-rv
).
Para correr un archivo compilado, pueden utilizar:
Adicionalmente pueden utilizar los métodos y variables estáticas de la clase CgenSupport para ayudarse a generar código.
Los nodos restantes que ustedes tienen que completar son los siguientes:
Assign
Declaration
Equal
LessEqual
LessThan
Not
Id
If
While
Para que tengan una idea de que tanto tienen que realizar, aquí van unas estadísticas:
La generación de código para assign es de pocos pasos. Primero se tiene que mandar recursivamente a code
de la expresión del nodo y cuando regrese el resultado estará en a0
, luego determinar a que distancia del frame pointer esta guardada esa variable utilizando a O
y por último guardar el resultado en ese espacio.
Utilicen O.lookup(name)
para determinar esa distancia. ¿Por qué lookup y no probe?...
En Declaration casi se tiene que hacer lo mismo que en Assign, la diferencia es que en Declaration se está creando una variable y se le está asignando un valor inicial y en Assign la variable ya fue creada y solo se le está asignado un nuevo valor. Tienen que llamar a code
de init
recursivamente, el resultado cuando regrese estará en a0
. Tienen que reservar el siguiente espacio disponible en el área de variables locales para la variable que está siendo creada y agregar esta información en la tabla O
. Luego guardar a0
en ese espacio. Por último como este nodo no es una expresión, tenemos que cargar en a0
el valor de void que es simplemente un 0.
Utilicen locals.inc()
para reservar el siguiente espacio y O.add(name, offset)
para guardar esa información en la tabla de símbolos.
Para cargar en a0
void pueden hacer lo siguiente:
Para LessEqual, LessThan e Equal, la generación de código es igual en Viper. Primero tienen que crear una nueva etiqueta, llamar recursivamente a code
del primer operando, hacer push de a0
en el stack, luego llamar recursivamente a code
del segundo operando, mover el resultado al registro t1
hacer pop del stack hacia el registro t2
, cargar en a0
el boolean true
efectuar la comparación correspondiente (<, <=, ==) utilizando los registros t1
y t2
, cargar el boolean false
en caso de que la comparación no se cumpla y por último definir la etiqueta que crearon.
Para crear una nueva etiqueta pueden utilizar el método nextLabel()
de CgenSupport. Para definir la etiqueta pueden utilizar emitLabelDef(label, p)
también de CgenSupport.
El Not es similar a las comparaciones, lo único es que aquí solo mandamos a llamar a code
del único operando que hay. Luego se tienen que realizar los mismos pasos que se mencionaron anterior mente.
Van a encontrar útil el método CgenSupport.emitBeqz
Como sabemos la distancia desde el frame pointer en la que el Id fue guardado podemos utilizar la tabla O
para obtener esa distancia y cargar en el registro a0
el valor de ese espacio en memoria.
Primero se tienen que crear dos etiquetas, una para iterar y otra para salir del ciclo, definir una de las primeras etiquetas (la de iterar), hacer code
de la condición, comparar a0
con 0 para saltar a la etiqueta para salir (recurden 0 es falso y 1 verdadero), crear un nuevo scope para el cuerpo del ciclo, hacer code
del body (para esto tienen que crear una copia del contador de variables locales), salir del scope que se acaba de crear, saltar a la etiqueta que nos sirve para iterar, definir la etiqueta para salir, por último cargar void
al registro a0
.
Utilicen el método locals.copy()
para crear una copia del contador de variables locales.
El If es similar al While, primero se crean dos etiquetas, una para la alternativa y otra para la salir de la consecuencia, luego se manda a llamar a code
del predicado del nodo, luego se tiene que comparar a0
con 0 para saltar a la alternativa, luego se crear un scope y se manda a llamar a code
de then
(se tiene que crear una copia de locals como se hizo en el While), salir del scope que se acaba de crear, saltar a la etiqueta de salir de la consecuencia, definir la etiqueta de la alternativa, crear un nuevo scope (aquí también se tiene que copiar locals), salir del scope que se acaba de crear, definir la etiqueta de salir y por último cargar en a0
el valor void
.
Listo han terminado de generar código para un lenguaje de bastante modesto, pero que puede hacer cosas interesantes todavía, por ejemplo:
Para probar lo que genera su implementación pueden utilizar lo siguiente:
En la carpeta examples/ hay un par de ejemplos.
Para probar su implementación pueden utilizar este comando:
Si todo está bien, tendrían que obtener la siguiente salida: