2- memoria de pares:

La estructura Jenoassembler-hax dispone de dos memorias principales muy diferentes (aunque ya veremos que en realidad son tres), una de ellas es la memoria del programa, donde contendremos el código que se va a ejecutar y que es capaz de localizar nativamente la dirección de etiquetas y de procedimientos. Ésta memoria apenas tenemos que administrarla una vez cargado el programa, tan sólo modificaremos el puntero con las instrucciones de flujo. La otra memoria es llamada memoria de pares, puesto que en realidad son dos memorias emparejadas, así podremos guardar parejas de "nombre"-"valor" que pueden ser accesadas usando su nombre como parámetro. El primer bloque de la memoria de pares, la que contiene los nombres es capáz de buscar automáticamente y devolver el valor contenido en el otro bloque. En síntesis es un sistema que funciona más o menos como las variables de toda la vida. Vamos a efectuar una operación compuesta de ejemplo usando la memoria de pares para los datos temporales:

(2+3) + (3-2)

o lo que es lo mismo en notación infija postasignación:

2+3[a]3-2[b]a+b[res]

// 1- efectuamos la primera operación y guardamos el resultado en la memoria de pares
//    su nombre será tmp01, usaremos el prefijo de literal puesto que tmp01 es un nombre, no un valor dinámico
mode #int
set0 #2
set1 #3
suma
set #tmp01,core_reg2

// 2- tenemos el valor contenido en tmp01
//    seguimos con la siguiente operación y la guardamos como tmp02:
set0 #3
set1 #2
resta
set #tmp02,core_reg2

// 3- ahora deberemos asignar los dos resultados a los dos registros principales para operar con ellos
//    los resultados intermedios están contenidos den tmp01 y tmp02:

set1 tmp02
set0 tmp01
suma

// 4- listo, ahora tenemos el resultado final en core_reg2, podemos escribirlo o usarlo:
busout #3,core_reg2

Cada vez que usamos la instrucción set hay que comprobar si el nombre de variable existe o no, y como es obvio, cada lectura de un valor de estos efectua otra búsqueda. No son especialmente lentas, en realidad depende del número de objetos contenidos en la memoria de pares, es decir, su coste es O(n) donde n es el número de nombres en memoria, así que el uso de registros en lugar de memoria de pares resulta más rápido siempre y cuando 



 
La máquina virtual haxbox dispone de tres cadenas de registros de uso general, llamados reg0, reg1 y reg2. El hecho de que sean tres y no dos o diez se explica fácilmente, dos de ellos están destinados a contener operandos y el tercero el resultado de cada operación básica, ya sea con números o con cadenas. Así pues si queremos diseñar un código que suma 2 + 3 el procedimiento sería el siguiente:

// 1- declarar el modo de los registros reg0 y reg1 
//    para operar deben de ser iguales:
mode0 int
mode1 int


// 2- introducir los operandos a los registros respectivos:
set0 2
set1 3


// 3- operar
suma

// 4- ahora tenemos el resultado contenido en reg2 y podemos enviarlo a la consola de salida
//    core_reg2 es la palabra reservada para recuperar el valor de reg2
busout 3,core_reg2


El código anterior funciona bien, pero no está en absoluto optimizado. Haxbox es muy permisivo en ciertos sentidos, en el punto 2 (set0 2 y set0 3) queremos guardar los operandos en los registros generales, pero el intérprete va a tratarlos como si fueran valores dinámicos, así que comprobará si pueden ser interpretados como variables, direcciones de memoria, fracciones de array o cualquier otro registro de la estructura. Puede que apenas notemos diferencia, pero esto llevará una fracción de tiempo que podemos ahorrar avisando a la máquina de que se trata de valores literales con el prefijo #, lo usaremos también en la instrucción modeX para indicar el modo y busout para indicar el satelite con el cual comunicar, no asi en el caso del segundo parámetro, ya que core_reg2 forma parte de la estructura dinámica:


// 1
mode0 #int
mode1 #int


// 2
set0 #2
set1 #3


// 3
suma

// 4
busout #3,core_reg2

