|
Grado en Ingeniería Informática
Procesadores de Lenguajes
Curso 2022/2023
|
Práctica 1 |
|
Características generales del lenguaje Tinto |
|
Objetivos |
- Presentar el lenguaje de programación Tinto
- Describir el proceso de compilación y ejecución de programas en
Tinto
- Presentar el procesador MIPS
- Presentar el simulador QT-SPIM
|
|
Características generales del
lenguaje de programación Tinto |
|
Tinto es un lenguaje de programación imperativo orientado a procesos (tipo
C). El objetivo de Tinto es definir un lenguaje de programación muy simple
que sirva de apoyo a la docencia de la asignatura de Procesadores de
Lenguaje. En cada uno de los aspectos de diseño de Tinto (especificación
léxica, sintáctica y semántica, tipos de datos, instrucciones, etc.) se ha
optado por simplificar el lenguaje de forma que sea posible afrontar el
desarrollo de un compilador tanto de forma manual como con la ayuda de
herramientas. De esta forma, el lenguaje Tinto servirá como ejemplo en las
sesiones prácticas en las que se explica cómo desarrollar un analizador
léxico, un analizador sintáctico, etcétera.
A continuación se muestra un ejemplo del contenido de un fichero fuente
descrito en lenguaje Tinto:
|
import Math;
library Ejemplo {
public int MaximoComunDivisor(int a,
int b) {
int mcd =
Math.min(a,b);
while(mcd>0) {
if( a%mcd == 0 && b%mcd == 0) return mcd;
mcd = mcd - 1;
}
return 1;
}
} |
Algunas de las características de Tinto son las siguientes:
Tinto incluye un número reducido de palabras reservadas (15 en total).
Comparado con C (32 palabras reservadas), C++ (50 palabras reservadas) o
Java (53 palabras reservadas) se puede valorar el esfuerzo de simplificación
realizado en el diseño del lenguaje. Las palabras reservadas de Tinto son
las siguientes: boolean, char, else, false,
if, import, int, library, native, private, public, return, true,
void y while.
El lenguaje incluye tan solo tres tipos de datos: int, char y
boolean. Se asume que todos ellos serán almacenados en registros de
32 bits. El tipo char se considera descrito en código ASCII de 8 bits
almacenados en el byte menos significativo (los 24 bits más significativos
deben tener el valor 0). El tipo boolean utiliza el valor 0 para
indicar falso y el valor 1 para indicar verdadero. Se considera también el
tipo void para indicar que una función no devuelve ningún dato.
Las funciones de Tinto están formadas por una lista de instrucciones.
Las instrucciones incluidas en el lenguaje son: declaraciones de variables
locales (se admite la asignación en el momento de la declaración y la
declaración conjunta de una lista de variables del mismo tipo),
instrucciones de asignación simple (por medio del operador = ),
instrucciones de ejecución de una función, instrucciones condicionales (if-else),
bucles (while) y finalización de las funciones (return).
Tinto incluye los siguientes operadores aritméticos: suma ( + ), resta ( - ), multiplicación ( * ), división ( / ) y
módulo ( % ).
Por su parte, los operadores lógicos incluidos en
Tinto son los siguientes: and con
cortocircuito ( && ), or con cortocircuito ( || ) y not ( ! ).
Los operadores relacionales permiten establecer
comparaciones entre los valores de distintas expresiones. Tinto
incluye cuatro operadores relacionales que pueden aplicarse a
expresiones de tipo int y char: mayor ( > ), menor ( < ),
mayor o igual ( >= ) y menor o igual ( <= ). Además se incluyen los
operadores igual ( == ) y distinto ( != ) que son válidos para todos los
tipos de datos (int, char y boolean).
Tinto permite definir literales para cada tipo
de datos: valores
de tipo int (en formato decimal, octal, hexadecimal y binario), valores de tipo
char (entre comillas simples que pueden incluir caracteres imprimibles,
caracteres de escape y caracteres en formato octal) y valores de tipo
boolean (true o false).
Los ficheros descritos en el lenguaje Tinto definen bibliotecas de
funciones. Cada fichero contiene una lista de bibliotecas importadas
(cláusulas import) y una biblioteca (library) cuyo nombre debe
coincidir con el nombre del fichero. La biblioteca está formada por una
lista de funciones que pueden ser públicas o privadas. El código de las funciones
puede contener llamadas a otras funciones públicas o privadas de la misma biblioteca.
También pueden contener llamadas a funciones públicas de otras bibliotecas.
En este último caso, para identificar a la función llamada se utiliza el nombre
de la biblioteca, seguido de un punto y del nombre de la función. Para poder
utilizar las funciones de una biblioteca, ésta debe haber sido importada
previamente en el fichero. El lenguaje permite definir también bibliotecas
nativas (native) que están formadas por una lista de declaraciones
de funciones. En este caso, el código de las funciones está desarrollado
directamente en ensamblador y no se describe en el fichero fuente. Gracias a
estas bibliotecas nativas se pueden utilizar funciones que usan
instrucciones de ensamblador específicas que no genera el compilador, como
llamadas al sistema (por medio de la instrucción syscall).
|
|
Proceso de compilación
y ejecución de programas en Tinto |
|
El compilador de Tinto se encuentra en el archivo
tintoc.jar. Para
realizar un proceso de compilación se debe ejecutar este archivo
desde una línea de comandos:
|
java -jar tintoc.jar options
|
El objetivo del compilador es compilar el archivo Main.tinto
que debe contener la función Main() que marca el comienzo
de la aplicación programada. A partir de esta biblioteca se
analizan y compilan el resto de bibliotecas importadas. Por
defecto el directorio de trabajo es desde aquel en el que se
ejecuta el comando de compilación, pero se puede modificar por
medio de las opciones. Por defecto las bibliotecas importadas
deben encontrarse en el directorio de trabajo pero se pueden
configurar otros directorios de búsqueda por medio de la opción
-I. El resultado de la compilación es un
archivo con extensión ".s" con el código ensamblador que
desarrolla la aplicación programada. Por defecto, el archivo
generado como salida se denomina "Application.s" pero
este nombre se puede modificar por medio de la opción
-o. Por medio de la opción -v
se le puede indicar al compilador que genere archivos con la
descripción del código intermedio en modo texto. Las opciones que
admite el compilador son las siguientes:
-
Path, indica directorio de trabajo.
-
-o Name, indica el nombre del fichero de salida (sin la extensión ".s").
-
-I Path, añade un directorio de búsqueda de archivos importados.
-
-v, indica que se generen los ficheros de código intermedio
(por defecto no se generarán).
La distribución del compilador de Tinto contiene algunos ejemplos
y un directorio llamado Utils que incluye
una biblioteca llamada Console. Esta biblioteca contiene
varios métodos de
presentación de información en la consola. Para utilizar correctamente
esta biblioteca es aconsejable añadir el directorio Utils
como directorio de búsqueda por medio de la opción -I. El
resultado de la compilación es un fichero escrito en ensamblador
del procesador MIPS. Para ejecutar este código se utiliza el
simulador Qt-Spim.
|
|
Características del procesador MIPS
|
|
MIPS (Microprocessor without Interlocked Pipeline Stages) es el nombre de
una familia de procesadores RISC desarrollados por
MIPS Technologies. La empresa concede
licencias a otros fabricantes para integrar la arquitectura MIPS en sus
productos (por ejemplo, CISCO, SGI, Toshiba, Sony, ...). La arquitectura
MIPS es la base de los procesadores de muchos routers de CISCO, de las
estaciones de trabajo de SGI, de las videoconsolas Nintendo 64, PlayStation,
PlayStation 2, PlayStation Portable, de las impresoras de HP, etc.
Algunas de las características más interesantes de la arquitectura MIPS
son las siguientes:
-
Describe procesadores RISC, cuyo código es más fácil de generar para un compilador.
-
Está muy bien documentada
(Arquitectura)
(Conjunto de instrucciones)
(Arquitectura de recursos privilegiados)
-
Es una arquitectura de 32/64 bits.
-
Contiene una unidad en coma flotante que admite los
formatos IEEE Standar 754 de 32 bits (float) y de 64 bits (double).
-
Podemos simular el código ensamblador por medio del simulador QT-SPIM.
La arquitectura cuenta con una unidad de propósito generar formada por 32
registros de 32 bits ($0 a $31)
y una unidad de coma flotante con 32 registros de 32 bits ($f0 a
$f31) que se pueden utilizar como 16 registros de 64 bits para
almacenar datos en formato double.
Los registros de propósito general pueden usarse libremente en las
instrucciones, aunque hay que tener en cuenta que el registro $0
está cableado a 0 y que el registro $31 almacena la dirección de
retorno cuando se ejecuta una instrucción JAL (jump and link). El
resto de registros se puede utilizar libremente aunque se
recomienda seguir un convenio de uso de los registros que indica
el tipo de dato que se debe almacenar en cada registro. Este
convenio incluye utilizar una serie de alias para referirse a los
registros. La siguiente tabla describe el convenio de uso de los
registros de MIPS.
El conjunto de instrucciones es sencillo y potente
(Instrucciones CPU)
(Instrucciones FPU)
(Tabla resumen). La descripción completa
de cada instrucción se encuentra aquí.
|
|
El simulador Qt-Spim
|
|
SPIM es un simulador que ejecuta programas descritos en ensamblador de
MIPS32. La herramienta ha sido programada por
James Larus
y se distribuye libremente. La versión más reciente se denomina QT-SPIM y se puede descargar desde
la página oficial.
El simulador desarrolla un conjunto mínimo de llamadas al sistema que
permiten acceder a una consola y acceder a ficheros. La tabla de llamadas al
sistema es ésta.
La ventana principal de Qt-Spim muestra tres paneles: el panel de la iquierda muestra el
contenido de los registros del procesador, el panel de la derecha muestra el
contenido de la memoria y el panel inferior se utiliza para mostrar mensajes.
El panel de registros tiene dos pestañas que permiten seleccionar entre los registros
de propósito general (GPR) y los registros de la unidad de coma flotante (FPR).
El panel de memoria tiene también dos pestañas que permiten mostrar el segmento
de texto (que contiene las instrucciones del programa a ejecutar) y el segmento de datos
(que contiene la pila y la memoria estática y dinámica).
Para ejecutar un programa (el fichero Application.s, en nuestro caso) en
primer lugar hay que cargarlo (Opción File->Load file). A continuación se puede ejecutar
completamente (F5) o paso a paso (F10). También es posible establecer puntos de parada
(breakpoints) y ejecutar el programa a saltos.
El proceso de ejecución se configura por medio de la opción (Simulator -> Settings).
En nuestro caso, las opciones "Enable Delayed Branches" y "Enable Delayed Loads" deben estar
marcadas para simular el procesador de forma realista. La opción "Accept Pseudo Instructions"
tambien debe estar marcadas, ya que el ensamblador generado por el compilador de Tinto
utiliza algunas pseudo-instrucciones. Es importante desmarcar la opción
"Load Exception Handler" ya que nuestro fichero Application.s ya contiene el código
de las excepciones.
El simulador contiene una consola en la que se pueden mostrar e introducir datos.
|
|
Ejercicios |
- Descargar e instalar el simulador QT-SPIM.
- Descargar el código de la práctica y descomprimirlo en un directorio.
- Ejecutar y comprobar los resultados de los ejercicios de ejemplo incluidos en el directorio examples.
- Escribir un programa que devuelva el número de la posición 5 de la sucesión de Fibonacci.Compilarlo, ejecutarlo y comprobar que es correcto.
|
|