Ensamblador de MIPS
Universidad Galileo
Diego José Figueroa
Ejecución de un programa
¿Qué es un intérprete?
Interpretación vs Compilación
Un intérprete ejecuta un programa, instrucción por instrucción.
¿Qué es un compilador?
Interpretación vs Compilación
Un compilador analiza un programa escrito en cierto lenguaje, y lo traduce a otro lenguaje, normalmente de bajo nivel.
Ejecutando un programa
El rol del Ensamblador
-
Los compiladores generan código en lenguaje ensamblador, porque:
- Es más simple.
- Podemos utilizar el mismo ensamblador para varios compiladores.
- Dejamos los detalles de la codificación y acceso al hardware al ensamblador.
El rol del Ensamblador
- Más razones:
- Separación de responsabilidades, el compilador se encarga de traducir hacia ensamblador, y el ensamblador de traducir a binario.
- Podemos intercambiar un ensamblador por otro, sin ningún problema.
- Es más fácil generar texto, sin preocuparnos por el endianness (big-endian, little-endian, ...).
El rol del Ensamblador
- Responsable de:
- Conversiones de MAL a TAL
- Calcular desfases para saltos condicionales.
- Dejar referencias de saltos y llamadas a funciones para el Linker.
- Codificar todo a binario.
- Leer y aplicar directivas.
- Generar object file (archivo de salida).
Directivas
- Comandos para el ensamblador.
- No generan instrucciones para la máquina.
- .text
- .data
- .asciiz
- etc.
Pseudo-instrucciones
- El ensamblador debe traducir estas instrucciones a instrucciones reales.
- Reemplaza la pseudo-instrucción, por una o más instrucciones reales.
- Utiliza $at para almacenar posibles resultados intermedios.
Codificando
- Para instrucciones simples (aritméticas, lógicas, etc.)
- Es el caso más fácil, la instrucción incluye todo lo necesario para codificarla (nombre de los registros, operandos, etc.)
- Para saltos condicionales
- Relativos al $PC.
- Se deben calcular hasta después de resolver las pseudo-instrucciones.
Saltos condicionales
¿Cómo calculamos los offsets?
- Utilizamos una estructura de datos, llamada Tabla de Símbolos.
- Parecido a un hash.
- La llave es un identificador numérico.
- El valor es la dirección a la que una etiqueta apunta.
Tabla de símbolos
- Cuando encontramos la declaración de una etiqueta:
- Asociamos esa etiqueta a un identificador en la tabla.
- Apuntamos la dirección de la etiqueta bajo ese identificador.
- Cuando encontramos una referencia a una etiqueta:
- Reemplazamos la etiqueta por el identificador asociado.
- Esto se hace sin importar si la etiqueta ya se conocía o no.
Cálculo de direcciones
-
Asumimos que comenzamos en 0:
- El área de texto comienza en 0.
- El área de data también comienza en 0.
- El Linker se encargará de colocar cada segmento en su dirección correspondiente, por ejemplo:
- Área de texto en 0x00400000
- Área de data en 0x10010000
Branch Delay Slot
- Optimización para arquitecturas con Pipelining.
- Consiste en siempre ejecutar la instrucción que sigue a un branch.
- Si el ensamblador puede poner una instrucción útil, lo hace.
- De lo contrario se coloca un nop (no operation).
- Sólo el ensamblador se preocupa por esto.
- Se hace para aprovechar el pipelining, cuando se logra determinar si se debe tomar el branch o no, ya hay una instrucción en el pipeline.
Ejemplo
fact: addi $sp $sp -8
sw $ra 4($sp)
sw $s0 0($sp)
mov $s0 $a0
beqi $s0 1 else
addi $a0 $a0 -1
jal fact
mul $v0 $v0 $s0
j done
else: li $v0 1
done: lw $s0 0($sp)
lw $ra 4($sp)
addi $sp $sp 8
jr $ra
Ejemplo
Ejemplo
Ejemplo
Ejemplo
Ejemplo
Ejemplo
Saltos condicionales
- Necesitamos una segunda pasada para calcular los offsets de todos los saltos condicionales.
- Es importante no modificar los saltos incondicionales, dejamos el ID, esperando para el linker.
Ejemplo
Codificación
- Ahora codificamos el programa a binario.
- El resultado es un archivo objeto (.o), el cual contiene el binario, y la tabla de símbolos.
-
La tabla es importante para el linker.
Linker
- El linker se encarga de juntar el programa y cualquier otro programa necesario, como librerías externas.
- Además, modifica las direcciones de inicio de cada archivo, por direcciones reales.
- Y actualiza las tablas de símbolo según corresponda.
- Actualiza los saltos incondicionales, apuntando la dirección real.
- Lo mismo para cualquier otra referencia absoluta.
Linker
- No necesita modificar los saltos condicionales, porque
- Estos saltos se expresan de forma relativa.
- Estos offsets ya fueron calculados, y no se ven afectados por el cambio de la dirección inicial.
- Esto se cumple para todo lo que no sea una referencia absoluta.
Linking
(comenzando en 0x0040000)
Último paso
- Ahora el ensamblador agrega una rutina de inicio (normalmente en la dirección 0x00400000 para MIPS), la cual configura el programa y hace el salto a la función main.
- El ensamblador indica esta función con la etiqueta __start.
- El programa está listo para ejecutarse.
Notas adicionales
- ¿El ensamblador sabe dónde están las instrucciones/datos de un programa respecto a otro?
- No, de eso se encarga el linker. El ensamblador sólo sabe de un programa a la vez.
- ¿El ensamblador ignora la instrucción nop?
- No, puede ser importante para el programador, por lo que la codifica como cualquier otra instrucción.
13 - Ensamblador de Mips
By Diego Figueroa
13 - Ensamblador de Mips
- 1,325