Itanium for Dummies (Organización del Computador II)

De Cuba-Wiki
Revisión del 01:41 16 nov 2006 de 24.232.28.173 (discusión) (+ Categorizacion)

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