isa.html
Tabla de contenidos
A la definición detallada del conjunto de instrucciones que es capaz de ejecutar un procesador se le denomina su “juego de instrucciones” (o, en ingles, Instruction Set architecture). Esta definición es la que determina de forma inequívoca el efecto de cada instrucción sobre las diferentes partes de la arquitectura del procesador. El número de instrucciones máquina puede llegar a ser muy elevado debido a que la misma instrucción (por ejemplo, la de suma) se puede ejecutar sobre diferentes tipos de datos y con diferentes variantes (números naturales, enteros, etc.)
La decisión de qué instrucciones es capaz de ejecutar un procesador es una de las más importantes y en buena medida es determinante en el rendimiento a la hora de ejecutar programas. Además, el juego de instrucciones y la arquitectura del procesador están interrelacionados. Por ejemplo, generalmente todas las instrucciones del lenguaje máquina de un procesador pueden utilizar los registros de propósito general, por lo que su número tiene un efecto directo en la codificación de instrucciones.
La decisión de qué instrucciones incluir en un procesador está también influenciada por la complejidad que requiere su diseño. Si una instrucción realiza una operación muy compleja, el diseño de los componentes digitales necesarios para su ejecución puede resultar demasiado complejo.
Considérese el siguiente ejemplo. ¿Debe un procesador incluir en su
lenguaje máquina una instrucción que dado un número real y los
coeficientes de un polinomio de segundo grado obtenga su valor?
Supóngase que esta instrucción se llama EPSG
(evaluar polinomio de segundo grado). Un posible formato de esta
instrucción se muestra en el ejemplo
5.1.
La instrucción realiza los cálculos con los cuatro primeros
parámetros tal y como se muestra en la ecuación
5.1 y almacena el resultado en el lugar especificado por el
parámetro dest
.
La ecuación
5.1 especifica las operaciones a realizar para evaluar el
polinomio, en este caso suma y multiplicación. Un procesador que no
disponga de la instrucción máquina EPSG
puede
obtener el mismo resultado pero ejecutando múltiples instrucciones.
El compromiso a explorar, por tanto, a la hora de decidir si incluir
una instrucción en el lenguaje máquina de un procesador está entre la
complejidad de las instrucciones y la complejidad del lenguaje. Si un
procesador soporta la ejecución de la instrucción EPSG
, requiere una estructura interna más compleja,
pues debe manipular sus múltiples operandos y ejecutar las operaciones
necesarias. En cambio, si un procesador ofrece la posibilidad de
realizar multiplicaciones y sumas, la evaluación del polinomio es
igualmente posible aunque mediante la ejecución de múltiples
instrucciones, con lo que no será una ejecución tan rápida. En general,
un lenguaje máquina con instrucciones sofisticadas requiere una
implementación más compleja del procesador. De igual forma, un lenguaje
máquina sencillo (pero que ofrezca las operaciones mínimas para poder
realizar todo tipo de cálculos) permite un diseño más simple.
De este compromiso se ha derivado a lo largo de los años una división de los procesadores en dos categorías dependiendo de la filosofía utilizada para el diseño de su lenguaje máquina:
-
Los procesadores que ejecutan un conjunto numeroso de instrucciones y algunas de ellas de cierta complejidad se les denomina de tipo CISC (Complex Instruction Set Computer). Las instrucciones más complejas son las que requieren múltiples cálculos y accesos a memoria para lectura/escritura de operandos y resultados.
El ejemplo más representativo de esta filosofía es la arquitectura IA-32. Su lenguaje máquina consta de instrucciones capaces de realizar operaciones complejas. Otro ejemplo de procesador CISC es el Motorola 68000, que aunque en la actualidad ha dejado paso a otro tipo de procesadores pero que está todavía presente en ciertos productos electrónicos y ha sido la inspiración de múltiples modelos actuales.
-
Los procesadores que ejecutan un conjunto reducido de instrucciones simples se denominan de tipo RISC (Reduced Instruction Set Computer). El número de posibles instrucciones es muy pequeño, pero a cambio, el diseño del procesador se simplifica y se consiguen tiempos de ejecución muy reducidos con el consiguiente efecto en el rendimiento total del sistema.
Ejemplos de algunos procesadores diseñados con esta filosofía son:
-
MIPS (Microprocessor without interlocked pipeline stages): utilizado en encaminadores, consola Nintendo 64, PlayStation y PlayStation 2 y PlayStation portátil (PSP).
-
ARM: presente en ordenadores portátiles, cámaras digitales, teléfonos móviles, televisiones, iPod, etc.
-
SParc (Scalable Processor architecture): línea de procesadores de la empresa Sun Microsystems. Se utilizan principalmente para servidores de alto rendimiento.
-
PowerPC: arquitectura inicialmente creada por el consorcio Apple-IBM-Motorola para ordenadores personales que está presente en equipos tales como servidores, encaminadores, es la base para el procesador Cell presente en la PlayStation 3, XBox 360, etc.
-
En la actualidad, esta división entre procesadores CISC y RISC se ha empezado a difuminar. La propia arquitectura IA-32 decodifica las instrucciones de su lenguaje máquina y las traduce a una secuencia de instrucciones más simples denominadas “microinstrucciones”. Se puede considerar, por tanto, que el lenguaje formado por estas microinstrucciones tiene una estructura cercana a la categoría RISC, mientras que el conjunto de instrucciones máquina es de tipo CISC.
Otra importante decisión a la hora de diseñar un lenguaje máquina es el formato en el que se van a codificar las instrucciones. Ateniendo a este criterio los procesadores se pueden dividir en:
-
Formato de longitud fija. Todas las instrucciones máquina se codifican con igual número de bits. De esta característica se derivan múltiples limitaciones del lenguaje. El número de operandos de una instrucción no puede ser muy elevado, pues todos ellos deben ser codificados con un conjunto de bits. Al igual que sucede con los operandos, el tipo de operación debe ser también codificado, y por tanto este tipo de lenguajes no pueden tener un número muy elevado de instrucciones.
Como contrapartida, un formato de instrucción fijo se traduce en una fase de decodificación más simple. El procesador obtiene de memoria un número fijo de bits en los que sabe de antemano que está contenida la instrucción entera. Los operandos generalmente se encuentran en posiciones fijas de la instrucción, con lo que su acceso se simplifica enormemente.
El procesador PowerPC es un ejemplo de procesador con formato fijo de instrucción. Todas ellas se codifican con 32 bits. En general, los procesadores de tipo RISC optan por una codificación con formato de longitud fija.
-
Formato de longitud variable. Las instrucciones máquina se codifican con diferente longitud. La principal consecuencia es que la complejidad de una instrucción puede ser arbitraria. En este tipo de lenguaje máquina se puede incluir un número elevado de instrucciones.
El principal inconveniente es la decodificación de la instrucción pues su tamaño sólo se sabe tras analizar los primeros bytes con lo que identificar una instrucción y sus operandos es más complejo.
Los procesadores con arquitectura IA-32 son un ejemplo de procesadores con formato variable de instrucciones. Dicho formato se estudia en mayor detalle en las siguientes secciones.
La arquitectura IA-32 codifica sus instrucciones máquina con un formato de longitud variable. Toda instrucción tiene una longitud entre 1 y 16 bytes. La figura 5.1 ilustra las diferentes partes de las que puede constar una instrucción así como su tamaño en bytes.
Las instrucciones comienzan por un prefijo de hasta cuatro bytes, seguido de uno o dos bytes que codifican la operación, un byte de codificación de acceso a operandos, un byte denominado escala-base-índice (scale-base-index), un desplazamiento de hasta cuatro bytes, y finalmente un operando inmediato de hasta cuatro bytes. Excepto los bytes que codifican la operación, el resto de componentes son todos opcionales, es decir, su presencia depende del tipo de operación.
Los prefijos son bytes que modifican la ejecución normal de una
instrucción de acuerdo a unas propiedades predefinidas. El procesador
agrupa estos prefijos en cuatro categorías y se pueden incluir hasta un
máximo de uno por categoría. Por ejemplo, el prefijo LOCK
hace que mientras se ejecuta la instrucción el
procesador tiene acceso en exclusiva a cualquier dispositivo que sea
compartido. Este prefijo se utiliza en sistemas en los que se comparte
memoria entre múltiples procesadores.
El código de operación codifica sólo el tipo de operación a realizar. Su tamaño puede ser de hasta 2 bytes y en ciertas instrucciones parte de este código se almacena en el byte siguiente denominado ModR/M. Este byte se utiliza en aquellas instrucciones cuyo primer operando está almacenado en memoria y sus ocho bits están divididos en tres grupos o campos tal y como ilustra la figura 5.2 y que almacenan los siguientes datos:
-
El campo Mod combinado con el campo R/M codifica uno de los 8 posibles registros de propósito general, o uno de los 24 posibles modos de direccionamiento.
-
El campo Reg/Opcode codifica uno de los ocho posibles registros de propósito general. En algunas instrucciones estos tres bits forman parte del código de operación.
-
El campo R/M codifica o uno de los ocho posibles registros de propósito general, o combinado con el campo Mod uno de los 24 posibles modos de direccionamiento.
Algunas combinaciones de valores en el byte ModR/M requieren información adicional que se codifica en el byte SIB cuya estructura se muestra en la figura 5.3.
Algunos de los modos de direccionamiento ofrecidos por el procesador requieren un factor de escala por el que multiplicar un registro denominado índice, y un registro denominado base. Estos tres operandos se codifican en el byte SIB con los bits indicados en cada uno de sus campos. Los campos que codifican el registro base y el índice tienen ambos un tamaño de 3 bits, lo que concuerda con el número de registros de propósito general de los que dispone el procesador. El factor de escala se codifica únicamente con 2 bits, con lo que sólo se pueden codificar 4 posibles valores.
El campo denominado “desplazamiento” es opcional, codifica un número de 1, 2 o 4 bytes y se utiliza para calcular la dirección de un operando almacenado en memoria. Finalmente, el campo denominado “inmediato” (también opcional) tiene un tamaño de 1, 2 o 4 bytes y codifica los valores constantes en una instrucción.
La figura
5.4 muestra un ejemplo de como se codifica la instrucción ADDL $4, 14(%eax, %ebx, 8)
que suma la constante 4
a un operando de 32 bits almacenado en memoria a partir de la dirección
cuya expresión es 14 + %eax
+ (%ebx
* 8) con 5 bytes con valores 0x8344D80E04.
En este caso, el código de operación está contenido en los primeros 8 bits (valor 0x83) y los 3 bits del campo Reg/Opcode del byte ModR/M y codifica la instrucción de suma de un valor constante de 8 bits a un valor de 32 bits almacenado en memoria.
Los valores 01 y 100 en los campos Mod y R/M del byte ModR/M respectivamente indican que la instrucción contiene en el byte SIB los datos que precisa el modo de direccionamiento para acceder al segundo operando así como la dirección en la que se almacena el resultado.
Los campos del byte SIB contienen los valores 11, 011 y 000 que
codifican respectivamente el factor de escala 8, el registro índice
%ebx
y el registro base %eax
así como el tamaño del desplazamiento que es
un byte. La instrucción concluye con un byte que codifica el
desplazamiento, seguido de un byte que codifica la constante a utilizar
como primer operando.
Para escribir programas que puedan ser ejecutados por un procesador, todas las instrucciones y datos se deben codificar mediante secuencias de ceros y unos. Estas secuencias son el único formato que entiende el procesador, pero escribir programas enteros en este formato es, aunque posible, extremadamente laborioso.
Una solución a este problema consiste en definir un lenguaje que contenga las mismas instrucciones, operandos y formatos que el lenguaje máquina, pero en lugar de utilizar dígitos binarios, utilizar letras y números que lo hagan más inteligible para el programador. A este lenguaje se le conoce con el nombre de lenguaje ensamblador.
El lenguaje ensamblador, por tanto, se puede definir como una representación alfanumérica de las instrucciones que forman parte del lenguaje máquina de un procesador. Tal y como se ha mostrado en la sección 5.2, la traducción de la representación alfanumérica de una instrucción a su representación binaria consiste en aplicar un proceso de traducción sistemático.
Considérese de nuevo la instrucción de lenguaje ensamblador
utilizada en la figura
5.4, ADDL $4, 14(%eax, %ebx, 8)
. Una
segunda forma de escribir esta instrucción puede ser ADDL 14[%eax, %ebx * 8], 4
. En este nuevo formato
se han cambiado el orden de los operandos así como la sintaxis
utilizada. Cualquiera de las dos notaciones es válida siempre y cuando
se disponga del programa que pueda traducirlo a su codificación en
binario entendida por el procesador (5 bytes con valores
0x8344D80E04).
El lenguaje ensamblador que se describe a continuación sigue la
sintaxis comúnmente conocida con el nombre de “AT&T” y sus principales características son
que los operandos destino se escriben en último lugar en las
instrucciones, los registros se escriben con el prefijo %
y las constantes con el prefijo $
.
Una sintaxis alternativa utilizada por otros compiladores es la conocida con el nombre de “Intel”. En ella, los operandos destino se escriben los primeros en una instrucción, y los registros y constantes no se escriben con prefijo alguno.
En principio es el programa ensamblador quien estipula la forma en la que se deben escribir las instrucciones. Por tal motivo, es posible que existan diferentes ensambladores con diferentes definiciones de su lenguaje, pero que produzcan el mismo lenguaje máquina. Existen también ensambladores capaces de procesar programas escritos en más de un formato, el programa gcc, incluido con el sistema operativo Linux es uno de ellos. En adelante se utilizará únicamente la sintaxis “AT&T”.
Las instrucciones del lenguaje máquina de la arquitectura IA-32 pueden tener uno de los tres siguientes formatos:
-
Operación
. Las instrucciones con este formato no precisan ningún operando, suelen ser fijos y por tanto se incluyen de forma implícita. Por ejemplo, la instrucciónRET
retorna de una llamada a una subrutina. -
Operación Operando
. Estas instrucciones incluyen únicamente un operando. Algunas de ellas pueden referirse de manera implícita a operandos auxiliares. Un ejemplo de este formato es la instrucciónINC %eax
que incrementa en uno el valor de su único operando. -
Operación Operando1, Operando2
. Un ejemplo de este tipo de instrucciones esADD $0x10, %eax
que toma la constante 0x10 y el contenido del registro%eax
, realiza la suma y deposita el resultado en este mismo registro. Como regla general, cuando una operación requiere tres operandos, dos fuentes y un destino (por ejemplo, una suma), el segundo operando desempeña siempre las funciones de fuente y destino y por tanto se pierde su valor inicial.
Algunas de las instrucciones del procesador tienen un formato diferente a estos tres, pero serán tratadas como casos excepcionales. El ejemplo 5.2 muestra instrucciones de los tres tipos descritos anteriormente escritas en lenguaje ensamblador.
Para escribir programas en lenguaje ensamblador se necesita una descripción detallada de todas y cada una de sus instrucciones. Dicha descripción debe incluir todos los formatos de operandos que admite, así como el efecto que tiene su ejecución en el procesador y los datos. Esta información se incluye en los denominados manuales de programación y acompañan a cualquier procesador.
En el caso de la arquitectura IA-32, su descripción detallada, el lenguaje máquina y funcionamiento se incluye en el documento de poco más de 2000 páginas que lleva por título IA-32 Intel architecture Software Developer's Manual y cuyo contenido está dividido en los siguientes tres volúmenes:
-
Volumen 1. Arquitectura básica (Basic architecture): describe la arquitectura básica del procesador así como su entorno de programación.
-
Volumen 2. Catálogo del juego de instrucciones (Instruction Set Reference): describe cada una de las instrucciones del procesador y su codificación.
-
Volumen 3. Guía para la programación de sistemas (System Programming Guide): describe el soporte que ofrece esta arquitectura al sistema operativo en aspectos tales como gestión de memoria, protección, gestión de tareas, interrupciones, etc.
El ejemplo 5.3 muestra la definición de la instrucción de suma de enteros que forma parte del lenguaje máquina de la arquitectura IA-32 tal y como consta en su manual.
Ejemplo 5.3. Descripción de la instrucción de suma de enteros en la arquitectura IA-32
ADD--Add | |||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||
Description Adds the first operand (destination operand) and the second operand (source operand) and stores the result in the destination operand. The destination operand can be a register or a memory location; the source operand can be an immediate, a register, or a memory location. (However, two memory operands cannot be used in one instruction.) When an immediate value is used as an operand, it is sign-extended to the length of the destination operand format. The ADD instruction performs integer addition. It evaluates the result for both signed and unsigned integer operands and sets the OF and CF flags to indicate a carry (overflow) in the signed or unsigned result, respectively. The SF flag indicates the sign of the signed result. This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically. Operation DEST ← DEST + SRC Flags Affected The OF, SF, ZF, AF, CF, and PF flags are set according to the result. |
La parte superior incluye las diferentes versiones de suma de enteros que soporta el procesador dependiendo de los tipos de operandos. La primera columna muestra los códigos de operación para cada una de las versiones y la segunda columna muestra la estructura en lenguaje ensamblador de cada una de ellas. La sintaxis utilizada en este documento es de tipo Intel (ver la sección 5.3.1), por tanto, el operando destino es el primero que se escribe.
La codificación de la instrucción ADD $4,
14(%eax, %ebx, 8)
utilizada en el figura
5.4 coincide con la mostrada por esta tabla en la octava fila ADD r/m32, imm8
, o en otras palabras, la suma de
una constante de ocho bits (imm8
) a un
registro o un dato en memoria (en el ejemplo, un dato en memoria).
En el código de operación, los símbolos “ib
”, “iw
” e “id
” significan respectivamente una constante
de 8, 16 o 32 bits. El símbolo “\r
” representa cualquiera de los registros
de propósito general del procesador. En la segunda y tercera columna el
prefijo “imm
”
seguido de un número representa una constante del tamaño en bits
indicado por el número. El prefijo “r/m
” seguido de un número significa que el
operando es o un registro o un dato en memoria del tamaño del número
indicado.
El documento continua con una descripción de palabra de la operación que realiza la instrucción. Se aclara que uno de los operandos es fuente y destino a la vez, y que no es posible sumar dos operandos que estén ambos en memoria.
La siguiente sección es una descripción funcional de la operación y se utiliza como resumen formal de la descripción textual que le precede. Algunas instrucciones, debido a su complejidad, son más fácilmente descritas mediante esta notación que mediante texto. Finalmente se mencionan aquellos bits de la palabra de estado del procesador que se modifican al ejecutar una de estas instrucciones.
Los operandos que utilizan las instrucciones de la arquitectura IA-32 se dividen en las siguientes categorías:
-
Constantes. El valor debe ir precedido del símbolo “
$
”. Se pueden especificar valores numéricos y cualquier letra o símbolo manipulable por el procesador. Las constantes numéricas se pueden escribir en base hexadecimal si se antepone el prefijo “0x
”, en base 8 (u octal) si se antepone el prefijo “0
”, o en binario si se antepone el prefijo “0b
”. Una constante numérica sin prefijo se considera escrita en base 10, por ejemplo:$0xFF23A013
,$0763
,0b00101001
,$255
.Las constantes que representan letras deben ir precedidas por la comilla simple
'
. Por ejemplo,$'A
representa la constante numérica que codifica el valor de la letra a mayúscula. -
Registro de propósito general. El nombre del registro contiene el prefijo
%
. Se pueden utilizar cualquiera de los ocho registros de propósito general así como sus diferentes porciones (ver la sección 4.1.2), por ejemplo:%eax
,%dh
,%esp
,%bp
. -
Dirección de memoria. El operando está almacenado a partir de la dirección de memoria dada en la instrucción. Se permite un amplio catálogo de formas para especificar la dirección de los operandos denominados “modos de direccionamiento” y se describen de forma detallada en el capítulo 7.
-
Operando implícito. No constan pero la instrucción hace uso de ellos. Por ejemplo, la instrucción
PUSH
deposita el único operando dado en la cima de la pila. La instrucción tiene como operando implícito el registro%esp
que contiene la dirección de memoria en la que está almacenado el dato de la cima y se le resta la constante 4 al final de la operación.La presencia o ausencia de operandos implícitos está contenida en la descripción detallada de las instrucciones máquina.
En la arquitectura IA-32 no todas las combinaciones posibles de tipos de operandos se pueden dar en todas las instrucciones. La arquitectura impone la restricción de que no se permite la ejecución de una instrucción con dos operandos explícitos que estén almacenados ambos en memoria. Además, no todas las combinaciones de instrucciones con tipos de operandos tienen sentido. La tabla 5.1 muestra ejemplos de instrucciones en lenguaje ensamblador correctas e incorrectas.
Tabla 5.1. Instrucciones con diferentes tipos de operandos
Instrucción | Correcta |
---|---|
PUSH $4 |
Sí |
POP $0b11011101 |
No. El operando de esta instrucción es el destino en el que almacenar el dato en la cima de la pila, y por tanto, no puede ser una constante. |
MOV $-4, %eax |
Sí. Primer operando es de tipo constante y el segundo de tipo registro. |
MOV %eax, $0x11011110 |
No. El segundo operando es el destino de la operación, y no puede ser una constante. |
MOV %eax, contador |
Sí. El segundo operando representa una dirección de memoria. |
MOV $'A, %eax |
Sí. ¿Qué tamaño
de datos se está moviendo en esta instrucción a %eax ? |
MOV $65, %eax |
Sí. Esta instrucción tiene una codificación idéntica a la anterior. |
MOV contador, resultado |
No. Instrucción con dos operandos, y ambos son de tipo dirección de memoria. |
MOV $-4, contador |
¿Qué tamaño de datos se transfiere a memoria? |
De los tipos de operandos presentados en la sección anterior, no todos tienen definido el tamaño de todos sus componentes. Tal y como se ha visto en el capítulo 2, cuando se procesan datos es preciso saber el tamaño utilizado para su codificación.
Considérese la instrucción utilizada como último ejemplo en la tabla
5.1, MOV $-4, contador
. A primera vista,
la instrucción puede parecer correcta, pues se mueve una constante a
una dirección de memoria representada, en este caso, por el símbolo
contador
. El primer operando, sin embargo,
puede ser representado por un número arbitrario de bits. Lo mismo
sucede con el segundo operando, pues al ser una dirección de memoria,
lo único que se puede asegurar es que se utilizarán tantos bytes de
memoria como sea preciso.
Como conclusión, la instrucción MOV $-4,
contador
a pesar de tener un formato correcto, es ambigua. El
mismo formato puede representar las instrucciones que almacena la
constante -4 representada por un número variable de bytes en la
dirección indicada por contador
. La
arquitectura IA-32 sólo permite 3 tamaños para sus operandos: 1 byte, 2
bytes (un word), o 4 bytes (un doubleword,
ver la tabla
4.1). Por tanto, la instrucción MOV $-4,
contador
, puede ser interpretada de tres formas diferentes
dependiendo del tamaño con el que se representa la constante y el
número de bytes utilizados para almacenar su valor en memoria (ambos
deben ser el mismo número, 1, 2 o 4).
Para solventar este problema, el lenguaje ensamblador permite la utilización de un sufijo en el código de instrucción que indica el tamaño de los operandos utilizados. Este sufijo es la letra “B” para operandos de 1 byte, “W” para operandos de 2 bytes (un word), y “L” para operandos de 4 bytes (un doubleword).
Por tanto, si se quiere codificar la instrucción que almacena la
constante -4 representada por 32 bits en la dirección indicada por
contador
se debe escribir MOVL $-4, contador
.
De todas las instrucciones posibles sólo algunas de ellas son ambiguas. Si alguno de los operandos es un registro, el tamaño del operando queda fijado por el tamaño del registro. La ambigüedad aparece cuando ninguno de los operandos es un registro, y por tanto no es posible deducir el tamaño. Se permite el uso del sufijo de tamaño en una instrucción que no lo requiera, siempre y cuando esté en consonancia con el tamaño de los operandos. La tabla 5.2 muestra ejemplos de utilización del sufijo de tamaño.
Tabla 5.2. Instrucciones con sufijos de tamaño
Instrucción | Comentario |
---|---|
PUSH $4 |
No es preciso el sufijo, los operandos de la pila son siempre de 32 bits. |
PUSHL $0b11011101 |
El sufijo es redundante y concuerda con el tamaño del operando. |
MOVB $-4, contador |
El sufijo es
imprescindible porque la instrucción almacena un único byte que
codifica el número -4 en complemento a dos en la posición de memoria
indicada por contador . |
MOV $-4, %ax |
No es preciso el
sufijo porque la presencia del operando %ax
hace que la constante se represente con 16 bits. |
MOVL %eax, contador |
La presencia del
registro %eax hace que el operando se
considere de 32 bits, y por tanto el sufijo es redundante pero
correcto. |
MOVB $'A, %eax |
Esta instrucción
es incorrecta porque contiene un error de sintaxis. El sufijo indica
tamaño de 1 byte y el segundo operando indica 4 bytes. El sufijo es
innecesario y la instrucción transfiere el número que codifica la
constante $'A como número de 32 bits. |
INCL contador |
La instrucción incrementa el valor de su único operando que está almacenado en memoria con lo que la ausencia de sufijo la haría ambigua. |
A continuación se describe el subconjunto de instrucciones de la arquitectura IA-32 necesario para poder codificar tareas básicas de programación y manipulación de datos de tipo entero y strings. La descripción del lenguaje máquina completo se puede encontrar en la documentación facilitada por el fabricante. Para simplificar su estudio, las instrucciones se dividen en categorías. Una descripción detallada de cada una de ellas se puede encontrar en el apéndice A.
En esta categoría se incluyen las instrucciones que permiten la
transferencia de datos entre registros y memoria tales como MOV
, PUSH
, POP
y XCHG
.
La instrucción MOV
recibe dos operandos y
transfiere el dato indicado por el primer operando al lugar indicado
por el segundo. Dada la restricción que impone el procesador de que en
una instrucción con dos operandos no pueden estar ambos en memoria, si
se quiere transferir datos de un lugar de memoria a otro, se deben
utilizar dos instrucciones y utilizar un registro de propósito
general.
Las instrucciones PUSH
y POP
también transfieren datos, aunque en este caso,
uno de los operandos es implícito y se refiere a la cima de la pila. La
instrucción PUSH
necesita como operando el
dato a colocar en la cima de la pila mientras que la instrucción POP
requiere un único operando para indicar el
lugar en el que depositar el dato contenido en la cima de la pila.
Ambas instrucciones modifican el registro %esp
que contiene la dirección de la cima de la
pila (tal y como se ha descrito en la sección 4.3).
Estas dos instrucciones aceptan como operando una posición de
memoria, por ejemplo PUSH contador
. El
procesador carga en la pila el dato en memoria en la posición con
nombre contador
. En este caso, a pesar de que
la transferencia se está realizando de memoria a memoria, la
arquitectura sí permite la operación. La restricción de dos operandos
en memoria aplica únicamente a aquellas instrucción con dos operandos
explícitos.
La instrucción XCHG
(del inglés exchange)
consta de dos operandos e intercambia sus valores por lo que modifica
los operandos (a no ser que tengan idéntico valor). No se permite que
los operandos estén ambos en memoria.
La tabla
5.3 muestra ejemplos correctos e incorrectos de la utilización de
este tipo de instrucciones. Se asume que los símbolos contador1
y contador2
se
refieren a operandos en memoria.
Tabla 5.3. Instrucciones de transferencia de datos
Instrucción | Comentario |
---|---|
MOV $4, %al |
Almacena el
valor 4 en el registro de 8 bits %al . |
MOV contador1, %esi |
Almacena los
cuatro bytes que se encuentran en memoria a partir de la posición que
representa contador1 en el registro %esi . |
MOV $4, contador1 |
Instrucción ambigua, pues no se especifica el tamaño de datos en ninguno de los dos operandos. |
MOVL contador, $4 |
Instrucción incorrecta. El segundo operando es el destino al que mover el primer operando, por lo tanto, no puede ser de tipo constante. |
MOV %al, %ecx |
Instrucción incorrecta. El tamaño de los dos operandos es inconsistente. El primero es un registro de 8 bits, y el segundo es de 32. |
PUSH $4 |
Instrucción correcta. Almacena el valor 4, codificado con 32 bits en la cima de la pila. No precisa sufijo de tamaño. |
POP $4 |
Instrucción incorrecta. El operando indica el lugar en el que almacenar el contenido de la cima de la pila, por tanto, no puede ser un valor constante. |
XCHG %eax, %ebx |
Instrucción correcta. |
XCHG %eax, contador1 |
Instrucción correcta. |
XCHG $4, %eax |
Instrucción incorrecta. Se intercambian los contenidos de los dos operandos, por lo que ninguno de ellos puede ser una constante. |
XCHG contador1, contador2 |
Instrucción incorrecta. Ambos operandos están en memoria, y el procesador no permite este tipo de instrucciones. |
En este grupo se incluyen aquellas instrucciones que realizan operaciones aritméticas sencillas con números enteros y naturales tales como la suma, resta, incremento, decremento, multiplicación y división.
Las instrucciones ADD
y SUB
realizan la suma y resta respectivamente de sus
dos operandos. En el caso de la resta, la operación realizada es la
sustracción del primer operando del segundo. Como tales operaciones
precisan de un lugar en el que almacenar el resultado, el segundo
operando desempeña las funciones de fuente y destino por lo que se
sustituye el valor del segundo operando por el valor resultante.
El procesador ofrece también las instrucciones INC
y DEC
que requieren
un único operando y que incrementan y decrementan respectivamente el
operando dado. Aunque las instrucciones ADD $1,
operando
e INC operando
realizan la
misma operación y se podría considerar idénticas, no lo son, pues INC
no modifica el bit de acarreo.
La instrucción NEG
recibe como único
operando un número entero y realiza la operación de cambio de
signo.
La tabla 5.4 muestra
ejemplos de utilización de este tipo de instrucciones. Se asume que el
símbolo contador
se refiere a un operando
almacenado en memoria.
Tabla 5.4. Instrucciones aritméticas
Instrucción | Comentario |
---|---|
ADDL $3, contador |
Suma la
constante 3 al número de 32 bits almacenado a partir de la posición
contador . El tamaño viene determinado por el
sufijo, que en este caso es imprescindible. |
SUB %eax, contador |
Deposita en
memoria el número de 32 bits resultante de la operación contador -%eax . |
NEGL contador |
Cambia de signo el número de 32 bits
almacenado en memoria a partir de la posición contador . |
La instrucción de multiplicación tiene dos variantes, IMUL
y MUL
para números
enteros y naturales respectivamente y su formato supone un caso
especial, pues permite la especificación de entre uno y tres
operandos.
La versión de IMUL
y MUL
con un único operando ofrece, a su vez la
posibilidad de multiplicar números de 8, 16 y 32 bits. Las
instrucciones asumen que el segundo multiplicando está almacenado en el
registro %al
(para números de 8 bits), %ax
(para números de 16 bits) y %eax
(para números de 32 bits). El tamaño del
número a multiplicar se deduce del operando explícito de la
instrucción.
Si se multiplican dos operandos de n bits, el resultado tiene tamaño
doble y debe representarse con 2n bits. Por tanto, si los operandos son
de 8 bits, el resultado de esta instrucción se almacena en %ax
, si son de 16 bits se almacena en los 32 bits
resultantes al concatenar los registros %dx:%ax
, y si los operandos son de 32 bits, en los
64 bits obtenidos al concatenar los registros %edx:%eax
. En estos dos últimos casos, los
registros %dx
y %edx
contienen los bytes más significativos del
resultado.
La versión de IMUL
y MUL
con dos operandos es más restrictiva que la
anterior. El segundo operando puede ser únicamente uno de los ocho
registros de propósito general (no puede ser ni una constante ni un
número en memoria) y el tamaño de ambos operandos puede ser de 16 o 32
bits. Para almacenar el resultado se utiliza el mismo número de bits
con los que se representan los operandos, con lo que se corre el
riesgo, si el resultado obtenido es muy elevado, de perder parte del
resultado. Esta última condición se refleja en los bits de estado del
procesador.
La versión de IMUL
y MUL
con tres operandos es la más restrictiva de
todas. Los dos primeros operandos son los multiplicandos y el primero
de ellos debe ser una constante. El tercer operando es el lugar en el
que se almacena el resultado y sólo puede ser un registro de propósito
general. Al igual que la versión con dos operandos, los únicos tamaños
que se permiten son de 16 y 32 bits, y el resultado se almacena en el
mismo tamaño que los operandos, por lo que de nuevo se corre el riesgo
de pérdida de bits del resultado.
La tabla 5.5
muestra ejemplos de utilización de este tipo de instrucciones. Se asume
que el símbolo contador
se refiere a un
operando almacenado en memoria.
Tabla 5.5. Instrucciones de multiplicación
Instrucción | Comentario |
---|---|
MULB $3 |
Multiplica el
número natural 3 representado en 8 bits por el registro implícito %al y deposita el resultado en %eax . El tamaño de los operandos lo determina el
sufijo B . |
IMUL %eax |
Multiplica el
número entero almacenado en %eax por sí mismo
(operando implícito). El resultado se almacena en el registro de 64
bits %edx:%eax . |
MUL contador, %edi |
Multiplica el
número natural de 32 bits almacenado a partir de la posición de memoria
representada por contador por el registro
%edi en donde se almacenan los 32 bits de
menos peso del resultado. |
IMUL $123, contador, %ecx |
Multiplica el número de 32 bits almacenado en
memoria a partir de la posición contador por
la constante $123 y almacena los 32 bits
menos significativos del resultado en %ecx . |
Las instrucciones de división de números naturales y enteros
devuelven dos resultados, el cociente y el resto, y se almacenan ambos
valores. De manera análoga a las instrucciones de multiplicación,
existen dos versiones IDIV
y DIV
para división de enteros y naturales
respectivamente y el tamaño del dividendo es el doble del divisor. De
esta forma, se permite dividir un número de 16 bits entre uno de 8, uno
de 32 entre uno de 16 y uno de 64 entre uno de 32.
Su formato admite de forma explícita un único operando que es el
divisor, y que puede ser un número de 8, 16 o 32 bits. El dividendo es
implícito y está almacenado en %ax
si el
divisor es de 8 bits, en el registro de 32 bits resultante de
concatenar %dx:%ax
si el divisor es de 16
bits, y en el registro de 64 bits resultante de concatenar %edx:%eax
si el divisor es de 32 bits.
Los dos resultados que se devuelven también tienen un destino
implícito y depende del tamaño de los operandos. Si el divisor es de 8
bits el cociente se almacena en %al
y el
resto en %ah
. Si el divisor es de 16 bits, se
utilizan %ax
y %dx
para cociente y resto respectivamente. En el caso de un divisor de 32
bits, el cociente se devuelve en %eax
y el
resto en %edx
.
La tabla 5.6 muestra
ejemplos de utilización de este tipo de instrucciones. Se asume que el
símbolo contador
se refiere a un operando
almacenado en memoria.
Tabla 5.6. Instrucciones de división
Instrucción | Comentario |
---|---|
IDIVB $-53 |
Divide el
registro %ax por la constante $-53 . El cociente se deposita en %al y el resto en %ah . |
IDIV %eax |
Se divide el
número de 64 bits obtenido al concatenar los registros %edx:%eax entre el propio registro %eax . En %eax se deposita
el cociente, y en %edx el resto. |
DIVW contador |
Divide el número de 32 bits almacenado en el
registro obtenido al concatenar %dx:%ax entre
el número de 16 bits almacenado a partir de la posición de memoria
indicada por contador . En %ax se almacena el cociente y en %dx el resto. |
En este grupo se incluyen las instrucciones de conjunción, disyunción, disyunción exclusiva y negación. La aplicación práctica de estas instrucciones no es a primera vista del todo aparente, sin embargo, suelen estar presentes en la mayoría de programas.
Las cuatro instrucciones lógicas consideradas son AND
, OR
, NOT
y XOR
para la
conjunción, disyunción, negación y disyunción exclusiva,
respectivamente.
Estas instrucciones tienen en común que realizan sus operaciones “bit a bit”. Es decir, el procesador realiza tantas operaciones lógicas como bits tienen los operandos tomando los bits que ocupan la misma posición y, por tanto, produciendo otros tantos resultados.
Considérese el caso de la instrucción de conjunción AND
con sus dos operandos. Al igual que en el caso
de instrucciones como la de suma o resta, el segundo operando es a la
vez fuente y destino. El procesador obtiene un resultado de igual
tamaño que sus operandos y en el que cada bit es el resultado de la
conjunción de los bits de idéntica posición de los operandos. Las
instrucciones de disyunción (OR
) y disyunción
exclusiva (XOR
) se comportan de forma
análoga.
La instrucción NOT
tiene un único operando
que es fuente y destino y cambia el valor de cada uno de sus bits.
La tabla 5.7 muestra ejemplos
de utilización de este tipo de instrucciones. Se asume que el símbolo
contador
se refiere a un operando almacenado
en memoria.
Tabla 5.7. Instrucciones lógicas
Instrucción | Comentario |
---|---|
AND $-1, %eax |
Calcula la
conjunción bit a bit entre la constante $-1 y
el registro %eax . ¿Qué valor tiene %eax tras ejecutar esta instrucción? |
ORL $1, contador |
Calcula la
disyunción bit a bit entre la constante $1 y
el número de 32 bits almacenado en memoria a partir de la posición
denotada por contador . |
NOTL contador |
Cambia el valor de los 32 bits almacenados a
partir de la posición de memoria que denota contador . El sufijo de tamaño es necesario para
definir el tamaño del operando. |
En este grupo se incluyen instrucciones que mediante desplazamientos efectúan operaciones aritméticas de multiplicación y división por potencias de dos. Además, se incluyen también instrucciones que manipulan sus operandos como si los bits estuviesen dispuestos de forma circular y permite rotaciones en ambos sentidos.
Las instrucciones de desplazamiento se subdividen a su vez en dos categorías: desplazamiento aritmético y desplazamiento lógico.
Las instrucciones de desplazamiento aritmético son aquellas que equivalen a multiplicar y dividir un número por potencias de 2. Un desplazamiento de un bit quiere decir que cada uno de ellos pasa a ocupar la siguiente posición (a derecha o izquierda) y por tanto, dependiendo de cómo se introduzcan nuevos valores y cómo se descarte el bit sobrante, dicha operación es idéntica a multiplicar por 2.
En adelante se asume que el bit más significativo de un número es el de más a su izquierda. La figura 5.5 muestra un desplazamiento aritmético a izquierda y derecha de un número de 8 bits.
Para que la equivalencia entre los desplazamientos de bits y la operación aritmética de multiplicación y división por 2 sean realmente equivalentes hay que tener en cuenta una serie de factores.
-
Si se desplaza un número a la izquierda, el nuevo bit menos significativo debe tener el valor cero.
-
Si se desplaza a la izquierda un número natural con su bit más significativo a uno se produce desbordamiento.
-
Si se desplaza un número a la derecha, el nuevo bit más significativo debe tener valor idéntico al antiguo.
Las instrucciones SAL
(Shift Arithmetic
Left) y SAR
(Shift Arithmetic
Right) desplazan su segundo operando a izquierda y derecha
respectivamente tantas veces como indica el primer operando. En ambas
instrucciones, el último bit que se ha descartado se almacena en el bit
de acarreo CF
. Estas instrucciones tienen la
limitación adicional de que el primer operando sólo puede ser una
constante o el registro %cl
.
La tabla
5.8 muestra ejemplos de utilización de este tipo de instrucciones.
Se asume que el símbolo contador
se refiere a
un operando almacenado en memoria.
Tabla 5.8. Instrucciones de desplazamiento aritmético
Instrucción | Comentario |
---|---|
SAR $4, %eax |
Desplaza 4 bits
a la derecha el contenido del registro %eax .
Esta operación es equivalente a multiplicar por 16 el registro %eax . |
SALB %cl, contador |
Desplaza el byte almacenado en la posición de
memoria denotada por contador tantas
posiciones a la izquierda como indica el registro %cl . El sufijo de tamaño es necesario porque a
pesar de que el primer operando es un registro, éste contiene sólo el
número de posiciones desplazar. El tamaño de los datos se deduce, por
tanto del segundo operando. |
Las instrucciones de desplazamiento no aritmético son SHR
y SHL
para desplazar
a derecha e izquierda respectivamente. El comportamiento y
restricciones son idénticas a las instrucciones anteriores con una
única diferencia. Los nuevos bits que se insertan en los operandos
tienen siempre el valor cero. Por tanto, dependiendo de los valores de
los operandos, las instrucciones SAR
y SAL
se pueden comportar de forma idéntica.
Las instrucciones de rotación permiten manipular un operando como si sus bits formasen un círculo y se rotan en ambos sentidos un número determinado de posiciones.
Las instrucciones ROL
y ROR
rotan a izquierda y derecha respectivamente el
contenido de su segundo operando tantas posiciones como indica el
primer operando. El último bit que ha traspasado los límites del
operando se almacena en el bit de acarreo CF
.
Las instrucciones RCL
y RCR
son similares a las anteriores con la excepción
que el bit de acarreo CF
se considera como
parte del operando. El bit que sale del límite del operando se carga en
CF
y éste a su vez pasa a formar parte del
operando.
La figura 5.6 ilustra el funcionamiento de estas instrucciones.
Al igual que las instrucciones de desplazamiento aritmético, el
primer operando puede ser o una constante o el registro %cl
. El tamaño del dato a manipular se deduce del
segundo operando, y si este está en memoria, a través del sufijo de
tamaño de la instrucción.
La tabla 5.9 muestra
ejemplos de utilización de este tipo de instrucciones. Se asume que el
símbolo contador
se refiere a un operando
almacenado en memoria.
Tabla 5.9. Instrucciones de rotación
Instrucción | Comentario |
---|---|
RCR $4, %ebx |
Rota el registro
%ebx cuatro posiciones a su derecha
utilizando el bit de acarreo CF . |
RCLL %cl, contador |
Rota a la
izquierda tantas posiciones como indica el registro %cl el operando de 32 bits almacenado en memoria a
partir de la posición denotada por contador .
A pesar de que el primer operando es un registro, la instrucción
necesita sufijo de tamaño, pues éste se deduce únicamente del segundo
operando que está en memoria. |
ROR %cl, %eax |
Rota a la
derecha el registro %eax tantas posiciones
como indica el registro %cl . El bit CF almacena el bit más significativo del
resultado. |
ROLL %cl, contador |
Rota a la izquierda tantas posiciones como
indica el registro %cl el número de 32 bits
almacenado en memoria a partir de la posición contador . De nuevo se precisa el sufijo de tamaño
porque éste se deduce únicamente a la vista del segundo operando. |
El procesador ejecuta una instrucción tras otra de forma secuencial a no ser que dicho flujo de ejecución se modifique. Las instrucciones de salto sirven para que el procesador, en lugar de ejecutar la siguiente instrucción, pase a ejecutar otra en un lugar que se denomina “destino del salto”.
La instrucción de salto JMP
(del inglés
jump)
tiene un único operando que representa el lugar en el que el procesador
debe continuar ejecutando. Al llegar a esta instrucción, el procesador
no realiza operación alguna y simplemente pasa a ejecutar la
instrucción en el lugar especificado como destino del salto. El único
registro, por tanto, que se modifica es el contador de programa.
A la instrucción JMP
se le denomina
también de salto incondicional por contraposición a las instrucciones
de salto en las que el procesador puede saltar o no al destino
dependiendo de una condición.
La arquitectura IA-32 dispone de 32 instrucciones de salto
condicional. Todas ellas comienzan por la letra J
seguida de una abreviatura de la condición que
determina si el salto se lleva a cabo o no. Al ejecutar esta
instrucción el procesador consulta esta condición, si es cierta
continua ejecutando la instrucción en la dirección destino del salto.
Si la condición es falsa, la instrucción no tienen efecto alguno sobre
el procesador y se ejecuta la siguiente instrucción.
Las condiciones en las que se basa la decisión de saltar dependen de
los valores de los bits de estado CF
, ZF
, OF
, SF
y PF
. La tabla 5.10
muestra para cada instrucción los valores de estos bits para los que se
salta a la instrucción destino.
Tabla 5.10. Instrucciones de salto condicional
Instrucción | Condición | Descripción | Instrucción | Condición | Descripción |
---|---|---|---|---|---|
JA mem JNBE mem |
CF = 0 y ZF = 0 |
Salto si mayor, salto si no menor o igual (sin signo) |
JBE mem JNA mem |
CF = 1 ó ZF = 1 |
Salto si menor o igual, salto si no mayor (sin signo) |
JAE mem JNB mem |
CF = 0 |
Salto si mayor o igual, salto si no menor (sin signo) |
JB mem JNAE mem |
CF = 1 |
Salto si menor, salto si no mayor o igual (sin signo) |
JE mem JZ mem |
ZF = 1 |
Salto si igual, salto si cero. |
JNE mem JNZ mem |
ZF = 0 |
Salto si diferente, salto si no cero. |
JG mem JNLE mem |
ZF = 0 y SF = OF |
Salto si mayor, si no menor o igual (con signo) |
JLE mem JNG mem |
ZF = 1 ó SF != OF |
Salto si menor o igual, si no mayor (con signo) |
JGE mem JNL mem |
SF = OF |
Salto si mayor o igual, si no menor (con signo) |
JL mem JNGE mem |
SF != OF |
Salto si menor, si no mayor o igual (con signo) |
JC mem |
CF = 1 |
Salto si acarreo es uno |
JNC mem |
CF = 0 |
Salto si acarreo es cero |
JCXZ mem |
%cx = 0 |
Salto si registro %cx es
cero. |
JECXZ mem |
%ecx = 0 |
Salto si
registro %ecx es cero. |
JO mem |
OF = 1 |
Salto si el bit de desbordamiento es uno. |
JNO mem |
OF = 0 |
Salto si el bit de desbordamiento es cero. |
JPO mem JNP mem |
PF = 0 |
Salto si paridad impar, si no paridad. |
JPE mem JP mem |
PF = 1 |
Salto si paridad par, si paridad. |
JS mem |
SF = 1 |
Salto si positivo. |
JNS mem |
SF = 0 |
Salto si negativo. |
En la tabla se incluyen instrucciones con diferente nombre e idéntica condición. Estos sinónimos son a nivel de lenguaje ensamblador, es decir, las diferentes instrucciones tienen una codificación idéntica y por tanto corresponden con la misma instrucción máquina del procesador.
La utilidad de estas instrucciones se debe entender en el contexto del flujo normal de ejecución de un programa. El resto de instrucciones realizan diferentes operaciones sobre los datos, y a la vez modifican los bits de la palabra de estado. Las instrucciones de salto se utilizan después de haber modificado estos bits y para poder tener dos posibles caminos de ejecución.
El ejemplo 5.4 muestra una porción de código ensamblador muestra un posible uso de las instrucciones de salto.
Ejemplo 5.4. Uso de saltos condicionales
MOV $100, %ecx dest2: DEC %ecx JZ dest1 ADD %ecx, %eax JMP dest2
La instrucción DEC %ecx
decrementa el
valor del registro %ecx
y modifica los bits
de la palabra de estado. La instrucción JZ
provoca un salto si ZF
= 1. Como
consecuencia, la instrucción ADD %ecx, %eax
se ejecuta un total de 100 veces.
Las instrucciones de salto condicional son útiles siempre y cuando
los valores de los bits de estado hayan sido previamente producidos por
instrucciones anteriores, como por ejemplo, operaciones aritméticas.
Pero en algunos casos, la ejecución de un salto condicional requiere
que se realice una operación aritmética y no se almacene su resultado,
sino simplemente que se realice una comparación. Por ejemplo, si se
necesita saltar sólo si un número es igual a cero, en lugar de ejecutar
una instrucción ADD
, SUB
, INC
o DEC
para que se modifique el bit ZF
sólo se necesita comprobar si tal número es cero
y modificar los bits de estado. Para este cometido el procesador
dispone de las instrucciones de comparación y comprobación.
Las instrucciones CMP
(comparación) y
TEST
(comprobación) realizan sendas
operaciones aritméticas de las que no se guarda el resultado obtenido
sino que únicamente se modifican los bits de estado.
La instrucción CMP
recibe dos operandos.
El primero de ellos puede ser de tipo constante, registro u operando en
memoria. El segundo puede ser únicamente de tipo registro u operando en
memoria. La instrucción no permite que ambos operandos estén en
memoria. Al ejecutar esta instrucción se resta el primer operando del
segundo. El valor resultante no se almacena en lugar alguno, pero sí se
modifican los bits de estado del procesador.
Considérese el código mostrado en el
ejemplo 5.5. La instrucción de comparación modifica los bits de
estado para que la instrucción de salto los interprete y decida si debe
saltar o continuar ejecutando la instrucción ADD
.
Ejemplo 5.5. Instrucción de comparación antes de salto condicional
CMP $0, %eax # Se calcula %eax - 0 JE destino ADD %eax, %ebx
La instrucción JE
produce un salto cuando
el bit de estado ZF
tiene el valor 1. Este
bit, a su vez se pone a uno si los operandos de la instrucción CMP
son iguales. Por tanto, la instrucción JE
, cuando va a continuación de una instrucción de
comparación, se puede interpretar como “salto si
los operandos (de la instrucción anterior) son iguales”.
En la mayoría de las instrucciones de salto condicional detalladas
en la sección 5.4.5, las últimas
letras del nombre hacen referencia a la condición que se comprueba
cuando se ejecutan a continuación de una instrucción de comparación.
Por ejemplo, la instrucción JLE
produce un
salto cuando los bits de condición cumplen ZF
= 1 o SF
!= OF
. Si
esta instrucción va precedida de una instrucción de comparación, ZF
es igual a 1 si los dos operandos son iguales.
Si SF
es diferente a OF
la resta ha producido un bit de signo, y el bit
de desbordamiento con valores diferentes. Esta situación se produce si
el segundo operando es menor que el primero, de ahí el sufijo LE
(del inglés less or equal) en la instrucción de
salto. La
tabla 5.11 muestra las combinaciones obtenidas del bit de
desbordamiento y la resta para el caso de enteros representados con 2
bits.
Tabla 5.11. Resta y bit de desbordamiento de dos enteros de 2 bits
OF, A-B | B | ||||
(-2) 10 | (-1) 11 | (0) 00 | (1) 01 | ||
A | (-2) 10 | 0, 00 | 0, 11 | 0, 10 | 1, 01 |
(-1) 11 | 0, 01 | 0, 00 | 0, 11 | 0, 10 | |
(0) 00 | 1, 10 | 0, 01 | 0, 00 | 0, 11 | |
(1) 01 | 1, 11 | 1, 10 | 0, 01 | 0, 00 |
El bit de signo y el de desbordamiento tienen valores diferentes
únicamente en el caso en que el primer operando de la resta es menor
que el segundo. Por tanto, la instrucción JLE
si se ejecuta a continuación de una instrucción CMP
se garantiza que el salto se lleva a cabo si el
segundo operando es menor que el primero.
Las instrucciones de salto cuya condición puede interpretarse con respecto a la instrucción de comparación que le precede son las que en la descripción mostrada en la tabla tabla 5.10 incluyen una comparación. Aunque estas instrucciones no debe ir necesariamente precedidas por una instrucción de comparación porque la condición se evalúa con respecto a los bits de estado, generalmente se utilizan acompañadas de éstas.
Para interpretar el comportamiento de una instrucción de comparación seguida de una de salto condicional se puede utilizar la siguiente regla mnemotécnica:
![]() |
Salto condicional precedido de comparación |
---|---|
Dada la siguiente secuencia de dos instrucciones en ensamblador: CMP B, A Jcond donde |
Por ejemplo, si la instrucción CMP $4,
%eax
va seguida del salto condicional JL
destino
, el procesador saltará a destino
si %eax
<
4.
La tabla 5.12 muestra posibles secuencias de instrucciones de comparación y salto condicional.
Tabla 5.12. Secuencias de instrucciones de comparación y salto condicional
Código | Comentario |
---|---|
inicio: inc %eax cmp $128, %eax jae final ... jmp inicio final: mov $'A, %cl ... |
El salto a final se produce si el registro %eax contiene un valor mayor o igual a 128. La
condición del salto es para operandos sin signo, es decir, el resultado
de la comparación se interpreta como si los operandos fuesen números
naturales. |
cmp $12, %eax jle menor mov $10, %eax .... jmp final menor: mov $100, %eax ... final: inc %ebx |
El salto a menor se
produce si el registro %eax es menor o igual
que 12. La condición del salto es para operandos con signo (números
enteros). |
La posibilidad de saltar a una posición de código dependiendo de una
condición está presente en la mayoría de lenguajes de programación de
alto nivel. Por ejemplo, en el lenguaje Java, la construcción if () {} else {}
se implementa a nivel de
ensamblador basado en instrucción de salto condicional.
La instrucción de comprobación TEST
es
similar a la de comparación, también consta de dos operandos, el
segundo de ellos puede ser únicamente de tipo registro o memoria y no
se permite que ambos sean de tipo memoria. La diferencia con CMP
es que se realiza una conjunción bit a bit de
ambos operandos. El resultado de esta conjunción tampoco se almacena,
pero sí modifica los bits de estado OF
, CF
(ambos se ponen a cero), SF
, ZF
y PF
.
La tabla 5.13 muestra posibles secuencias de instrucciones de comprobación y salto condicional.
Tabla 5.13. Secuencias de instrucciones de comprobación y salto condicional
Código | Comentario |
---|---|
testl $0x0080, contador jz ignora .... ignora: incl %ebx |
El salto a ignora se produce si el operando de 32 bits
almacenado en memoria a partir de la posición contador tiene su octavo bit igual a cero. Esta
instrucción precisa el sufijo de tamaño. |
test 0xFF00FF00, %eax jnz pl .... jmp final pl: mov %eax ... |
El salto a pl se
produce si alguno de los bits en las posiciones 8 a 15 o 24 a 31 del
registro %eax es igual a uno. |
Una de las construcciones más comunes en la ejecución de programas es la invocación de porciones de código denominadas subrutinas con un conjunto de parámetros. Este mecanismo es en el que está basada la invocación de procedimientos, métodos o funciones en los lenguajes de programación de alto nivel.
Para implementar este mecanismo, el procesador dispone de dos
instrucciones. La instrucción CALL
tiene un
único parámetro que es la posición de memoria de la primera instrucción
de una subrutina. El efecto de esta instrucción es similar a la de
salto incondicional con la diferencia de que el procesador guarda
ciertos datos en lugares para facilitar el retorno una vez terminada la
la ejecución de la subrutina.
La instrucción RET
es la que se utiliza al
final de una subrutina para retomar la ejecución en el punto anterior a
la invocación mediante la instrucción CALL
.
No recibe ningún parámetro y el procesador gestiona internamente el
lugar en el que debe continuar la ejecución.
En el capítulo 8 se estudia con todo detalle la utilización de estas instrucciones para implementar construcciones presentes en lenguajes de programación de alto nivel.