Programación en Lenguaje Ensamblador

-El Verdadero Lenguaje de las Máquinas-

SET: Cuando solo necesitas un SI o un NO

–Y una Técnica para hacer Comparaciones Multiples–

La gente tiende a confundir el juicio con la condena y la programación no es la excepción. De seguro alguno de ustedes ha escuchado esa frase de boca de fanáticos religiosos (conocidos en internet como ‘moralfags’ que dicen que “no hay que juzgar a otros”. Siempre he pensado que ese tipo de personas que dicen que no juzgan a otras personas es porque en realidad las condenan sin darse tiempo a juzgarlas y conocerlas primero.

En el mundo de la programación se tiende a hacer juicio y condena en la que probablemente sea la mas nefasta de las estructuras de control después del GOTO. Me refiero al IF. Una instrucción que se encarga en un solo paso de hacer pruebas lógicas, ver si la condición se cumple o no y luego dar o no un salto que definirá si un bloque de instrucciones va a ser o no ejecutado (o debería decir sentenciado). En esta entrada voy a contarles de una cierta instrucción que a diferencia de los fanáticos juzga sin condenar. Se trata de la instrucción llamada SET.

SET es una instrucción de los procesadores Intel muy peculiar, pues aunque no se trata de una instrucción condicional si se usa en pruebas condicionales. Su función es devolver un cero o un uno en su único operando que puede ser un registro o una posición de memoria. Para decidir si el operando va a ser 0 o 1 se usa el contenido del registro banderas EFLAGS en combinación con otro operando implícito en el propio mnemónico de la instrucción. Por si no lo recuerdan, el mnemónico de una instrucción es su nombre en letras legibles que traduce el compilador y que es completamente idependiente del opcode binario que es el que realmente se ejecuta.

La instrucción SET no se escribe sola. Su otro operando implícito son combinaciones de letras relacionadas con los bits del registro EFLAGS. Pueden encontrarse con mnemónicos tales como SETE, SETZ, SETC, SETO, SETAE, SETS, SETNBE, SETNS y otros mas. En realidad se trata de la misma instrucción. Las letras después de SET indican la condición que se ha de verificar en el registro EFLAGS, de hecho son los mismos sufijos de la instrucción de salto condicional JE, JZ, JC, etc. Aunque a diferencia del salto condicional, SET no salta ni altera el orden de ejecución del código. Tan solo pone en cero o uno el operando destino dependiendo de si la condición que preguntamos se cumplió o no.

Uso de SET en Comparaciones Multiples

Veamos a SET en acción. Digamos que en un juego tenemos una cantidad limitada de balas y que solo podemos disparar si tenemos suficientes balas en reserva. La lógica no es demasiado dificil, tan solo hay que leer el contador de balas restantes, si hay una o mas llamamos a la rutina de disparo y si no hay activamos el ruido del arma vacia. Pero supongamos que también a la pistola se le puede adaptar un silenciador, si la pistola tiene silenciador los villanos no sabrán donde está el jugador pero si no lo tiene podrán escuchar el tiro e iran tras el héroe. Con los IF necesitaríamos hacer un anidamiento en el que primero vemos si hay o no balas y luego si hay o no silenciador. Pero supongamos que puede darse el caso de que podamos o no estar bajo el agua. La pistola normal no podría ser disparada bajo el agua aunque estuviera completamente cargada. Por lo que tendríamos que ver si estamos o no bajo el agua y ver si podemos o no disparar. Pero imaginemos que en otras escenas del juego hay sitios con concentraciones de gas inflamable como una instalación minera o una fábrica. Si el jugador dispara cuando está dentro de la nube de gas va a causar una explosión, por lo que hay que probar esa condición también. Voy a dejar a la imaginación como se haría esto usando el IF (o peor aun un SELECT CASE múltiple) y mostraré lo sencillo que es hacer este mismo problema usando SET en combinación con las máscaras de bit.

Lo primero que hay que recordar es que juicio y condena van separados así que el código va a separarse en dos partes. En la primera se van a hacer todas las pruebas necesarias para saber las condiciones en la que la pistola es disparada y en la segunda se va a llamar una sola de varias posibles funciones que llevarán a cabo la acción. Lo primero que necesitamos es definir una variable donde cada bit indique si cada una de las condiciones se cumple o no. Llamemos a esta variable “condiciones” De momento tenemos 4 condiciones que son 1) Hay o no hay balas; 2)Hay o no hay silenciador; 3) Hay o no hay agua cubriéndonos y 4) Hay o no hay gas inflamable alrededor. Para guardar esta condición definimos una variable de un byte y asignamos esas cuatro condiciones a 4 bits menos significativos en ese orden. Cuando vayamos a hacer la prueba lo primero que debemos hacer es poner a cero la variable. Luego se hacen las pruebas de esta manera:

