Tabla de contenidos
En este capítulo se estudia una parte concreta de la ejecución de cada instrucción máquina: la obtención de los operandos. Tras recibir una instrucción el procesador debe obtener los operandos necesarios que están almacenados en registros de propósito general, en la propia instrucción o en memoria. El acceso a los dos primeros es sencillo, pero el acceso a memoria puede ser arbitrariamente complejo.
A pesar de que cada dato almacenado en memoria tiene una dirección, en la práctica, se suelen utilizar un conjunto de operaciones aritméticas para obtenerla. La figura 7.1 muestra un ejemplo de las operaciones necesarias para obtener la dirección de un elemento en una tabla de enteros.
Supóngase una tabla de números enteros de 32 bits almacenados a partir de la posición 100 de memoria. El único dato del que se dispone es dicho valor. ¿Cómo se puede acceder al elemento con índice 3 de la tabla? Sabiendo que los enteros tienen 4 bytes de tamaño, sumando a la dirección base de la tabla el tamaño de los 3 números anteriores se obtiene la dirección del entero deseado.
Los cálculos para obtener una dirección de memoria suelen requerir operaciones de suma, resta, multiplicación y división y por tanto pueden realizarse utilizando las instrucciones aritméticas del procesador. Para acceder a un operando en memoria se calcula primero su dirección con las operaciones pertinentes y luego se accede a memoria.
Pero estos dos pasos resultan ser extremadamente frecuentes en la ejecución de los programas convencionales. Una parte importante de las instrucciones ejecutadas por un procesador están destinadas al cálculo de la dirección de un operando que se necesita para una operación posterior.
A la vista de esta situación, el diseño de los procesadores ha ido incorporando a la propia instrucción máquina la posibilidad de realizar ciertos cálculos sencillos para la obtención de la dirección de sus operandos. La estrategia consiste en incluir los cálculos más comunes como parte de la instrucción y de esta forma conseguir secuencias de instrucciones más compactas, puesto que las operaciones aritméticas se hacen igual pero sin necesidad de ejecutar múltiples instrucciones, y por consiguiente se obtiene una mayor eficiencia en la ejecución.
Para ilustrar la ventaja de los modos de direccionamiento considérese la situación en la que el procesador debe acceder a un operando en memoria cuya dirección d se obtiene mediante la ecuación 7.1. Supóngase que se debe sumar el valor 300 al número de 32 bits almacenado en esta posición. Una posible secuencia de instrucciones para realizar tal operación se ilustra en el ejemplo 7.1
Ejemplo 7.1. Cálculo de la dirección de un operando mediante instrucciones
mov %ebx, %ecx # %ecx = %ebx sal $2, %ecx # %ecx = (%ebx * 4) add %eax, %ecx # %ecx = %eax + (%ebx * 4) add $1000, %ecx # %ecx = 1000 + %eax + (%ebx * 4) addl $300, (%ecx) # Sumar 300 a la posición indicada por %ecx
Las cuatro primeras instrucciones calculan el valor de la dirección del
operando y es únicamente la última la que realiza el acceso y la suma de la
constante $300
. Esta última instrucción está accediendo a
memoria de una forma especial. La expresión de su segundo operando
(%ecx)
indica que el operando está en memoria en la posición
contenida en el registro %ecx
. Se precisa el sufijo de tamaño
porque el primer operando es una constante, y el segundo operando, a pesar
de contener el nombre de un registro, en realidad especifica una dirección
de memoria.
Supóngase ahora que el lenguaje máquina del procesador permite escribir la siguiente instrucción:
addl $300, 1000(%eax, %ebx, 4)
El efecto de esta instrucción es exactamente el mismo que el de las cinco
instrucciones del ejemplo 7.1. Se realiza la suma
de 300 y el número de 32 bits almacenado en memoria en la posición obtenida
al sumar 1000, el contenido del registro %eax
y el contenido
del registro %ebx
previamente multiplicado por cuatro. Este
segundo operando es a la vez fuente y destino.
La existencia de tal instrucción en el lenguaje máquina tiene la ventaja de que en una instrucción el procesador recibe mucha más información sobre la operación a realizar. En el primer caso se precisan cinco instrucciones, o lo que es lo mismo, cinco ciclos de ejecución como los descritos en la sección 4.2. En el segundo caso, con una única instrucción el procesador dispone de todos los ingredientes para realizar los cálculos. La mejora en rendimiento no se deriva del número de operaciones aritméticas, pues son exactamente las mismas en ambos casos, sino del número de instrucciones que se ejecutan.
Pero a cambio, el principal inconveniente de esta solución es que la codificación y longitud de las instrucciones se complica tanto como complejas sean las operaciones que se permiten en una instrucción. En este caso se realizan dos sumas y una multiplicación para obtener la dirección de un operando que a su vez participa en la suma final.
Al conjunto de métodos que el lenguaje máquina de un procesador ofrece para
acceder a sus operandos se les denomina modos de
direccionamiento. El número de métodos diferentes depende de
cada procesador y varía enormemente entre diferentes diseños. Los modos de
direccionamiento del Intel Pentium contemplan el acceso a operandos como
los utilizados en la instrucción addl $300, 1000(%eax, %ebx,
4)
.
La figura 7.2 ilustra el funcionamiento de los modos de direccionamiento de un procesador.
En general un modo de direccionamiento es un procedimiento que dado un conjunto de bits o campos de la instrucción calcula el lugar en el que se encuentra almacenado un operando.
Para especificar en detalle el funcionamiento de los modos de direccionamiento se precisa una notación para referirse a los componentes de las operaciones necesarias.
La “dirección efectiva de un operando” que se denota por de es el lugar en el que se encuentra almacenado un operando. Esta dirección no tiene por qué ser una posición de memoria. Existen modos de direccionamiento en los que la dirección efectiva de un operando es un registro de propósito general.
La instrucción de la que se obtienen los datos necesarios para calcular
de se denota por inst
, la dirección
de memoria a partir de la cual está almacenada es
@inst, y los diferentes campos de esta instrucción
se denotan por inst
c1, ...,
inst
cn. Por campos se entienden
aquellos bits que forman parte de la instrucción y que codifican los
diferentes elementos necesarios para el cálculo de la dirección efectiva.
Cuando el campo inst
ci codifica uno
de los registros de propósito general, dicho registro se denota por
Rci. La expresión MEM[n] denota el contenido de
memoria almacenado a partir de la posición n. La acción de modificar el
contenido del registro R con el dato d se denota R ← d.
A continuación se estudian en detalle los modos de direccionamiento disponibles en la arquitectura IA-32 así como su sintaxis en ensamblador. Para cada uno de ellos se especifica la fórmula utilizada para obtener la dirección efectiva del operando y su valor.
Tal y como se ha visto en la sección 5.3.3, los operandos de una instrucción se dividen en cuatro categorías: constantes, registros, direcciones de memoria y operandos implícitos. Excepto estos últimos, el resto se obtienen a través de diferentes modos de direccionamiento. A continuación se presentan en orden creciente de complejidad para finalmente comparar todos ellos con el más complejo.
Es el modo de direccionamiento utilizado para obtener operandos de tipo
constante, es decir, aquellos que tienen el prefijo $
en
ensamblador. El operando está incluido en la propia instrucción. La
expresión de su dirección efectiva se muestra en la ecuación 7.2.
El valor k representa el número de bytes a partir de la dirección en la que está almacenada instrucción en la que se codifica la constante. La figura 7.3 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.
El primer operando de la instrucción es la constante $3
que tal y como se ve en la figura, en lenguaje máquina se codifica en
el último byte. Por tanto, la dirección efectiva del operando es
de = @inst + 4. Cuando el
procesador necesita este operando, lo obtiene directamente de los bytes
de la instrucción.
A pesar de que el sufijo de la instrucción indica que los operandos han
de ser de 32 bits, el primer operando se codifica con un único
byte. Esta aparente inconsistencia no es más que un mecanismo que
utiliza el procesador para que el código máquina sea más
compacto. Cuando en una instrucción se codifica un operando en modo
inmediato se utilizan 1, 2 o 4 bytes dependiendo de su valor. En este
ejemplo, como el valor es $3
sólo precisa un byte.
Es el modo de direccionamiento utilizado para obtener operandos
almacenados en uno de los ocho registros de propósito general. La
instrucción contiene un campo
inst
c1 de 3 bits que codifica los
ocho posibles registros. La expresión de la dirección efectiva y el
valor del operando se muestran en la ecuación 7.3.
La dirección efectiva del operando, en este caso, no es una dirección de memoria, sino la de uno de los registros de propósito general. La figura 7.4 muestra el funcionamiento de este modo de direccionamiento y un ejemplo.
En la figura, el código de operación 0x89
no sólo indica
que se realiza una operación de mover, sino que el primer operando es
de tipo registro. El nombre del registro está codificado en el segundo
byte. También en este byte, se codifica el tipo del segundo operando,
que es igualmente de tipo registro. Para ello se utiliza el campo R/M
del byte ModR/M.
Los modos de direccionamiento restantes se refieren todos ellos a operandos almacenados en memoria y se diferencian en los cálculos para obtener la dirección efectiva de.
En este modo de direccionamiento la dirección efectiva corresponde con una dirección de memoria y forma parte de los bytes que codifican la instrucción. En otras palabras, la propia instrucción, en su codificación incluye una dirección de la que obtener uno de sus operandos. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.4.
Como la instrucción contiene la dirección efectiva, ésta está contenida en memoria desplazada k bytes con respecto a la dirección @inst. El operando está en memoria en la posición que indica de, de ahí la doble expresión MEM[]. La figura 7.5 muestra el funcionamiento de este modo de direccionamiento.
Como muestra la figura, la dirección de memoria ocupa 4 de los bytes
que codifican la instrucción. En el ejemplo que se muestra, la
dirección es 0x0000059A
pues los datos se almacenan en
little endian. La representación en
ensamblador de este modo de direccionamiento es mediante una
etiqueta. El ensamblador asigna a cada una de ellas un valor, y cuando
se utiliza en una instrucción se reemplaza el símbolo por el valor de
su dirección de memoria.
El modo registro indirecto accede a un operando en memoria utilizando como dirección el valor contenido en uno de los registros de propósito general. La palabra “indirecto” hace referencia a que primero se obtiene el valor del registro y luego se utiliza dicho valor como dirección de memoria. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.5.
Para codificar este modo de direccionamiento sólo es preciso incluir el código del registro de propósito general a utilizar, por tanto con 3 bits es suficiente. La responsabilidad de que en el registro utilizado para la indirección esté contenida una dirección de memoria correcta recae totalmente en el programador. La figura 7.6 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.
Tal y como muestra la figura, la sintaxis para denotar este modo de direccionamiento en una instrucción es con el nombre del registro escrito entre paréntesis. El operando se obtiene de memoria mediante la dirección almacenada en el registro. La principal ventaja de este modo de direccionamiento es que, al estar almacenada la dirección en un registro, esta se puede manipular con las operaciones aritméticas que ofrece el procesador como cualquier otro dato.
Por ejemplo, la instrucción MOV (%esp), %eax
carga en el
registro %eax
el valor almacenado en la cima de la
pila. La instrucción es correcta porque el registro %esp
contiene la dirección de memoria de la cima de la pila.
Considérese la secuencia de instrucciones del ejemplo 7.2. La primera instrucción simplemente copia
el valor del puntero de pila en el registro %eax
. La
siguiente instrucción suma la constante $4
al valor
almacenado en la cima de la pila. La instrucción necesita el sufijo de
tamaño porque el segundo operando es un registro indirecto, con lo que
especifica la dirección de memoria del operando, pero no su tamaño.
Ejemplo 7.2. Acceso a los elementos de la pila con el modo registro indirecto
mov %esp, %eax addl $4, (%eax) add $4, %eax addl $4, (%eax)
La instrucción add $4, %eax
muestra como una dirección de
memoria se puede manipular como un dato numérico. Al sumarle
$4
el nuevo valor obtenido es la dirección de memoria del
elemento almacenado en la pila justo debajo de la cima. La última
instrucción suma la constante $4
al operando almacenado en
memoria en la dirección contenida en el registro %eax
, o
lo que es lo mismo, al dato almacenado debajo de la cima de la pila.
En el ejemplo anterior, se ha manipulado el contenido de los datos almacenados en la pila sin modificar el puntero a su cima. Esto es posible gracias a que se han hecho los cálculos con una dirección de memoria que es una copia del puntero a la cima.
El modo registro indirecto se puede utilizar para acceder a cualquier
dato en memoria. En el ejemplo 7.3 se muestra
una secuencia de instrucciones que acceden a elementos de una tabla de
datos de 16 bits almacenados a partir de la dirección de memoria
representada por la etiqueta tabla
.
Ejemplo 7.3. Acceso a los elementos de una tabla con el modo registro indirecto
mov $tabla, %eax mov (%eax), %bx add $2, %eax mov (%eax), %cx add $6, %eax mov (%eax), %dx
La primera instrucción almacena en el registro %eax
la
dirección de memoria representada por la etiqueta
tabla
. El valor exacto que se carga en el registro es
imposible saberlo de antemano, pues depende de dónde en memoria estén
almacenados los datos, pero igualmente se puede utilizar con los modos
de direccionamiento.
La segunda instrucción accede al primer elemento de la tabla con el
modo registro indirecto. El operando destino es el registro
%bx
que por tanto, fija el tamaño de dato a ser
transferido a 16 bits y no es necesario el sufijo de tamaño. La
siguiente instrucción suma la constante $2
al registro
%eax
y se accede de nuevo a un elemento de la tabla
mediante registro indirecto. En este caso se almacena en el registro
%cx
el número de 16 bits almacenado en segunda
posición. La instrucción add $6, %eax
hace que el registro
contenga la dirección del elemento en la quinta posición, o lo que es
equivalente, con índice 4. La última instrucción accede a este elemento
de la tabla y lo almacena en %dx
.
El funcionamiento del modo auto-incremento es similar al modo registro indirecto, con la salvedad de que el registro, tras ser utilizado para el acceso a memoria, incrementa su valor en una constante. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.6
El efecto de la modificación del valor contenido en el registro es que
la dirección pasa ahora a apuntar a una posición de memoria cuatro
bytes más alta que el valor anterior. En principio, cualquier registro
de propósito general puede ser utilizado, pero en el caso concreto de
la arquitectura IA-32, este modo únicamente se utiliza en la
instrucción POP
y con el registro %esp
. La
figura 7.7 ilustra el funcionamiento de
este modo de direccionamiento así como un ejemplo de la instrucción
POP
.
Al tratarse de la única instrucción del Intel Pentium que utiliza este
modo de direccionamiento y que únicamente se utiliza el registro
%esp
, la codificación en la instrucción de este modo de
direccionamiento es un caso especial, pues está implícita en el código
de operación. El byte con valor 0x58
no sólo codifica la
operación POP
sino también la utilización del modo
auto-incremento con el registro %esp
para obtener el dato
a almacenar.
El efecto de este modo de direccionamiento corresponde con el
comportamiento de la instrucción POP
. El operando
implícito es el dato de la cima de la pila que se obtiene mediante la
dirección de memoria almacenada en %esp
. Este operando se
almacena donde indica el único operando explícito de la instrucción, y
a continuación se incrementa el valor de %esp
de forma
automática en 4 unidades. Como consecuencia, el puntero de pila apunta
a la nueva cima.
El modo auto-decremento es similar al auto-incremento pues realiza una indirección y modifica el registro utilizado, pero la modificación de la dirección se realiza antes de acceder a memoria. La funcionalidad de este modo de direccionamiento se puede considerar complementaria a la anterior. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.7
La dirección efectiva no está directamente contenida en el registro
especificado, sino que previamente se resta la constante 4 a su valor y
se accede a memoria con el valor resultante. Además, este valor, tras
acceder a memoria, se almacena de nuevo en el registro. En principio
cualquier registro de propósito general puede utilizarse para este modo
de direccionamiento, pero en el caso de la arquitectura IA-32, este
modo únicamente se utiliza en la instrucción PUSH
y con el
registro %esp
. La figura 7.8
ilustra el funcionamiento de este modo de direccionamiento así como un
ejemplo de la instrucción PUSH
.
Al igual que en el caso del modo auto-incremento, al ser
PUSH
la única instrucción que utiliza este modo en el
Intel Pentium, su codificación está implícita en el código de operación
0x6A
. El operando implícito, por tanto es la dirección de
la nueva cima en la que almacenar el valor dado como operando explícito
y que se obtiene restando 4 del registro %esp
.
El modo de direccionamiento base + desplazamiento obtiene la dirección efectiva del operando mediante la utilización de dos elementos. Este es el primer ejemplo en el que el procesador, como paso previo para la obtención de los operandos, obtiene más de un dato de la instrucción y lo utiliza para calcular la dirección efectiva. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.8.
En este modo, la dirección efectiva no está contenida en ningún lugar, sino que es el resultado de la suma del contenido de un registro y de un valor almacenado como parte de la instrucción. El procesador obtiene estos dos valores, los suma, y el resultado lo utiliza para acceder a memoria y obtener el operando. El nombre de este modo se deriva de que al registro utilizado se le conoce como el nombre de “base” mientras que el valor numérico adicional se conoce como “desplazamiento”. Como la operación que se realiza entre ambos es la suma, se puede considerar que la dirección efectiva del operando se obtiene partiendo de un registro base cuyo valor se desplaza tanto como indica la constante.
La sintaxis para especificar este modo de direccionamiento tiene dos
posibles formatos. El primero consiste en escribir el nombre del
registro entre paréntesis precedido de una constante entera sin prefijo
alguno, como por ejemplo la instrucción INCL 12(%ecx)
. La
constante puede ser escrita en cualquiera de los formatos permitidos
por el ensamblador: decimal, binario, octal, hexadecimal o letra.
El segundo formato permitido consiste en escribir el nombre del
registro entre paréntesis precedido por el nombre de una etiqueta
previamente definida, como por ejemplo la instrucción SUB %eax,
dato(%ecx)
. El valor dato
hace referencia a la
dirección de memoria en la que esta etiqueta ha sido definida, por lo
que la dirección efectiva se obtiene sumando la dirección de la
etiqueta con el valor contenido en el registro base. En los programas
ensamblador se utilizan ambas notaciones de forma muy frecuente. La
figura 7.9 ilustra el funcionamiento de este
modo de direccionamiento así como dos ejemplos.
La instrucción ADD %eax, 16(%ebx)
utiliza el primer
formato de este modo en su segundo operando. La instrucción codifica el
desplazamiento con sólo 8 bits al tener un valor entre -128 y 127. El
byte ModR/M con valor 0x43
codifica que el modo de
direccionamiento del primer operando
es registro y que el del segundo es base + desplazamiento con el
registro %ebx
y una constante de 8 bits.
La instrucción ADD %eax, contador(%ecx)
tiene una
codificación de 6 bytes. Los cuatro últimos codifican la dirección de
memoria que representa la etiqueta contador
. El byte
ModR/M con valor 0x81
en este caso codifica el modo de
direccionamiento del primer operando que es registro y que el del
segundo es con registro base %ebx
y desplazamiento de 32
bits.
Este modo de direccionamiento ofrece un mecanismo muy eficiente para acceder a tablas de elementos. Supóngase que se ha definido una tabla de números enteros de 32 bits y se escribe el código que se muestra en el ejemplo 7.4.
Ejemplo 7.4. Acceso a una tabla de enteros con modo base + desplazamiento
.data tabla: .int 12, 32, -34, -1, 1232, 435 .text .global main main: ... mov $0, %ebx mov $0, %ecx ADD tabla(%ebx), %ecx ADD $4, %ebx ADD tabla(%ebx), %ecx ADD $4, %ebx ADD tabla(%ebx), %ecx ADD $4, %ebx ...
Los enteros se almacenan a partir de la posición de memoria que
representa la etiqueta tabla
. Las dos primeras
instrucciones cargan el valor 0 en los registros %ebx
y
%ecx
. Las siguientes instrucciones suman el valor de los
tres primeros de la tabla y depositan el resultado en el registro
%ecx
. Para ello, el registro %ebx
contiene el
valor que sumado a la dirección de tabla
se obtiene la
dirección de los sucesivos elementos. La instrucción ADD
tabla(%ebx), %ecx
se repite sin modificación alguna y accede a
elementos sucesivos porque el registro base va cambiando su valor. Este
tipo de instrucciones son muy eficientes si se quiere procesar todos
los elementos de una tabla, pues basta con escribir un bucle que vaya
incrementando, en este caso, el valor del registro %ebx
.
La utilización de constantes como desplazamiento se utiliza
generalmente para acceder a elementos almacenados en posiciones
alrededor de una dirección dada que se almacena en el registro
base. Supóngase que el registro %edx
contiene la dirección
de memoria a partir de la cual están almacenados, por este orden, dos
números enteros y cuatro letras ASCII. El código que se muestra en la
ejemplo 7.5 accede a los enteros y las cuatro
letras con el registro %edx
como base y con diferentes
desplazamientos.
Ejemplo 7.5. Definición y acceso a una tabla de enteros
.data .int 34 dato: .int 12, 24 .ascii "abcd" .text .global main main: ... mov $dato, %edx mov 0(%edx), %ecx add 4(%edx), %ecx mov 8(%edx), %ah mov 9(%edx), %al mov 10(%edx), %bh mov 11(%edx), %bl add -4(%edx), %ecx ...
La primera instrucción almacena en %edx
la dirección de
memoria que representa la etiqueta dato
. En la segunda
instrucción se utiliza el modo de direccionamiento base +
desplazamiento pero con un desplazamiento igual a cero. El entero con
valor 12 se almacena en el registro %ecx
. Esta instrucción
pone de manifiesto que el modo base + desplazamiento con un
desplazamiento igual a cero, es equivalente al modo registro indirecto.
La siguiente instrucción add 4(%edx), %ecx
suma el valor
del segundo entero al registro %ecx
. El desplazamiento
tiene el valor 4 debido a que los enteros almacenados a partir de la
etiqueta son de 4 bytes. La siguiente instrucción accede a la primera
letra y la almacena en el registro de 8 bits %ah
. En este
caso el desplazamiento es 8 porque las letras están almacenadas en la
posición siguiente a la del último byte del último entero de la
definición anterior. Las siguientes instrucciones cargan las siguientes
letras en los registros de 8 bits %al
, %bh
y
%bl
.
En este modo de direccionamiento, el número que precede al registro
entre paréntesis es un entero, y por tanto puede tener valor
negativo. La última instrucción muestra un ejemplo de este caso. De
igual forma que se acceden a posiciones a continuación de la
especificada por una etiqueta, también se puede acceder a posiciones
previas mediante desplazamientos negativos. El procesador suma este
desplazamiento al valor del registro y accede a esa posición de
memoria. Con las definiciones del ejemplo la instrucción está sumando
al registro %ecx
el número 34 almacenado justo antes de la
etiqueta.
El modo de direccionamiento base + índice es similar al anterior puesto que el procesador obtiene la dirección efectiva sumando de nuevo dos números, pero la diferencia es que ambos números se obtienen de registros de propósito general. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.9.
Al igual que en caso del modo de direccionamiento anterior, la
dirección efectiva no tiene por qué encontrarse en ningún registro sino
que se obtiene sumando el valor contenido en los dos registros
especificados. No existe restricción alguna sobre los valores que
contienen los registros, el procesador realiza la suma y obtiene el
operando de la posición de memoria obtenida como resultado. La sintaxis
para especificar este modo de direccionamiento es mediante dos
registros separados por una coma y entre paréntesis, por ejemplo
CMPL $32, (%eax, %esi)
.
A pesar de que este modo se denomina base + índice, los dos registros especificados son idénticos a todos los efectos, con lo que cualquiera de los dos puede ser el registro base o el índice. La figura 7.10 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.
La dirección efectiva se obtiene sumando los registros
%ebx
y %edi
que previamente deben tener los
valores pertinentes para que la dirección de memoria resultante sea la
correcta. Supóngase una tabla con 100 elementos, cada uno de ellos, a
su vez es una tabla de 5 enteros. Para acceder a un número se precisan
dos índices, el primero para seleccionar uno de los 100 elementos, y el
segundo para seleccionar uno de los 5 posibles enteros. Se quiere
acceder a los cinco números del elemento tabla[32]
almacenados a partir de la dirección de memoria contenida en el
registro %eax
. El ejemplo 7.6
muestra cómo acceder a estos elementos utilizando el modo de
direccionamiento base + índice.
Ejemplo 7.6. Acceso a los enteros de un elemento de una tabla
.... mov $0, %ebx mov (%eax, %ebx), %ecx add $4, %ebx add (%eax, %ebx), %ecx add $4, %ebx add (%eax, %ebx), %ecx add $4, %ebx add (%eax, %ebx), %ecx add $4, %ebx add (%eax, %ebx), %ecx ...
La primera instrucción carga el valor cero en %ebx
. A
continuación se accede al primer entero del elemento de la tabla
sumando la dirección a partir de donde están almacenados, que está
contenida en %eax
y el valor en %ebx
. Como
este último registro tiene el valor cero, el acceso es idéntico al
obtenido si se utiliza el modo registro indirecto.
A continuación se suma 4 al registro %ebx
y se accede de
nuevo con el mismo modo en este caso para sumar el contenido en memoria
al registro %ecx
. Tras ejecutar esta instrucción en
%ecx
se obtiene la suma de los dos primeros
elementos. Mediante sucesivos incrementos del registro
%ebx
y luego accediendo a los elementos con el modo base +
índice se calcula la suma de los cinco números en el registro
%ecx
.
La misma secuencia de instrucciones tendría un efecto idéntico si se intercambian los nombres de los registros en el paréntesis que especifica el modo base + índice.
En este modo de direccionamiento toman parte tres elementos de la instrucción para obtener la dirección efectiva del operando. Un registro, denominado el índice, ofrece un valor que se multiplica por un factor de escala especificado por una constante, y el resultado se suma a una segunda constante entera denominada desplazamiento. El factor de escala puede tener únicamente los valores 1, 2, 4 y 8. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.10.
La característica más importante de este modo de direccionamiento es el producto entre el registro índice y la escala. Este último factor, en lugar de ser un número entero, tan sólo puede una de las cuatro primeras potencias de dos. La razón para esta restricción es que la operación de multiplicación de enteros es extremadamente costosa en tiempo como para que forme parte del cálculo de la dirección efectiva. En cambio, la multiplicación por estas potencias de dos es muy eficiente pues el resultado se obtiene mediante el desplazamiento del valor del registro. Si el factor de escala utilizado es 1, este modo de direccionamiento es idéntico al modo base + desplazamiento.
La sintaxis de este modo es ligeramente contra-intuitiva, pues se especifica el registro índice y el factor de escala separados por coma pero precedidos por una coma adicional, entre paréntesis y este a su vez precedido por el entero que codifica el desplazamiento. La figura 7.11 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.
La dirección efectiva del segundo operando de la instrucción de la
figura se obtiene multiplicando el contenido de %eax
por
el factor de escala 8 y sumando el desplazamiento. Al igual que en el
caso del modo base + desplazamiento, este último elemento puede ser un
número entero o una etiqueta previamente definida. Considérese el
ejemplo de una tabla de enteros de 64 bits almacenados
a partir de la etiqueta coeficientes
. El ejemplo 7.7 muestra la definición y manipulación
de estos datos con el modo índice escalado + desplazamiento.
Ejemplo 7.7. Acceso a una tabla de enteros de 64 bits con modo índice escalado + desplazamiento
.data coeficientes: .quad 21, 34, 56, 98 .text .global main main: ... mov $0, %eax mov coeficientes(, %eax, 8), %ebx inc %eax add coeficientes(, %eax, 8), %ebx inc %eax add coeficientes(, %eax, 8), %ebx inc %eax add coeficientes(, %eax, 8), %ebx ...
Los números definidos por la directiva .quad
son de 64
bits y por tanto ocupan 8 bytes. Tras cargar el valor 0 en el registro
%eax
, las siguientes instrucciones acceden a los números
de forma sucesiva utilizando este registro como índice. La
multiplicación del registro índice por el factor de escala 8 que
coincide con el tamaño de los datos hace que el índice coincida con el
valor que se utilizaría en un lenguaje de alto nivel como Java para
acceder a los números: coeficientes[3]
se accede mediante
la expresión coeficientes(, %eax, 8)
si %eax
contiene el valor 3.
Este modo de direccionamiento es el más complejo que ofrece el procesador y se puede considerar como la combinación de los modos base + desplazamiento e índice escalado + desplazamiento. La dirección efectiva se calcula sumando tres números: el desplazamiento, el contenido de un registro base y la multiplicación de un registro índice por un factor de escala que puede tener los valores 1, 2, 4 u 8. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.11.
Este modo de direccionamiento precisa cuatro elementos que están contenidos en la codificación de la instrucción. El campo que más espacio requiere es el desplazamiento que puede ser un valor entero o el nombre de una etiqueta previamente definida, por lo que se precisan 32 bits. El factor de escala, al poder tomar únicamente cuatro valores, se puede codificar con 2 bits.
La sintaxis de este modo es también una combinación de los anteriores. Entre paréntesis se escribe el registro base, el registro índice y el factor de escala, por este orden y separados por comas. El paréntesis va precedido del valor del desplazamiento. La figura 7.12 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.
El efecto de este modo de direccionamiento es que la dirección efectiva
se obtiene sumando un registro, una constante (el desplazamiento) y un
segundo registro susceptible de ser escalado. Al igual que en el modo
índice escalado + desplazamiento, la multiplicación por la escala se
realiza desplazando el operando cero, una, dos o tres posiciones hacia
su bit de más peso. Al igual que en el resto de modos que utilizan un
desplazamiento, este puede ser un número entero o una etiqueta
previamente definida. En la instrucción DIVL 4(%ecx, %edi,
4)
mostrada en la figura, la dirección efectiva del operando es
4 + %ecx + (%edi * 4)
, por lo que se asume que el registro
%ecx
contiene el valor de una dirección con respecto a la
cual se accede al operando. En la instrucción INCB contador(%ecx,
%edi, 4)
el desplazamiento es una dirección de memoria en base
de la cual se obtiene la dirección efectiva del operando.
Como ejemplo de utilización de este modo de direccionamiento
considérese que se ha definido una tabla de 10 enteros en Java a partir
de la posición de memoria con etiqueta num
. A continuación
se quiere acceder al elemento de la tabla con índice almacenado en el
registro %ecx
e incrementar su valor en una unidad. El
código mostrado en el ejemplo 7.8 muestra
las instrucciones necesarias para esta operación.
Ejemplo 7.8. Acceso a una tabla de enteros en Java con modo base + índice escalado + desplazamiento
.data num: .int 10, -1, 32, 345, -3556, 4, 21, 23, 15, 6543, 23 .text .global main main: ... mov $4, %ebx incl num(%ebx, %ecx, 4) ...
Como Java almacena el tamaño de una tabla en los cuatro primeros bytes,
la dirección del elemento con índice en %ecx
se obtiene
mediante la expresión num + 4 + (%ecx
* 4) que se puede
calcular de forma eficiente utilizando el modo base + índice escalado +
desplazamiento. Si se quisiese incrementar el valor de todos los
elementos de la tabla se puede escribir un bucle que incremente el
valor del registro %ecx
desde cero hasta el tamaño de la
tabla menos uno e incremente cada uno de ellos con una instrucción
idéntica a la del ejemplo.
Se han estudiado los modos de direccionamiento que ofrece el procesador como mecanismo eficiente de acceso a memoria. No todos los cálculos de la dirección efectiva de un operando pueden realizarse en una sola instrucción por medio de estos modos, tan sólo aquellos que requieran el tipo de operaciones que ofrece alguno de ellos. Los modos de direccionamiento son por tanto, un recurso que el procesador ofrece para ganar eficiencia en la ejecución de programas, pero que de ninguna forma limita la forma de obtener la dirección efectiva.
A la hora de programar en ensamblador y acceder a datos en memoria, la técnica para acceder a los datos es tener en cuenta qué operaciones se deben realizar para obtener su dirección, y si éstas pueden ser incluidas en una misma instrucción como modo de direccionamiento mejor que realizar estos cálculos con instrucciones máquina adicionales.
Supóngase que se define una matriz de enteros con m filas y n columnas
a partir de la posición representada por la etiqueta
matriz
. Los valores m y n son enteros y están almacenados
en memoria con etiquetas del mismo nombre. Las matrices se almacenan en
memoria utilizando múltiples estrategias. Las dos más comunes son por
filas y por columnas. En el primer formato, se almacenan los elementos
de una fila en posiciones contiguas de memoria y a continuación los de
la siguiente fila. En el segundo formato, los elementos de una columna
ocupan posiciones de memoria consecutivas, y a continuación se almacena
la siguiente columna. Supóngase que los elementos de esta matriz están
almacenados por filas. La figura 7.13 muestra
la distribución de los datos en memoria mediante su definición en
ensamblador. Cada posición de la matriz contiene un número formado por
el número de fila seguido del número de columna.
Para acceder a un elemento de la matriz se precisan dos índices (i, j), donde 0≤ i < m y 0≤ j < n. Dados los índices (i, j), la expresión de la dirección efectiva de este elemento según la definición de la figura 7.13 se muestra en la ecuación 7.12.
Supóngase que se tiene que acceder al elemento en la posición que
indican los registros %eax
y %ebx
para
incrementar en una unidad su valor mediante la instrucción
INC
. Dada la funcionalidad ofrecida en los modos de
direccionamiento, no es posible acceder al elemento con una única
instrucción, pues el cálculo de su dirección efectiva requiere
operaciones no contempladas.
Pero una porción de la ecuación 7.12 sí puede
ser calculada por el modo de direccionamiento base + índice escalado +
desplazamiento. Como desplazamiento se utiliza el valor de la etiqueta
matriz
, la segunda multiplicación se puede ejecutar como
un índice escalado, por lo que tan sólo es preciso obtener el resultado
de (i * n * 4) y almacenarlo en un registro. El ejemplo 7.9 muestra una posible secuencia de
instrucciones para acceder e incrementar el elemento.
Ejemplo 7.9. Instrucciones para incrementar un elemento de una matriz de enteros
... mull n sal $2, %eax incl matriz(%eax, %ebx, 4) ...
La instrucción mull n
multiplica el número de columnas por
el índice que indica el número de fila. Tal y como funciona esta
instrucción, al especificar un operando de 32 bits mediante el sufijo,
resultado se almacena en el registro de 64 bits obtenido al concatenar
%edx:%eax
. Se asume que el resultado no sobrepasa los 32
bits de %eax
. A continuación la instrucción sal $2,
%eax
desplaza el registro dos posiciones a su izquierda que
equivale a multiplicar por cuatro. Con esto se obtiene en
%eax
el término de la ecuación 7.12
que falta para poder utilizar el modo base + índice escalado +
desplazamiento tal y como muestra la instrucción incl
matriz(%eax, %ebx, 4)
.
Como efectos colaterales de este cálculo se ha perdido el valor inicial
del índice almacenado en %eax
así como el valor del
registro %edx
ambos modificados por la instrucción de
multiplicación. A modo de comparación, el ejemplo 7.10 muestra una secuencia alternativa de
instrucciones para realizar la misma operación pero que únicamente
utiliza el modo registro indirecto.
Ejemplo 7.10. Instrucciones para incrementar un elemento de una matriz de enteros utilizando el modo registro indirecto
... mull n sal $2, %eax sal $2, %ebx add %ebx, %eax add $matrix, %eax incl (%eax) ...
Las instrucciones adicionales realizan los cálculos equivalentes al
modo base + índice escalado + desplazamiento solo que utilizando
instrucciones máquina del procesador. El resultado de esta secuencia es
casi idéntico al anterior (en este caso se ha perdido también el valor
dado en el %ebx
) pero se ha utilizado un número muy
superior de instrucciones, con lo que su ejecución es mucho menos
eficiente.
Este ejemplo pone de manifiesto cómo el acceso a los operandos puede realizarse de múltiples formas, pero para obtener una ejecución eficiente y código compacto debe seleccionarse aquella que haga uso de los modos de direccionamiento ofrecidos por el procesador.
Una vez estudiados los modos de direccionamiento que ofrece el procesador se puede intuir el tipo de circuito digital utilizado para calcular la dirección efectiva de un operando almacenado en memoria. La figura 7.14 muestra una posible implementación.
Este circuito calcula la dirección efectiva para los modos de direccionamiento cuyo operando se encuentra en memoria, es decir, todos excepto inmediato y registro. De los bits que codifican la instrucción se obtienen los cuatro posibles elementos el cálculo de la dirección efectiva y mediante la utilización de las señales de control b, i, e y d se activa la participación de cada una de ellos. Por ejemplo, el modo índice escalado + desplazamiento requiere que la señal i seleccione la entrada del multiplexor que procede del banco de registros, la señal d seleccione el valor obtenido de la instrucción, y las dos señales restantes seleccionen la entrada constante de sus respectivos multiplexores.
Los modos de direccionamiento son los diferentes procedimientos que utiliza el procesador dentro de la ejecución de una instrucción para acceder a sus operandos. Las diferentes formas que permite el procesador Intel Pentium para acceder a sus operandos se muestran en la tabla 7.1.
Tabla 7.1. Modos de direccionamiento del Intel Pentium
Modo de direccionamiento | Dirección efectiva | Operando | Condiciones adicionales |
---|---|---|---|
Inmediato | |||
Registro | |||
Absoluto | |||
Registro indirecto | |||
Auto-incremento | |||
Auto-decremento | |||
Base + desplazamiento | |||
Base + índice | |||
Índice escalado + desplazamiento | |||
Base + índice escalado + desplazamiento |
Asumiendo que los campos de una instrucción máquina son ci1, ci2, ci3, ci4,... escribir la fórmula del cálculo de la dirección efectiva del operando y explicar su significado para los siguientes modos de direccionamiento: (Utilícese la notación (x) para denotar “el contenido de x”).
Registro Indirecto:
Absoluto:
Base + Índice:
Base + Índice Escalado + Desplazamiento:
Supóngase que de todos los modos de direccionamiento del Pentium, los únicos que se pueden utilizar son el modo registro, modo inmediato y el modo registro indirecto. Escribir la secuencia de instrucciones equivalentes a las siguientes: (es decir que si se reemplaza la instrucción con las instrucciones de cada respuesta, el programa resultante es idéntico).
MOV matrix(%ebx), %eax
MOV table(, %ecx, 4), %eax
Un procesador llamado PDP-11 contiene en su juego de instrucciones dos modos de direccionamiento que no posee el Intel Pentium.
Modo Autoincremento Indirecto: Se representa
como [Reg]++
. El procesador accede a la posición
de memoria contenida en el registro Reg
y de
dicha posición de memoria obtiene la dirección de memoria del
operando. El registro Reg
queda incrementado en
cuatro unidades.
Modo Indexado Indirecto: Se representa como
$desp[Reg]
. El procesador accede a la posición
de memoria resultante de sumar Reg
y
$desp
, y de dicha posición de memoria obtiene la
dirección de memoria del operando.
Especificar cómo deben traducirse las siguientes instrucciones del PDP-11 a instrucciones del Intel Pentium para que la ejecución sea equivalente.
MOV [%eax]++, %ebx
MOV %ecx, $desp[%ecx]
Considerando el circuito de la figura 7.14, rellenar los valores de las señales b, i, e y d para cada uno de los modos de direccionamientos de la siguiente tabla. La entrada constante de los multiplexores se selecciona poniendo la señal de control con valor idéntico a esta.
Modo de direccionamiento | Valor de b | Valor de i | Valor de e | Valor de d |
---|---|---|---|---|
Absoluto | ||||
Registro indirecto | ||||
Base + desplazamiento | ||||
Base + índice | ||||
Índice escalado + desplazamiento | ||||
Base + índice escalado + desplazamiento |