Al usar el prefijo literal (#) conseguimos saltarnos una serie de búsquedas de valor dinámico que no darán ningún resultado y por lo tanto devolverá el mismo valor de entrada, por lo que se trata de trabajo inútil y debemos evitarlo. Por otro lado existe una órden para definir los modos de los tres registros en un mismo valor, en lugar de mode0 y mode1 podemos usar mode [tipo] lo cual nos ahorra una línea, así tendremos:


mode #int
set0 #2
set1 #3
suma
busout #3,core_reg2

Ahora si podemos decir que tenemos el código optimizado, la ejecución de este código dará como resultado una línea de texto en la consola en la que vendrá escrito el resultado de 2+3, es decir 5. Cada operación debe de ser efectuada de una en una y, como hemos dicho el resultado lo encontraremos siempre en core_reg2. Hemos dicho antes que haxbox dispone de tres cadenas de registros, en este caso sólo hemos usado el primer registro de cada cadena. Para cambiar entre unos registros y otros de cada cadena hay que modificar el valor del puntero de cada registro. Para ello existe la órden pX, donde x indica un identificador numérico (0, 1 o 2) y a la que le añadimos un parámetro que será la nueva posición del puntero. Imaginemos que queremos realizar una operación similar a la siguiente:

(2+3) + (3-2)

o lo que es lo mismo en notación infija postasignación:

2+3[a]3-2[b]a+b[res]

Como hemos explicado deberemos efectuar las operaciones de una en una y siempre con dos operandos, en este caso debemos efectuar tres operaciones, dos de ellas con valores literales y otra última con los resultados de las mismas. Tres registros se nos quedan cortos si queremos efectuar esta operación sin desperdiciar la memoria por eso haxbox dispone de cadenas de registros. Debemos usar la instrucción p0, p1 y p2 para guardar temporalmente los valores que nos interesan. El problema se podría resolver así:


// 1- el código comienza en p0=0, p1=0 y p2=0
//    efectuamos la primera operación:
mode #int
set0 #2
set1 #3
suma

// 2- tenemos el resultado de la suma contenido en reg2 p2 0
//    seguimos con la siguiente operación tras cambiar de posición p2:
p2 #1
set0 #3
set1 #2
resta


// 3- ahora deberemos asignar los dos resultados a los dos registros principales para operar 
//    los resultados intermedios están contenidos den core_reg2 p2 0 y core_reg2 p2 1:

set1 core_reg2
p2 #0
set0 core_reg2
suma


// 4- listo, ahora tenemos el resultado final en core_reg2 p2 0, podemos escribirlo o usarlo:
busout #3,core_reg2

// 5- hemos modificado varios registros de la cadena reg2, a veces necesitamos estar seguros de 
//  que  una cadena de registros está vacía para no generar errores, para eso usamos flashX o flash
flash


Esta vez hemos terminado usando flash (también existe flash0, flash1 y flash2), ésta órden símplemente limpia toda la cadena de registros, así no tendremos sorpresas inesperadas al continuar con el código puesto que nos aseguramos de que tenemos la memoria limpia. Existen 6 instrucciones importantes para manejar los registros genearles, éstos son: (X significa un identificador de cadena de registros, 0 para reg0, 1 para reg1 y 2 para reg2)


flash       para vaciar todos los registros de reg0, reg1 y reg2 a la vez
flashX     vacía todos los registros de la cadena de registros regX
setX        asigna un valor al registro señalado por regX
setX+      asigna valores múltiples separados por coma, uno en cada pX de regX
pX           cambia la dirección a la que apunta el puntero (pX) de regX
mode      cambia el modo en el que se operan los datos de todos los registros
modeX    cambia el modo en el que se operan los datos del registro regX
 
También existen varias palabras reservadas para leer datos y otra información de la estructura de registros generales, estas son las más importantes:

core_regX    devuelve el valor contenido en regX en el registro apuntado por pX
core_pX        devuelve un número que indica la posición actual del puntero de regX (pX)
core_numX   devuelve un número que representa la cantidad de objetos no nulos dentro de regX
core_joinX    devuelve una cadena de texto con todos los registros de regX concatenados

Abdab software