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 el procesador Intel Pentium. 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. El propio modelo Pentium 4 decodifica las instrucciones de su lenguaje máquinas 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.
El Intel Pentium es un ejemplo de procesador con formato variable de instrucciones. Dicho formato se estudia en mayor detalle en las siguientes secciones.
El procesador Intel Pentium 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 del Intel Pentium 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ón RET
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ón INC %eax
que incrementa en uno el valor de
su único operando.
Operación Operando1, Operando2
. Un ejemplo de este
tipo de instrucciones es ADD $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 del procesador Intel Pentium, y más en concreto de la arquitectura IA-32, la descripción detallada del lenguaje máquina, su arquitectura 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 del procesador Intel Pentium 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 del Intel Pentium 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 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
. El procesador Intel Pentium 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 del Intel Pentium 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.
El Intel Pentium 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.
Utilizando cualquier buscador de internet, localiza los tres volúmenes del documento IA-32 Intel Architecture Software Developer's Manual. Utilizando el volumen 2, responde a las siguientes preguntas:
Una duda común sobre la instrucción de pila POP
es la
siguiente. El incremento del registro apuntador de pila
%esp
, ¿se hace antes o después de escribir el dato de
la cima de la pila en el lugar indicado en la instrucción?
¿Qué código de operación en hexadecimal tiene la instrucción
PUSH $4
?
¿Qué hace la instrucción LAHF
? ¿Cuántos operandos
recibe?
¿Qué hace la operación NOP
? ¿Qué diferencia hay entre
la instrucción NOP
y la instrucción XCHG %eax,
%eax
?
¿Qué hace la instrucción STC
?
¿Qué flags de la palabra de estado modifica la ejecución de una instrucción de resta?
Pensar una situación en un programa en la que la única posibilidad de multiplicar dos números sea mediante la instrucción con un único operando.
Enunciar las condiciones que deben cumplir los operandos para que las
instrucciones SAL
y SHL
se comporten de forma
idéntica. Enunciar estas condiciones para las instrucciones
SAR
y SHR
.