Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Índice
PRÓLOGO
1. ¿Qué es este tutorial? ..........................................................................................6
2. ¿A quién va dirigido este tutorial?.......................................................................6
3. Cómo leer este tutorial........................................................................................7
4. Notaciones utilizadas ..........................................................................................8
Pág. 2
www.macprogramadores.org
Pág. 3
www.macprogramadores.org
Pág. 4
www.macprogramadores.org
Pág. 5
www.macprogramadores.org
PRÓLOGO
Aunque muchos de los aspectos que vamos a comentar son independientes del sistema
operativo que usemos, otros si que varían dependiendo del sistema operativo en cuyo
caso haremos una comparativa entre como se ha implementado ese aspecto en un
sistema operativo concreto. Éste puede ser o bien Darwin (el kernel open source de
Mac OS X), o bien Linux (ya sea LinuxPPC, MkLinux o cualquier otra distribución
de Linux para PowerPC), aunque principalmente nos centraremos en explicar el
diseño de Darwin.
Pág 6
www.macprogramadores.org
Para poder conseguir este objetivo, este tutorial está diseñado para personas con
ciertos conocimientos de programación y de arquitectura de computadores, en
concreto el documento exige que el lector tenga conocimientos de programación en C,
así como de arquitectura de computadores al nivel que se da en una primera
asignatura de arquitectura de computadores de cualquier carrera técnica o superior de
Informática. Este requisito no excluye necesariamente a lectores autodidactas, pero
estos deben de conocer los conceptos básicos de representación binaria y de
organización de un computador. Aun así en el apéndice A he explicado con cierta
profundidad los métodos de representación binaria de punto fijo y punto flotante
(según el estándar IEEE 754), así como los mecanismos de aritmética binaria más
usados. Si el lector no conoce, o no recuerda bien estos métodos sería recomendable
que visitase este apéndice al principio del libro, o bien, durante su lectura si encuentra
dificultades para su comprensión.
Otros requisitos, que sin ser indispensables, si que ayudarían mucho al lector serían
que el lector hubiese programado ya alguna vez en ensamblador, o bien del PowerPC,
o bien de cualquier otro microprocesador, ya que los conceptos entre distintos
procesadores varían más bien poco. Por último, y especialmente dirigido a los últimos
temas sería recomendable conocer el diseño de un sistema operativo tal como se
explica en un primer curso de sistemas operativos en una carrera universitaria de
Informática. Este requisito es el menos importante ya que estos conceptos si que se
explican bastante bien a lo largo del tutorial.
Pág 7
www.macprogramadores.org
4. Notaciones utilizadas
En general, cuando describamos una instrucción ensamblador vamos a utilizar las
minúsculas para indicar valores que tengamos que escribir literalmente, mientras que
vamos a utilizar mayúsculas para indicar valores que deban ser sustituidos por su
valor, es decir, si escribimos el formato de una instrucción, escribiremos:
cmp CRF,L,rA,rB
cmp 2,0,r5,r6
Donde CRF y L han sido sustituidos por un valor, al igual que la A y B de rA y rB,
mientras que cmp se escribe literalmente por ser minúsculas.
Pág 8
www.macprogramadores.org
Aunque inicialmente los fabricantes pensaban que cuantas más instrucciones tuviera
su microprocesador más potente seria, estudios realizados en la universidad de
Berkeley y Stanford han demostrado que al aumentar este juego de instrucciones
aumentaba mucho la complejidad del cableado del micro y el número de ciclos de
CPU que necesitaban para ejecutar una instrucción también aumentaba. Además
observaron que el número de instrucciones distintas que necesita un ordenador para
ejecutar cualquier programa se podía reducir a un número pequeño de primitivas, y
que en las máquinas CISC que estaban usando en aquel entonces muchas
instrucciones eran redundantes. Esto dio lugar a la aparición de las máquinas RISC de
las cuales un buen ejemplo es la arquitectura SPARC o la arquitectura POWER de
IBM usada por sus máquinas RS/6000, o bien, como no, el PowerPC, que es una
evolución de la arquitectura POWER desarrollada conjuntamente por IBM, Motorola
y Apple.
El tiempo necesario para ejecutar un programa depende del producto de tres factores:
El número de instrucciones del programa, el número de ciclos necesarios para ejecutar
una instrucción y la duración de cada ciclo (velocidad del reloj). Los programas
hechos para máquinas RISC tienen un mayor número de instrucciones que su
correspondiente versión CISC, pero a cambio, el número de ciclos de cada instrucción
disminuye. El tercer factor (tiempo de cada ciclo) es un factor que depende más de la
tecnología y materiales empleados en la construcción del micro, y que con el tiempo
se supone ira mejorando. Para coger lo mejor de ambos mundos, POWER y su
evolución el PowerPC, son máquinas que no siguen una arquitectura RISC estricta
Pág 9
www.macprogramadores.org
(como por ejemplo SPARC), sino que incluyen instrucciones adicionales para
operaciones comunes que están ahí sólo para reducir el tamaño del programa, sin por
ello llegar a la complejidad de las arquitecturas CISC.
Por un lado Motorola esta fabricando chips que siguen esta arquitectura de uso
doméstico, y que aunque ha hecho algún micro de 64 bits, la mayoría de los micros de
que disponen son de 32 bits.
Por otro lado IBM está fabricando microprocesadores tanto de 32 bits para sus
workstation como de 64 bits para sus máquinas grandes (de la serie pSeries).
Apple por su parte compra los micros a Motorola, en concreto las últimas máquinas
de Apple (los G4) disponen de micros de la serie MPC74xx
http://e-www.motorola.com/webapp/sps/site/taxonomy.jsp?
nodeId=03M943030450467M98653
http://www-1.ibm.com/servers/eserver/pseries/hardware/
workstations/ (Workstations de 32 bits)
http://commerce.www.ibm.com/content/home/shop_ShopIBM/en_
US/eServer/pSeries/pSeries.html (Servidores de 64 bits de alto
rendimiento)
Es importante también destacar que los PowerPC no sólo se usan para ordenadores de
escritorio, sino que se usan en videoconsolas como la Nintendo Gamecube, o los
PowerPC de Motorola de la serie MPC4xx para dispositivos empotrados, los cuales
son más baratos de fabricar aunque por ejemplo carecen de unidad de punto flotante y
de tablas de paginación.
Nosotros nos vamos a centrar en estudiar los micros de 32 bits, que son los que utiliza
Mac OS X. De hecho, las únicas máquinas que vamos a estudiar son las de Apple.
Pág 10
www.macprogramadores.org
Para ello se dividió la arquitectura del PowerPC en tres entornos. El fabricante tiene
libertad a la hora de fabricar un micro que implemente los tres entornos, o sólo alguno
de ellos. Estos entornos son:
Todos los micros deben de implementar el UISA, mientras que el VEA y OEA son
opcionales, aunque si un micro implementa el OEA también debe de implementar el
VEA.
OEA
VEA
UISA
Pág 11
www.macprogramadores.org
El entorno VEA añade dos nuevos registros llamados TBU (Time Base Upper) y TBL
(Time Base Lower), que sólo están disponibles en los micros que implementan VEA.
Por último OEA define otros muchos registros especiales llamados SPR (Special
Purpose Registers), que no veremos hasta más adelante, y a los que sólo se puede
acceder en modo supervisor. Estos registros nos permiten controlar cosas como las
tablas de paginación y segmentación, la traducción de direcciones lógicas a
direcciones virtuales y reales, el manejo de excepciones, el acceso a dispositivos, etc.
Normalmente estos registros no son accedidos más que por el sistema operativo.
Una característica importante de los sistemas RISC, y por tanto del PowerPC es que, a
diferencia de los sistemas CISC, las únicas instrucciones que transfieren datos entre
memoria y los registros son instrucciones diseñadas con el fin de leer o escribir en
memoria, y todas las demás instrucciones siempre trabajan con datos previamente
cargados en registros. El hecho de que las instrucciones de los sistemas RISC sólo
puedan tener como operadores registros disminuye mucho los modos de
direccionamiento de las instrucciones del micro, y en consecuencia la complejidad del
juego de instrucciones. Compárese esta organización con los sistemas CISC, donde
las instrucciones pueden tener como uno de sus operadores una dirección de memoria,
o bien un registro.
Esto también hace que en los sistemas RISC sea muy típico que una instrucción tenga
hasta 3 operandos. Por ejemplo la instrucción de suma recibe dos registros como
origen y un tercero como destino. Esto también es una diferencia respecto a los
sistemas CISC donde las instrucciones suelen recibir sólo dos operadores, con lo que
operaciones como la de suma tienen que depositar el resultado de la suma en uno de
los registros origen, dando lugar a un sistema menos flexible.
Como adelantamos antes, los procesadores PowerPC tienen dos niveles de privilegio:
Modo supervisor. Usado sólo por el sistema operativo para acceder a los recursos
definidos por el OEA.
Pág 12
www.macprogramadores.org
Modo usuario. Usado por las aplicaciones y el sistema operativo para realizar
operaciones consideradas “no peligrosas”. Es el modo que usamos para acceder a los
recursos definidos por UISA y VEA.
4. Byte ordering
Los bytes de la memoria se numeran empezando a contar por 0. Cada número es la
dirección de memoria de un byte. En este sentido los bytes son unidades indivisibles y
no existe problema respecto a la forma de ordenar los bits de un byte en memoria. El
problema surge con las variables cuyo tamaño es mayor a un byte.
En este caso existen dos formas de colocar los bytes que forman una variable en
memoria llamadas:
Por ejemplo, el número 617163 en binario se escribe como 1001 0110 1010 1100
1011. Si lo queremos guardar en memoria necesitaremos una variable de tamaño
suficiente para almacenarlo.
Los tamaños típicos de variables enteras (vistas desde el punto de vista de C) son:
Big-Endian
Little-Endian
Pág 13
www.macprogramadores.org
que tiene usar la organización little-endian es que debemos de guardar los bytes del
número al revés de como se lee, lo cual dificulta su lectura.
5. Alineación
PowerPC dispone de instrucciones que permiten transferir entre memoria y los
registros tanto bytes, como halfwords (16 bits), words (32 bits), o doublewords (64
bits). En las máquinas de 32 bits, estos últimos sólo se usan para los datos en
representación de punto flotante con precisión doble que queramos guardar en los
FPR, mientras que en los procesadores de 64 bits, los GPRs tienen 64 bits con lo que
es su tamaño por defecto.
Como regla general, debemos colocar las variables en zonas de memoria cuya
dirección sea múltiplo del tamaño de la variable que estamos guardando.
Si esto no se hace el microprocesador tiene que hacer dos accesos a memoria, uno
para leer los cuatro primeros bytes alineados y otro para leer los siguientes 4 bytes,
para finalmente componer el valor de la variable, lo cual enlentece el acceso.
Una características importante de las máquinas RISC, es que todas las instrucciones
tienen el mismo tamaño, a diferencia de las máquinas CISC donde las instrucciones
Pág 14
www.macprogramadores.org
tienen un tamaño diferente. En PowerPC todas las instrucciones tienen 32 bits (tanto
en arquitecturas de 32 bits como de 64 bits), lo cual simplifica mucho al procesador el
acceso a las instrucciones de forma consecutiva. Además en PowerPC las
instrucciones siempre tienen que estar alineadas en direcciones de memoria múltiplos
de 4, ya que PowerPC es literalmente incapaz de acceder a instrucciones que no
estuvieran correctamente alineadas en memoria.
Pág 15
www.macprogramadores.org
1. Herramientas necesarias
Vamos a empezar viendo qué herramientas de programación en ensamblador existen
para Mac OS X y como se pueden usar.
Los primero que vamos a necesitar es obtener las Development Tools que podemos
conseguir gratuitamente de la Apple Developer Connection (ADC) en:
http://developer.apple.com/tools/index.html
#include <stdio.h>
int main ()
{
printf(MENSAJE);
return 0;
}
$ cc saluda.c -o saluda
$ ./saluda
Hola mundo
1. Preprocesado
2. Generación del código ensamblado (compilación)
3. Generación del código objeto (ensamblado)
4. Enlazado
Pág 16
www.macprogramadores.org
GNU ld lo que hace es juntar todos los ficheros de código objeto reubicable en otro
fichero que es el fichero ejecutable (cuarto paso).
$ cc -E saluda.c
Vemos que por la salida estándar obtenemos el código preprocesado con el fichero
<stdio.h> incluido y MENSAJE sustituido.
$ cc -S saluda.c
Si queremos obtener el código objeto (fichero .o) del programa usamos la opción -c
$ cc -c saluda.c
Esto genera el fichero saluda.o que después podemos enlazar junto con otros
ficheros de código objeto.
$ cc -c saluda.s
$ as saluda.s -oD.o
De hecho esta es la principal herramienta que vamos a usar para compilar los
programas en ensamblador que hagamos a lo largo de este tutorial.
También , antes de continuar, conviene comentar que las Development Tools también
traen una herramienta visual llamada ProjectBuilder que nos permite de forma más
“visual” compilar programas C, C++, Objective-C, Java o ensamblador, aunque
internamente esta herramienta llama a las herramientas de GNU para compilar. En
este tutorial vamos a hablar siempre de los comandos y opciones de GNU pero si el
lector lo prefiere puede usar esta herramienta e indicar las opciones que aquí demos
en las correspondientes opciones de que dispone ProjectBuilder.
Pág 17
www.macprogramadores.org
Por último, se muestra una tabla con las extensiones de fichero que reconoce el
compilador de GNU, y para que se utiliza cada una.
Pág 18
www.macprogramadores.org
2. El programa mínimo
Ya que sabemos como se usa el compilador, vamos a escribir un programa mínimo
para ver como se compila y enlaza un programa ensamblador en Mac OS X.
En primer lugar, en este programa hemos utilizado los 3 tipos de comentarios que
soporta el lenguaje:
Comentario Descripción
/* ··· */ Comentario multilínea de C
// Comentario de una sola línea de C
; Comentario de una sola línea propio del ensamblador as
Todo programa debe de disponer de la directiva .text, que como veremos indica la
parte del programa que corresponde al programa, y que en consecuencia es de sólo
lectura. Más adelante veremos otra directiva llamada .data que sirve para indicar el
trozo del programa que corresponde a los datos, y que será de lectura/escritura.
.align es otra directiva que pide al compilador que alinee la siguiente instrucción a
una dirección múltiplo de 4. El 2 lo que indica es que queremos que la dirección tenga
sus últimos 2 bits a cero, es decir, de la forma xxxx xx00, o lo que es lo mismo que
sea múltiplo de 2a, siendo a la alineación pedida.
.globl sirve para declarar como global el siguiente símbolo que aparece. La función
main() debe de ser un símbolo global para que Mac OS X pueda acceder a ella.
Obsérvese que la función se llama _main y no main, esto es así porque todos los
símbolos sufren un name-mangling al estilo C (poner un _ delante) antes de meterlos
en la tabla de símbolos.
blr es la única instrucción ensamblador que tiene el programa y que lo que hace es
retornar de la llamada a la función main(). Como veremos la dirección a la que
Pág 19
www.macprogramadores.org
$ cc basico.s -o basico
$ ./basico
Pág 20
www.macprogramadores.org
3. El lenguaje ensamblador
Ahora que ya sabemos cómo se hace un programa en ensamblador, vamos a comentar
brevemente cuales son los principales elementos del lenguaje ensamblador, así como
la sintaxis de las sentencias que soporta.
Un programa en ensamblador esta formado por una serie de sentencias, cada una de
las cuales sigue este formato:
Una etiqueta es una marca que ponemos para referirnos a la dirección de memoria
de la instrucción o dato que estamos compilando. Después podemos usar esta etiqueta
desde otros puntos del programa para referirnos a esta dirección de memoria. Por
ejemplo, las instrucciones de salto indican la dirección a la que saltar dando el nombre
de la etiqueta, o las instrucciones de acceso a memoria también usan etiquetas para
indicar la dirección de memoria a la que acceder.
Los operandos, son parámetros que opcionalmente reciben las instrucciones, bien
sean instrucciones ensamblador, directivas o macros. Los operandos a recibir
dependen de la instrucción a ejecutar, y si hay más de uno se suelen separar por
comas.
Las distintas partes de la sentencia se pueden separar tanto por espacio como por
tabulador, pero normalmente existe la costumbre de separar por espacio, excepto en el
caso de la etiqueta donde se suele poder un tabulador al principio si no existe la
etiqueta, o bien poder un tabulador después de la etiqueta.
Por ejemplo:
Pág 21
www.macprogramadores.org
mflr r0
inicio: stwu r1,-80(r1)
mr r30,r1
bcl 20,31,inicio
En esta sección vamos a comentar cuales son los principales elementos que componen
el lenguaje ensamblador.
3.2.1. Literales
Los caracteres, los cuales se representan encerrados entre comillas simples. Por
ejemplo: ‘A’, ‘a’, ‘2’,‘?’. Cuando el compilador los encuentra los sustituye por
el valor ASCII del carácter correspondiente.
li r3,‘A’
Pág 22
www.macprogramadores.org
Las cadenas de caracteres, las cuales se representan encerradas entre comillas dobles,
como por ejemplo “Hola mundo”. El compilador las sustituye por los códigos
ASCII de sus caracteres.
Estas se utilizan sobre todo para reservar trozos de memoria con la directiva .ascii
así:
Los números en punto flotante, se representan de una forma un poco especial, cuyo
formato general sería:
0flt_char[{+-}[dec...][.[dec...]]e[{+-}][dec...]]
Cuando usamos uno de estos literales con las directivas .single y .double que
sirven para reservar memoria para un número en punto flotante de precisión simple o
doble, respectivamente, la directiva ignora el tipo del literal, y sólo se tiene en cuenta
el tipo de la directiva, aun así es recomendable indicar el tipo.
Por ejemplo:
Pág 23
www.macprogramadores.org
3.2.2. Identificadores
o Una etiqueta, que sirve para referirnos a un trozo del programa o a una
variable.
o Una constante, que es un nombre al que le asociamos un literal.
“maximo relativo”
“diferencia en pixeles”
Aunque por homogeneidad con los demás lenguajes es mejor no usar esta forma, que
da lugar a confusión con las cadenas de caracteres, y en vez de ello usar guiones bajos
o mayúsculas y minúsculas para separar palabras.
MaximoRelativo
diferencia_en_pixeles
Las etiquetas deben de estar precedidas por : cuando se declaran, pero no cuando se
usan. Por ejemplo:
········
inicio: stwu r1,-80(r1) ; Declaracion
········
bcl 20,31,inicio ; Uso
Respecto al ámbito de las etiquetas, estas sólo son visibles dentro del fichero que las
declara, pero podemos hacer las etiquetas de ámbito global (para poder acceder a
ellas desde otros ficheros) con la directiva .globl:
.global A
A: lwi r4,5
Pág 24
www.macprogramadores.org
Esto hace a la etiqueta A accesible desde otros módulos. La directiva debe preceder a
la etiqueta que vamos a declarar como global.
También podemos usar las llamadas etiquetas numéricas, que son etiquetas que se
pueden redefinir en distintas partes de un mismo fichero.
Estas etiquetas se crean con los dígitos del 0 al 9, y también deben de ir precedidas
por :. Aunque puede haber muchas declaraciones de la misma etiqueta en distintas
partes del fichero, sólo la etiqueta numérica inmediatamente anterior y siguiente
pueden ser accedidas desde un punto concreto del programa. Para ello usamos el
nombre digitob (back) y digitof (forward), respectivamente.
Por ejemplo:
1: instruccionA
·················
1: instruccionB
·················
b 1 ; Salta a instruccionB
b 1b ; Salta a instruccionB
b 1f ; Salta a instruccionC
·················
1: instruccionC
Llamamos operando a cualquier identificador o literal que pueda ser usado como
parámetro en una instrucción, directiva o macro. Ejemplos de operandos son final,
inicio, 45, ‘A’
Las reglas de precedencia y asociatibidad de estos operadores también son las mismas
que en el lenguaje C.
Visto esto, vamos a ver que llamamos expresión a una combinación de operandos y
operadores. (p.e. 3*a+b)
Las expresiones siempre se evalúan a valores de 32 bits, a pesar de que se puedan usar
operandos de distintos tamaños. Por ejemplo se podrían usar valores declarados con
las directivas .byte (8 bits) o .short (16 bits), pero después de evaluar la
expresión tendremos un valor de 32 bits.
Cuando se evalúa una expresión su resultado puede ser absoluto, reubicable o externo,
dependiendo de la expresión evaluada.
Una expresión tiene un valor reubicable si su valor se fija respecto a una dirección de
memoria base como un offset respecto a esa dirección. Cuando este valor reubicable
lo procesa el enlazador, se convierte en un valor absoluto.
Un ejemplo típico de expresiones reubicables son las etiquetas, las cuales tienen una
dirección respecto a la base de su sección. Como veremos la memoria está dividida en
Pág 26
www.macprogramadores.org
A las expresiones reubicables sólo las podemos sumar y restar valores constantes, así
como hacer la resta de expresiones reubicables (pero no la suma). Las operaciones de
multiplicación y división, así como las demás operaciones, están prohibidas en las
expresiones reubicables.
Por último, una expresión es externa, si alguno de sus operandos no esta definido en
el fichero de la expresión, sino que es un identificador global situado en otro módulo.
Este resulta a veces útil como operando de una instrucción, directiva, macro o
expresión.
Existen dos directivas que nos permiten avanzar el valor del location counter:
Por ejemplo:
.align 2
Esta directiva avanza el location counter tantos bytes como diga avance, rellenando
con bytes con el valor de relleno, o ceros si no se indica.
Pág 27
www.macprogramadores.org
Por ejemplo:
Sólo existe una excepción que son las sentencias de asignación directa, las cuales
tienen la forma:
identificador = expresion
La cuales sirven para declarar constantes que se puedan usar más adelante en el
programa.
.text
.align 2
.globl _main
_main:
li r3,operando1
li r4,operando2
add r5,r3,r4
blr
Pág 28
www.macprogramadores.org
// Directivas .set
.set operando1,3
.set operando2,5
.text
.align 2
.globl _main
_main:
li r3,operando1
li r4,operando2
add r5,r3,r4
blr
Las sentencias de asignación directa y las directivas .set sólo nos permiten
almacenar valores literales:
var1 = 3 ; Correcto
var1 = r3 ; Error ensamblado
.set var1 3; correcto
.set var1 r3 ; Error ensamblado
#define dividendo r3
#define divisor r4
#define cociente r5
divw cociente,dividiendo,divisor
Las definiciones pueden aparecer en cualquier parte del programa, aunque se suelen
poner al principio, y el preprocesador las sustituye por su valor antes de pasar el
programa al ensamblador.
Pág 29
www.macprogramadores.org
4. Acceso a memoria
En esta sección vamos a explicar una serie de conceptos fundamentales para poder
acceder a memoria.
Aunque ya explicaremos más adelante todo esto, cada segmento se almacena en una
tabla de página distinta, y el separar las instrucciones en un segmento aparte de sólo
lectura tiene tres ventajas:
1
Como veremos más adelante existen más segmentos, pero por simplicidad vamos a
empezar suponiendo que sólo existen estos dos
Pág 30
www.macprogramadores.org
.data
············
············
.text
············
············
············
Cuando el enlazador recibe los ficheros de código objeto, este fusiona todos los
segmentos de un mismo tipo bajo un único segmento.
Cuando el enlazador reúne todos los ficheros objetos para generar el ejecutable, tiene
que asignar direcciones absolutas a las direcciones relativas que depositó el
compilador, para ello simplemente concatena todos los segmentos del mismo tipo, y
luego calcula las direcciones absolutas de cada una de las direcciones reubicables.
Cada segmento a su vez está dividido en una o más secciones que nos dan un mayor
nivel de precisión a la hora de indicar como tratar los datos de esa sección.
Vamos a comentar qué puede tener cada sección (del segmento de código y del de
datos), para que sirve cada una, así como que directivas se usan para delimitar cada
sección.
La siguiente tabla resume las directivas usadas para cada tipo de sección que puede
contener el segmento de código:
Pág 31
www.macprogramadores.org
.destructor (__TEXT,__destructor)
Usada sólo por los
destructores de C++
.fvmlib_init0 (__TEXT,__ fvmlib_init0) Estas secciones las
.fvmlib_init1 (__TEXT,__ fvmlib_init1) debe de usar
solamente el sistema
de memoria virtual de
las librerías de enlace
dinámico. Nosotros
nunca debemos poner
nada aquí.
.symbol_stub (__TEXT,__symbol_stub) Usadas para llamar a
.picsymbol_stub (__TEXT,__picsymbol_stub) funciones de librerías
de enlace dinámico.
Como veremos más
adelante
.text Esta directiva se usa para indicar que estamos en el segmento de código, y si
no usamos ninguna otra directiva para especificar la sección, entonces estamos en la
llamada sección de código regular, que es la sección por defecto, la cual debe
contener únicamente instrucciones ensamblador.
.const Esta directiva se usa para crear una sección de datos constantes. Si los datos
no van a cambiar durante la ejecución del programa se pueden guardar en el segmento
de código (en vez de en el segmento de datos), con las consiguientes ventajas que
aporta. Por ejemplo respecto a la paginación.
.literal4 Se usa para guardar sólo datos constantes de 4 bytes, es decir enteros y
variables float. Al ser sólo datos de 4 bytes siempre permanecen alineados. Durante
el ensamblado el compilador reúne todas las variables declaradas en esta sección que
tengan el mismo valor, para que aparezcan sólo una vez en memoria.
.literal8 Igual que antes, pero usada para guardar datos constantes de 8 bytes.
Principalmente números double. Durante el ensamblado el compilador reúne todas
las variables declaradas en esta sección que tengan el mismo valor.
.cstring Usada para todas las cadenas de caracteres constantes del programa.
Durante el ensamblado el compilador reúne todas las variables declaradas en esta
sección que tengan el mismo valor, para que aparezcan sólo una vez en memoria.
Estas directivas sólo indican un cambio de sección, pero no reservan memoria. Para
indicar la cantidad de memoria a reservar y valor inicial de esta memoria reservada
tenemos las directivas:
Pág 32
www.macprogramadores.org
Directiva Descripción
.byte [valor] Reserva espacio para un byte, y le asigna el valor dado
en valor, ó 0 si no se especifica.
.short [valor] Reserva espacio para una variable entera de 2 bytes, y
le asigna el valor dado en valor, ó 0 si no se
especifica.
.long [valor] Reserva espacio para una variable entera de 4 bytes, y
le asigna el valor dado en valor, ó 0 si no se
especifica.
.single [valor] Reserva espacio para una variable de punto flotante con
precisión simple (4 bytes), y le asigna el valor dado en
valor, ó 0 si no se especifica.
.double [valor] Reserva espacio para una variable de punto flotante con
precisión doble (8 bytes), y le asigna el valor dado en
valor, ó 0 si no se especifica.
.ascii cadena Reserva espacio para la cadena dada en cadena. No
pone el 0 de final de cadena
.asciz cadena Reserva espacio para la cadena dada en cadena. Y
pone un 0 al final de la cadena.
.fill Pone el valor dado en valor tantas veces como diga
repeticiones, repeticiones. El tamaño de la variable puede ser
tamaño, valor 1,2 ó 4 según diga tamaño
.space n_bytes, Pone el valor dado en valor tantas veces como diga
[valor] n_bytes, o ceros si no damos valor
.text
.const
c1: .byte ‘A’
c2: .byte ‘B’
.literal4
i: .long 12
f: .float 0r1.34e0
.literal8
d: .double 0d56.e7
.cstring
msg: .ascii "Hola mundo\013\000"
.text ; Ahora van las instrucciones en ensamblador
; en una seccion de codigo regular
lwz r4,0(r9)
lwz r5,0(r11)
Una optimización que aplica el compilador a los datos marcados como .const,
.literal4, .literal8 o .cstring es que si el mismo valor aparece varias
Pág 33
www.macprogramadores.org
La optimización que hace el compilador al reunir todas las variables con el mismo
valor en la misma dirección de memoria puede confundir al programador, por ejemplo
si hacemos:
.literal4
A1: .long 0
A2: .long 0
A3: .long 0
A4: .long 0
No estamos reservando espacio para 4 números de 32 bits sino que al tener un mismo
valor (0 en nuestro ejemplo) el compilador sólo reserva espacio para un variable de
32 bits, y las etiquetas A1, A2, A3, A4 apuntan a la misma dirección de memoria.
.data
A1: .long 0
A2: .long 0
A3: .long 0
A4: .long 0
Las demás directivas que aparecen en la tabla las comentaremos cuando hayamos
avanzado más.
La siguiente tabla resume las directivas usadas para cada tipo de sección que puede
contener el segmento de datos:
dinámico. Nosotros no
debemos usarla.
.const_data (__DATA,__const) Para almacenar datos
constantes en librerías de
enlace dinámico
Para reservar memoria en cada una de estas secciones del segmento de datos, además
de poder usar las directivas que vimos antes para el segmento de código (.byte,
.short, .long, .single, .double, .ascii, .asciz, .fill, .space),
podemos usar las siguientes dos directivas, las cuales reservan memoria sin inicializar,
cosa que no tiene sentido hacerlo en el segmento de código por ser de sólo lectura,
pero si tiene sentido en el segmento de datos:
Directiva Descripción
.comm etiqueta, tamaño Reserva tamaño bytes y crea la etiqueta global
etiqueta que apunta a esta zona de memoria
sin inicializar.
.lcomm etiqueta, Igual a .comm, sólo que la etiqueta es de ámbito
tamaño local, con lo que no es accesible desde fuera del
módulo
Estas dos directivas reservan siempre memoria dentro del segmento de datos, con lo
que aunque aparezcan en el segmento de código la reserva se produce en el segmento
de datos regular.
.data
A: .long 60 ; Crea una variable de 4 bytes con
; un valor de 60 en la sección
; (__DATA,__data)
.static_data
B: .long 3 ; Crea una variable de 4 bytes con
; un valor de 3 en la seccion
; (__DATA,__static_data)
.comm C, 4 ; Reserva 4 bytes sin inicializar
; en la sección (__DATA,__static_data)
.text
Pág 35
www.macprogramadores.org
Las tablas anteriores muestran el nombre que se da a cada uno de las secciones que
hemos comentado.
.const
A: .long 20
.section __TEXT,__const
A: .long 20
La ventaja de esta directiva es que nos permite crear nuevos nombres de segmentos y
secciones.
Pág 36
www.macprogramadores.org
Cuando el compilador genera el código objeto, reúne todas las secciones del mismo
tipo que aparezcan a lo largo del fichero fuente, de forma que el segmento del fichero
objeto tiene como mucho una sección de cada tipo.
.data
··········
··········
.const
··········
··········
.text
··········
··········
.const
··········
··········
Cuando el enlazador enlaza los ficheros objeto, vuelve a reunir las secciones del
mismo tipo de los distintos ficheros objeto, para que sólo haya una sección de cada
tipo en el segmento del ejecutable.
Hace años, las memoria que tenían que direccionar las máquinas era relativamente
pequeña (p.e 28B ó 216B), con lo que las instrucciones ensamblador podían incluir la
dirección de memoria a la que acceder como parte de la instrucción, llamado
indireccionamiento inmediato o bien usaban un registro para almacenar la dirección
a la que acceder, llamado indireccionamiento de registro.
Cuando este espacio de memoria fue creciendo, los fabricantes se dieron cuenta de
que incluir direcciones de memoria tan largas en las instrucciones (indireccionamiento
inmediato), aumentaba mucho el tamaño de los programas con lo que decidieron que
las instrucciones debían de usar sólo indireccionamiento con registro.
En las máquinas CISC es muy típico que la dirección de memoria a la que vayamos a
acceder forme parte de la instrucción.
Pág 37
www.macprogramadores.org
movb %al,dir
Para mover el byte bajo del registro AL a la dirección de memoria indicada en dir.
Sin embargo aquí surge un problema conocido como el problema del bootstraping,
que es el de cómo almacenamos la primera dirección de memoria de 32 bits en un
registro. Es decir, ninguna instrucción del PowerPC puede permitirse el lujo de gastar
32 bits para guardar este valor que queremos meter en un registro.
La solución que se usa pasa por usar 2 instrucciones, una de ella carga los 16 bits altos
del registro, y la otra los 16 bits bajos.
Para obtener la parte alta y la parte baja de una dirección de 32 bits (que posiblemente
saquemos de una etiqueta) se usan los operadores lo16() hi16() y ha16() tal
como se explica a continuación.
Vamos a ver cómo se cargan los 32 bits de una dirección de memoria en dos partes,
cada una de las cuales carga 16 bits.
En primer lugar comentar que sí hay instrucciones que pueden recibir como operando
un valor de 16 bits, que es el que luego cargan en el registro.
Pág 38
www.macprogramadores.org
(rA|0) es una notación muy usada en las instrucciones del PowerPC que significa
que aquí podemos dar uno de los 32 registros de GPR excepto r0, o bien un 0, en
cuyo caso significa que este operando vale 0, con lo que en rD se almacena el valor
de sumar 0 a SIMM, es decir el valor de rD=0+SIMM.
La razón por la que podemos indicar cualquiera de los registros menos el r0, es que
en la codificación binaria de la instrucción, el código 0 se utiliza para indicar un 0
binario, y no el contenido del registro r0.
addis r2,0,hi16(expr)
A continuación tenemos que cargar los 16 bits bajos de expr en el registro, para lo
cual podemos usar la instrucción:
rA es el destino de la operación
rS es uno de los operandos.
UIMM (Unsigned IMMediate) es el otro operando.
Luego ahora ya podemos escribir las dos instrucciones que cargan una dirección de
memoria de 32 bits en un registro.
addis r2,0,hi16(expr)
ori r2,r2,lo16(expr)
Aun queda por ver cuando y como se usa ha16(), que lo vamos a ver en el siguiente
punto.
Pág 39
www.macprogramadores.org
Los modos de indireccionamiento son las formas en que podemos indicar una
dirección de memoria en la que las instrucciones de nuestro programa quieren leer o
escribir.
Como sabemos, una instrucción consta de un campo opcode, que indica que hace la
instrucción, y de unos operandos. Los operandos pueden estar codificados
directamente dentro de la instrucción, llamado operando inmediato, o situado en
memoria en cuyo caso tememos que hacer un indireccionamiento del operando.
Las instrucciones que usan este modo de indireccionamiento tienen codificado dentro
de la instrucción un número con signo de 16 bits que actúa como índice. (operador d),
al cual se le extiende el signo hasta los 32 bits y se le suma con un GPR (operador rA)
para generar la dirección efectiva a la que acceder.
Pág 40
www.macprogramadores.org
En PowerPC existe la regla de que siempre que se usan registros para indireccionar
memoria se usa la forma (rA|0), donde si rA es 0, se le suma 0 a d (en vez del
contenido de r0), con lo que r0 se puede usar para instrucciones que realizan
operaciones aritméticas, pero no se puede usar nunca para indireccionar.
0 15 16 31
Extensión del signo d
Sí
¿rA=0? O
+
No
Store
GPR (rS/rS) Load Memoria principal
Las siguientes tabla muestran las principales instrucciones de acceso a memoria que
usan este modo de indireccionamiento:
Instrucción Descripción
lbz rD,d(rA) (Load Byte and Zero) El byte en la dirección efectiva
d(rA) se carga en el byte bajo de rD, los demás bytes de
rD quedan a 0
lbzu rD,d(rA) (Load Byte and Zero with Update) Igual a lbz sólo que la
dirección efectiva se guarda en rA una vez realizada la
operación de carga
Pág 41
www.macprogramadores.org
Instrucción Descripción
stb rS,d(rA) (STore Byte) El byte menos significativo de rS se guarda
en la posición de memoria dada por d(rA)
stbu rS,d(rA) (STore Byte with Update) Igual a stb, sólo que después de
guardar el dato en memoria, en rA se guarda la dirección
efectiva calculada como d(rA)
sth rS,d(rA) (STore Half-word) El half-word menos significativo de rS
se guarda en la posición de memoria dada por d(rA)
sthu rS,d(rA) (STore Half-word with Update) Igual a sth, sólo que
después de guardar el dato en memoria, en rA se guarda la
dirección efectiva calculada como d(rA)
stw rS,d(rA) (STore Word) El valor de rS se guarda en la posición de
memoria dada por d(rA)
stwu rS,d(rA) (STore Byte with Update) Igual a stw, sólo que después de
guardar el dato en memoria, en rA se guarda la dirección
efectiva calculada como d(rA)
Pág 42
www.macprogramadores.org
4.4.2. Ejemplo
Pág 43
www.macprogramadores.org
Obsérvese que al principio de cada segmento (que no de cada sección) hemos puesto
una etiqueta, SD (Segmento de Datos) y SC (Segmento de Código), las cuales nos van
a ser muy útiles, ya que ahora para referirnos a cualquier otra etiqueta del segmento
podemos dar una dirección relativa a esta etiqueta.
Estas instrucciones aparecen en las tablas del apartado 4.4.1 y se caracterizan porque
son iguales a las instrucciones de acceso a memoria normales, sólo que su nombre
acaba en u, por ejemplo en vez de llamarse lwz (Load Word and Zero) se llaman
lwzu (Load Word and Zero with Update).
Pág 44
www.macprogramadores.org
Por ejemplo si queremos leer un array de elementos de tipo half-word podemos hacer
un bucle así:
.data
.lcomm A,20 ; 10 elementos de 2 bytes
.text
···········
addis r3,0,hi16(A)
ori r3,r3,lo16(A)-2
DESDE 1 HASTA 10
lhzu r2,2(r3)
; Procesamos r2
FIN_DESDE
Hasta ahora hemos visto que para acceder a memoria primero tenemos que cargar en
un registro una dirección de memoria, para lo cual usábamos dos instrucciones:
addis r3,0,hi16(var)
ori r3,r3,lo16(var)
Vamos a ver ahora que usando un pequeño truco vamos a poder cargar en un registro
sólo la parte alta de la dirección, y después aprovechamos el indireccionamiento de
registro base e índice inmediato para indicar la parte baja de la dirección, más o
menos así:
addis r3,0,ha16(var)
lwz r2,lo16(var)(r3)
Obsérvese que ahora, para cargar la parte alta de la dirección en el registro, usamos
ha16() en vez de hi16(). Como dijimos en el apartado 4.3 ha16(expr) evalúa
a los 16 bits altos de expresión, incrementando 1 si el bit del signo de lo16(expr)
es 1.
¿Por qué se hace este incremento? La razón es que si el bit de signo de lo16(expr)
es 1, es que este número es negativo con lo que al calcular lo16(expr)(rA), el
número está restando al valor del registro, pero en realizad para calcular la dirección
efectiva debería de sumar a rA el valor de lo16(expr), aunque como hemos
partido el número de 32 bits de la etiqueta en dos números de 16 bits, y el índice
inmediato de la instrucción codificada se interpreta como un SIMM (Signed
IMMediate), la instrucción interpreta el bit alto de lo16(expr) como un bit de
signo, en vez de como un bit de peso que es lo que es. Si sumamos uno a la parte alta
de la dirección, cuando este bit vale 1, solucionamos el problema.
Pág 45
www.macprogramadores.org
Ahora cuando la instrucción calcula d(rA) como la suma de la parte alta más la parte
baja, si intentamos calcular la dirección efectiva dir como
hi16(dir)+lo16(dir) tenemos:
Podemos modificar el ejemplo anterior para que use ha16() en vez de hi16() así:
Pág 46
www.macprogramadores.org
Ahora no usamos punteros a la base del segmento, sino que cuando queremos acceder
a una variable, primero cargamos la parte alta de la dirección con:
addis r5,0,ha16(dir)
stw r4,lo16(dir)(r5)
Las instrucciones que usan este modo de indireccionamiento usan dos GPR (llamados
rA y rB) para calcular la dirección efectiva como la suma de estos. A rA se le llama
registro base, y a rB registro índice. En concreto se calcula como (rA|0)+rB, es
decir, rA puede ser uno de los registros de r1 a r31 (pero no r0), o bien 0, en cuyo
caso para calcular la dirección efectiva se usa sólo el valor de rB.
Pág 47
www.macprogramadores.org
0 5 6 10 11 15 16 20 21 31
Codificación de la instrucción Opcode rD/rS rA rB subopcode
0 31
GPR (rB)
Sí
¿rA=0? O
No
Dirección efectiva
GPR (rA)
Store
GPR (rS/rS) Load Memoria principal
Las siguientes tablas muestran las principales instrucciones que usan este modo:
Pág 48
www.macprogramadores.org
Instrucción Descripción
stbx rS,rA,rB (STore Byte indeXed) El byte menos significativo de rS se
guarda en la posición de memoria dada por (rA|0)+rB
stbux rS,rA,rB (STore Byte with Update indeXed) Igual a stbx, sólo que
después de guardar el dato en memoria, en rA se guarda la
dirección efectiva calculada como (rA|0)+rB
sthx rS,rA,rB (STore Half-word indeXed) El half-word menos
significativo de rS se guarda en la posición de memoria
dada por (rA|0)+rB
sthux rS,rA,rB (STore Half-word with Update indeXed) Igual a sthx, sólo
que después de guardar el dato en memoria, en rA se guarda
la dirección efectiva calculada como (rA|0)+rB
stwx rS,rA,rB (STore Word indeXed) El valor de rS se guarda en la
posición de memoria dada por (rA|0)+rB
stwux rS,rA,rB (STore Byte with Update indeXed) Igual a stwx, sólo que
después de guardar el dato en memoria, en rA se guarda la
dirección efectiva calculada como (rA|0)+rB
Pág 49
www.macprogramadores.org
Estas instrucciones lo que hacen es cargar muchos datos de memoria a los registros
indicados en la instrucción.
Instrucción Descripción
lmw rD,d(rA) (Load Multiple Word) Carga los word almacenados a partir
de d(rA) en los registros que van desde r(D) a r(D+n)
siendo n=(31-D)
stmw rS,d(rA) (STore Multiple Word) Guarda a partir de la dirección de
memoria d(rA) los valores de los registros que van desde
r(S) a r(S+n) siendo n=(31-S)
La instrucción lmw carga n words en los registros que van desde rD hasta r31.
La dirección de memoria apuntada por d(rA) debe de estar alineada a una posición
múltiplo de 4, o se producirá una excepción.
Por ejemplo para cargar un array de 20 words desde memoria a registros podemos
hacer:
.const
A: .long 3456 ; 20 enteros
.long -565
··········
.long 1767
.text
addis r2,0,ha16(A)
lmw r11,A(r2)
La instrucción carga los registros que van de r11 a r31 con los 20 números del array
A.
Pág 50
www.macprogramadores.org
Estas instrucciones, al igual que antes, nos permiten leer un bloque de memoria, pero
a diferencia de antes:
Instrucción Descripción
lswi rD,rA,n (Load String Word Indirect) Carga los n primeros bytes a
partir de la dirección de memoria dada por (rA|0) en los
registros que van desde rD en adelante
stswi rS,rA,n (Store String Word Indirect) Guarda a partir de la dirección
de memoria (rA|0) los n primeros bytes empezando a
contar por el byte más significativo de rD en adelante
lswx rD,rA,rB (Load String Word indeXed) Carga los n=XER[25-31]
primeros bytes a partir de la dirección de memoria dada por
(rA|0)+rB en los registros que van desde rD en adelante
stswi rS,rA,rB (STore String Word indeXed) Guarda a partir de la
dirección de memoria (rA|0)+rB los n=XER[25-31]
primeros bytes empezando a contar por el byte más
significativo de rS en adelante
.cstring
saludo: .ascii “Hola mundo!\000”
mensaje: .org 255
.text
addis r2,0,hi16(saludo)
ori r2,r2,lo16(saludo)
lswi r10,r2,12 ; Rellena los registros
; r10,r11,r12,r13
addis r2,0,hi16(mensaje)
ori r2,r2,lo16(mensaje)
addi r3,0,12
mtxer r3 ; En XER cargamos el numero
; de bytes a leer
andi r3,r3,0
stswi r10,r2,r3 ; Pasa r10,r11,r12,r13 a memoria
Pág 51
www.macprogramadores.org
4.6. Mnemonics
Un característica de los sistemas RISC es que intentan disponer del menor juego de
instrucciones posible, con lo que si existen varias instrucciones que realizan la misma
operación, sólo implementan una de ellas.
Aun así en PowerPC no existe ninguna instrucción que cargue 16 bits en registro, sino
que en vez de esto se utilizan los siguientes mnemonics:
Vemos que están implementados como llamadas a addi (ADD Immediate) y addis
(ADD Immediate Shift). Estos mnemonics nos permiten cargar los 16 bits altos y los
16 bits bajos por separado. Pero no las dos partes, vemos porqué.
Por ejemplo, para cargar un número de 32 bits en el registro r2, podemos hacer:
numero = 1768937;
addis r2,0,ha16(numero)
addi r2,r2,lo16(numero)
2
Aunque en castellano se pueden traducir por mnemónicos, hemo preferido mantener
el término inglés para facilitar la comprensión de la documentación
Pág 52
www.macprogramadores.org
numero = 1768937;
lis r2,ha16(numero)
li r2,lo16(numero)
numero = 1768937;
addis r2,0,ha16(numero)
addi r2,0,lo16(numero)
Es decir, primero hemos cargado la parte alta y luego la segunda instrucción borra el
valor del registro para cargar la parte baja.
numero = 1768937;
lis r2,ha16(numero)
addi r2,r2,lo16(numero)
numero = 1768937;
lis r2,hi16(numero)
ori r2,r2,lo16(numero)
SD:
.data
A: .long 45
B: .long 0
C: .long 37
·············
.text
; En r2 obtenemos la direccion base del
Pág 53
www.macprogramadores.org
; segmento
lis r2,ha16(A)
; Hacemos que r3 apunte a A, r4 apunte a B y r5
; apunte a C
la r3,A(r2)
la r4,B(r2)
la r5,C(r2)
Pág 54
www.macprogramadores.org
Para el estudio de las instrucciones de trabajo con enteros las hemos dividido en los
siguientes grupos:
Antes de meternos con el estudio de estas instrucciones vamos a explicar dos registros
que se usan para almacenar el resultado de ejecutar estas operaciones: Los registros
CR y XER.
En cualquier momento el valor de este registro puede ser leído con la instrucción:
El acceso a CR es a nivel de campo, de forma que para cada campo se modifican los 4
bits del campo o no se modifica ninguno, para ello mascara es un número de 8 bits
que actúa como máscara a la hora de indicar que campos del CR serán afectados, de
Pág 55
www.macprogramadores.org
forma que los bits de mascara que tengan 1 indican que ese campo debe actualizarse
con el contenido de sus correspondientes 4 bits del registro rS y los que tengan 0
indican que ese campo no debe modificarse.
Por ejemplo, si queremos copiar todos los bits del registro r2 al registro CR haremos:
cmp CRF,L,rA,rB
cmp 2,0,r5,r6
Bit Descripción
0 El primer operando es menor al segundo
1 El primer operando es mayor al segundo
2 Los operandos son iguales
3 Summary Overflow (SO). Hubo un overflow. Este bit es una copia del estado
final del bit XER[SO]
Pág 56
www.macprogramadores.org
cmp 0,r5,r6
mfcrf 128,r2 ; Copiamos el campo CR0 a r2
andi r2,4 ; Quitamos todos los bits menos el bit
; que es el que indica que que son iguales
Significado de los bits del campo de CR0 en las instrucciones con enteros
acabadas en punto (.)
Bit Descripción
0 El registro destino ha recibido un valor negativo
1 El registro destino ha recibido un valor positivo
2 El registro destino ha recibido un cero
3 Summary Overflow (SO). Hubo un overflow. Este bit es una copia del estado
final del bit XER[SO]
Como veremos en el tema de pipelines del Capitulo 3, estas instrucciones pueden ser
más lentas que sus correspondientes versiones sin punto (las que no modifican el
campo CR0), con lo que sólo debemos usarlas si nos interesa conocer este resultado.
Pág 57
www.macprogramadores.org
addi. r7,r5,r6
mfcrf 128,r2 ; Copiamos el campo CR0 a r2
andi r2,4 ; Quitamos todos los bits menos el bit
; que es el que indica que r7 tiene un 0
Por último queda por comentar que el campo CR1 se suele utilizar para reflejar el
resultado de ejecutar una operación con números en punto flotante con instrucciones
de punto flotante acabadas en punto (.), como por ejemplo fadd. o fabs., tal
como muestra la tabla siguiente.
Significado de los bits del campo de CR1 en las instrucciones en punto flotante
acabadas en punto (.)
Bit Descripción
4 Floating-point eXception (FX)
5 Floating-point Enabled eXception (FEX)
6 Floating-point inValid eXception (VX)
7 Floating-point Overflow eXception (OX)
El valor de este registro, no es más que el contendido de los bits de otro registro
FPSCR[0-3], que como veremos en 7.4 es el que almacena los resultados de realizar
operaciones en punto flotante.
El registro XER es un registro que, al igual que CR, sirve para mostrar el resultado de
ejecutar una operación con enteros, que nos da información sobre posibles problemas
durante la ejecución de la instrucción aritmética, así como otras informaciones
asociadas a operaciones aritméticas.
Pág 58
www.macprogramadores.org
Para cada operación aritmética que pueda producir un acarreo suelen existir dos
instrucciones, una con indicación de acarreo y otra sin ella. Por ejemplo addi no
activa el bit de acarreo si se produce y addic si que lo activa. Esto es así porque el
activar el bit de acarreo puede producir retrasos en la ejecución de las instrucciones
siguientes del pipeline tal como explicará en el Capítulo 3.
Lo mismo pasa con los overflow, hay una instrucción que no lo detecta (add) y otra
que sí (addo), incluso hay una que detecta el acarreo y el overflow (addco).
Por ejemplo, si queremos leer el contenido del registro XER y pasarlo a r5 haríamos:
En el apartado 5.7.4 veremos mnemonics que nos permiten acceder al registro XER
de forma más cómoda.
Pág 59
www.macprogramadores.org
La cual copia los bits CA, OV y SO de XER a CR para que los bits de CR se
sincronicen con los de XER, es decir, tengan el mismo valor que los bits de XER.
La siguiente tabla resume las instrucciones aritméticas de suma de enteros que existen
en PowerPC:
Instrucción Descripción
addi rD,rA,SIMM (ADD Immediate) Calcula la suma de (rA|0)+SIMM
y la pone en rD.
addis rD,rA,SIMM (ADD Immediate Shift) Calcula la suma de
(rA|0)+(SIMM||0x0000) y la pone en rD
add rD,rA,rB (ADD) La suma rA+rB se deposita en rD
add. rD,rA,rB (ADD) Igual que add, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
addo rD,rA,rB (ADD Overflow) Igual que add, sólo que XER[OV] se
pone a 1 si hay overflow
addo. rD,rA,rB (ADD Overflow) Igual que addo, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
addic rD,rA,SIMM (ADD Immediate Carrying) Igual que addi sólo que
XER[CA] a se pone a 1 si hay acarreo
addic. rD,rA,SIMM (ADD Immediate Carrying) Igual que addic sólo que
CR0 se actualiza tal como explicamos en el apartado 5.1
addc rD,rA,rB (ADD Carrying) Igual que add sólo que XER[CA] se
pone a 1 si hay acarreo
addc. rD,rA,rB (ADD Carrying) Igual que addc sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
Pág 60
www.macprogramadores.org
addco rD,rA,rB (ADD Carrying with Overflow) Igual que add sólo que
si hay acarreo XER[CA] se pone a 1 y XER[OV] se
pone a 1
addco. rD,rA,rB (ADD Carrying with Overflow) Igual que addco sólo
CR0 se actualiza tal como explicamos en el apartado 5.1
adde rD,rA,rB (ADD Extended) La suma rA+rB+XER[CA] se pone
en rD
adde. rD,rA,rB (Add Extended) Igual que adde, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
addeo rD,rA,rB (ADD Extended with Overflow) Igual que adde, sólo
que XER[OV] se pone a 1 si hay overflow
addeo. rD,rA,rB (ADD Extended with Overflow) Igual que addeo, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
addme rD,rA (ADD Minus one Extended) La suma
rA+XER[CA]+0xFFFFFFFF se guarda en rD
addme. rD,rA (ADD to Minus one Extended) Igual que addme, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
addmeo rD,rA (ADD to Minus one Extended with Overflow) Igual que
addme, sólo que XER[OV] se pone a 1 si hay overflow
addmeo. rD,rA (ADD to Minus one Extended with Overflow) Igual que
addmeo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
addze rD,rA (ADD to Zero Extended) La suma rA+XER[CA] se
deposita en rD
addze. rD,rA (ADD to Zero Extended) Igual que addze, sólo que
CR0 se actualiza tal como explicamos en el apartado 5.1
addzeo rD,rA (ADD to Zero Extended with Overflow) Igual que
addze, sólo que XER[OV] se pone a 1 si hay overflow
addzeo. rD,rA (ADD to Zero Extended with Overflow) Igual que
addzeo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
SIMM ≡ Signed IMMediate
UIMM ≡ Unsigned IMMediate
(rA|0) ≡ El valor del registro rA o bien 0 binario
SIMM||0x0000 ≡ A SIMM se le concatena 0x0000 al final, es decir, desplazamos
SIMM 16 bits a la izquierda
XER[OV] ≡ Indica que nos estamos refiriendo al bit OV del registro XER
A todos los registros que reciben como operandos estas instrucciones se les considera
números con signo.
Por ejemplo, vamos a hacer un programa llamado sumalong.s3 que sume dos
números de 64 bits. Al tener los registros de PowerPC sólo 32 bits tenemos que usar
3
En C de GNU para PowerPC el tipo long ocupa 32 bits, debiendo usarse long
long para su correspondiente tipo de 64 bits
Pág 61
www.macprogramadores.org
dos registros para almacenar un número. Imaginemos que queremos sumar los
números guardados en r2||r3 y r4||r5 y depositar la suma en r6||r7
En este caso primero tendremos que sumar con acarreo r3+r5 y si hay acarreo,
pasarlo a la suma r2+r4. La suma r2+r4 no la hacemos con acarreo, ya que si
hubiera acarreo este se perdería, luego en este caso vamos a considerar al acarreo un
overflow, el cual podríamos detectar para dar un mensaje de error.
addc r7,r3,r5
Que pone la suma r3+r5 en r7, y activa el bit XER[CA] si hay acarreo.
addeo r6,r2,r4
Pág 62
www.macprogramadores.org
addc r7,r3,r5
addeo r6,r2,r4
// Guardamos el resultado que tenemos en r6||r7 en C
lis r10,ha16(C)
stw r6,lo16(C)(r10)
stw r7,lo16(C+4)(r10)
blr
Esta instrucción se utiliza para saber si la operación anterior tuvo acarreo, en cuyo
caso a rA se le suma 1, y si no hubo acarreo se queda valiendo lo mismo.
Esta instrucción se utiliza cuando vamos a sumar números de más de 64 bits. Como
ejemplo, vamos a hacer ahora un programa llamado sumalong2.s que sume
números de 128 bits.
r3 r4 r5 r6
r7 r8 r9 r10
Pág 63
www.macprogramadores.org
Pág 64
www.macprogramadores.org
La siguiente tabla resume las instrucciones aritméticas de resta de enteros que existen
en PowerPC:
Instrucción Descripción
subf rD,rA,rB (SUBtract From) Calcula rB-rA y lo deposita en rD.
subf. rD,rA,rB (SUBtract From) Igual que subf, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
subfo rD,rA,rB (SUBtract From with Overflow) Igual que subf, sólo
que XER[OV] se pone a 1 si hay overflow
subfo. rD,rA,rB (SUBtract From with Overflow) Igual que subfo, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
subfc rD,rA,rB (SUBtract From Carring with Overflow) Calcula rB-
rA y lo deposita en rD, y si hay acarreo pone XER[CA]
a1
subfc. rD,rA,rB (SUBtract From Carring with Overflow) Igual que
subfc, sólo que CR0 se actualiza tal como explicamos
en el apartado 5.1
subfco rD,rA,rB (SUBtract From Carrying with Overflow) Igual que
subfc, sólo que XER[OV] se pone a 1 si hay overflow
subfco. rD,rA,rB (SUBtract From Carrying with Overflow) Igual que
subfco, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
subfe rD,rA,rB (SUBtract From Extended) Calcula rB-rA+XER[CA]
y lo deposita en rD.
subfe. rD,rA,rB (SUBtract From Extended) Igual que subfe, sólo que
CR0 se actualiza tal como explicamos en el apartado 5.1
subfeo rD,rA,rB (SUBtract From Extended with Overflow) Igual que
subfe, sólo que XER[OV] se pone a 1 si hay overflow
subfeo. rD,rA,rB (SUBtract From Extended with Overflow) Igual que
subfeo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
subfme rD,rA,rB (SUBtract From Minus one Extended) Calcula
~rA+XER[CA]+0xFFFFFFFF y lo deposita en rD.
subfme. rD,rA,rB (SUBtract From Minus one Extended) Igual que
subfme, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
subfmeo rD,rA,rB (SUBtract From Minus one Extended with Overflow)
Igual que subfme, sólo que XER[OV] se pone a 1 si
hay overflow
subfmeo. rD,rA,rB (SUBtract From Minus one Extended with Overflow)
Igual que subfmeo, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
Pág 65
www.macprogramadores.org
Al igual que en la suma, a todos los registros que reciben como operandos estas
instrucciones se les considera números con signo.
Aunque no hay ninguna operación para la resta con un operando inmediato, su efecto
se puede conseguir con addi, con el operador inmediato negado. Existen mnemonics
para la resta que se describen en el apartado 5.7.1.
Instrucción Descripción
neg rD,rA (NEGate) Calcula el complemento a 2 de rA y lo
deposita en rD, es decir, calcula ~rA+1
neg. rD,rA (NEGate) Igual que neg, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
nego rD,rA (NEGate with Overflow) Igual que neg, sólo que
XER[OV] se pone a 1 si hay overflow
nego. rD,rA (NEGate) Igual que nego, sólo que CR0 se actualiza
tal como explicamos en el apartado 5.1
Pág 66
www.macprogramadores.org
Instrucción Descripción
mulli rD,rA,SIMM (MULtiply Low Immediate) Los 32 bits bajos del
producto rA*SIMM se depositan en rD.
mullw rD,rA,rB (MULtiply Low Word) Calcula los 32 bits bajos de
rA*rB. Esta instrucción se puede combinar con
mulhw para calcular un producto completo, de 64 bits
mullw. rD,rA,rB (MULtiply Low Word) Igual que mullw, sólo que
CR0 se actualiza tal como explicamos en el apartado
5.1
mullwo rD,rA,rB (MULtiply Low Word with Overflow) Igual que
mullw, sólo que XER[OV] se pone a 1 si hay overflow
mullwo. rD,rA,rB (MULtiply Low Word with Overflow) Igual que
mullwo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
mulhw rd,rA,rB (MULtiply High Word) Calcula los 32 bits altos de
rA*rB. Esta instrucción se puede combinar con
mullw para calcular un producto completo, de 64 bits
mulhw. rD,rA,rB (MULtiply High Word) Igual que mulhw, sólo que
CR0 se actualiza tal como explicamos en el apartado
5.1
mulhwo rD,rA,rB (MULtiply High Word with Overflow) Igual que
mulhw, sólo que XER[OV] se pone a 1 si hay overflow
mulhwo. rD,rA,rB (MULtiply High Word with Overflow) Igual que
mulhwo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
mulhwu rD,rA,rB (MULtiply High Word Unsigned) El contenido de rA y
rB es considerado como números de 32 bits sin signo y
calcula los 32 bits de la parte alta del producto,
depositándola en rD
mulhwu. rD,rA,rB (MULtiply High Word Unsigned) Igual que mulhwu,
sólo que CR0 se actualiza tal como explicamos en el
apartado 5.1
La instrucción mulli sólo debe usarse cuando estemos seguros de que los 16 bits
altos del registro rA estén a 0, sino se producirá una perdida de datos por
desbordamiento.
Para evitar este problema es preferible cargar el número SIMM en un registro y operar
después con mulhw y mullw.
Pág 67
www.macprogramadores.org
Instrucción Descripción
divw rD,rA,rB (DIVide Word) El dividendo es rA, el divisor es rB, y
el cociente se deposita en rD. La instrucción no nos da
el resto de la operación.
divw. rD,rA,rB (DIVide Word) Igual que divw, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
Pág 68
www.macprogramadores.org
divwo rD,rA,rB (DIVide Word with Overflow) Igual que divw, sólo
que XER[OV] se pone a 1 si hay overflow
divwo. rD,rA,rB (DIVide Word with Overflow) Igual que divwo, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
divwu rD,rA,rB (DIVide Word Unsigned) Calcula la división con
números sin signo. El dividendo es rA, el divisor es rB,
y el cociente se deposita en rD. La instrucción no nos d
el resto de la operación.
divwu. rD,rA,rB (DIVide Word Unsigned) Igual que divwu, sólo que
CR0 se actualiza tal como explicamos en el apartado
5.1
divwuo rD,rA,rB (DIVide Word Unsigned with Overflow) Igual que
divwu, sólo que XER[OV] se pone a 1 si hay overflow
divwuo. rD,rA,rB (DIVide Word Unsigned with Overflow) Igual que
divwuo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
Instrucción Descripción
cmpi CRFD,L,rA,SIMM (CoMPare Immediate) Compara como números con
signo al número depositado en el registro rA con el
número SIMM con extensión de signo. El resultado de
la comparación se deposita en CRFD
cmp CRFD,L,rA,rB (CoMPare) Compara rA y rB tratándolos como
números con signo y el resultado lo deposita en CRFD
cmpli CRFD,L,rA,UIMM (Compare Logical Immediate) El número depositado
en el registro rA se compara con el número
0x0000||UIMM , tratando a los operandos como
números sin signo. El resultado de la comparación se
deposita en CRFD
cmpl CRFD,L,rA,rB (CoMPare Logical) Compara a los números
depositados en rA y rB como números sin signo. El
resultado se deposita en CRFD
Pág 69
www.macprogramadores.org
Las instrucción cmpi recibe como operando un número con signo (SIMM) de 16 bits
sobre el que realiza una extensión del signo a 32 bits, mientras que la instrucción
cmpli recibe como operando un número sin signo (UIMM) de 16 bits, sobre el que
extiende los 16 bits altos rellenándolos con ceros.
Esto permite usar las instrucciones que reciben números con signo (cmpi y cmp)
para comparaciones aritméticas, y las instrucciones que reciben números sin signo
(cmpli y cmpl) para comparaciones lógicas.
Por ejemplo si queremos comparar como números sin signo los registros r3 y r4 y
depositar el resultado de la comparación en el campo 3 de CR haríamos:
cmp 3,0,r3,r4
cmp 0,r3,r4
Este grupo de instrucciones siempre consideran a los operandos como números sin
signo, luego las instrucciones que reciben operandos inmediatos , los reciben del tipo
UIMM, y los extienden a 32 bits rellenando con ceros.
Las instrucciones con punto (.) modifican el campo 0 de CR tal como explicamos en
el apartado 5.1. Sin embargo, ninguna instrucción lógica modifica los bits
XER[SO,OV,CA].
La siguiente tabla muestra las instrucciones lógicas con enteros que existen en
PowerPC:
Instrucción Descripción
andi. rD,rA,UIMM (AND Immediate) Realiza un and lógico entre rA y
0x0000||UIMM y lo deposita en rD
andis. rD,rA,UIMM (AND Immediate Shifted) Realiza un and lógico entre
rA y UIMM||0x0000 y el resultado lo deposita en rD
and rD,rA,rB (AND) Realiza un and lógico entre rA y rB y el
resultado se deposita en rD
and. rD,rA,rB (AND) Igual que add, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
Pág 70
www.macprogramadores.org
Instrucción Descripción
ori rD,rA,UIMM (OR Immediate) Realiza un or lógico entre rA y
0x0000||UIMM y lo deposita en rD
oris rD,rA,UIMM (OR Immediate Shifted) Realiza or lógico entre rA y
UIMM||0x0000 y el resultado lo deposita en rD
or rD,rA,rB (OR) Realiza un or lógico entre rA y rB y el resultado
se deposita en rD
or. rD,rA,rB (OR) Igual que or, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
orc rD,rA,rB (OR with complement) Realiza un or lógico entre rA y
~rB (complemento a 1 de rB) y el resultado lo deposita
en rD
orc. rD,rA,rB (OR with Complement) Igual que orc, sólo que CR0
se actualiza tal como explicamos en el apartado 5.1
nor rD,rA,rB (No OR) Al resultado del or lógico entre rA y rB se le
hace el complemento a 1 y se deposita en rD, es decir,
en rD se guarda ~(rA|rB)
Instrucción Descripción
xori rD,rA,UIMM (eXclusive OR Immediate) Realiza un xor lógico entre
rA y 0x0000||UIMM y lo deposita en rD
xoris rD,rA,UIMM (eXclusive OR Immediate Shifted) Realiza xor lógico
entre rA y UIMM||0x0000 y el resultado lo deposita
en rD
xor rD,rA,rB (eXclusive OR) Realiza un xor lógico entre rA y rB y
el resultado se deposita en rD
xor. rD,rA,rB (eXclusive OR) Igual que xor, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
Pág 71
www.macprogramadores.org
A eqv se le llama la operación de equivalencia, porque comprueba que todos los bits
sean iguales en cuyo caso el resultado es 0xFFFFFFFF, en caso contrario, los bits que
diverjan valen 0. El resultado sólo será 0x00000000 si todos los bits son distintos.
Ambas operaciones mueven los bits a izquierda o derecha, la diferencia está en que
las operaciones de desplazamiento pierden los bits que salen por un extremo,
entrando ceros por el otro extremo, mientras que en las operaciones de rotación
cuando los bits salen por un extremo, entran por el otro extremo.
Pág 72
www.macprogramadores.org
Instrucción Descripción
slw rD,rS,rC (Shift Left Word) Desplaza a la izquierda los bits de rS
tantas veces como diga rC (que debe ser un número
comprendido entre 0 y 31), el resultado se copia en rD
srw rD,rS,rC (Shift Right Word) Desplaza a la derecha los bits de rS
tantas veces como diga rC (que debe ser un número
comprendido entre 0 y 31), el resultado se copia en rD
srawi rD,rS,C (Shift Right Algebraic Word Inmediate) Desplaza a la
derecha los bits de rS tantas veces como diga C (que
debe ser un número comprendido entre 0 y 31), al
resultado se le extiende el bit de signo y se copia en rD
srawi. rD,rS,C (Shift Right Algebraic Word Inmediate) Igual que
srawi, sólo que CR0 se actualiza tal como explicamos
en el apartado 5.1
sraw rD,rS,rC (Shift Right Algebraic Word) Desplaza a la derecha los
bits de rS tantas veces como diga rC (que debe ser un
número comprendido entre 0 y 31), al resultado se le
extiende el bit de signo y se copia en rD
sraw. rD,rS,rC (Shift Right Algebraic Word) Igual que sraw, sólo que
CR0 se actualiza tal como explicamos en el apartado
5.1
Entre las operaciones se diferencia claramente entre los desplazamientos sin signo
(slw y srw), y desplazamientos con signo o algebraicos (srawi y sraw). En los
desplazamientos con signo, después de hacer el desplazamiento se extiende el signo
de forma que el bit de signo nunca se modifica, es decir, si al empezar el
desplazamiento es un 0 (positivo) se mantiene y por la izquierda entran ceros, y si es
un 1 (negativo) se mantiene y por la izquierda entran unos.
Sin embargo, en los desplazamientos sin signo el bit de signo no es distinto de los
demás.
Pág 73
www.macprogramadores.org
li r4,16
srw r2,r3,r4 ; r2=0x0000FFFF
Obsérvese que faltan las operaciones de desplazamiento a la izquierda con signo, esto
es porque hay mnemonics que equivalen a ellas y que describiremos en el apartado
5.7.3.
Las operaciones de rotación reciben como operandos dos números: MB (Mask Begin)
y ME (Mask End), que forman una máscara, la cual indica qué bits de los 32 bits que
tiene un registro son los que nos interesan. Uno de los números indica el principio de
la máscara y otro el final de la máscara, una vez que se realiza la operación de
rotación, sólo obtenemos los bits que estén dentro de la máscara, los bits que caigan
fuera de la máscara siempre valdrán 0.
5 28
Instrucción Descripción
rlwinm rD,rS,N,MB,ME (Rotate Left Word Immediate theN and with
Mask) El contenido de rS se rota a la izquierda el
número de veces especificado en N. Se genera una
máscara con unos desde el bit MB hasta el bit ME, y
lo demás con ceros. Al resultado de la rotación se
le hace un and binario con la máscara, y el
resultado se deposita en rD
rlwinm. rD,rS,N,MB,ME (Rotate Left Word Immediate theN and with
Mask) Igual que rlwinm, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
rlwnm rD,rS,rN,MB,ME (Rotate Left Word theN and with Mask) El
contenido de rS se rota a la izquierda el número
de veces especificado en los 5 bits bajos de rN. Se
genera una máscara con unos desde el bit MB hasta
el bit ME, y lo demás con ceros. Al resultado de la
rotación se le hace un and binario con la máscara,
y el resultado se deposita en rD
rlwnm. rD,rS,rN,MB,ME (Rotate Left Word theN and with Mask) Igual que
rlwnm, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
Pág 74
www.macprogramadores.org
rlwinm rD,rS,2,0,10
rlwinm rD,rS,2,0,31
Ahora la máscara va desde MB=0 hasta ME=31, es decir, abarca todos los bits del
registro y en rD obtenemos:
Por contra, la instrucción rlwimi si que tiene en cuenta el valor de rD, ya que sólo
se cambian los bits de rD que caen bajo la máscara, dejando los demás bits tal como
están. Por ejemplo si tenemos los registros rS y rD con estos valores:
Pág 75
www.macprogramadores.org
Y hacemos:
rlwimi rD,rS,2,0,10
Y si hacemos:
rlwimi rD,rS,2,0,31
5.7. Mnemonics
5.7.1. Mnemonics para la resta
Como decíamos en el apartado 5.3.2, aunque no hay ninguna operación para resta con
un operando inmediato, su efecto se puede conseguir con un mnemonic que llame a
addi. A continuación se detallan que mnemonics hay para la resta inmediata.
Mnemonic Equivalente a
subi rD,rA,SIMM addi rD,rA,-SIMM
subi. rD,rA,SIMM addi. rD,rA,-SIMM
subis rD,rA,SIMM addis rD,rA,-SIMM
subis. rD,rA,SIMM addis. rD,rA,-SIMM
subic rD,rA,SIMM addic rD,rA,-SIMM
subic. rD,rA,SIMM addic. rD,rA,-SIMM
Por otro lado la operaciones de resta reciben los operandos de una forma poco natural,
ya que reciben primero el sustraendo y luego el minuendo, cuando lo normal es
recibirlos al revés, para facilitar la comprensión del programa se han creado
mnemonics que reciben los operandos en el orden inverso:
cmp CRF,L,rA,rB
Tal que en arquitecturas de 32 bits L siempre debe valer 0. Para evitar tener que pasar
siempre un 0 en este operando se han diseñado los mnemonics de la siguiente tabla:
Pág 77
www.macprogramadores.org
cmpw 2,rA,rB
Podemos poner:
cmpw cr2,rA,rB
Para ayudar al programador se han creado una serie de mnemonics que realizan las
operaciones más comunes. En concreto estos mnemonics nos permiten realizar las
siguientes operaciones:
Immediate
INSert from Left inslwi rD,rS,n,b rlwimi rD,rS,32-
Word Immediate (n>0) b,b,(b+n)-1
INSert from Right insrwi rD,rS,n,b rlwimi rD,rS,32-
Word Immediate (n>0) b+n,b,b+n-1
ROTate Left Word rotlwi rD,rS,n rlwinm rD,rS,n,0,31
Immediate
ROTate Right Word rotrwi rD,rS,n rlwinm rD,rS,32-n,0,31
Immediate
ROT Left Word rotlw rD,rS,rN rlwnm rD,rS,rN,0,31
Todas estas instrucciones disponen de su equivalente versión con punto (.) que
modifican el contenido de CR0 como explicamos en el apartado 5.1
Existe un mnemonic que nos permite emular la operación nop (No OPeration)
mediante una llamada a la instrucción or con operandos que hacen que la instrucción
no modifique nada:
Pág 79
www.macprogramadores.org
En esta sección se comentan algunas operaciones de alto nivel no triviales que son
muy típicas de necesitar usar en un programa hecho en ensamblador.
Vamos a ver como se calcula el valor absoluto de un número sin usar sentencias
condicionales, que de hecho todavía no hemos visto.
Si (a≥0) => a
Si (a<0) => complemento2(a)
2. Hacer un xor a a con b. De esta forma si a≥0 entonces c=a pero si a<0 entonces
c=complemento1(a)
De esta forma:
; r3 contiene el valor de a
srawi r4,r3,31 ; r4 = (r3<0) ? -1 : 0
xor r5,r4,r3 ; r5 = (r3<0) ? -a : a
sub r6,r5,r4 ; r6 = (a<0) ? (-a+1) : a
Pág 80
www.macprogramadores.org
Por ejemplo, supongamos que a=-6 veamos como opera el programa con los bits:
Obsérvese que si a hubiera valido 6, r4 hubiera tenido todos sus bits a cero, con lo
que las operaciones xor y subf no hubieran afectado el valor de a
Para ello sabemos que el máximo y mínimo de dos números a, b si puede representar
como:
min(a,b) = (a<=b)?a:b
max(a,b) = (a>=b)?a:b
; r3 = a
; r4 = b
subc r5,r4,r3 ; r5 = r4-r3
subfe r6,r4,r4 ; r6 = (r4>r3)?0:-1
and r5,r5,r6 ; r5 = (r4>r3)?0:(r4-r3)
add r7,r3,r5 ; r7 = (r4>r3)?r3:r4
; r7 = min(r3,r4)
1. Calculamos la diferencia entre los números: c=b-a, la cual puede ser positiva o
negativa. En la instrucción subc usamos acarreo para luego poder comprobarlo.
Pág 81
www.macprogramadores.org
Remplazando and por andc (AND with Complement to 1) el código anterior nos
permite calcular max(a,b)
; r3 = a
; r4 = b
subc r5,r4,r3 ; r5 = r4-r3
subfe r6,r4,r4 ; r6 = (r4>r3)?0:-1
andc r5,r5,r6 ; r5 = (r4>r3)?(r4-r3):0
add r7,r3,r5 ; r7 = (r4>r3)?r4:r3
; r7 = max(r3,r4)
El algoritmo anterior sólo funciona si ambos números son positivos, si los números
pueden ser positivos o negativos necesitamos aplicar un algoritmo como el siguiente:
; r3 = a
; r4 = b
xoris r5,r3,0x8000 ; c = a+128
xoris r6,r5,0x8000 ; d = b+128
; Ahora el problema es analogo al del minimo sin signo
subc r5,r6,r5 ; e = d-c = b-a+256 = b-a
subfe r6,r6,r6 ; f = (d>c)?0:-1
and r5,r5,r6 ; g = (d>c)?0:(d-c)
add r5,r3,r5 ; r5 = a+g
; r5 contendra la solucion
Pág 82
www.macprogramadores.org
Obsérvese que cambiar el signo a un número con signo y interpretarle como número
sin signo equivale a sumar 128 al número. Por ejemplo:
Igual que antes, remplazando and por andc (AND with Complement to 1) el código
anterior nos permite calcular max(a,b)
; r3 = a
; r4 = b
xoris r5,r3,0x8000 ; c = cambio signo a
xoris r6 ,r5,0x8000 ; d = cambio signo b
; Ahora el problema es analogo al del máximo sin signo
subc r5,r6,r5 ; e = d-c
subfe r6,r6,r6 ; f = (d>c)?0:-1
andc r5,r5,r6 ; g = (d>c)?0:(d-c)
add r5,r3,r5 ; r5 = a+g
; r5 contendra la solucion
n = d*c+r
Donde:
Pág 83
www.macprogramadores.org
n d c r
7 3 2 1
-7 3 -2 -1
7 -3 -2 1
-7 -3 2 -1
Obsérvese que según esta regla para el valor del resto que hemos dado siempre se
cumple la fórmula n=d*c+r.
El algoritmo del cálculo del resto en una división con signo se limita a aplicar la
fórmula: n=d*c+r => r=n-d*c, y es el siguiente:
; rN Dividendo
; rD Divisor
divw rT,rN,rD ; c = n/d
mullw rT,rT,rD ; c*d
sub rT,rN,rT ; r = n-c*d
En las divisiones de número sin signo el algoritmo es similar, sólo que ahora se usa
divwu en vez de divw:
; rN Dividendo
; rD Divisor
divwu rT,rN,rD ; c = n/d
mullw rT,rT,rD ; c*d
sub rT,rN,rT ; r = n-c*d
Pág 84
www.macprogramadores.org
El siguiente apartado describe técnicas para división de números de 32 bits. Aun así
estas técnicas se pueden extender a número de 64 bits.
srawi rC,rN,rK
addze rC,rC
Para todo divisor d distinto de 0, la división entre d se puede calcular como una
multiplicación y alguna de suma o desplazamiento. La idea básica es multiplicar el
dividendo n por un magic number (m) de forma que los 32 bits altos del producto de
dos números represente el cociente. Para lo cual usaremos la instrucción mulhw de
PowerPC. Con vistas a que el resultado de la división quede en los 32 bits altos del
producto el magic number debe estar comprendido entre m=(232/n) y m=(264/n),
ya que al multiplicar m por n tendremos un número comprendido entre
C=n*(232/n)=232 y C=n*(264/n)=264, que es la parte alta que nos interesa a
partir de la cual calculamos el cociente c como c=C/232.
Pág 85
www.macprogramadores.org
Los detalles son complicados, especialmente para algunos divisores, como por
ejemplo el 7.
Pág 86
www.macprogramadores.org
El método general siempre se reduce a uno de estos tres casos, ilustrados con la
división entre 3, 5 ó 7. En el caso de la división entre 3 el multiplicador se puede
representar en 32 bits, y por eso en este caso después del mulhw el desplazamiento a
la derecha es 0. En el caso de la división entre 5, el multiplicador también se
representa con 32 bits, pero el desplazamiento a la derecha es uno. En el caso de la
división entre 7, el multiplicador no se puede representar en 32 bits, pero los 32 bits
bajos del multiplicador son representables en 32 bits. Entonces, el programa
multiplica por los 32 bits bajos del multiplicador y después corrige el producto
añadiendo n*232, es decir, añade n a la parte alta del producto. Para d=7, el
desplazamiento a la derecha es 2.
Para la mayoría de los divisores, existe más de un multiplicador que nos dan el
resultado correcto con este método. En este caso, en general lo mejor es usar el
multiplicador más bajo ya que este puede implicar un desplazamiento de cero bits a la
izquierda, ahorrándonos la instrucción srawi.
El procedimiento para dividir entre una constante negativa es análogo. Esto es así
gracias a que la división de enteros satisface la propiedad: n/(-d)=-(n/d). Con lo
que para dividir entre una constante negativa, primero dividimos entre su
correspondiente constante positiva, y luego al resultado así obtenido le cambiamos el
signo.
Este programa es el mismo que el de la división entre +7, excepto que usa el
multiplicador de signo opuesto, resta en vez de añadir, y desplaza c en vez de n a la
derecha 31 posiciones. (En el caso de d=+7 también podríamos desplazar c en vez de
n 31 veces a la derecha, pero habría menos paralelismo en el código).
La siguiente tabla muestra los magic number y desplazamientos para los números más
comunes.
Pág 87
www.macprogramadores.org
El algoritmo para calcular los magic numbers y desplazamientos de los divisores está
más allá de los objetivos de este tutorial. Aquel que este interesado en conocerlo pude
hacerlo en la web de IBM en el documento: Warren, Henry S., Jr., IBM Research
Report: RC 18601 [1992]. Changing Division by a constant to Multiplication in
Two’s Complement Arithmetic.
Pág 88
www.macprogramadores.org
La operación se realiza sobre el registro de 128 bits res:dvd. Cada iteración incluye
los siguientes pasos:
Antes de empezar este bucle el programa desplaza dvd a la izquierda tantas veces
como ceros a la izquierda tenga con el fin de evitar repeticiones de bucle innecesarias.
.data
n: .long 2,1 ; Dividendo
d: .long 1,0 ; Divisor
.comm c,8 ; Cociente
.comm r,8 ; Resto
Pág 89
www.macprogramadores.org
.text
.globl _main
_main:
// Cargamos el dividendo y divisor en los registros
// Para ello usamos lswi (Load String Word
// Immediate) que carga 32 bytes consecutivos en
// dvdl-dvsh
lis r2,ha16(n)
addi r2,r2,lo16(n)
lswi dvdh,r2,16
Pág 90
www.macprogramadores.org
Pág 91
www.macprogramadores.org
// Retornamos
blr
Pág 92
www.macprogramadores.org
6. Instrucciones de bifurcación
En esta sección vamos a comentar con que instrucciones de bifurcación cuenta
PowerPC.
Las instrucciones de bifurcación nos permiten alterar el flujo normal del programa.
Para ello alteran el valor del contador de programa. En PowerPC, a diferencia de otras
arquitecturas, nunca se puede hacer referencia explícita a este registro, es decir, este
registro no se puede leer en PowerPC, y sólo se puede modificar indirectamente al
ejecutar instrucciones de bifurcación. En otros sistemas es muy típico llamar a este
registro PC (Program Counter) o IP (Instruction Pointer). Nosotros vamos a referirnos
a el como IP, aunque este registro no tienen un nombre explícito en PowerPC, por no
poder referirnos diréctamenta a él.
Por otro lado las instrucciones de bifurcación pueden ser de salto relativo o absoluto.
Las instrucciones de bifurcación de salto absoluto especifican la dirección completa
(32 bits) de la dirección de salto, obsérvese que como todas las instrucciones de
PowerPC ocupan 32 bits, la dirección absoluta de salto no se puede codificar dentro
de la instrucción, sino que debe de estar en un registro.
Como veremos, el tamaño de esta dirección relativa puede ser de 14 bits o de 24 bits,
y como los últimos dos bits no es necesario almacenarlos, nos permiten dar saltos de
hasta ±32.768 bytes (214+2-1=32.768) y de hasta ±33.554.432 bytes (224+2-
1
=33.554.432) respectivamente, es decir, sumamos 2 al exponente porque los dos
últimos bits no es necesario almacenarlos con lo que podemos coger otros 2 bits de la
izquierda, y le restamos 1 porque el desplazamiento puede ser positivo o negativo.
Pág 93
www.macprogramadores.org
o Salto relativo
o Salto absoluto
o Salto condicional relativo
o Salto condicional absoluto
o Salto condicional al Link Register
o Salto condicional al Count Register
0 5 6 29 30 31
18 LI AA LK
(Codificación de la instrucción)
0 5 6 29 30 31
Extensión de signo LI 0 0
0 31
IP (Instruction Pointer) +
0 31
Dirección de salto
Pág 94
www.macprogramadores.org
Instrucción Descripción
b D (Branch) Salta a la dirección calculada como la suma de
D más el valor actual del IP. Esta instrucción tiene
AA=0 y LK=0
bl D (Branch then Link) Igual que b, sólo que en el registro
LR se almacena la dirección de la siguiente instrucción
a la instrucción de salto. Esta instrucción tiene AA=0 y
LK=1
Las instrucciones de salto absoluto que vamos a ver en esta sección reciben como
operando una dirección que indica la posición absoluta a la que realizar el salto.
0 5 6 29 30 31
0000 00 LI 0 0
0 31
Dirección de salto
Obsérvese que al ser los primeros 6 bits siempre 0, esta instrucción sólo nos permite
acceder a los primeros 226=67.108.864 bytes del espacio de memoria de 232 bytes que
tiene un proceso, con lo que es una instrucción poco usada.
Pág 95
www.macprogramadores.org
Instrucción Descripción
ba D (Branch Absolute) Salta a la dirección dada en D. Esta
instrucción tiene AA=1 y LK=0
bla D (Branch then Link Absolute) Igual que ba, sólo que en
el registro LR se almacena la dirección de la siguiente
instrucción a la instrucción de salto. Esta instrucción
tiene AA=1 y LK=1
ba fin
········
fin: blr
0 5 6 10 11 15 16 30 31
OpCode BO BI AA LK
BI (Branch Input) especifica los bits de CR usados como condición a evaluar para el
salto de acuerdo a la siguiente tabla:
BI Bit CRn a
Dec Bin evaluar Descripción
0 00000 CR0[0] Negative (LT). El resultado de una instrucción con punto
(.) es negativo
1 00001 CR0[1] Positive (GT). El resultado de una instrucción con punto
(.) es positivo
2 00010 CR0[2] Zero (EQ). El resultado de una instrucción con punto (.)
es cero
3 00011 CR0[3] Summary Overflow (SO). Copia del bit XER[SO] de la
anterior instrucción ejecutada
4 00100 CR1[0] Copia de FPSCR[FX]
5 00101 CR1[1] Copia de FPSCR[FEX]
6 00110 CR1[2] Copia de FPSCR[VX]
7 00111 CR1[3] Copia de FPSCR[OX]
8 01000 CRn[0] Menor que:
Pág 96
www.macprogramadores.org
Recuérdese, que como explicamos en el apartado 5.1 el campo CR0 se suele usar para
comprobar el resultado de una operación con punto (.), como por ejemplo add., de
este resultado podíamos mirar si era positivo, negativo, cero, o había habido un
overflow.
Los demás campos (CR2 hasta CR7) se dejaban para las operaciones de comparación,
aunque los resultados de las comparaciones también se pueden depositar en CR0 y
CR1, sin embargo en la tabla hemos supuesto que se usan para instrucciones con
punto.
En la tabla n se refiere a los campos de CR que van desde CR2 hasta CR7, donde los
cuatro bits de cada campo se interpretan como menor que, mayor que, igual y
overflow, respectivamente.
Cada uno de los 32 valores de la tabla indica que bit de los 32 bits del registro CR
queremos comprobar.
Pág 97
www.macprogramadores.org
BO Descripción
0000y Decrementa el registro CTR y después salta si CTR≠0 y la condición es
FALSE
0001y Decrementa el registro CTR y después salta si CTR=0 y la condición es
FALSE
001zy Salta si la condición es FALSE
0100y Decrementa el registro CTR y después salta si CTR≠0 y la condición es
TRUE
0101y Decrementa el registro CTR y después salta si CTR=0 y la condición es
TRUE
011zy Salta si la condición es TRUE
1z00y Decrementa el registro CTR y después salta si CTR≠0
1z01y Decrementa el registro CTR y después salta si CTR=0
1z1zz Salta siempre
z Es un bit que se reserva para el futuro, y que de momento debe ser siempre 0
y Indica si es más probable que el salto se realice o que no se realice, su uso se
explicará cuando veamos los pipeline en el Capítulo 3, en principio se puede dejar
siempre a 0
Pág 98
www.macprogramadores.org
0 5 6 10 11 15 16 30 31
16 BI BO BD AA LK
(Codificación de la instrucción)
0 31
¿Cumple Sí
condición? Extensión signo BD 0 0
No
0 31
+ Instruction Pointer (IP)
0 31
Siguiente instrucción
0 31
Dirección de salto
Instrucción Descripción
bc BO,BI,D (Branch Conditional) Si se cumplen las condiciones
dadas por BI y BO salta a la dirección calculada como
la suma de D más el valor actual del IP. Esta instrucción
tiene AA=0 y LK=0
bcl BO,BI,D (Branch Conditional then Link) Igual que bc, sólo que
en el registro LR se almacena la dirección de la
siguiente instrucción a la instrucción de salto. Esta
instrucción tiene AA=0 y LK=1
Por ejemplo, imaginemos que queremos hacer una operación sólo si el valor del
registro r2 es menor a 5, entonces haríamos:
Pág 99
www.macprogramadores.org
cmpwi r2,5
bc 12,0,fin
; Hacemos la operación que sea
······························
fin: ; Otras operaciones
······························
0 5 6 10 11 15 16 30 31
16 BI BO BD AA LK
(Codificación de la instrucción)
0 15 16 29 31
¿Cumple Sí
condición? 0000 0000 0000 0000 BD 0 0
No
0 31
Siguiente instrucción
0 31
Dirección de salto
Obsérvese que en este caso el campo BD sólo tiene 14 bits, con lo que, si tenemos en
cuenta los 2 ceros que siempre van al final (ya que las instrucciones se alinean a
direcciones múltiplos de cuatro) podemos direccionar sólo los 216=65.535 bytes lo
cual hace que esta instrucción se utilice muy poco en la práctica.
Pág 100
www.macprogramadores.org
Instrucción Descripción
bca BO,BI,D (Branch Conditional Absolute) Si se cumplen las
condiciones dadas por BI y BO salta a la dirección dada
en D. Esta instrucción tiene AA=0 y LK=0
bcla BO,BI,D (Branch Conditional then Link Absolute) Igual que
bca, sólo que en el registro LR se almacena la
dirección de la siguiente instrucción a la instrucción de
salto. Esta instrucción tiene AA=0 y LK=1
Con las instrucciones de salto que conocemos hasta ahora tenemos un problema si
queremos saltar a una dirección de memoria absoluta que este más allá de las
direcciones a las que podemos llegar con las instrucciones de salto absoluto que
hemos visto.
Para solucionar este problema existe otra instrucción en la que la dirección de salto se
guarda en un registro llamado CTR (CounT Register).
Una vez puesta la dirección a la que queremos saltar en el registro CTR podemos
saltar a esta dirección con las siguientes instrucciones de PowerPC:
Instrucción Descripción
bcctr BO,BI (Branch Conditional to CounT Register) Salta a la
dirección de memoria almacenada en el CTR. Esta
instrucción tiene LK=0
bcctrl BO,BI (Branch Conditional to CounT Register then Link)
Igual que bcctr sólo que almacena en el LR la
dirección de la siguiente instrucción a la instrucción de
salto. Esta instrucción tiene LK=1
Pág 101
www.macprogramadores.org
0 5 6 10 11 15 16 20 21 30 31
19 BI BO 0000 528 LK
(Codificación de la instrucción)
0 29
¿Cumple Sí
condición? CTR (CounT Register)
No
30 31
|| 00
0 31
Siguiente instrucción
0 31
Dirección de salto
Antes veíamos que las instrucciones de salto, antes de saltar, podían almacenar en el
registro LR (Link Register) la dirección de la siguiente instrucción a la instrucción de
salto. Esto es especialmente útil para hacer llamadas a subrutinas, ya que ahora
podemos retornar de esa llamada volviendo a la dirección que dejamos almacenada en
LR.
Queda por ver como se trata otro problema, que es el problema de que una llamada a
una subrutina llame a su vez a otra subrutina guardando esta también en LR la
dirección de retorno, y borrando la anterior dirección. Como explicaremos en el
apartado 8, la solución está en guardar el valor de LR en la pila antes de llamar a otra
función. Las instrucciones que vamos a ver ahora son las que nos permiten retornar de
la llamada.
Pág 102
www.macprogramadores.org
0 5 6 10 11 15 16 20 21 30 31
19 BI BO 0000 16 LK
(Codificación de la instrucción)
0 29
¿Cumple Sí
condición? LR (Link Register)
No
30 31
|| 00
0 31
Siguiente instrucción
0 31
Dirección de salto
Las instrucciones condicionales de salto al Link Register que tiene PowerPC son:
Instrucción Descripción
bclr BO,BI (Branch Conditional to Link Register) Si se cumplen las
condiciones dadas por BI y BO salta a la dirección dada
en el registro LR. Esta instrucción tiene LK=0
bclrl BO,BI (Branch Conditional to Link Register then Link) Igual
que bclr, sólo que en el registro LR se almacena la
dirección de la siguiente instrucción a la instrucción de
salto. Esta instrucción tiene LK=1
Por último comentar que además de cargar el LR usando instrucciones con el bit
LK=1, el LR es un registro especial (SPR), en concreto el SPR8 y para
leerlo/modificarlo usamos dos instrucciones que nos permiten acceder a los SPR que
como vimos en el apartado 5.2 son:
Pág 103
www.macprogramadores.org
6.2. Mnemonics
blr es un mnemonic que ya hemos usado muchas veces para retornar de una función
main().
Pág 104
www.macprogramadores.org
Estos mnemonics no reciben el operando BO, pero sí que tienen que recibir 2
operandos:
cmpwi cr5,r3,0
bf 22,fin
Que significa que no salte si CR5 tiene el bit de igualdad activo, es decir, si la
comparación anterior concluyó que r3 valía 0. Véase el apartado 6.1.3 para una mejor
descripción del operando BI.
Para simplificar la codificación del operando BI se han creado una serie de símbolos
tal como describe la siguiente tabla:
cmpwi cr5,r3,0
bf cr5+eq,fin
Donde queda mucho más claro poner cr5+eq que poner 22.
En caso de que para la comparación se usara el campo CR0 no haría falta poner cr0,
es decir, podemos hacer:
cmpwi cr0,r3,0
bf eq,fin
Pág 105
www.macprogramadores.org
Pág 106
www.macprogramadores.org
Abreviatura Descripción
lt Less Than
le Less than or Equal
eq EQual
ge Greater than or Equal
gt Greater Than
nl Not Less
ne Not Equal
ng Not Greater than
so Summary Overflow
ns Not Summary Overflow
un UNordered (para comparaciones en punto flotante)
nu Not Unordered (para comparaciones en punto flotante)
Obsérvese que las instrucciones que actualizan LR se escriben igual que las que no lo
actualizan, pero se las añade una l al final. La excepción esta en las instrucciones de
tipo bcla donde la l se pone antes de la última a. Por ejemplo, en vez de poner
bleal se pone blela.
Para indicar el campo de CR a comprobar se puede usar su valor numérico o bien uno
de los mnemonics definidos en la tabla del apartado 5.7.2.
Por ejemplo, si queremos hacer algo sólo cuando en r3 haya un número menor de 0
haríamos:
cmpwi cr2,r3,0
bge cr2,fin
; Hacer algo
············
fin: ; Otras cosas
············
CTR y LR son registros especiales (en concreto SPR9 y SPR8 respectivamente), que
comentamos que podíamos acceder a ellos con las instrucciones:
Pág 107
www.macprogramadores.org
Además existen mnemonics que nos permiten acceder a ellos más fácilmente:
Mnemonic Descripción
mfctr rD (Move From CTR) Copia el contenido de CTR en rD
mtctr rS (Move to CTR) Copia el contenido de rS en CTR
mflr rD (Move From LR) Copia el contendo de LR en rD
mtlr rS (Move To LR) Copia el contenido de rS en CTR
También tenemos mnemonics que nos permiten encender, apagar, copiar e invertir un
determinado bit del registro CR:
Las condicionales simple y doble son las sentencias if y if-else de C, las cuales
van a evaluar una expresión cuyo resultado se deposita en un campo de CR y en
función de este resultado se ejecuta una o otra parte.
if (a>0)
{
// Hacer esto
}
else if (a<0)
{
// Hacer lo otro
}
Pág 108
www.macprogramadores.org
else
{
// Hacer lo de mas allá
}
switch (x)
{
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
// Hacer algo
}
Pág 109
www.macprogramadores.org
El test de rango es especialmente útil cuando, como en el ejemplo anterior, todos los
valores en un determinado rango ejecutan el mismo código.
Obsérvese que cmpli comprueba tanto la condición r4<0 como r4>5 ya que, como
estamos haciendo una comparación lógica (si signo), si se cumpliera que r4<0
entonces r4 sería negativo y su primer bit sería 1, con lo que r4 sería considerado un
número muy grande.
Una tercera forma de hacer esta comparación es usando una tabla de salto, en la cual
tenemos guardadas las direcciones a las que hay que saltar para cada caso.
Pág 110
www.macprogramadores.org
switch (x)
{
case 0:
// Codigo del caso 0
case 1:
// Codigo del caso 1
case 2:
// Codigo del caso 2
case 3:
// Codigo del caso 3
case 4:
// Codigo del caso 4
case 5:
// Codigo del caso 5
·················
}
tabla contiene las direcciones a las que hay que saltar en cada caso, lo cual es
especialmente útil cuando todos los casos son valores consecutivos que saltan a
direcciones distintas.
Pág 111
www.macprogramadores.org
Para las instrucciones de salto en los bucles se han creado los siguientes mnemonics:
Equivale a
Condición de salto bc bca bclr
Decrementa CTR y salta si CTR≠0 bdnz bdnza bdnzlr
Decrementa CTR y salta si CTR≠0 y la condición es true bdnzt bdnzta bdnztlr
Decrementa CTR y salta si CTR≠0 y la condición es false bdnzf bdnzfa bdnzflr
Decrementa CTR y salta si CTR=0 bdz bdza bdzlr
Decrementa CTR y salta si CTR=0 y la condición es true bdzt bdzta bdztlr
Decrementa CTR y salta si CTR=0 y la condición es false bdzf bdzfa bdzflr
Equivale a
Condición de salto bc bca bclr
Decrementa CTR y salta si CTR≠0 bdnzl bdnzla bdnzlrl
Decrementa CTR y salta si CTR≠0 y la condición es true bdnztl bdnztla bdnztlrl
Decrementa CTR y salta si CTR≠0 y la condición es false bdnzfl bdnzfla bdnzflrl
Decrementa CTR y salta si CTR=0 bdzl bdzla bdzlrl
Decrementa CTR y salta si CTR=0 y la condición es true bdztl bdztla bdztlrl
Decrementa CTR y salta si CTR=0 y la condición es false bdzfl bdzfla bdzflrl
La siguiente tabla ayuda a recordar las abreviaturas usadas por estos mnemonics:
Abreviatura Descripción
t True
f False
d Decrement
z Zero
nz Not Zero
Todos estos mnemonics actúan sobre el registro CTR y sólo reciben como operando la
dirección a la que saltar, es decir, tienen la forma:
MNEMONIC ETIQUETA
A continuación vamos a poner ejemplos de como se usan estos mnemonics para cada
uno de los bucles de C.
Pág 112
www.macprogramadores.org
int acumulador = 0;
int contador = 1;
do
{
acululador += contador;
contador++;
}
while (contador<=100);
li r2,0 ; r2 es el acumulador
li r3,1 ; r3 es el contador
do: add r2,r2,r3 ; r2 += r3
add r3,r3,1 ; r3++
cmpwi r3,100
ble do ; r3<=100
fin:
li r2,0 ; r2 es el acumulador
li r3,1 ; r3 es el contador
li r4,100 ; Numero de repeticiones
mtctr r4 ; Carga el CTR
do: add r2,r2,r3 ; r2 += r3
add r3,r3,1 ; r3++
bdnz do ; Hasta que CTR llege a 0
fin:
Obsérvese que el contador se lleva en r3 y no se coge el valor del CTR, eso es así
porque el acceso al registro CTR es más lento que el acceso a un GPR, con lo que es
preferible llevar el contador en un registro aparte, aunque la condición de terminación
la pongamos en CTR.
El bucle anterior tiene la condición de salida al final, con lo que siempre se repite al
menos una vez, si movemos la comprobación al principio, ya podemos hacer un bucle
que se repita como mínimo cero veces.
Pág 113
www.macprogramadores.org
Como ejemplo vamos a hacer un bucle que cuente la longitud de una cadena de
caracteres, tal como hace la función strlen() de C.
for (inicialización;condición;actualización)
{
cuerpo
}
Inicialización
No
Actualización Condición Fin
Sí
Cuerpo
Pág 114
www.macprogramadores.org
Las sentencias break y continue que pudieran encontrarse en el cuerpo del bucle
pueden implementarse como un salto incondicional.
Como ejemplo vamos a hacer un programa que dado un número nos dice si es primo,
para ello hacemos un bucle que divida al número por todos sus divisores y si es
divisible por alguno de ellos entonces es que no es primo.
.data
SD: .comm esprimo, 4 ; Aqui se deposita si es primo
n: .long 13 ; Número que queremos ver si es
primo
.text
.globl _main
_main:
lis r2,ha16(SD) ; r2 apunta a la base del
; segmento de datos
lwz r3,lo16(n)(r2) ; r3 es el numero a comprobar
addi r4,r3,-2; ; Calculamos el contador para CTR
mtctr r4 ; Fijamos el CTR
; Inicialización
addi r4,r3,-1 ; r4 es el contador
; Condición. Acaba si llega a 1 el contador
; significando que es primo
bucle:
cmpwi r4,1
beq primo
; Cuerpo
; Dividimos y multiplicamos r3 por r4
; Si es el mismo número es que es divisible
divw r5,r3,r4
mullw r5,r5,r4
cmpw r5,r3
beq noprimo
;Actualización
addi r4,r4,-1
bdnz bucle
primo:
li r10,1
b fin
noprimo:
li r10,0
fin: stw r10,lo16(esprimo)(r2)
blr
Pág 115
www.macprogramadores.org
Podemos realizar operaciones lógicas con los bits (no los campos) del registro CR,
cuyo resultado podemos volver a guardar en otro campo de CR.
Instrucción Descripción
crand CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un and
binario con el bit de la posición CRBB y el
resultado se almacena en el bit CRBD
cror CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un or binario
con el bit de la posición CRBB y el resultado se
almacena en el bit CRBD
crxor CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un xor
binario con el bit de la posición CRBB y el
resultado se almacena en el bit CRBD
crnand CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un nand
binario con el bit de la posición CRBB y el
resultado se almacena en el bit CRBD
crnor CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un nor
binario con el bit de la posición CRBB y el
resultado se almacena en el bit CRBD
creqv CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un xor
binario con el bit de la posición CRBB y el
complemento a 1 del resultado se almacena en el
bit CRBD
crandc CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un and
binario con el bit de la posición CRBB y el
complemento a 1 del resultado se almacena en el
bit CRBD
crorc CRBD,CRBA,CRBB Al bit de la posición CRBA se le hace un or binario
con el bit de la posición CRBB y el complemento a
1 del resultado se almacena en el bit CRBD
mcrf CRD,CRS Los 4 bits del campo CRS se copian en el campo
CRD
Pág 116
www.macprogramadores.org
cmpwi cr2,rA,5
cmpwi cr3,rB,3
crand 17,9,13
bng cr4,fin
; Haz algo
·············
fin: ·············
Hay que tener en cuenta que crand opera a nivel de bit de CR, no a nivel de campo
de CR, con lo que tenemos que decirle que bits origen leer y en que bit destino
depositarlo.
Si las comparaciones las depositamos en los campos CR2 y CR3, el bit que indica si
se cumple la condición “mayor que” es el segundo de los 4 bits del campo, luego
tendremos que leer los bits 9 y 13. Para depositar el resultado en CR4 debemos
depositarlo también en el segundo bit de CR4 que es el bit 17.
Por último, bng comprueba si cr4 contiene la condición “mayor que” en cuyo caso
significa que cr2 y cr3 también cumplían la condición.
Pág 117
www.macprogramadores.org
7.1. Introducción
Respecto a los formatos de tipos de datos, PowerPC soporta sólo los tipos simple y
doble. El tipo doble extendido no lo soporta PowerPC directamente debiéndose
implementar el trabajo con números en este formato por software.
El procesador de PowerPC tiene como tipo de dato por defecto los números en
formato doble, lo cual significa que a no ser que se lo pidamos explícitamente todos
los cálculos y los resultados se obtienen sobre datos de tipo doble. Aun así el
procesador dispone de instrucciones para convertir entre representaciones simple y
doble, así como de operaciones que nos permiten trabajar con datos en formatos
simple.
Los registros FPR siempre trabajan con números en formato doble, aunque podemos
leer/almacenar en memoria tanto números en formato simple como en formato doble.
Las reglas que sigue PowerPC son las siguientes:
Pág 118
www.macprogramadores.org
Por otro lado, el sistema de punto flotante dispone de otros dos registros que permiten
modificar el funcionamiento de la unidad de punto flotante, así como obtener
resultados de esta, que son los registros FPSCR y CR.
FPSCR (Floating Point Status and Control Register) es un registro de 32 bits que
almacena el estado de la unidad de punto flotante de procesador. En el se indican
cosas como el modo de redondeo a utilizar, o el estado de las excepciones del
procesador.
El registro FPSCR (Floating Point Status and Control Register) se encuentra dividido
en campos de 4 bits llamados FPSCR0 hasta FPSCR7, y muchas de las operaciones
que trabajan con él operan en estos campos. La siguiente figura muestra los
principales campos del registro FPSCR y cual es su utilidad.
0 3 4 7 8 11 12 15 16 19 20 23 24 27 28 31
Bits de
redondeo
Flags de Flags de Flags de Códigos de Flags de
summary excepción excepción condición habilitación
exception inválida de excepción
La siguiente tabla describe detalladamente el propósito de cada uno de los bits del
registro FPSCR.
Pág 119
www.macprogramadores.org
Pág 120
www.macprogramadores.org
A los bits del registro de FPSCR podemos acceder a nivel de registro, a nivel de
campo o a nivel de bit individual.
La siguiente tabla resume las instrucciones de que dispone PowerPC para acceder a
los bits de registro FPSCR:
Pág 121
www.macprogramadores.org
queda indefinido.
mtfsf FM,fD (Move To FPSCR Fields) Los bits 32- Registro
mtfsf. FM,fD 63 del registro fD se copian al registro
FPSCR bajo el control de la máscara de
campos FM, la cual indica que campos
se deben copiar. FM puede tener hasta 8
bits de los cuales los activos indican los
campos a copiar.
mtcrfs CRFD,FS (Move To CR from FPSCR) El Campo
contenido del campo FS del registro
FPSCR se copia en el campo CRFD del
registro CR. Todos los bits de excepción
copiados (excepto FEX y VX) son
borrados en FPSCR
mtfsfi FD,UIMM (Move To FPSCR Field Immediate) El Campo
mtfsfi. CRFD,UIMM contenido de UIMM se deposita en el
campo FD
mtfsb0 BD (Move To FPSCR Bit 0) El bit de la Bit
mtfsb0. BD posición BD del registro FPSCR es
borrado. Los bits FEX y VX no pueden
borrarse explícitamente
mtfsb1 BD (Move To FPSCR Bit 1) El bit de la Bit
mtfsb1. BD posición BD del registro FPSCR es
encendido. Los bits FEX y VX no
pueden encenderse explícitamente
Las instrucciones que llevan punto (.) producen una actualización del registro CR.
El sistema de punto flotante del ensamblador del PowerPC dispone de los mismos
cinco flags de excepción que recomienda el estándar IEEE 754, sólo que algunos de
ellos están desdoblados en varios flags con el fin de poder precisar mejor la causa de
la excepción.
Pág 122
www.macprogramadores.org
No ¿Redondeo? Sí
FI<–0
FR<–0 FI<–1
FR<–1 FR<–0
Pág 123
www.macprogramadores.org
Para que se activen los flags de excepción debemos de habilitar los llamados flags de
habilitación de excepción, de los cuales hay uno para cada tipo principal, tal como
muestra la siguiente tabla:
Además de estos flags tenemos los flags de resumen (summary), los cuales se
activan cuando se produce cualquier excepción, estos flags son útiles ya que lo
primero que podemos hacer es comprobar estos flags viendo si ha habido algún
problema, y cuando se activan podemos llamar a una rutina que determine la causa
exacta del problema.
Los bits 16-19 indican el resultado de una comparación, la cual debe activar sólo uno
de los cuatro bits < (menor que), > (mayor que), = (igual) ó ? (sin relación de orden).
Los bits 16-19 combinados con el bit 15 (bit de clase) nos pueden servir para saber a
que clase pertenece el resultado de una instrucción de punto flotante (número
normalizado, número denormalizado, cero, NaN o infinito).
Pág 124
www.macprogramadores.org
Result Flags (Bits 15-19) Resultado para una Resultado para otra operación
C < > = ? comparación
0 0 0 0 1 Sin relación de orden No aplicable
0 0 0 1 0 == (Igual) +0
0 0 1 0 0 > (Mayor que) Número normalizado positivo
0 0 1 0 1 No aplicable +∞
0 1 0 0 0 < (Menor que) Número normalizado negativo
0 1 0 0 1 No aplicable -∞
1 0 0 0 1 Sin relación de orden Quiet NaN
1 0 0 1 0 == (Igual) -0
1 0 1 0 0 > (Mayor que) Número denormalizado positivo
1 1 0 0 0 < (Menor que) Número denormalizado negativo
Por ejemplo, para saber de qué tipo es el número que obtenemos como resultado de
una suma en punto flotante podríamos hacer un programa tal que así:
Pág 125
www.macprogramadores.org
7.4. El registro CR
La siguiente tabla muestra cuáles son los bits del registro FPSCR que se copian al
registro CR en caso de usar instrucciones con punto.
Bit Descripción
4 Contiene el valor del bit FX del registro FPSCR que indica que alguna
excepción se ha producido
5 Contiene el valor del bit FEX del registro FPSCR que indica que alguna
excepción para la que su flag de habilitación de excepción estaba encendido
se ha producido
6 Contiene el valor del bit VX del registro FPSCR que indica que alguna
invalid exception se ha producido
7 Contiene el valor del bit OX del registro FPSCR que indica que se ha
producido un desbordamiento
El valor del campo CR1 del registro CR se puede consultar tras ejecutar una
instrucción para ver si se ha producido una excepción de la siguiente manera:
fadd. f0,f1,f2
bt 5,excepcion ; Si FEX está activo
; No ha habido excepcion
·············
·············
excepcion:
mcrfs 2,1 ; Copia FPSCR[4-7] a CR2
bt 6,invalid ; Miramos los bits de CR para ver que
bt 7,overflow ; tipo de excepción se ha producido
bt 8,underflow
bt 9,divbyzero
bt 10,inexact
Pág 126
www.macprogramadores.org
invalid:
mcrfs 2,2 ; Copia FPSCR[8-11] a CR2
mcrfs 3,3 ; Copia FPSCR[12-15] a CR3
mcrfs 4,5 ; Copia FPSCR[20-23] a CR4
; Ahora podemos saber el tipo exacto de invalid
; operation en base al flag de excepción invalid
; operation que este activo
overflow:
; Se ha producido un overflow
underflow:
; Se ha producido un underflow
divbyzero:
; Se ha intentado dividr entre cero
inexact:
; Redondeo inexacto
PowerPC permite tanto usar flags de habilitación de traps, como usar flags de
habilitación de excepción, será el diseñador del sistema operativo quien deba tomar
esta decisión.
La siguiente tabla describe los valores que pueden tomar los flags FE0 y FE1:
Pág 127
www.macprogramadores.org
Antes de que PowerPC pueda operar con un dato en punto flotante situado en
memoria, debe de cargarlo en alguno de los FPR. Análogamente PowerPC puede
depositar los datos en memoria, una vez que acaba de trabajar con ellos.
Como se explicó en el aparatado 7.2, los registros FPR siempre trabajan con números
en formato doble, aunque podemos leer/almacenar en memoria tanto números en
formato simple como en formato doble.
Las instrucciones de acceso a memoria para punto flotante (al igual que pasaba con
las de acceso a memoria con enteros) se pueden dividir en dos tipos:
Instrucción Descripción
lfs fD,d(rA) (Load Floating-point Single) El word en la dirección de
memoria d(rA) se interpreta como un número en punto
flotante de precisión simple, y se carga en fD convertido a
punto flotante de precisión doble
lfsu fD,d(rA) (Load Floating-point Single with Update) Igual a lfs, sólo
que rA se actualiza con el valor de d(rA) después de leer
la memoria
lfd fD,d(rA) (Load Floating-point Double) El doble-word en la dirección
de memoria d(rA) se carga en fD
lfdu fD,d(rA) (Load Floating-point Double with Update) Igual a lfd,
sólo que rA se actualiza con el valor de d(rA) después de
leer la memoria
Instrucción Descripción
sfs fD,d(rA) (Store Floating-point Single) El contenido de fD se
convierte a precisión simple y se guarda en el word de la
dirección de memoria apuntada por d(rA)
Pág 128
www.macprogramadores.org
Instrucción Descripción
lfsx fD,rA,rB (Load Floating-point Single indeXed) El word en la
dirección de memoria (rA|0)+rB se interpreta como un
número en punto flotante de precisión simple, y se carga en
fD convertido a punto flotante de precisión doble
lfsux fD,rA,rB (Load Floating-point Single with Update indeXed) Igual a
lfsx, sólo que rA se actualiza con el valor de
(rA|0)+rB después de leer la memoria
lfdx fD,rA,rB (Load Floating-point Double indeXed) El doble-word en la
dirección de memoria (rA|0)+rB se carga en fD
lfdux fD,rA,rB (Load Floating-point Double with Update indeXed) Igual a
lfdx, sólo que rA se actualiza con el valor de
(rA|0)+rB después de leer la memoria
Instrucción Descripción
sfsx fD,rA,rB (Store Floating-point Single indeXed) El contenido de fD se
convierte a precisión simple y se guarda en el word de la
dirección de memoria apuntada por (rA|0)+rB
sfsux fD,rA,rB (Store Floating-point Single with Update indeXed) Igual a
sfsx, sólo que rA se actualiza con el valor de
(rA|0)+rB después de escribir la memoria
sfdx fD,rA,rB (Store Floating-point Double indeXed) El contenido de fD
se guarda en memoria, en el doble-word apuntado por
(rA|0)+rB
sfdux fD,rA,rB (Store Floating-point Double with Update indeXed) Igual a
sfdx, sólo que rA se actualiza con el valor de
(rA|0)+rB después de escribir la memoria
Pág 129
www.macprogramadores.org
PowerPC dispone de las operaciones aritméticas que propone el estándar IEEE 754:
o Suma
o Resta
o Multiplicación
o Multiplicación-suma
o División
o Raíz cuadrada (opcional)
o Redondeo a entero (opcional)
Las dos últimas son operaciones opcionales, lo que significa que no todos los micros
las poseen, debemos de consultar el manual del microprocesador para ver si la
soporta.
Todas ellas disponen de una versión con punto (.) que actualiza el registro CR.
Casi todas las operaciones se proporcionan tanto para precisión simple como para
precisión doble. Las instrucciones de precisión simple se diferencian porque tienen
una s al final de su nombre.
; f1 = 7.0
; f2 = 2.0
fdiv f0,f1,f2 ; f3 = 3.5
La operación fsel se utiliza para conseguir el mismo efecto que el operador ?: del
lenguaje C, donde asignamos un valor u otro a fD en función de una condición sin
hacer saltos, los cuales como explicaremos cuando veamos los pipelines en el
Capítulo 3, degradan más el rendimiento del programa.
Pág 131
www.macprogramadores.org
IEEE 754 requiere que el sistema de numeración en punto flotante disponga de las
siguientes operaciones de conversión:
Instrucción Descripción
frsp fD,fS (Floating Round to Single Precision) Redondea el dato
frsp. fD,fS almacenado en fS al número más cercano que pueda ser
representado en formato simple, y los guarda en fD (en
formato doble)
fctiw fD,fS (Floating Convert To Integer Word) El número
fctiw. fD,fS almacenado en fD lo convierte a entero de 32 bits, usando
el modo de redondeo activo, y lo deposita en los bits
fD[32-63], quedando los bits fD[0-31] indefinidos.
fctiwz fD,fS (Floating Convert To Integer Word round toward Zero) El
fctiwz. fD,fS número almacenado en fD lo convierte a entero de 32 bits
eliminando los decimales, y lo deposita en los bits
fD[32-63], quedando los bits fD[0-31] indefinidos.
Pág 132
www.macprogramadores.org
Bit Significado
0 fA < fB
1 fA > fB
2 fA = fB
3 fA ? fB (unordered)
Además de los bits del campo CR que especifiquemos, los bits FPSCR[16-19]
también se activan convenientemente.
Instrucción Descripción
fcmpo CRFD,fA,fB (Floating CoMPare Ordered) Compara fA con fB,
produciendo una VXVC si alguno de los operandos es un
NaN. El resultado se deposita en el campo de CR dado por
CRFD.
fcmpu CRFD,fA,fB (Floating CoMPare Unordered) Compara fA con fB,
pudiendo ser alguno de los operandos en un NaN. El
resultado se deposita en el campo de CR dado por CRFD.
La diferencia entre estas dos instrucciones es que fcmpo se usa cuando no esperamos
encontrar un NaN, mientras que fcmpu se usa cuando queremos contemplar esta
posibilidad.
Pág 133
www.macprogramadores.org
Estas instrucciones no alteran el registro FPSCR, y sólo las que tienen punto (.)
alteran el registro CR.
Instrucción Descripción
fmr fD,fS (Floating Move Register) Copia el contenido de fS a fD
fmr. fD,fS
fneg fD,fS (Floating Negate) Copia el contenido de fS a fD y cambia
fneg. fD,fS el signo durante la copia
fabs fD,fS (Floating ABSolute value) Copia el contenido de fS a fD y
fabs. fD,fS pone el bit de signo a 0 durante la copia
fnabs fD,fS (Floating Negate ABSolute value) Copia el contenido de
fnabs. fD,fS fS a fD y pone el bit de signo a 1 durante la copia
Pág 134
www.macprogramadores.org
En esta sección vamos a comentar varias técnicas que permiten incrustar instrucciones
ensamblador dentro de un programa C. Para ello tenemos la directiva asm, un
ejemplo de su uso sería el siguiente:
#include <stdio.h>
int main () {
asm ( "addis r2,r3,1 \n sub r5,r6,r7" );
return 0;
}
Cuando el compilador de C encuentra la directiva asm, el texto que está dentro de las
comillas se pasa tal cual al ensamblador, para que lo ensamble junto con el resto del
programa. Obsérvese que las instrucciones se separan por \n, que es la forma de
indicar un retorno de carro.
Si ahora hiciésemos:
$ cc -S cyasm.c
Cuando nos refiramos a las variables C desde ensamblador tendremos que usar este
nombre. Por ejemplo:
#include <stdio.h>
int G=4;
int main () {
asm ( "addis r2,0,ha16(_G) \n lwz r3,lo16(_G)(r2) \n”
"addi r3,r3,1 \n stw r3,lo16(_G)(r2)" );
printf("La variable incrementada es %i",G);
return 0;
}
Pág 135
www.macprogramadores.org
A las variables locales no se les asigna nombre, con lo que tenemos que usar otras
técnicas para acceder a ellas desde ensamblador, como veremos en breve.
Los nombres que usa C++ para estas funciones son _suma__Fii y _suma__Fiii
respectivamente.
Para poder saber el nombre que da a los símbolos C++ podemos usar el comando nm
(Name Mangling), que recibe como argumento un fichero .o, o un fichero ejecutable
(con información de depuración), y nos muestra los símbolos que contiene:
$ nm cyasm.o
0000007c D _G
000000a0 s ___FRAME_BEGIN__
00000000 T _main
U _printf
U _suma__Fii
U _suma__Fiii
U dyld_stub_binding_helper
Pág 136
www.macprogramadores.org
#include <stdio.h>
int main () {
int dividendo=14;
int divisor=3;
int cociente;
asm ( "divw %0,%1,%2" : "=r" (cociente)
: "r" (dividendo) , "r" (divisor));
printf("El resultado de la división es %i",cociente);
return 0;
}
Dentro de la instrucción, a los operandos nos referimos usando los nombres %0 a %9,
con lo que el máximo número de operandos está limitado a 10.
Cada operando lleva asociadas unas constraints que indican el tipo del operando
según la siguiente tabla:
Constraint Significado
r Registro GPR
b Registro GPR distinto de r0
f Registro FPR
m Referencia a memoria
i Operando inmediato entero. Son literales cuyo valor es conocido en
tiempo de compilación
F Operando inmediato en punto flotante de doble precisión. Son
literales cuyo valor es conocido en tiempo de compilación
X Se acepta un operando de cualquier tipo: registro, dirección de
memoria o operando inmediato.
Pág 137
www.macprogramadores.org
Modificador Significado
= Operando de sólo escritura. No se garantiza que contenga el valor
previo de la variable
+ Operando de lectura/escritura. Antes de usarse va a contener el valor
de la variable o expresión, y después de ejecutarse las instrucciones
ensamblador su valor se guardará apropiadamente
& No asignar el mismo registro al operando de entrada y de salida
addi rD,(rA|0),SIMM
addi r3,r0,1
En este caso debemos de usar la constraint b, que significa usar uno de los registros
de r1 a r31. Con lo que el problema no se producirá. Es decir la forma correcta de
codificar la instrucción anterior es:
La r del primer operando no hace falta cambiarla por b, ya que este operando puede
usar al registro r0, es el segundo operando el que no puede.
Pág 138
www.macprogramadores.org
lis r9,r31,ha16(_N)
addi r9,lo16(_N)(r9)
lwz r3,0(r9)
Los operandos marcados con la constraint = son operandos de sólo salida y deben de
usarse sólo para escritura, es decir, nunca debe usarse un operando para
lectura/escritura.
#include <stdio.h>
int N=4;
int main () {
asm ( " addi %0,%0,1 " : "=r" (N) );
printf("N incrementada vale %i",N);
return 0;
}
#include <stdio.h>
int N=4;
int main () {
asm ( " addi %0,%0,1 " : "+r" (N) );
printf("N incrementada vale %i",N);
return 0;
}
Otra forma, más compleja pero igual de válida, de resolver este problema es declarar
dos operandos, uno de lectura y otro de escritura. La conexión entre estos debe de
expresarse con constraints que indican que ambos deben de estar en el mismo registro
cuando la instrucción se ejecute.
#include <stdio.h>
int N=4;
Pág 139
www.macprogramadores.org
int main () {
asm ( " addi %0,%1,1 " : "=r" (N) :"0" (N) );
printf("N incrementada vale %i",N);
return 0;
}
Sólo este tipo de constraints garantizan que un operando esté en el mismo lugar que
otro, el mero hecho de que dos operandos usen la misma expresión C (N en nuestro
caso), no es suficiente para garantizar que nos refiramos al mismo registro en el
código ensamblador. Es decir, el siguiente programa podría no asignar el mismo
registro a %0 que a %1:
Otra cosa importante es que podemos evitar los efectos laterales que se producirían si
el programa C estuviese usando uno de los registros que usamos desde ensamblador.
Por ejemplo, esta instrucción daría problemas si el registro r3 estuviese siendo
usados por el programa C.
Para evitar esto, podemos avisar al compilador de que las instrucción modifica el
registro r3, usando la llamada lista de registros modificados, que se pone en un
tercer campo separada por dos puntos:
Aquí podemos proteger cuantos registros sea necesario poniendo su nombre entre
comillas y separándolos por coma.
Pág 140
www.macprogramadores.org
Por último debemos comentar que la directiva asm supone que las instrucciones que
estamos ejecutando no tienen más efectos laterales que el de modificar los operandos
de salida. Esto no significa que no podamos usar instrucciones con efectos laterales,
sino que debemos tener cuidado con las optimizaciones que pudiera hacer el
compilador, ya que si los operandos de salida no se usan en ningún punto del
programa el optimizador del compilador podría eliminar nuestras instrucciones
ensamblador. También podría aplicar otras optimizaciones como sacarlas fuera de un
bucle, remplazar dos instrucciones que calculan una subexpresión por una sola
guardando el operando de salida en un registro y ejecutar la instrucción una sola vez.
#include <stdio.h>
int main () {
int dividendo=14;
int divisor=3;
int cociente;
asm ( "divw %[c],%[n],%[d]"
: [c] "=r" (cociente)
: [n] "r" (dividendo)
, [d] "r" (divisor));
printf("El resultado de la división es %i",cociente);
return 0;
}
Los nombres simbólicos que damos a los operandos no tienen porque coincidir con
los nombres de las variables C a las que los asociamos.
Pág 141
www.macprogramadores.org
9. Llamada a funciones
Vamos a ver ahora cual es el mecanismo de llamada a funciones en Mac OS X. Esto
nos va a permitir poder llamar a funciones escritas en otros lenguajes, como p.e. C,
desde ensamblador, o viceversa, poder llamar a funciones escritas en ensamblador
desde C.
Además de estos tipos la siguiente tabla muestra los tipos de datos para AltiVec.
Pág 143
www.macprogramadores.org
Pág 144
www.macprogramadores.org
Cada vez que una función llama a otra se crea en la pila un nuevo frame, el cual
almacena toda la información que necesita el procedimiento para sus autogestión.
Frame
Variables locales
Callee
Área de parámetros
Área de enlace
GPR1
GPR1 en todo momento apunta a la cima de la pila con la que estamos trabajando.
El frame esta dividido en las cuatro áreas que vamos a comentar a continuación:
Pág 145
www.macprogramadores.org
Como un procedimiento puede llamar a varios procedimientos, este área deberá tener
un tamaño suficiente como para acoger la lista de parámetros más larga de todos los
procedimientos a los que vaya a llamar.
A este área se la considera volátil en el sentido de que una vez que llamamos a un
procedimiento, este si quiere puede modificar el valor de los parámetros aquí
depositados, esto se hace, por ejemplo, cuando una función como parte de un cálculo
que esta llevando a cabo, modifica el valor de sus parámetros, depositando en ellos
valores intermedios que necesita para calcular un valor final.
El área de enlace está formado por 3 palabras y tiene un offset relativo a la posición
del puntero a pila antes de llamar al procedimiento.
Los valores de este área lo fija en parte el caller (al que pertenece el área de enlace) y
en parte el callee, en cada llamada que le hagamos. En concreto aquí encontramos:
o Offset 0: Back chain. El caller (el dueño del área) guarda aquí el valor del
puntero a pila antes de que el callee lo decremente para crear el nuevo frame.
o Offset 4: El callee guarda aquí el valor del registro CR. Este valor sólo tiene
que guardarlo el callee si modifica algún campo no volátil de CR (CR2-CR4
son los no volátiles) durante su ejecución.
o Offset 8: El callee guarda aquí la dirección de retorno al caller, es decir, el
valor del registro LR. Este valor lo guarda el callee sólo si el callee modifica el
valor de este registro (llama a otro procedimiento), sino no hace falta
guardarlo.
Obsérvese que el área de enlace está en una posición fija que el callee puede conocer
(a un determinado offset de la posición del puntero a la cima del frame del caller).
Esto es necesario para que el callee pueda acceder a la información del área de enlace,
así como al área de parámetros, que está inmediatamente después del área de enlace.
A estos datos debe de acceder el callee antes de decrementar el puntero a pila (crear
su frame), es decir, el callee debe recoger los parámetros y fijar los valores del área de
enlace, antes de crear su frame.
Pág 146
www.macprogramadores.org
Vamos a comentar qué acciones son las que se realizan en el prólogo y epílogo de la
llamada a una función.
Antes de nada debemos comentar que el orden concreto en que se ejecutan las
acciones del prólogo y epílogo, no vienen dados por las especificaciones de Mac OS
X, sino que la especificaciones se limitan a decir que acciones hay que llevar a cabo y
no en que orden deben de ejecutarse.
stwu r1,-tamanoFrame(r1)
Pág 147
www.macprogramadores.org
En este ejemplo suponemos que no existen variables locales ni hay que guardar
registros no volátiles luego necesitamos crear un frame de 12 bytes para el área de
enlace, pero como la especificación dice que los frames siempre deben de tener un
tamaño múltiplo de 16, creamos un frame de 16 bytes con un padding de 4 bytes.
Pág 148
www.macprogramadores.org
Lo primero que hace el epílogo es destruir el frame del callee recuperando el valor de
r1, que tiene almacenado en el área de enlace del callee, y después recupera los
valores de CR y LR para finalmente retornar.
Por último vamos a ver que realmente el compilador cc de Mac OS X actúa como
aquí hemos explicado. Para ello vamos a crear un fichero llamado llamadas.c así:
void funcion_b(void)
{
}
void funcion_a()
{
funcion_b();
}
int main ()
{
funcion_a();
return 0;
}
$ cc -S llamadas.s
Usando la versión 2.95.2 del compilador obtenemos una salida como esta (la cual
puede variar ligeramente en otra versión del compilador):
.text
.align 2
.globl _funcion_b
_funcion_b:
; Prologo
stmw r30,-8(r1) ; Guarda r30 y r31 en su área de
; registro guardados
stwu r1,-48(r1) ; Crea su frame de 48 bytes
; (3*16 bytes)
mr r30,r1 ; Pone el puntero a pila en r30
L6:
; Epílogo
lwz r1,0(r1) ; Recoge el puntero a pila del
; área de enlace del caller
lmw r30,-8(r1) ; Recupera los valores de r30 y r31
blr ; Retorna
.align 2
.globl _funcion_a
_funcion_a:
Pág 149
www.macprogramadores.org
; Prólogo
mflr r0 ; Recoge en r0 el LR
stmw r30,-8(r1) ; Guarda r30 y r31 en su área de
; registro guardados
stw r0,8(r1) ; Guarda el LR en el área de enlace
; del caller
stwu r1,-80(r1) ; Crea su frame de 80 bytes
; (5*16 bytes)
mr r30,r1 ; Pone el puntero a pila en r30
; Llamada
bl _funcion_b
L7:
; Epílogo
lwz r1,0(r1) ; Recoge el puntero a pila del
; área de enlace del caller
lwz r0,8(r1) ; Recoge el LR del área de enlace
; del caller
mtlr r0 ; Fija la dirección de retorno
lmw r30,-8(r1) ; Recupera los valores de r30 y r31
blr ; Retorna
.align 2
.globl _main
_main:
; Prólogo
mflr r0
stmw r30,-8(r1) ; Guarda r30 y r31 en su área de
; registros guardados
stw r0,8(r1) ; Guarda LR en el área de enlace
; del caller
stwu r1,-80(r1) ; Crea su frame de 80 bytes
; (5*16 bytes)
mr r30,r1 ; Pone el puntero a pila en r30
; Llamada
bl _funcion_a
; Epílogo
li r3,0 ; Pone un 0 en el retorno de la
main()
b L8
L8:
lwz r1,0(r1) ; Recupera el puntero a pila del
; área de enlace de su caller
lwz r0,8(r1) ; Recupera el LR del área de enlace
; de su caller
mtlr r0
lmw r30,-8(r1) ; Restaura los registros r30 y r31
blr ; Retorna
Pág 150
www.macprogramadores.org
cc -S -O3 llamadas.c
Obteniendo:
.text
.align 2
.globl _funcion_b
_funcion_b:
blr
.align 2
.globl _funcion_a
_funcion_a:
b _funcion_b
.align 2
.globl _main
_main:
mflr r0
stw r0,8(r1)
stwu r1,-64(r1)
bl _funcion_a
li r3,0
lwz r0,72(r1)
la r1,64(r1)
mtlr r0
blr
Que como se puede apreciar a simple vista es un programa mucho más pequeño y
optimizado.
Pág 151
www.macprogramadores.org
Como el caller puede llamar a varias funciones durante su ejecución, el tamaño del
área de parámetros del caller debe ser el mayor de los tamaños que va a necesitar para
llamar a cada una de las funciones que llama (o puede llamar). Este es un tamaño que
siempre se puede saber en tiempo de compilación mirando el prototipo de cada una de
las funciones que llama.
Pág 152
www.macprogramadores.org
Para ver como se colocan estos bytes en el área de parámetros, primero convertimos
los parámetros en una estructura tal que así:
struct Parametros
{
int pi1;
float pf2; +44
double pd3; ps9
+40
short ps4; pf8
double pd5; +36
ps7
char pc6; +32
short ps7; pc6
+28
float pf8;
short ps9; pd5
}; +20
ps4
+16
Esta estructura sirve como plantilla para
construir el área de parámetros de la pila. pd3
El elemento que acaba en la dirección de +8
memoria más baja es pi1, y a partir de pf2
+4
ahi van ocupando direcciones de pi1
0
memoria positivas a lo largo del área de
Zona
memoria, respetando las reglas de Vacia
padding que hemos dado. Luego la
organización exacta de los parámetros
sería la de la figura.
“Las 8 primeras palabras de los parámetros no se pasan en la pila, sino que se usan
los registros para pasar los parámetros”.
Esta optimización es muy importante porque la mayoría de las funciones tienen pocos
parámetros y así evitamos tener que acceder a memoria para hacer el paso de
parámetros.
Pág 153
www.macprogramadores.org
Otra peculiaridad es que si los parámetros son de tipo float o double se pasan en
los registros FPR1 a FPR13, aunque los registros que hubieran ocupado si los
hubiéramos guardado en GPRs quedan reservados, pero sin contener el valor, es decir,
no se pueden usar para pasar parámetros de tipos escalares. Esta regla en principio no
tiene ninguna utilidad práctica, y sólo da lugar a un desperdicio, pero la regla se
mantiene por compatibilidad con otros sistemas programables en PowerPC, como
puedan ser AIX de IBM.
Por último respecto a los parámetros que sean vectores (AltiVec) estos se pasan en los
registros v2 a v13, y en el caso de los vectores, su presencia no afecta a los registros
GPR ni FPR. El caller no debe reservar espacio en el área de parámetros de la pila a
no ser que su número exceda el número de parámetros que podemos pasar en los
registros de AltiVec.
En el ejemplo anterior tenemos que cambiar la distribución de los parámetros para que
se pasen de acuerdo a la regla que hemos visto, con lo que ahora la distribución de
parámetros quedaría como muestra la siguiente figura.
Vemos que los parámetros pi1 a pc6 no se guardan realmente el memoria sino en
registros. También observamos como parámetros como pf2, pd3 ó pd5 producen un
consumo de GPRs a pesar de que su valor no se almacena en estos registros, sino en
FPRs.
Pág 154
www.macprogramadores.org
+44
ps9
+40
pf8 FPR4
+36
ps7
+32
pc6 GPR10
+28
GPR9
pd5 FPR3
GPR8
+20
ps4 GPR7
+16
GPR6
pd3 FPR2
GPR5
+8
pf2 GPR4 FPR1
+4
pi1 GPR3
0
Zona
Vacia
double s = suma(3,4.5,6.7,3.2);
Pág 155
www.macprogramadores.org
double* p = (double*)(&n+1);
Cuando una función retorna un valor, la forma de devolverlo depende del tipo del
valor retornado:
9.6. Ejemplo
Para acabar este apartado vamos a hacer un ejemplo de como se implementaría una
función recursiva que calcula el factorial de un número en ensamblador.
#include <stdio.h>
int main()
{
int n=100002;
int sol = factorial(n);
printf("El factorial de %i es %i",n,sol);
Pág 156
www.macprogramadores.org
return 0;
}
.set tamanoFrame,16
.text
.align 2
.globl _factorial
_factorial:
; Prólogo
mflr r0 ; Recoge en r0 el LR
stw r0,8(r1) ; Guarda el LR en el área
; de enlace del caller
stwu r1,-tamanoFrame(r1) ; Crea su frame
; Cuerpo de la función
cmpwi r3,1 ; Si (n>1)
bgt sigue
li r3,1 ; Retorna 1
b epilogo
sigue:
stw r3,tamanoFrame+12(r1) ; Guarda r3 en el área de
; parámetros del padre
; para poder hacer otra
; llamada recursiva
subi r3,r3,1 ; Decrementa n
bl _factorial ; Llama a factorial
mr r4,r3 ; Recoge el retorno y
; lo guarda en r4
lwz r3,tamanoFrame+12(r1) ; Recupera el parámetro
; del área de enlace
; del padre
mulhw r5,r3,r4 ; Calcula n*factorial(n-1)
cmpwi r5,0 ; Si hay acarreo
bne acarreo ; devolvemos 0
mullw r3,r3,r4 ; r3 = n*factorial(n-1)
b epilogo
acarreo:
li r3,0
epilogo:
; Epílogo
lwz r1,0(r1) ; Recoge el puntero a pila
; de su área de enlace
; con lo que destruye
; el frame
lwz r0,8(r1) ; Recoge el LR del área de
; enlace del caller
mtlr r0 ; Fija la dirección de
; retorno
blr ; Retorna
Pág 157
www.macprogramadores.org
Como la función se vuelve a llamar a si misma tiene que guardar el parámetro pasado
en el registro r3 en el área de parámetros del caller antes de llamarse recursivamente,
y cuando retorna de la llamada vuelve a ir a memoria a recuperar el parámetro
guardado para poder calcular n*factorial(n-1).
Pág 158
www.macprogramadores.org
Aunque, como se dijo en el prólogo, este libro presupone que el lector está
familiarizado con la representación binaria y su aritmética, un repaso podría ayudar a
un lector que llevase algún tiempo sin tocar este tema.
ai bi ci si ci+1
0 0 0 0 0
0 1 0 1 0
1 0 0 1 0
1 1 0 0 1
0 0 1 1 0
0 1 1 0 1
1 0 1 0 1
1 1 1 1 1
Por ejemplo para sumar 23 y 56, primero los pasamos a binario, y después aplicando
la regla anterior tenemos:
4
En castellano muchas veces se les llama coma fija y coma flotante, ya que en
castellano el delimitador de la parte fraccionaria es la coma y no el punto
Pág 159
www.macprogramadores.org
Vemos que en el quinto bit empezando por la derecha ha habido acarreo, al igual que
en el sexto bit, y este se ha llevado hasta el séptimo bit.
El semisumador toma dos bits ai y bi como entrada, y produce como salida un bit de
suma si y un bit de acarreo ci+1. Como ecuaciones lógicas:
si=ai*(~bi) + (~ai)*bi
ci+1=ai*bi.
Sumador
Sumador
completo
Sumador
completo ······· Sumador
completo completo
El bit menos significativo entra por el sumador más a la derecha. Vemos que la salida
del i-esimo sumador alimenta el acarreo del sumador (i+1)-esimo. Como el acarreo de
orden inferior es 0, el primer sumador basta con que sea un semisumador. Sin
embargo, más tarde veremos que inicializar el bit de acarreo de orden inferior a 1, es
útil para realizar la resta.
Pág 160
www.macprogramadores.org
La resta actúa de forma parecida a la suma, sólo que ahora cuando el bit del minuendo
es 0 y el bit de sustraendo es 1, en vez de “pasar” un bit al dígito de mayor peso, se le
“pide” un bit.
ai bi ci si ci+1
0 0 0 0 0
0 1 0 1 1
1 0 0 1 0
1 1 0 0 0
0 0 1 1 1
0 1 1 0 1
1 0 1 0 0
1 1 1 1 1
Por ejemplo para calcular 56-23, primero los pasamos a binario, y después aplicando
la regla anterior tenemos:
Ya en el bit más a la derecha vemos que hemos tenido que pedir al nivel superior, y el
acarreo de petición se ha mantenido hasta el cuarto bit empezando a contar por la
derecha.
Lo más curioso de todo es que si quisiéramos hacer un restador hardware, bastaría con
aprovechar el circuito del sumador, invirtiendo las entradas del sustraendo y poniendo
a 1 en bit de acarreo de más a la derecha como muestra la siguiente figura:
Sumador
Sumador
completo
Sumador
completo ······· Sumador
completo completo
Pág 161
www.macprogramadores.org
(minuendo) 14 1111
(sustraendo) 7 - 0111 -
ææ æææ
(diferencia) 7 0111
1110 (minuendo)
+ 1001 (sustraendo en complemento a 2)
ææææ
10111
3. Descartar el overflow
1110 (minuendo)
+ 1001 (sustraendo en complemento a 2)
ææææ
10111
La razón por la cual el circuito anterior funciona como restador puede entenderse
mejor ahora. Los cuatro inversores convierten el sustraendo binario en su forma en
complemento a 1 y el acarreo con su bit de entrada puesto a 1 convierte el sustraendo
en complemento a 2.
Sumador
Sumador
completo
Sumador
completo ······· Sumador
completo completo
00100010 (34)
x 01000011 (67)
————————
00100010
00100010
00000000
00000000
00000000
00000000
00100010
00000000
———————————————
000100011100110
El multiplicador hardware más sencillo opera sobre dos números sin signo
produciendo cada vez un bit como muestra la siguiente figura. Los números que
vamos a multiplicar son an-1...a1a0 y bn-1...b1b0 los cuales se colocan en los
registros A y B (con el bit menos significativo a la derecha, como es habitual). El
registro P se pone inicialmente a cero:
Desplazamiento
P A
Pág 163
www.macprogramadores.org
Después de n pasos el producto aparece en los registros P:A, conteniendo A los bits
menos significativos.
Este algoritmo se puede implementar fácilmente en hardware usando tres registros tal
como muestra el siguiente diagrama:
Desplazamiento
P A
Para calcular la división a/b el algoritmo más sencillo procede de la siguiente forma:
Pág 164
www.macprogramadores.org
Inicialmente tendremos:
Pasos 1-9:
Como los 9 bits más a la izquierda de A son ceros, P recibirá 9 bits 0 y P-B siempre
será negativo con lo que el programa se limitará a desplazar los ceros a la izquierda
obteniendo:
Las últimas 7 repeticiones son las que van depositando en A el cociente de la división,
y en P el resto.
10º paso:
Desplazamos
11º paso:
Desplazamos
12º paso:
Desplazamos
Pág 165
www.macprogramadores.org
13º paso:
Desplazamos
14º paso:
Desplazamos
15º paso:
Desplazamos
16º paso:
Desplazamos
Pág 166
www.macprogramadores.org
00011011 (27)
11100100 (complemento a 1)
1 +
————————
11100101 (-27)
11100101 (-27)
00011010 (complemento a 1)
1 +
————————
00011011 (27)
La gran ventaja que tiene representar los números en complemento a 2 es que para
hacer una suma basta con sumarlos como si fueran números sin signo. Por ejemplo si
queremos calcular 34+(-17), primero los representamos en complemento a 2 y luego
sumamos.
Pág 167
www.macprogramadores.org
00010001 (17)
11101110 (complemento a 1)
1 +
————————
11101111 (-17)
00100010 (34)
11101111 (-17) +
————————
100010001 (17)
Vemos que la suma produce un acarreo en el bit de orden superior que simplemente
se descarta.
00100010 (+34)
11101111 (-17) -
————————
100110011 (+51)
(-80)+(-120) tendremos:
10110000 (-80)
10001000 (-120) +
————————
100111000 (56)
Ahora el acarreo de entrada del bit más significativo es 0, mientras que el acarreo de
salida del bit más significativo es 1, al ser distintos indica que ha habido un
desbordamiento.
Pág 168
www.macprogramadores.org
Primero, ¿qué debe hacerse cuando hay un desbordamiento de enteros? Antes de nada
aclarar que no debe confundirse el desbordamiento (overflow) con el acarreo. Cuando
sumamos o restamos números en complemento a 2, es normal que se produzca un
acarreo en el último bit, que simplemente se descarta. El desbordamiento es distinto,
se debe a que el número obtenido no es correcto ya que no se puede representar en un
registro del tamaño usado. P.e. 34+(-17) producía un acarreo que se descartaba sin
más. (-80)+(-120) produce un desbordamiento que hace que el resultado de la suma
obtenido en el registro no sea el correcto.
Consideremos primero la aritmética sin signo. Hay tres enfoques: poner a 1 un bit de
desbordamiento, causar un trap en caso de desbordamiento, o no hacer nada con el
desbordamiento. En el último caso el software tiene que comprobar si se va a producir
o no desbordamiento, con lo que es la solución menos apropiada y de hecho sólo se
usó en las máquinas MIPS.
Flag Descripción
SO (Summary Overflow) Se activa cuando hay un overflow y queda activo
hasta que lo desactivamos explícitamente con mtxer. Es útil para saber
si durante la ejecución de una serie de instrucciones hubo un overflow
OV (OVerflow) Indica si la última operación aritmética produjo overflow
CA (Carry) Indica si la última operación aritmética produjo acarreo
Después PowerPC dispone de varias operaciones, unas en las que no se detecta nada
(addi y add), otras en las que sólo se detecta el acarreo (addc y adde) y otras en
las que se activa el overflow (addco, addeo, addmeo y addzeo).
¿Qué ocurre en el caso de la aritmética con signo?. Obsérvese que mientras que el la
aritmética sin signo el acarreo implica overflow, aquí puede ser deseable ignorarlo,
como pasa en el caso de la suma de números en complemento a 2, donde el acarreo
simplemente se ignora. Esta es la razón de que existan instrucciones como addi o
add que lo ignoran. Además el ignorar el acarreo puede ser útil en circunstancias en
las que por la lógica del programa sabemos que no se va a producir, porque acelera la
ejecución de instrucciones tal como se explicará en el Capítulo 3.
Pág 169
www.macprogramadores.org
Sin embargo sólo hay una representación no entera cuyo uso se ha extendido
ampliamente, y es la representación en punto flotante. En este sistema, la
representación de un número se divide en tres partes: un signo, un exponente y una
mantisa. Y el valor del número así representado se calcula como:
Aunque esta fórmula no vale para calcular números en binario, si que nos será útil en
los ejemplos ya que las personas estamos más familiarizadas con números en base 10.
Pág 170
www.macprogramadores.org
Obsérvese que la mantisa nunca tiene signo, ya que el signo se separa aparte en el
campo destinado a tal propósito, sin embargo el exponente siempre es un número con
signo.
Obsérvese también que un mismo número en punto flotante puede tener muchas
representaciones. P.e. -1,5*10-2=-0,15*10-1=-0,015*100=-0,00015*101
Ejemplo: Los números de precisión simple utilizan 1 bit para el signo, 23 para la
fracción y 8 para el exponente polarizado ¿Cómo se empaquetaría el número en punto
flotante 150*2-9?
mantisa = 1,0010110
exponente = 11111110 (-2)
Pág 171
www.macprogramadores.org
Por último empaquetamos el número, para lo cual, la fracción se calcula como los
dígitos a la derecha de la coma y el exponente polarizado se calcula como el
exponente no polarizado más 2n-1=27=128, es decir el exponente polarizado será -
2+128=126:
De los cuatro formatos definidos por el IEEE, sólo el formato simple es obligatorio de
implementar, el formato doble es recomendado y todas las implementaciones de IEEE
754 existentes lo implementan. Por último los formatos simple extendido y doble
extendido sólo los tienen algunas implementaciones. PowerPC implementa los
formatos simple, doble y doble extendido, pero no el simple extendido.
En lenguaje C estos formatos están representados por los siguientes tipos de datos:
El tamaño de los campos para los formatos simple y doble está estandarizado por el
IEEE y son los que se muestran el la siguiente tabla, pero el tamaño de los campos
para los formatos simple extendido y doble extendido no están estandarizados por el
IEEE, sino que el IEEE sólo da unos tamaños mínimos para cada campo.
Vamos a ver más detenidamente como lo implementa cada uno: Tanto PowerPC
como SPARC lo implementan en un número de 128 bits (16 Bytes) así:
Pág 172
www.macprogramadores.org
1b 15 b 112b
16B
Intel, sin embargo, lo implementa en un número de 80 bits (10 Bytes) como el que
muestra la siguiente figura:
16b 1b 24 b 1b 63b
10B
La especificación dice que se de los 10 Bytes sólo se usan 8 dejando 2 bytes vacíos.
El bit explicit leading almacena el 1 que hay a la izquierda de la fracción, que aunque
normalmente es implícito, aquí se hace explícito. Esto será útil cuando veamos los
números desnormalizados en el apartado 3.1 donde veremos que aquí puede ir un 0.
Por último comentar que los exponentes con valor máximo y mínimo (0 y 255 en el
caso del formato simple) se utilizan con valores especiales, como muestra la siguiente
tabla, y que comentaremos en los siguientes apartados.
El número 0 es uno de estos valores especiales y se representa poniendo todos los bits
a cero, salvo quizá el de signo que se puede activar para representar el -0. A efectos
prácticos 0 y -0 son idénticos, pero existen determinadas ocasiones en las que se
comportan de forma diferente, por ejemplo al calcular 1/-0 = -∞. en principio el lector
no debería de darle más importancia, salvo para recordar que el cero en notación de
punto flotante se puede representar de dos formas distintas.
Pág 173
www.macprogramadores.org
Antes comentamos que la mantisa era igual a la fracción con un 1 delante de la coma
binaria. Esto nos limita el número más pequeño que podemos representar. Por
ejemplo en notación simple donde el exponente tiene 8 bits y la fracción tiene 23 bits,
el número más pequeño que podemos representar es el ±1*2-127, que en decimal nos
viene a dar el número ±5,8*10-39.
Una característica del estándar IEEE es que permite representar números por debajo
de este umbral, a los que llaman números denormalizados, para ello lo que hacemos
es dejar el exponente polarizado a 0 de forma que ahora la parte fraccionaria se
interpreta como si a la izquierda de la coma hubiera un 0 en vez de un 1.
lim 1/x
x->∞
Para una discusión matemática más a fondo sobre este tema puede consultar:
“Underflow and the Reliability of Nuerical Software”, James Demmel, y
“Combatting the effect of Underflow and Overflow in determining Real Roots of
Polynomials” de S. Linnainmaa.
Pág 174
www.macprogramadores.org
Otra peculiaridad del estándar IEEE 754 es que nos permite representar los valores
+∞ y -∞, para ello utiliza los siguientes patrones de bits especiales:
Por último la otra gran peculiaridad del estándar IEEE 754 es que puede representar
números no válidos que se obtienen en cálculos especiales como por ejemplo 0/0,
∞+(-∞), o la raíz de un número negativo. Estos son valores indefinidos en el campo de
los números reales y se representan con el valor especial NaN (Not a Number). Este
valor se codifica dejando el exponente a su valor máximo y dejando una fracción
distinta de cero, con lo que más que haber un número NaN, hay una familia completa
de NaN.
El valor NaN se propaga entre las operaciones aritméticas, de forma que si uno de los
operandos de una operación aritmética es NaN, el resultado de la operación también
será NaN.
Los NaN pueden ser de dos tipos: quiet NaN y signaling NaN. Cuando un signaling
NaN se encuentra en una operación aritmética, si está activo el tratamiento de
excepciones se produce una excepción. Cuando se encuentra un quiet NaN no se
produce.
Los signaling NaN no tienen por qué ser producidos por operaciones aritméticas no
válidas, nosotros mismos podemos crearlos manualmente, por ejemplo para rellenar
un área de memoria sin inicializar, de forma que si el programa encuentra un número
de estos podemos saber que el programa a accedido a un trozo de memoria sin
inicializar.
Pág 175
www.macprogramadores.org
Los NaN toman distintos valores en su parte fraccionaria indicando la causa del error.
La siguiente tabla muestra los valores que puede tomar el campo de la fracción:
Para indicar si el número es un signaling NaN o un quiet NaN se usa el bit más
significativo de la fracción. Para indicar la causa del NaN se usan los valores de la
tabla anterior puestos a la derecha de la fracción y desplazados 8 posiciones a la
izquierda tal como muestra la figura:
Pág 176
www.macprogramadores.org
Por último vamos a hacer un estudio de cuáles son los rangos de los números
máximos y mínimos que podemos representar con cada formato.
Vamos a explicar como se calculan estos rangos. Sólo vamos a ver como se
calcularían para el formato simple, aunque el mismo razonamiento se puede aplicar
para los demás tipos.
El número máximo representable en formato simple sería aquel que tiene activos
todos los bits de la fracción y el exponente toma el valor máximo +126, ya que +127
se usa para representar los infinitos, luego este número seria:
Para calcular el número mínimo normalizado sería aquel que tiene el 1 de la izquierda
de la coma de la mantisa, pero toda la parte fraccionaria a 0 y como exponente -127 (-
128 se usa para representar el cero y los números denormalizados), luego seria:
Pág 177
www.macprogramadores.org
Un hecho evidente con el que nos vamos a encontrar cuando trabajamos con números
en punto flotante es que tenemos que representar infinitos números reales usando sólo
un conjunto finito (aunque muy grande) de números binarios en notación de punto
flotante. Para afrontar este problema vamos a utilizar redondeos, donde lo que
hacemos es representar un número real usando el número en punto flotante más
cercano a él.
Un caso claro donde se aprecian los problemas de redondeo es en el hecho de que los
números en punto flotante decimal y punto flotante binario se representan de forma
distinta. Por ejemplo el número 183234,373 tiene una representación exacta en punto
flotante decimal, pero si lo intentamos pasar a notación de punto flotante binaria
obtenemos un número periódico: 1,0110010 11110000 10011000...
Un hecho importante que conviene resaltar es el de que los números en punto flotante
se encuentran desigualmente distribuidos, de forma que los números pequeños (los
más cercanos a 0) están más juntos entre si que los números más grandes (los más
cercanos a ±∞).
Para ver este hecho podemos dibujar los números en una línea de coordenadas
suponiendo que tenemos números en notación de punto flotante binario con una parte
fraccionaria de 3 bits. En este caso se cumple la regla de que entre 2n-1 y 2n habrá un
total de 8 números uniformemente distribuidos. Esta regla será cierta para cualquier n,
aunque la distancia se dobla cada vez que incrementamos n tal como observamos en
la siguiente figura.
0 2-1 20 21 22 23 24
Esto implica que la precisión que consiguen los redondeos cuando estamos trabajando
con números pequeños sea mucho mayor que la que se consigue cuando estemos
trabajando con números grandes. Obsérvese que en parte esto es normal, ya que no es
la misma la precisión con la que trabaja, por ejemplo, un microscopio, en la que una
micra puede echar a perder todos los cálculos, que la precisión que necesita un
Pág 178
www.macprogramadores.org
Ya que los errores de redondeo son inherentes a los números en punto flotante, es
importante buscar un método para medirlos. Consideremos como ejemplo un sistema
de representación de números en punto flotante decimal y con una fracción de 3
dígitos. Si el resultado de un cálculo en punto flotante nos da 3,12*10-2, y el cálculo
con una precisión infinita es 0,0314, es claro que el error es de dos unidades en el
último dígito. Del mismo modo, si el número real 0,0314159 se representa en nuestro
sistema de punto flotante como 3,14*10-2, entonces el error es de 0,159 unidades del
último dígito. Un método muy usado para medir los errores es medir el error usando
como magnitud las unidades en el último dígito (uud), en el ejemplo anterior los
errores serian respectivamente 0,2 uud y 0,159 uud.
En general se busca que los sistemas aritméticos que diseñemos tengan un error
máximo de 0,5 uud, en cuyo caso al sistema aritmético se le considera correcto.
Téngase en cuenta que éste es un sistema de medición de errores relativo ya que si por
ejemplo un número que estamos calculando con precisión infinita vale 4,56323*1020 y
el sistema de punto flotante nos devuelve el número 4,56*1020, aunque el error es de
0,323 uud, el error absoluto es de 323*1017=32.300.000.000.000.000.000 unidades.
Sin embargo, los errores de redondeo que se pueden producir en números pequeños
son también pequeños. Por ejemplo en precisión simple si intentamos representar un
número con exponente 0 el error máximo que podemos cometer durante el redondeo
es de 0,5 uud, es decir 2-23/2=5,9*10-9, que es un número bastante pequeño.
El estándar IEEE define 4 modos de redondeo, los cuales indican cómo realizar un
redondeo cuando un número real no se puede representar exactamente en la notación
de punto flotante utilizada.
Los otros modos de redondeo son redondeo hacia cero, redondeo hacia +∞ y
redondeo hacia -∞. Todo sistema de numeración en punto flotante que siga el
estándar IEEE 754 debe disponer de un mecanismo que nos permita cambiar este
modo de redondeo. En el caso de PowerPC se usa el los flag RN del registro FPSCR
para indicar el tipo de redondeo de acuerdo a la siguiente tabla:
Pág 179
www.macprogramadores.org
Pág 180
www.macprogramadores.org
5. Las excepciones
Cuando se produce una situación anómala en la ejecución de instrucciones de punto
flotante se produce una excepción. IEEE 754 recomienda que existan flags asociados
a las excepciones, en el caso de PowerPC estos flags están en el registro FPSCR. Al
empezar un programa su ejecución todos los flag de excepción están apagados.
Cuando se produce una excepción se activa el flag apropiado, pero la aplicación se
continúa ejecutando. Después la aplicación puede consultar los flag de excepción o
bien modificarlos.
El estándar también recomienda que para cada tipo de excepción haya un flag de
habilitación de trap de excepción, de forma que si hay una excepción con su flag de
trap habilitado se llame al manejador de trap. Además recomienda el uso del flag de
habilitación de excepción, que cuando están activos indican que si una excepción se
produce se encienda su correspondiente flag de excepción. Si están apagados, el flag
de excepción no se encenderá a pesar de que se produzca la excepción. Los
microprocesadores que sólo disponen de flags de habilitación de excepción no permite
que el sistema operativo pueda reaccionar ante una excepción, sino que es el propio
programa el que, tras ejecutar una instrucción, debe de comprobar si se ha encendido
algún flag de excepción.
El usar los flags de habilitación de excepción se considera mejor que el uso de traps,
ya que a la hora de ejecutar instrucciones como:
fdiv f0,f1,f2
fadd f2,f3,f4
PowerPC permite tanto usar flags de habilitación de traps como usar flags de
habilitación de excepción, será el diseñador del sistema operativo quien deba tomar
esta decisión.
IEEE 754 define cinco tipos de excepciones que vamos a detallar. Las
implementaciones son libres de disponer de más flags de excepción si lo consideran
apropiado, tal como pasa en PowerPC que dispone de una gran cantidad de flags de
excepción, aunque básicamente las excepciones se pueden resumir en estas cinco.
Además estos flag pueden ser flags retenido (sticky), no serlo, o bien existir un flag
de retenido y su correspondiente de no retenido, a elección de la implementación.
Pág 181
www.macprogramadores.org
5. Inexact. Ocurre siempre que hay que redondear un número por no existir una
representación exacta de ese número en punto flotante. Se usa porque el programa
puede estar interesado en saber si ha habido redondeo, los cuales se vuelven
especialmente perjudiciales cuando hay una acumulación de operaciones con
redondeo.
Pág 182
www.macprogramadores.org
6.1. Redondeo
Con el fin de poder desarrollar software que se pueda ejecutar de la forma más
homogenea posible en distintas plataformas, el estándar del IEEE ha definido una
regla respecto a cómo deben de realizarse las operaciones aritméticas entre números
en punto flotante:
Esta regla que en principio parece difícil de cumplir, por la dificultad que tiene un
ordenador para realizar cálculos con precisión infinita, no es tan difícil como parece,
de hecho, veremos que podemos obtener los resultados que pide la regla, con sólo un
poco más de esfuerzo.
Para facilitar el estudio vamos a suponer que tenemos un sistema de punto flotante
decimal con tres bit para la mantisa. Hay dos formas de redondeo que se pueden
presentar durante la suma:
2,34*102
8,51*102 +
––––––––
10,85*102 ––––> Redondea a 10,8*102
2,34*102
2,56*100 +
––––––––
2,3656*102 ––––> Redondea a 2,37*102
Pág 183
www.macprogramadores.org
9,51*102
6,42*101 +
––––––––
10,152*102 ––––> Redondea a 10,2*102
En cada uno de estos casos la suma se debe calcular con más dígitos con el fin de
realizar correctamente el redondeo. Se ha demostrado que para que el resultado de una
suma en punto flotante sea el mismo que si redondeásemos el resultado de una suma
con precisión infinita, basta con disponer de dos bits adicionales a la derecha de los
registros del sumador, llamados bits de guarda.
4,5674*100
2,5001*10-4 +
–––––––––
4,56765001*100 ––––> Redondea a 4,5677*100
Aunque aquí pudiera parecer que se necesita mantener doble número de dígitos para
realizar un redondeo correcto, ya que el 1 más a la derecha de 2,5001 determina si el
resultado es 4,5676 ó 4,5677, después de una pequeña reflexión se puede ver que sólo
es necesario saber si hay o no más dígitos distintos de cero pasadas las posiciones de
guarda, esta información se puede almacenar en un sólo bit llamado bit de retención
(stricky bit), que se implementa examinando cada dígito que está despreciado debido
a un desplazamiento. Tan pronto como aparece un dígito distinto de cero, el bit de
retención se pone a 1 y permanece con este valor. Para implementar el redondeo al par
más cercano simplemente añadimos el bit de retención a la derecha del dígito más a la
derecha justo antes de redondear.
1. Si e1<e2, intercambiar los operandos para que satisfagan la regla de que d=e1-
e2≥0
2. Desplazar m2 a la derecha d posiciones con el fin de equiparar los exponentes,
es decir que e1=e2. Dicho con más precisión, poner m2 alineado a la izquierda
de un registro con |m|+2 bits, siendo |m| el número de bits de la fracción en la
notación utilizada, al que sumamos 2 bits de guarda. Después desplazamos a la
derecha los bits d veces y, si durante el desplazamiento, por la derecha del
registro sale algún 1 activamos el bit de retención
3. Añadir el bit de retención a m2
4. Sumar m1+m2 así puestos en registros de |m|+2 bits, depositando el resultado
en un registro de |m|+3 bits. Si hubiese acarreo en el bit más significativo
Pág 184
www.macprogramadores.org
m1= 4567400
m2= 0000250
Quedando como bits de guarda de a2 5 y 0, y como bit de retención el or binario de
0,0,1 que es 1.
m1= 4567400
m2= 0000251
m3= 04567651
Al no haber habido acarreo en el dígito más significativo no hace falta desplazar, con
lo que en el paso 5 tras redondear obtenemos:
a3= 4,5677*100
Pág 185
www.macprogramadores.org
67,45
x 34,98
–––––––––
2359,4010
Pág 186
www.macprogramadores.org
1. Usando el divisor de enteros del apartado 1.1.4 dividir las dos mantisas m1 y
m2 para obtener un cociente de |m| bits en el registro A y un resto en el registro
P.
2. Si el resto r3 así obtenido es mayor a la mitad del divisor entonces al cociente
se le suma uno, si no se deja igual, es decir, si 2*r3>a2 entonces a2=a2+1, si
2*r3=a2 se aplica el método de redondeo que este activo, si no a2 se deja
como está.
3. Para calcular el exponente resultado e3, se calcula como e3=e1-e2.
Dividendo=2352
Divisor=1275
Obteniendo:
Cociente = 1844
Resto = 900
Ahora como el resto es mayor a 1/2 cociente debemos sumar uno al cociente
obteniendo:
Cociente = 1845
Resto = 900
Pág 187
www.macprogramadores.org
9. Comparaciones y conversiones
IEEE 754 define que un sistema en punto flotante debe de disponer de operaciones
que permitan comparar números en punto flotante. La tricotomía comparativa usual
de los números reales se extiende en el estándar para que sólo una de estas cuatro
comparaciones sea cierta:
o a<b
o a>b
o a=b
o a y b no mantienen relación de orden
IEEE 754 también requiere que el sistema de numeración en punto flotante disponga
de las siguientes operaciones de conversión:
Pág 188