Itanium for Dummies (Organización del Computador II)
Por ahora este es un rejunte de ejercicios de dificultad progresiva que puede ayudarte a ir entendiendo como funciona Itanium. La mayoría de las funciones van a estar escritas en assembler pero las llamamos desde C. Comenzamos con lo más basico, como crear una funcion que ensamble y luego vamos agregando funcionalidad como por ejemplo tomar parametros, devolver resultados, etc.
Código C
Simplemente llama a las funciones que vamos a hacer en assembler, los extern le avisan al compilador que las funciones están en otro archivo de objeto y serán enlazadas por el linker.
#include <stdio.h> // estas funciones las hacemos en assembler extern int ia64_ret33(); extern int ia64_un_param(int); extern int ia64_suma(int, int); extern int ia64_mas_diez(int); extern int ia64_longitud(const char *); extern int ia64_copiar(char *, const char *); extern int ia64_toupper(char *); // algunas constantes y variables de prueba char buffer[20]; const char *str = "Hola Itanium!"; // punto de entrada int main(int argc, char *argv[]) { // devuelve 33 printf("ia64_ret33() : %d\n", ia64_ret33()); // devuelve el parametro de entrada printf("ia64_un_param(55) : %d\n", ia64_un_param(55)); // suma dos numeros printf("ia64_suma(44, 50) : %d\n", ia64_suma(44, 50)); // suma diez al numero pasado llamando otra funcion printf("ia64_mas_diez(21) : %d\n", ia64_mas_diez(21)); // calcula la longitud de un string printf("ia64_longitud('%s') : %d\n", str, ia64_longitud(str)); // copia un string printf("ia64_copiar(buffer, str) : %d\n", ia64_copiar(buffer, str)); printf("buffer : %s\n", buffer); printf("ia64_longitud(buffer) : %d\n", ia64_longitud(buffer)); // devuelve la cantidad de cambios que debio realizar printf("ia64_toupper(buffer) : %d\n", ia64_toupper(buffer)); printf("buffer : %s\n", buffer); return 0; }
Retornando valor
Esta función no hace casi nada, solo devuelve 33. Sirve para ver la sintaxis del ensamblador y cómo exportar una funcion de assembler a C en ia64.
// exporta la funcion .global ia64_ret33 .align 32 .section .text .proc ia64_ret33 ia64_ret33: mov r8 = 33 // en r8 se devuelve el resultado br.ret.sptk.many b0;; // retorno .endp ia64_ret33
En r8 se debe poner el valor de retorno de las funciones.
Usando alloc
Partiendo de la base anterior ahora agregamos una nueva función (a la section text) y la exportamos. Esta función devuelve el mismo parámetro que se le pasó como entrada.
.global ia64_un_param .proc ia64_un_param ia64_un_param: alloc loc0 = ar.pfs, 1, 1, 0, 0 mov r8 = in0 mov ar.pfs = loc0 br.ret.sptk.many b0 .endp ia64_un_param
La funcion alloc organiza la ventana de registros y reserva los registros que vamos a usar como entrada, variables locales y salida. Su uso es: alloc s = ar.pfs, in, local, out, rot donde s es el registro en el cual se salva la configuración anterior de ar.pfs, in es la cantidad de parámetros de entrada de la función, local es la cantidad de variables locales que vamos a usar, out es la cantidad de parametros que se van a pasar a una función que vamos a llamar (Importante: no confundir out con la cantidad de valores que devuelve una función porque no es eso). Rot es la cantidad de registros que van a rotarse (ver Software Pipelining), este numero debe ser múltiplo de 8.
Si llamamos a mas de una funcion, entonces en out tenemos que poner la mayor cantidad de parametros que tenemos que pasar. Por ejemplo, si llamamos a la funcion A que recibe 2 parametros y a B que recibe 1, out tiene que ser 2, y el parametro a B pasarlo en out0.
La parte loc0 = ar.pfs lo que hace es guardar el frame anterior en el registro loc0, entonces si llamamos a otra funcion dentro del cuerpo de esta, no lo perdemos. Como no llamamos a ninguna funcion en este caso, la linea que restituye el ar.pfs al final no es necesaria, pero es buena practica hacerlo siempre para no olvidarlo cuando si lo necesitemos.
Suma
Otro ejemplo de manejo de entrada/salida, esta función suma dos numeros y devuelve el restultado.
.proc ia64_suma ia64_suma: alloc loc0 = ar.pfs, 2, 1, 0, 0 add r8 = in0, in1 mov ar.pfs = loc0 br.ret.sptk.many b0 .endp ia64_suma
Llamando funciones
Esta función recibe un solo parámetro y llama a la función suma anteriormente definida. Devuelve x + 10 donde x es el parámetro de entrada.
.proc ia64_mas_diez ia64_mas_diez: alloc loc0 = ar.pfs, 1, 2, 2, 0 mov out0 = in0 mov out1 = 10 mov loc1 = b0 br.call.sptk.many b0 = ia64_suma mov b0 = loc1 mov ar.pfs = loc0 br.ret.sptk.many b0;; .endp ia64_mas_diez
Notar que como llamamos a una funcion usando el registro de direccion b0, estamos pisando la direccion de retorno a la funcion que llamo a ia64_mas_diez, por lo que es necesario guardarlo en un registro local al principio de la funcion y restaurar el valor original antes de hacer el ret.
Comparaciones
Calcula la longitud de un string (terminado en 0).
.proc ia64_longitud ia64_longitud: alloc loc0 = ar.pfs, 1, 2, 0, 0 mov r8 = 0 loop1: ld1 loc1 = [in0], 1 cmp.eq p1, p2 = loc1, r0 (p2) add r8 = r8, r0, 1 (p2) br.cond.dptk.few loop1 mov ar.pfs = loc0 br.ret.sptk.many b0;; .endp ia64_longitud
String copy
Copia un string de origen a un buffer destino, ambos pasados como parámetros.
.proc ia64_copiar ia64_copiar: alloc loc0 = ar.pfs, 2, 2, 0, 0 mov r8 = 0 loop2: ld1 loc1 = [in1], 1 st1 [in0] = loc1, 1 cmp.eq p1, p2 = loc1, r0 (p2) add r8 = r8, r0, 1 (p2) br.cond.dptk.few loop2 mov ar.pfs = loc0 br.ret.sptk.many b0;; .endp ia64_copiar
Comparación anidada
Esta función tiene una comparación un poco más dificil (que no está lograda de la mejor manera posible, creo). Lo que hace es pasar un string a mayúsculas.
.proc ia64_toupper ia64_toupper: alloc loc0 = ar.pfs, 1, 2, 0, 0 mov r8 = 0 loop3: ld1 loc1 = [in0], 0 cmp.eq p0, p2 = loc1, r0 (p2) cmp.ge.unc p0, p4 = 65, loc1 (p4) and loc1 = ~32, loc1 (p2) add r8 = r8, r0, 1 st1 [in0] = loc1, 1 (p2) br.cond.dptk.few loop3 mov ar.pfs = loc0 br.ret.sptk.many b0;; .endp ia64_toupper
Conclusión
Felicitaciones! Ya no sos mas un Dummy, ahora podés seguir con el resto de los apuntes o ejercicios que están en la pagina de Organización del Computador II =)
Apéndice: Reserva de datos
Si en un archivo de ensamblador Itanium escribís ".section .data" debajo podes reservar datos, la siguiente tabla explica como hacerlo.
label: .skip size | Reserva size bytes dejando la etiqueta label apuntando al primero |
label: data1 9 | Almacena los valores dados en bytes sucesivos de memoria (1 byte) |
label: data2 99 | Almacena los valores dados en words sucesivos de memoria (2 bytes) |
label: data4 | Almacena los valores dados en dwords sucesivos de memoria (4 bytes) |
label: data8 | Almacena los valores dados en qwords sucesivos de memoria (8 bytes) |
label: string "texto" | Almacena la representación ASCII del texto dado |
label: stringz "texto" | Almacena la representación ASCII del texto dado seguido de un 0 |
label: real4 | Almacena los valores dados en punto flotante de simple precision |
label: real8 | Almacena los valores dados en punto flotante de doble precision |