Primero comprobamos que tenemos balas en reserva viendo si el contador es cero ya sea con un CMP, un OR. Luego hacemos un SETNZ EAX. Si todavía tenemos balas (balas no es cero) EAX va a contener un 1, si ya no hay balas EAX será cero. Luego tenemos que mover ese resultado al bit menos significativo (bit 0) de la variable ‘condiciones’. Esto se hace con un OR [condiciones], EAX. De este modo el bit mas bajo de la variable ‘condiciones va a ser 1 si hay balas en reserva y 0 si ya no hay. Luego comprobamos si hay o no hay silenciador. Para ponerlo simple digamos que tenemos una variable que es diferente de cero si tenemos equipado el silenciador y cero si no lo tenemos. Hacemos un CMP con la variable ‘silenciador’ y un 0 y luego ejecutamos SETNE EAX. Esto pone a 1 EAX si los numeros comparados no son iguales(Not Equal) y 1 si si lo son. Antes de hacer el OR para mandar este bit a la variable ‘condiciones’ desplazamos el contenido del acumulador un bit a la izquierda con un SHL EAX, 1 para asegurarnos que el bit que vamos a copiar es el bit 1. Luego comparamos el agua y el gas del mismo modo que las otras dos condiciones anteriores sin olvidar desplazar el acumulador tantos bits como sea necesario para no activar bits de condición equivocados. Al final de este procedimiento los 4 bits de la variable ‘condiciones’ contienen las condiciones en las que se encuentra el jugador antes de disparar. Ojo que hasta ahora solo hemos ejecutado un bloque secuencial de instrucciones. Hasta ahora no se ha hecho o dejado de hacer ninguna instrucción condicional.

Venga la Sentencia…

Ahora si venga la sentencia. Antes de ver como se toma una decisión en un solo movimiento veamos las posibles cosas que pueden pasar. Dependiendo de las condiciones en las que disparemos podemos tener 1) Un disparo ruidoso; 2) Un ‘click’ del arma vacía; 3) Un disparo silenciado. 4) Un montón de burbujas y una advertencia de que no podemos dispara bajo el agua. 5) Una explosión de gas inflamable. El tiro ruidoso se da cuando hay balas, no hay silenciador, no estamos bajo el agua y no hay gas. Para hacerlo la variable de ‘condiciones’ debe de contener 0001. El click de arma vacia va a sonar si el bit mas bajo de ‘condiciones’ es 0, las burbujas van a salir si el bit… bueno, ya saben. El asunto es que en lugar de hacer condicionales anidadas y cosas complicadas todo se reduce a comparar la variable condiciones con una máscara de bits que cumpla con las condiciones que queremos probar usando las técnicas de enmascaramiento que se discutieron en la entrada anterior. Por cierto. Si una secuencia lineal de saltos condicionales no es lo bastante eficiente y rápida para nuestras necesidades hay un modo de acelerar el proceso usando una técnica llamada Jump Tables o Dispatch Tables que consiste en hacer 3 arrays paralelos que combinan las máscaras de condición, las posiciones de llamada de las funciones a ejecutar y un sistema de índices. Esto es demasiado complicado y merece su propia entrada aparte para explicarla pero es bueno que sepan que esta técnica existe. Por cierto, podemos extender el juego agregando mas propiedades para las armas y condiciones usando estos mismos algoritmos. Por ejemplo que una de las armas sea un arco y flecha que no detone las nubes de gas o un arma basada en energía eléctira que mate al jugador si la dispara estando bajo el agua.

Palabras Finales y la ‘Mascara MEH’

Lo que si tengo que advertirles es que a veces cuando probamos condiciones no siempre nos importan todos los bits. En el ejemplo anterior, si estamos bajo el agua no nos importa si tenemos o no balas o si hemos o no equipado el silenciador. Simplemente saldrán burbujas del arma cuando disparemos. Esta condición es mas seria de lo que parece. Cuando hay bits que no nos importa su contenido y que deseamos ignorar necesitamos recurrir a la máscara MEH, conocida también como ‘no me importa’ o DO NOT CARE. Esta máscara en realidad es una máscara AND que tiene unos en los bits importantes y cero en los que no nos interesan. Se aplica al resultado del XOR que se usa para hacer comparaciones múltiples.

El efecto a nivel lógico de la máscara MEH es el de poner en cero las posiciones de bit que no van a influir en el resultado. Por ejemplo, si no nos importa nada de lo que suceda la máscara MEH va a contener todos los bits a cero. De modo que no importa que resultado nos de el XOR de la comparación múltiple. Al hacerle un AND con la máscara MEH en cero el resultado siempre va a ser cero y por lo tanto el resultado de la comparación siempre va a ser verdadero. Para los que todavía no entienden porqué un resultado igual a cero les tengo que recordar que CERO e IGUAL tienen el mismo significado en lógica digital. Pues cuando se comparan dos cantidades estas se restan y la resta de dos cantidades iguales siempre va a ser cero. De hecho los mnemónicos SETZ (Activar si el resultado es cero) y SETE (Activar si son iguales) tienen el mismo código máquina. La misma lógica aplica para los saltos condicionales JE y JZ.

No puedo terminar esta entrada sin hablar del formato binario de SET. Como lo indica el primer dibujo de esta entrada, los primeros 12 bits de la instrucción son siempre 0000 1111 1001. Los 4 bits siguientes indican la condición a evaluar.Hay 16 posibilidades donde la mitad son condiciones normales y la otra mitad sus negaciones. El bit N indica las negaciones y es el que hace la diferencia entre SETE y SETNE. El último byte del opcode tiene los bits 5 a 3 siempre en cero y usa los bits 0, 1, 2, 6 y 7 para elegir el operando donde se va a almacenar el resultado. Recuenden que es importante saber el formato binario de las instrucciones no nada mas para sentirse como Neo de Matrix sino para que estén bien seguros de que las combinaciones de instrucciones y operandos que están usando son relamente válidas.

Bien, pues hasta aquí ha llegado el tema de las máscaras de bits y todas sus incomp/rendidas posibilidades. Aunque hay muchos pero muchísimos mas ejemplos ahí afuera. Puede que lo único que dejé sin tocar fue el hecho de que los procesadores Intel ya cuentan con instrucciónes que prueban, manipulan y alteran el contenido de bits individuales dentro de los registros y memoria. Algunas de estas instrucciones son Bit Test (BT), Bit Clear (BC) y otras de las que hablaré otro dia si me da la gana. Y aunque estas instrucciones existen, para comparaciones multiples y filtrado de datos muy pesados siguen siendo mas rápidas, efecientes y convenientes las infames máscaras de bits.

septiembre 21, 2011 - Posted by | Uncategorized | ,

1 comentario »

  1. Una muy buena explicación, no conocía la potencia del SET

    Comentario por lolo | septiembre 28, 2011 | Responder


Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: