Está en la página 1de 188

Tutorial de ensamblador del

PowerPC para Mac OS X


www.macprogramadores.org

Í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

CAPÍTULO 1: Introducción al PowerPC


1. La arquitectura del PowerPC ..............................................................................9
2. Los entornos del PowerPC................................................................................11
3. Los registros del PowerPC................................................................................12
4. Byte ordering....................................................................................................13
5. Alineación ........................................................................................................14

CAPÍTULO 2: Empezando a programar


1. Herramientas necesarias....................................................................................16
2. El programa mínimo .........................................................................................19
3. El lenguaje ensamblador ...................................................................................21
3.1. Sintaxis del lenguaje .................................................................................21
3.2. Elementos del lenguaje .............................................................................22
3.2.1. Literales ............................................................................................22
3.2.2. Identificadores...................................................................................24
3.2.3. La expresiones ..................................................................................25
3.2.4. El location counter ............................................................................27
3.3. Las sentencias de asignación directa..........................................................28
3.4. Las definiciones ........................................................................................29
4. Acceso a memoria ............................................................................................30
4.1. Segmentación del programa ......................................................................30
4.2. Las secciones ............................................................................................31
4.2.1. Secciones del segmento de código.....................................................31
4.2.2. Secciones del segmento de datos .......................................................34
4.2.3. Crear nuevas secciones......................................................................36
4.2.4. Agrupar las secciones........................................................................37
4.3. El problema del indireccionamiento de memoria en las máquinas RISC....37
4.4. Modos de indireccionamiento ...................................................................40
4.4.1. Indireccionamiento de registro base e índice inmediato ....................40
4.4.2. Ejemplo.............................................................................................43
4.4.3. Instrucciones de acceso a memoria con actualización de registro.......44
4.4.4. Uso del operador ha16() ................................................................45
4.4.5. Indireccionamiento de registro base y registro índice.........................47
4.5. Carga y almacenamiento de bloques de bytes............................................50
4.6. Mnemonics ...............................................................................................52
5. Instrucciones de trabajo con enteros..................................................................55
5.1. El registro CR (Condition Register) ..........................................................55
5.2. El registro XER.........................................................................................58
5.3. Instrucciones aritméticas ...........................................................................60
5.3.1. Instrucciones aritméticas de suma......................................................60
5.3.2. Instrucciones aritméticas de resta ......................................................65

Pág. 2
www.macprogramadores.org

5.3.3. Instrucciones de negación aritmética .................................................66


5.3.4. Instrucciones aritméticas de multiplicación........................................67
5.3.5. Instrucciones aritméticas de división .................................................68
5.4. Instrucciones de comparación de enteros...................................................69
5.5. Instrucciones lógicas con enteros ..............................................................70
5.6. Instrucciones de rotación y desplazamiento con enteros ............................72
5.6.1. Instrucciones de desplazamiento con enteros .....................................72
5.6.2. Instrucciones de rotación con enteros ................................................74
5.7. Mnemonics ...............................................................................................76
5.7.1. Mnemonics para la resta....................................................................76
5.7.2. Mnemonics para las operaciones de comparación..............................77
5.7.3. Mnemonics para operaciones de desplazamiento y rotación...............78
5.7.4. Mnemonics para acceder al registro XER ..........................................79
5.7.5. Otros mnemonics ..............................................................................79
5.8. Operaciones comunes con enteros.............................................................80
5.8.1. Valor absoluto...................................................................................80
5.8.2. Máximo y mínimo de un número sin signo........................................81
5.8.3. Máximo y mínimo de un número con signo.......................................82
5.8.4. Resto de una división ........................................................................83
5.8.5. División entre una constante entera ...................................................84
5.8.6. División de 64 bits en máquinas de 32 bits ........................................88
6. Instrucciones de bifurcación .............................................................................93
6.1. Tipos de calculo de la dirección de salto de una instrucción ......................93
6.1.1. Instrucciones de salto relativo............................................................94
6.1.2. Instrucciones de salto absoluto ..........................................................95
6.1.3. Las instrucciones de salto condicional ...............................................96
6.1.4. Instrucciones condicionales de salto relativo .....................................98
6.1.5. Instrucciones condicionales de salto absoluto ..................................100
6.1.6. Instrucciones condicionales de salto al Count Register ....................101
6.1.7. Instrucciones condicionales de salto al Link Register ......................102
6.2. Mnemonics .............................................................................................104
6.2.1. Mnemonics para saltos incondicionales ...........................................104
6.2.2. Mnemonics para saltos condicionales ..............................................104
6.2.3. Mnemonics para acceder a los registros CR, CTR y LR...................107
6.3. Implementación en ensamblador de las sentencias de control de flujo más
conocidas del lenguaje C ....................................................................................108
6.3.1. Condicional simple y doble .............................................................108
6.3.2. Condicional múltiple .......................................................................109
6.4. Los bucles...............................................................................................111
6.4.1. Mnemonics para bucles ...................................................................112
6.4.2. Bucle do-while ................................................................................113
6.4.3. Bucle while .....................................................................................113
6.4.4. Bucle for .........................................................................................114
6.5. Operaciones lógicas con los campos del registro CR ...............................116
7. Instrucciones de trabajo con números en punto flotante...................................118
7.1. Introducción............................................................................................118
7.2. Los registros de punto flotante ................................................................118
7.3. El registro FPSCR...................................................................................119
7.3.1. Instrucciones para acceder a los bits de registro FPSCR ..................121
7.3.2. Los flags de excepción ....................................................................122

Pág. 3
www.macprogramadores.org

7.3.3. Los bits de condición y el bit de clase..............................................124


7.3.4. Los bits de redondeo .......................................................................126
7.4. El registro CR .........................................................................................126
7.5. Manejo de traps.......................................................................................127
7.6. Instrucciones de carga y almacenamiento................................................128
7.7. Instrucciones aritméticas .........................................................................130
7.8. Instrucciones de conversión ....................................................................132
7.9. Instrucciones de comparación .................................................................133
7.10. Instrucciones de movimiento de datos .................................................134
8. Incrustar código ensamblador en un programa C.............................................135
8.1. Integración entre C y ensamblador ..........................................................135
8.2. Acceso a variables C desde ensamblador.................................................135
8.3. Expresiones C como operandos de instrucciones ensamblador ................136
8.3.1. Las constraints y los modificadores .................................................137
8.3.2. Expresiones C en gcc 3.1.................................................................141
9. Llamada a funciones .......................................................................................142
9.1. Tipos de datos.........................................................................................142
9.2. Mecanismo general de llamada a procedimientos ....................................143
9.3. Convención del uso de los registros.........................................................143
9.4. Estructura de la pila ................................................................................145
9.4.1. Las áreas del frame..........................................................................145
9.5. Paso de control a un procedimiento .........................................................147
9.5.1. El prólogo y el epílogo ....................................................................147
9.5.2. Los procedimientos terminales ........................................................152
9.5.3. Paso de parámetros..........................................................................152
9.5.4. Funciones con un número variable de parámetros............................155
9.5.5. Retorno de una función ...................................................................156
9.6. Ejemplo ..................................................................................................156

Pág. 4
www.macprogramadores.org

APÉNDICE A: Aritmética binaria


1. Técnicas básicas de aritmética entera ..............................................................159
1.1. Números sin signo ..................................................................................159
1.1.1. Suma con transmisión de acarreo.....................................................159
1.1.2. Resta con petición de acarreo ..........................................................161
1.1.3. Multiplicación en base 2..................................................................163
1.1.4. División en base 2 ...........................................................................164
1.2. Números con signo .................................................................................167
1.2.1. Representación................................................................................167
1.2.2. Suma y resta de números en complemento a 2.................................167
1.3. Aspectos del sistema ...............................................................................169
2. Introducción al punto flotante .........................................................................170
3. Formato de los datos en punto flotante............................................................171
3.1. Números denormalizados........................................................................174
3.2. Números especiales.................................................................................175
3.3. Rangos máximos y mínimos en los números en punto flotante ................177
4. El problema del redondeo en punto flotante ....................................................178
4.1. La precisión en punto flotante .................................................................178
4.2. Error absoluto y relativo..........................................................................179
4.3. Modos de redondeo.................................................................................179
5. Las excepciones..............................................................................................181
6. Suma en punto flotante ...................................................................................183
6.1. Redondeo................................................................................................183
6.2. El algoritmo de la suma...........................................................................184
7. Multiplicación en punto flotante .....................................................................186
8. División y resto en punto flotante ...................................................................187
9. Comparaciones y conversiones .......................................................................188

Pág. 5
www.macprogramadores.org

PRÓLOGO

1. ¿Qué es este tutorial?


Este tutorial está dirigido a explicar el diseño y funcionamiento de las máquinas de
Apple actuales, haciendo especial hincapié en su microprocesador: El PowerPC. La
información que se da aquí es más cualitativa que cuantitativa, en el sentido de que se
explica como está estructurado y organizado el computador, así como la
programación de éste, más que a hacer hincapié en las diferencias de velocidad y
rendimiento de los diferentes modelos de PowerPC que hay actualmente en el
mercado.

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.

A veces también haremos comparativas entre como se implementa un determinado


aspecto en PowerPC, y como se implementa en otras máquinas, como por ejemplo las
máquinas x86 de Intel. Esto ayuda al lector a tener una visión más global de la
arquitectura de los ordenadores modernos, y normalmente verá también las ventajas o
inconvenientes que tiene PowerPC frente a sus competidores.

2. ¿A quién va dirigido este tutorial?


Antes de escribir un documento técnico es importante plantearse la pregunta de a
quién va dirigido el escrito, esto ayuda a la hora de decidir qué materias explicar y
hasta qué nivel de profundidad hacerlo.

A lo largo de mis estudios e investigaciones he visto muchos libros del tipo


“empezamos desde cero y lo damos todo”, a veces los llaman “para todos los niveles”.
Este tipo de afirmaciones, aunque puedan estar impuestas por una editorial ambiciosa,
son poco realistas, especialmente cuando el escrito es sobre un tema lo
suficientemente amplio. Por mi experiencia he observado en muchas ocasiones que
este tipo de libros suelen empezar muy despacio y a mitad del libro el autor aumenta
el ritmo con el fin de “poder meter” un poco más de temario. En este tutorial he
intentado hacerlo al revés: empezar más deprisa con los primeros temas y dedicar más
tiempo a las últimas partes, que además son las más interesantes.

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.

3. Cómo leer este tutorial


En principio el tutorial está pensado para ser leído de principio a fin siguiendo el
orden de los capítulos. Los apéndices, por contra, están pensados para contener
información que no está muy relacionada y que se puede leer en cualquier orden o
bien esquivarlos si el lector ya está familiarizado con ese tema.

En el tutorial aparecen multitud de tablas, por ejemplo a la hora de describir las


instrucciones que existen para un determinado propósito. Es muy común el pasar una
tabla sin fijarse en su contenido. En parte esto es normal porque el lector se centra en
los conceptos y en la estructura del documento y no en valores concretos. Aun así
recomendamos al lector que intente leer la utilidad de esos datos resumidos en la
tabla, ya que ayudan más de lo que pudiera parecer a poder seguir la explicación del
texto.

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

Cuando la vayamos a ejecutar con determinados valores escribiremos:

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.

Fernando López Hernández


Madrid, Noviembre del 2002

Pág 8
www.macprogramadores.org

CAPITULO 1: INTRODUCCIÓN AL POWERPC

1. La arquitectura del PowerPC


A lo largo de la historia de la informática, han ido apareciendo distintas generaciones
de ordenadores. Una parte importante de estos ordenadores ha sido el
microprocesador. El microprocesador introduce una diferencia definitiva en la forma
en que trabaja un ordenador, y es a lo que nosotros llamaremos una arquitectura.
Tipos de arquitecturas de microprocesadores conocidos a lo largo de la historia han
sido, el Z80 (usado por los antiguos Spectrum), el Motorola 68000 (usado hasta hace
pocos años por los Macintosh), el microprocesador x86 de Intel (usado actualmente
por los PCs), los SPARC usados en las máquinas de Sun, o el PowerPC que es el que
vamos a estudiar en este tutorial. Los procesadores con una misma arquitectura
forman familias, en el sentido de que para una misma arquitectura van surgiendo
procesadores con características parecidas, pero a los que se van añadiendo mejoras.

PowerPC es una nueva arquitectura que incorpora importantes ventajas conceptúales


respecto a los anteriores.

Una ventaja importante es que PowerPC es una arquitectura RISC (Reduced


Instruction Set Computing), es decir, que dispone de un juego de instrucciones
reducido, frente a otras arquitecturas como x86 ó M68000 que se las conoce como
CISC (Complex Instruction Set Computing) los cuales disponen de un juego de
instrucciones mucho más amplio.

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.

PowerPC es una arquitectura diseñada para funcionar tanto en máquinas de 32 bits


como en máquinas de 64 bits, es decir, que los registros del microprocesador y las
direcciones de memoria pueden ser o bien de 32 bits o bien de 64 bits, que es a lo que
se llama la palabra (word) del computador. Aun así, en ambos casos siguen el
mismo modelo, y disponen del mismo juego de instrucciones, que es el modelo y
juego de instrucciones del PowerPC.

Aunque esta arquitectura fue inicialmente diseñada en común, actualmente la


fabricación de máquinas que siguen el modelo del PowerPC se ha dividido en dos:

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

Podemos consultar los micros de que dispone Motorola aquí:

http://e-www.motorola.com/webapp/sps/site/taxonomy.jsp?
nodeId=03M943030450467M98653

Y los micros de IBM aquí:

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

2. Los entornos del PowerPC


Un objetivo que se fijó durante el diseño del PowerPC fue que esta arquitectura se
pudiera utilizar para la fabricación de procesadores dirigidos a distintos dispositivos
electrónicos, que iban desde los micros para máquinas automáticas y sistemas
empotrados hasta los microprocesadores para workstations y servidores de alto
rendimiento.

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:

User Instruction Set Architecture (UISA). Define el juego de instrucciones de


usuario de que dispone el micro (también llamado entorno de resolución de
problemas). Aquí se definen aspectos como los registros, tipos de datos, operaciones
en punto flotante del microprocesador y formas de acceso a memoria.

Virtual Environment Architecture (VEA). Define operaciones adicionales de


usuario, que normalmente escapan a lo que un programador de aplicaciones puede
necesitar controlar, como puedan ser las caches.

Operating Environment Architecture (OEA). Define el juego de instrucciones del


supervisor (también llamadas operaciones privilegiadas), que son operaciones a las
que normalmente sólo tiene acceso el sistema operativo. Como puedan ser los
métodos de paginación o segmentación de la memoria, técnicas de sincronización o
gestión de excepciones (también llamadas interrupciones). A estas instrucciones sólo
se puede acceder si el micro se encuentra en modo supervisor, si está en modo
usuario, que es el modo normal de funcionamiento, solo se puede acceder a las
operaciones de UISA y VEA.

Por ejemplo un micro como el MPC106 de Motorola que es un micro pequeño


pensado para dispositivos empotrados puede disponer sólo de UISA, mientras que un
micro de alto rendimiento como el MPC7455 de Motorola implementa los tres
entornos.

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

3. Los registros del PowerPC


En entorno UISA define el siguiente conjunto de registros:

o 32 registros de 32 bits de propósito general llamados GPR (General Purpose


Registers). Estos registros sirven para las operaciones comunes del día a día,
como sumas, comparaciones, etc. En las máquinas de 64 bits, estos registros
son de 64 bits.
o 32 registros de 64 bits para operaciones en coma flotante llamados FPR
(Floating Point Registers). Estos registros nos permiten hacer operaciones
matemáticas con números en representación de punto flotante, tanto de
precisión simple como doble. El tamaño de estos registros es siempre de 64
bits, independientemente de si estamos en una máquina de 32 bits o de 64 bits.
o Otros registros diversos como son el Condition Register (CR), Floating-Point
Status and Control Register (FPSCR), XER, Link Register (LR) y Count
Register (CTR).

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:

Big-Endian. Donde el byte más significativo se coloca en la dirección de memoria


más baja (el primero).
Little-Endian. Donde el byte menos significativo se coloca el la dirección de
memoria más baja (el primero).

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:

Tipo dato Tamaño en bytes Rango Valores


char 1 byte -128..+127
short 2 bytes -32.768..+32.767
int 4 bytes -2.147.483.648..+ 2.147.483.647

En nuestro caso necesitaremos una variable de tipo int. A continuación se muestra


como se almacenaría esta variable en big-endian y en little-endian en memoria.

Big-Endian

0000 0000 0000 1001 0110 1010 1100 1011


0 1 2 3

Little-Endian

1100 1011 0110 1010 0000 1001 0000 0000


0 1 2 3

En cualquier caso, cuando apuntamos a una variable en memoria siempre se apunta al


primer byte (dirección de memoria más baja) de la variable.

Como veremos, la organización en big-endian tiene la ventaja de que al escribir el


número lo escribimos de izquierda a derecha, tal como se lee. El único inconveniente

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.

Fabricantes de microprocesadores como IBM, o Sun siguen la organización big


endian, por desgracia hay un microprocesador muy usado, que es el x86 de Intel, que
usa la organización little-endian.

En PowerPC por defecto se usa big-endian, aunque existen técnicas de compatibilidad


que permiten acceder a los datos de memoria en little-endian. Esto es especialmente
útil para mantener compatibilidad con software escrito para procesadores que usan
little-endian y para poder acceder a estructuras de datos donde los datos se almacenan
en formato little-endian (p.e los ficheros .bmp de Windows).

Obsérvese que en el caso de los registros del microprocesador no existe el problema


del orden de los bytes que lo componen, ya que los registros son unidades indivisibles
de 32 bits.

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.

Estas instrucciones de acceso a memoria funcionan más rápido si el acceso lo


hacemos a una dirección de memoria que sea múltiplo del tamaño de los datos a
transferir, por ejemplo si accedemos a variables de tipo int (4 bytes), el acceso más
rápido se consigue cuando accedemos a una dirección de memoria múltiplo de 4.

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.

La siguiente tabla muestra la alineación recomendada para cada tamaño de dato.

Operando Longitud Dirección alineada


byte 1 byte xxxx
halfword 2 bytes xxx0
word 4 bytes xx00
doubleword 8 bytes x000

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

CAPÍTULO 2: EMPEZANDO A PROGRAMAR

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

Dentro de estas herramientas encontramos el conocido compilador gcc de GNU, que


en este caso se llama cc y es el que nos va a permitir compilar código C, C++,
Objective-C y ensamblador desde la línea de comandos.

Para probar este comando podemos escribir un fichero llamado saluda.c de la


forma:

#include <stdio.h>

#define MENSAJE “Hola mundo\n”

int main ()
{
printf(MENSAJE);
return 0;
}

Y compilarlo desde la línea de comandos con:

$ cc saluda.c -o saluda
$ ./saluda
Hola mundo

El proceso de generación de un ejecutable a partir de un código fuente en C y C++


tiene básicamente 4 pasos:

1. Preprocesado
2. Generación del código ensamblado (compilación)
3. Generación del código objeto (ensamblado)
4. Enlazado

El compilador de GNU lo que hace cuando recibe un programa en lenguaje C es


pasarlo a lenguaje ensamblador (segundo paso), y después pasa ese código a otro
subsistema llamado ensamblador, que en GNU es el comando as (tercer paso), el
cual genera el código binario reubicable de ese programa en un formato especial
llamado código objeto. Por último el enlazador que en nuestro caso es el comando de

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).

Podemos pedir a cc que realice todas o sólo alguna de estas fases.

Si lo que queremos es sólo preprocesar un fichero podemos usar la opción -E así:

$ 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.

Si lo que queremos es obtener el código ensamblador del programa C anterior


(compilar), podemos usar la opción -S así:

$ cc -S saluda.c

Esto genera otro fichero llamado saluda.s en el que obtendremos el código


ensamblador del programa anterior.

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.

También podemos compilar un fichero .s (código ensamblador) para obtener su


correspondiente código objeto con esta misma opción:

$ cc -c saluda.s

En cualquier caso, para obtener el código objeto de un fichero en ensamblador, cc lo


que hace es llamar al comando as. Esta herramienta también la podemos llamar
nosotros para compilar un fichero en ensamblador. Es decir, podemos hacer:

$ 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.

Extensión Descripción Qué hace con ellos cc


.c Código fuente C Preprocesa, ensambla,
compila y enlaza
.cpp Código fuente C++ Preprocesa, ensambla,
.cc compila y enlaza
.cxx
.C

.m Código fuente Objective-C Preprocesa, ensambla,


compila y enlaza

.h Ficheros de cabecera C, C++ o Objective- No usados directamente


C
.i Código C preprocesado (Si se lo pasamos Ensambla, compila y
al compilador no lo preprocesa) enlaza
.ii Código C++ preprocesado (Si se lo Ensambla, compila y
pasamos al compilador no lo preprocesa) enlaza
.s Código ensamblador que no debe ser Compila y enlaza
preprocesado
.S Código ensamblador que debe ser Preprocesa, compila y
preprocesado enlaza
.o Archivo de código objeto Enlaza
.a Librería de enlace estático Enlaza
.so Librería de enlace dinámico Enlaza

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.

Para ello escribimos un fichero llamado basico.s de la forma:

/* Descripción: Programa básico en ensamblador


* Escrito por: Fernando López Hernández
*/

.text // Empieza la sección de código


.align 2
.globl _main ; Hacemos global la función main()
_main:
blr ;Retorna de la función main

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

Los comentarios de C son comentarios que elimina el preprocesador, con lo que


cuando as va a generar el código objeto, estos comentarios ya han desaparecido. No
pasa lo mismo con el tercer comentario, que es el comentario propio de 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

retorna esta llamada se almacena en un registro del microprocesador llamado LR


(Link Register), cuyo principal uso es almacenar direcciones de retorno de las
funciones.

Ahora ya lo podemos compilar y ejecutar:

$ 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.

3.1. Sintaxis del lenguaje

Un programa en ensamblador esta formado por una serie de sentencias, cada una de
las cuales sigue este formato:

[etiqueta:] [instruccion [operandos]] [; comentario]

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.

La instrucción puede ser uno de estos tres elementos:

o Una instrucción ensamblador, que debe ensamblar el lenguaje.


o Una directiva, las cuales no generan código, si no que sirven para cambiar el
comportamiento del ensamblador durante el proceso de ensamblado. Aunque
no generan código si que pueden reservar memoria, como veremos. Una
característica de las directivas es que todas empiezan por un punto (.).
o Una macro, las cuales se crean con la directiva .macro, como veremos en el
Capítulo 3.

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.

El comentario en ensamblador se precede por ;, y, como dijimos, también


podemos usar los comentarios C, aunque el preprocesador los elimina antes de pasar
el fichero al ensamblador.

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

De esta forma el programa resulta más fácil de leer.

A continuación se muestra el ejemplo de un programa que suma dos números.


Desafortunadamente, como todavía no sabemos hacer llamadas al sistema, no vamos a
poder imprimir el resultado de la ejecución. Pero, aun así este ejemplo nos va a servir
para ver algunas instrucciones elementales de acceso a registros y de suma.

Al programa le vamos a llamar sumaregistros.s y va a ser el siguiente:

/* Descripción: Programa que suma dos números situados en


* registros
* Escrito por: Fernando López Hernández
*/
.text // Sección de código
.align 2
.globl _main
_main:
li r3,2
li r4,5
add r5,r3,r4
blr

El programa usa la instrucción li para cargar en el registro r3 un 2 y en el registro


r4 un 5, para después, con la instrucción add calcular r3+r4 y almacenar el
resultado en r5.

3.2. Elementos del lenguaje

En esta sección vamos a comentar cuales son los principales elementos que componen
el lenguaje ensamblador.

3.2.1. Literales

Un literal es una representación escrita de un valor. Dentro de los literales


encontramos:

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í:

.ascii “Hola mundo”

El compilador no pone el 0 de final de cadena, aunque si queremos que lo ponga


podemos usar la directiva .asciz

.asciz “Hola mundo”

Los números enteros, los cuales se pueden representar en decimal, octal o


hexadecimal.

o Los números en decimal se representan en su forma natural: 4, -37. No


pueden empezar por 0.
o Los números en hexadecimal se representan precedidos por 0x, por ejemplo:
0x45, 0xF259B4C2. Para las letras se pueden usar mayúsculas o minúsculas
indistintamente, es decir podemos escribir 0x3F ó 0x3f
o Los números en octal empiezan por 0. Por ejemplo, 037, 041241

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...]]

ftp_char indica si el número es un número real de precisión simple (r) o de


precisión doble (d). El primer dec... indica la parte entera, el segundo dec... la
parte decimal, y por último va una e seguida de la parte exponencial del número. Con
unos ejemplos seguro que queda más claro:

Número Precisión Representación


1.34 doble 0d1.34e0
0.00045 doble 0d45.0e-4
2456 float 0r2456.0e0

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

F1: .single 0r2456.0e0 ; Forma recomentable de reservar


; memoria para un float de
; 32 bits
F2: .single 0d2456.0e0 ; Tambien reserva 32 bits
D1: .double 0d1.34e0 ; Forma recomentable para
; reservar memoria para un
; double de 64 bits
D2: .double 0r1.34e0 ; Tambien reserva 64 bits

3.2.2. Identificadores

Un identificador es un nombre que damos a uno de estos dos elementos:

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.

Cada identificador consiste en una secuencia de caracteres alfanumérica, que no


puede empezar por un número, y en la que se diferencian mayúsculas de minúsculas.

Como curiosidad, en ensamblador los identificadores pueden tener espacios, en cuyo


caso debemos de encerrarlos entre comillas dobles. Por ejemplo:

“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

3.2.3. Las expresiones

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’

Llamamos operador a un cálculo que ejecutamos sobre uno o más operandos. El


ensamblador reconoce los mismos operadores que el lenguaje C, los cuales se
resumen en la siguientes tabla:

Operador Nombre Descripción


- Menos unário El complemento a 2 de un número
~ Negado binario Complemento a uno de un número
! Negado lógico El resultado es 0 si el operando es distinto de 0, y -1
en caso contrario
+ Suma La suma de dos números
- Resta La resta de dos números
* Multiplicación El producto de dos números
/ División División entera de dos números. Trunca los posibles
decimales
% Módulo El resto de la división entera
>> Desplazamiento a El resultado es el valor del primer operando
la derecha desplazado a la derecha, tantas veces como diga el
segundo operando. El desplazamiento es siempre
aritmético, respecto a que no modifica el bit del
signo
Pág 25
www.macprogramadores.org

aritmético, respecto a que no modifica el bit del


signo
<< Desplazamiento a El resultado es el valor del primer operando
la izquierda desplazado a la izquierda, tantas veces como diga el
segundo operando. El desplazamiento es siempre
aritmético, respecto a que no modifica el bit del
signo
& and binario El and binario de los dos operandos
| or binario El or binario de los dos operandos
^ xor binario El xor binario de los dos operandos
&& and lógico El resultado es 1 si ambos operandos son distintos
de 0, y 0 en caso contrario
|| or lógico El resultado es 1 si alguno de los operandos son
distintos de 0, y 0 en caso contrario
< Menor que
> Mayor que
<= Menor o igual que
>= Mayor o igual que
== Igual
!= Distinto

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 absoluto si:

o Los operandos de la expresión son literales.


o Los operandos de la expresión son identificadores a los que hemos asignado
un valor literal.
o La expresión es el resultado de la diferencia de dos operandos reubicables, y
ambos operandos pertenecen a una misma sección.

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

secciones, y las direcciones de memoria se suelen dar respecto a la sección en la que


estamos situados.

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.

En general, se aplican las mismas restricciones a las expresiones externas, excepto


que tampoco se puede hacer la resta de operandos si ambos son externos, es decir
externo1-externo2 está prohibido si externo1 y externo2 son
identificadores externos.

3.2.4. El location counter

El location counter es un símbolo que en todo momento tiene la dirección de


memoria de la instrucción que está siendo ensamblada. El símbolo usado para
referirse al location counter es el punto (.).

Este resulta a veces útil como operando de una instrucción, directiva, macro o
expresión.

El location counter es por naturaleza un valor reubicable.

Existen dos directivas que nos permiten avanzar el valor del location counter:

.align alineacion [, relleno]

que nos permite avanzar el puntero a la siguiente posición en la que haya


alineacion bits con 0 a la derecha. Es decir, a la siguiente posición que sea
múltiplo de 2alineacion

relleno indica con que byte rellenar. Si no se indica rellena de ceros.

Por ejemplo:

.align 2

Avanza el location counter hasta la siguiente posición que sea múltiplo de 4,


rellenando de ceros.

.org avance [, relleno]

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:

.org 100, 0xFF

Rellena los siguientes 100 bytes con 0xFF

3.3. Las sentencias de asignación directa

Antes comentamos que todas las sentencias en ensamblador tenían la forma:

[etiqueta:] [instruccion [operandos]] [; comentario]

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.

Por ejemplo, el programa anterior lo podríamos haber hecho así:

// Sentencias de asignación directa


operando1 = 3
operando2 = 5

.text
.align 2
.globl _main
_main:
li r3,operando1
li r4,operando2
add r5,r3,r4
blr

El uso de las sentencias de asignación directa es equivalente al uso de la directiva


.set, excepto que esta última requiere la asignación de expresiones absolutas.

Es decir, también podríamos haber hecho:

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

3.4. Las definiciones

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

En ensamblador también podemos usar definiciones (#define) que el


preprocesador sustituye convenientemente, lo cual es especialmente útil para asignar a
los registros nombres más significativos:

#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.

4.1. Segmentación del programa

Sabemos que el proceso de generación de un ejecutable consta principalmente de dos


fases:

1. Ensamblado. Consiste en transformar los ficheros fuente en ficheros de código


objeto (.o). Esto se puede hacer con la opción -c de cc
2. Enlazado. El enlazador (el comando ld en nuestro caso) combina todos los
ficheros objeto en un sólo fichero ejecutable.

En el fichero ejecutable generado, la distribución del programa en memoria, como


mínimo estará dividido en dos zonas de memoria a las que se llama segmentos1:

o Segmento de código. Es donde se almacenan las instrucciones del programa


en sí. En consecuencia, es un segmento de sólo lectura.
o Segmento de datos. Es donde se almacenan los datos con los que opera el
programa, con lo que es un segmento de lectura/escritura.

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:

o Si el sistema operativo quiere descargar esta página, no tiene que almacenarla


primero en memoria secundaria (swap), ya que puede volver a leerla del
fichero del ejecutable, cosa que no pasa con el segmento de datos, ya que éste
seguramente haya cambiado respecto a su contenido inicial.
o Si el programa intenta realizar una operación de modificación de los datos en
el segmento de código, lo cual seguramente se deba a una pérdida de
estabilidad del programa, se produce una excepción que podrá tratar el sistema
operativo.
o Ayuda a una mejor organización modular del programa.

Cuando nosotros escribimos un fichero fuente, debemos indicar el segmento en el que


estamos trabajando con las directivas .text (segmento de código) y .data
(segmento de datos).

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.

Otro concepto importante que va unido a los segmentos es el de reubicación. Cuando


el compilador genera código objeto, éste almacena todas las referencias a memoria
como direcciones reubicables, es decir, como offsets respecto a una dirección base 0,
que es el principio del 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.

4.2. Las secciones

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.

4.2.1. Secciones del segmento de código

La siguiente tabla resume las directivas usadas para cada tipo de sección que puede
contener el segmento de código:

Directiva Sección Descripción


.text (__TEXT,__text) Almacena código de
programa
.const (__TEXT,__const) Variables constantes
.literal4 (__TEXT,__literal4) Variables constantes
de 4 bytes
.literal8 (__TEXT,__literal8) Variables constantes
de 8 bytes
.cstring (__TEXT,__cstring) Cadenas de
caracteres constantes
.constructor (__TEXT,__constructor) Usada sólo por los
constructores de C++

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.

El compilador de C usa esta sección para almacenar variables globales marcadas


como const, las tablas de salto de la sentencia switch, o los valores de los
operandos constantes de las sentencias.

.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

Estas directivas se pueden usar en cualquier sección y lo que hacen es reservar la


memoria indicada.

Por ejemplo, podemos usar estas directivas así:

.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

veces en distintos ficheros de código objeto (aunque aparezcan con distinto


identificador), utiliza sólo una zona de memoria para todos los códigos objetos que
accedan a ellas.

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.

Si quisiéramos reservar memoria para 4 variables de 32 bits cada una deberíamos de


haber usando .const así:

.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.

4.2.2. Secciones del segmento de datos

La siguiente tabla resume las directivas usadas para cada tipo de sección que puede
contener el segmento de datos:

Directiva Sección Descripción


.data (__DATA,__data) Sección de datos regular
.static_data (__DATA,__static_data) Almacena datos estáticos
.non_lazy_symbol (__DATA, El compilador guarda en
_pointer __nl_symbol_pointer) esta sección punteros a
símbolos non-lazy (excepto
punteros a funciones)
.lazy_symbol (__DATA, El compilador guarda en
_pointer __la_symbol_pointer) esta sección punteros a
símbolos lazy.
.dyld (__DATA,__dyld) Esta sección se usa para el
enlazado con funciones de
librerías de enlace
dinámico. Nosotros no
debemos usarla. Pág 34
www.macprogramadores.org

dinámico. Nosotros no
debemos usarla.
.const_data (__DATA,__const) Para almacenar datos
constantes en librerías de
enlace dinámico

.data Es la sección de datos regular donde se almacenan datos variables a no ser


que se especifique otra sección.

.static_data Es una sección que aunque actualmente no usa el compilador, fue


puesta para que el compilador pudiera separar datos globales y estáticos en secciones
distintas.

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.

Si aparecen en otra sección del segmento de datos, la reserva, como normalmente, se


produce en la sección donde aparecen.

Por ejemplo si hacemos:

.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

; Ahora van las instrucciones en ensamblador en la


;seccion regular del segmento de codigo
(__TEXT,__text)
lwz r4,0(r9)
lwz r5,0(r11)
.comm D, 20 ; Reserva 20 bytes sin inicializar
; en la sección (__DATA,__data)
; Mas instrucciones en la seccion regular
; del segmento de codigo (__TEXT,__text)
mtlr r0
lmw r30,-8(r1)

4.2.3. Crear nuevas secciones

Siempre podemos pedir un cambio de sección usando la directiva .section

Esta directiva tiene el formato general:

.section segmento, seccion

segmento Indica el segmento, y en principio será __TEXT o __DATA, aunque


existen más segmentos que de momento no comentaremos.

seccion Indica el nombre de la sección dentro del segmento.

Las tablas anteriores muestran el nombre que se da a cada uno de las secciones que
hemos comentado.

Obsérvese que el nombre del segmento va siempre en mayúsculas y el de la sección


en minúsculas.

Luego en vez de haber puesto:

.const
A: .long 20

Podríamos haberlo hecho con la directiva .section así:

.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

4.2.4. Agrupar las secciones

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.

Por ejemplo si en el fichero fuente tenemos:

.data
··········
··········
.const
··········
··········
.text
··········
··········
.const
··········
··········

El fichero objeto correspondiente tendrá dos segmentos: de código (__TEXT) y de


datos (__DATA), y el segmento de datos a su vez tendrá dos secciones: la de código
regular (__TEXT,__text) y las dos secciones declaradas con .const se reúnen
en una sección de código constante (__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.

4.3. El problema del indireccionamiento de memoria en las


máquinas RISC

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

Por ejemplo en x86 podemos usar la instrucción:

movb %al,dir

Para mover el byte bajo del registro AL a la dirección de memoria indicada en dir.

En máquinas RISC como PowerPC o SPARC sólo se permite el indireccionamiento


de registro.

Además, como comentamos en el Capítulo 1, todas las instrucciones de PowerPC


ocupan 32 bits, con lo que no podemos meter una dirección de memoria (de 32 bits)
dentro de la instrucción, ya que sólo la dirección de memoria ocuparía los 32 bits
disponibles para codificar la instrucción

El enfoque del indireccionamiento con registro soluciona el problema, ya que ahora la


instrucción ensamblador lo único que contiene es el número de registro donde está la
dirección de memoria a acceder. Los registros que se usan para indicar direcciones de
memoria son los GPR, de los cuales hay 32 (de r0 a r31), con lo cual la instrucción
sólo gasta 5 bits (25=32), en vez de 32 bits.

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.

o lo16(expresion) evalúa a los 16 bits bajos de expresion


o hi16(expresion) evalúa a los 16 bits altos de expresión
o ha16(expresion) evalúa a los 16 bits altos de expresión incrementando 1
si el bit del signo de lo16(expresion) es 1. Como vamos a explicar en
breve, esto permite cargar el valor correcto de una dirección de memoria en un
registro cuando este bit vale 1.

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.

Entre ellas encontramos la instrucción:

addis rD,(rA|0),SIMM /* ADD Immediate Shift */

Esta instrucción recibe tres operandos:

Pág 38
www.macprogramadores.org

rD Es el registro destino de la operación


rA Es un registro origen de la operación de suma.
SIMM Es un valor de 16 bits que actúa como segundo operando. SIMM significa
Signed IMMediate, es decir se considera como un número de 16 bits con signo.

(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.

Luego ahora podemos usar la instrucción:

addis r2,0,hi16(expr)

Esta instrucción carga el valor de SIMM en rD, e inmediatamente después desplaza


este valor a la derecha 16 posiciones, para cargar los 16 bits altos de expr en el
registro r2.

A continuación tenemos que cargar los 16 bits bajos de expr en el registro, para lo
cual podemos usar la instrucción:

ori rA,rS,UIMM /* OR Immediate */

rA es el destino de la operación
rS es uno de los operandos.
UIMM (Unsigned IMMediate) es el otro operando.

La operación calcula el OR binario entre rS y 0000||UIMM y lo deposita en rA.

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

4.4. Modos de indireccionamiento

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.

Como hemos comentado en el apartado 4.3, este indireccionamiento puede ser un


indireccionamiento inmediato, cuando en el operando almacenamos la dirección de
memoria a la que acceder (sólo en máquinas CISC), o un indireccionamiento de
registro, cuando en el operando almacenamos el registro que tiene la dirección de
memoria a la que acceder.

Es muy típico que el indireccionamiento de registro se haga sumando a el valor del


registro indireccionando una variable, lo cual permite que podamos referirnos a
direcciones de memoria contiguas (p.e. arrays, variables de un segmento). En este
caso se llama registro base al registro a cuyo contenido le sumamos una variable
llamada índice. El índice puede ser una constante codificada dentro de la instrucción,
en cuyo caso tenemos un indireccionamiento de registro base e índice inmediato, o
bien ser un segundo registro, en cuyo caso tenemos un indireccionamiento de
registro base y registro índice.

El PowerPC dispone sólo de dos modos de indireccionamiento. Estos son


relativamente pocos si los comparamos con la gran cantidad de modos de
indireccionamiento de que suelen disponer los procesadores CICS como x86 o
Motorola 68000.

En principio el disponer sólo de dos modos de indireccionamiento simplifica la


construcción de programas sin penalización en el rendimiento, es más al disponer de
pocos modos de indireccionamiento se simplifica el cableado del micro, que tiene que
descodificar menos instrucciones, mejorando el rendimiento.

Los dos modos de indireccionamiento que existen en PowerPC son:

o Indireccionamiento de registro base e índice inmediato


o Indireccionamiento de registro base y registro índice

4.4.1. Indireccionamiento de registro base e índice inmediato

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.

La siguiente figura muestra cómo se genera la dirección efectiva en las indirecciones


de registro e índice inmediato.
0 5 6 10 11 15 16 31
Codificación de la instrucción Opcode rD/rS rA d

0 15 16 31
Extensión del signo d


¿rA=0? O

+
No

GPR (rA) Dirección efectiva

Store
GPR (rS/rS) Load Memoria principal

Estas instrucciones siempre reciben un operando de la forma d(rA), que significa


que rA es el registro base, y sobre el se calcula un desplazamiento (que puede ser
positivo o negativo) dado por la variable de 16 bits con signo d.

Las siguientes tabla muestran las principales instrucciones de acceso a memoria que
usan este modo de indireccionamiento:

Instrucciones de carga de enteros con indireccionamiento de registro base e


índice inmediato

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

lhz rD,d(rA) (Load Half-word and Zero) El half-word en la dirección


efectiva d(rA)se carga en los dos bytes bajos de rD, los
demás bytes de rD quedan a 0
lhzu rD,d(rA) (Load Half-word and Zero with Update) Igual a lhz sólo
que la dirección efectiva se guarda en rA una vez realizada
la operación de carga
lha rD,d(rA) (Load Half-word Algebraic) El half-word en la dirección
efectiva d(rA)se carga en los dos bytes bajos de rD, los
demás bytes de rD se rellenan con el bit más significativo
del half-word cargado, es decir, expande el signo
lhau rD,d(rA) (Load Half-word Algebraic with Update) Igual a lha sólo
que la dirección efectiva se guarda en rA una vez realizada
la operación de carga
lwz rD,d(rA) (Load Word and Zero) El word en la dirección efectiva
d(rA)se carga en rD. Obsérvese que al medir un GPR 32
bits no carga 0 en el resto del registro si el GPR es de 32
bits, pero si lo haría si la instrucción se ejecuta en una
máquina 64 bits
lwzu rD,d(rA) (Load Word and Zero with Update) Igual a lwz sólo que la
dirección efectiva se guarda en rA una vez realizada la
operación de carga

Instrucciones de almacenamiento de enteros con indireccionamiento de registro


base e índice inmediato

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

Como ejemplo vamos a hacer un programa, en un fichero llamado suma.s, que


calcula la suma de dos números almacenados en memoria, y deposita el resultado en
una tercera variable de memoria.

/* Descripción: Programa que suma dos números


* situados en memoria
* Escrito por: Fernando López Hernández
*/

.data // Segmento de datos


SD:
.lcomm C,4 ; Reserva 4 bytes sin inicializar

.text // Segmento de código


SC:
.const // Seccion (__TEXT,__const)
A: .long 3 ; Variable constante con el
; primer operando valiendo 3
B: .long 5 ; Variable constante con el
; segundo operando valiendo 5
.text // Sección (__TEXT,__text)
.globl _main
.align 2
_main:
// Cargamos la dirección base del segmento
// de código en r8
addis r8,0,hi16(SC)
ori r8,r8,lo16(SC)
// Cargamos la dirección base del seg de datos en r9
addis r9,0,hi16(SD)
ori r9,r9,lo16(SD)
// Cargamos A, B en r2 y r3
lwz r2,lo16(A-SC)(r8)
lwz r3,lo16(B-SC)(r8)
// Calculamos la suma en r4
add r4,r2,r3
// Guardamos el resultado que tenemos en r4 en C
stw r4,lo16(C-SD)(r9)
blr

El anterior programa tiene tanto un segmento de datos (.data) como un segmento de


código (.text).

El único dato que se guarda en el segmento de datos es la variable C, ya que es la


única que va a ser modificada por el programa.

Pág 43
www.macprogramadores.org

El segmento de código tiene dos secciones:

o Una es la declarada por .const en la que declaramos las variables A y B, ya


que son variables de sólo lectura, con lo que es mejor guardarlas en el
segmento de código.
o La otra sección es una sección de código regular, declarada al volver a usar
.text, en la cual se guarda el programa en sí.

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.

En concreto hemos guardado en r8 el valor de SC y en r9 el valor de SD usando el


mecanismo de carga de una dirección en dos instrucciones que comentamos antes:

// Cargamos la dirección base del seg de código en r8


addis r8,0,hi16(SC)
ori r8,r8,lo16(SC)
// Cargamos la dirección base del seg de datos en r9
addis r9,0,hi16(SD)
ori r9,r9,lo16(SD)

Luego, cuando queramos referirnos a las demás etiquetas de un segmento sólo


tendremos que dar un desplazamiento relativo respecto al principio del segmento, es
decir, si queremos acceder a las variables A o B usamos lo16(A-SC)(r8) y
lo16(B-SC)(r8) respectivamente. Análogamente cuando queramos acceder a C
usaremos lo16(C-SD)(r9)

4.4.3. Instrucciones de acceso a memoria con actualización


de registro

Existe una variante de las instrucciones de acceso a memoria por indireccionamiento


de registro base e índice inmediato que son las instrucciones de acceso a memoria por
indireccionamiento de registro base e índice inmediato con actualización.

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).

Estas instrucciones después de realizar el acceso a memoria guardan en el registro que


estamos usando para indireccionar la memoria (que suele llamarse rA), la dirección
de memoria efectiva a la que han accedido. Esto es especialmente útil a la hora de
recorrer estructuras de datos como los arrays, donde los datos ocupan posiciones de
memoria consecutivas.

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

4.4.4. Uso del operador ha16()

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.

Con un ejemplo queda más claro.

Supongamos que queremos acceder a la dirección de 32 bits:

Pág 45
www.macprogramadores.org

dir = 01101001 01101011 10010111 00110100

Si calculamos lo16(dir), hi16(dir), ha16(dir) tenemos:

lo16(dir) = 10010111 00110100

hi16(dir) = 01101001 01101011

ha16(dir) = 01101001 01101100

Es decir, como el bit alto de lo16(dir) es 1 hemos sumado 1 a ha16(dir)

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:

hi16(dir) = 01101001 01101011 00000000 00000000

lo16(dir) = 11111111 11111111 10010111 00110100 +


_________________________________________________
dir = 01101001 01101010 10010111 00110100

Que no es el valor de la dirección efectiva dir a la que queríamos acceder.

Aquí, al ser el bit de signo de lo16(dir) negativo, se ha extendido el signo antes


de sumar.

Mientras que si la suma la hacemos de ha16(dir)+lo16(dir) si obtenemos el


valor de la dirección efectiva dir:

ha16(dir) = 01101001 01101100 00000000 00000000

lo16(dir) = 11111111 11111111 10010111 00110100 +


_________________________________________________
dir = 01101001 01101011 10010111 00110100

Podemos modificar el ejemplo anterior para que use ha16() en vez de hi16() así:

/* Descripción: Programa que suma dos números


* situados en memoria usando ha16()
* Escrito por: Fernando López Hernández
*/

.data // Segmento de datos


.lcomm C,4 ; Reserva 4 bytes sin inicializar

.text // Segmento de código


.const // Seccion (__TEXT,__const)
A: .long 3 ; Variable constante con el primer
; operando valiendo 3

Pág 46
www.macprogramadores.org

B: .long 5; Variable constante con el segundo


; operando valiendo 5
.text // Sección (__TEXT,__text)
.globl _main
.align 2
_main:
// Cargamos A en r2
addis r5,0,ha16(A)
lwz r2,lo16(A)(r5)
// Cargamos B en r3
addis r5,0,ha16(B)
lwz r2,lo16(B)(r5)
// Calculamos la suma en r4
add r4,r2,r3
// Guardamos el resultado que tenemos en r4 en C
addis r5,0,ha16(C)
stw r4,lo16(C)(r5)
blr

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)

Y luego accedemos a memoria usando un indireccionamiento de registro base e índice


inmediato:

stw r4,lo16(dir)(r5)

4.4.5. Indireccionamiento de registro base y registro índice

Vamos a ver el otro tipo de indireccionamiento a memoria de que dispone el


PowerPC: El indireccionamiento de registro base y registro índice.

La figura siguiente muestra cómo se genera la dirección efectiva en las indirecciones


de registro y registro índice.

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)


¿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:

Instrucciones de carga de enteros con indireccionamiento de registro base y


registro índice
Instrucción Descripción
lbzx rD,rA,rB (Load Byte and Zero indeXed) El byte en la dirección
efectiva (rA|0)+rB se carga en el byte bajo de rD, los
demás bytes de rD quedan a 0
lbzux rD,rA,rB (Load Byte and Zero with Update indeXed) Igual a lbzx
sólo que la dirección efectiva se guarda en rA una vez
realizada la operación de carga
lhzx rD,rA,rB (Load Half-word and Zero indeXed) El half-word en la
dirección efectiva (rA|0)+rB se carga en los dos bytes
bajos de rD, los demás bytes de rD quedan a 0
lhzux rD,rA,rB (Load Half-word and Zero with Update indeXed) Igual a
lhzx sólo que la dirección efectiva se guarda en rA una
vez realizada la operación de carga
lhax rD,rA,rB (Load Half-word Algebraic indeXed) El half-word en la
dirección efectiva (rA|0)+rB se carga en los dos bytes
bajos de rD, los demás bytes de rD se rellenan con el bit
más significativo del half-word cargado, es decir, expande
el signo
lhaux rD,rA,rB (Load Half-word Algebraic with Update indeXed) Igual a
lhax sólo que la dirección efectiva se guarda en rA una
vez realizada la operación de carga

Pág 48
www.macprogramadores.org

vez realizada la operación de carga


lwzx rD,rA,rB (Load Word and Zero indeXed) El word en la dirección
efectiva (rA|0)+rB se carga en rD. Obsérvese que al
medir un GPR 32 bits no carga 0 en el resto del registro si el
GPR es de 32 bits, pero si lo haría si es la instrucción se
ejecuta en una máquina 64 bits
lwzux rD,rA,rB (Load Word and Zero with Update indeXed) Igual a lwzx
sólo que la dirección efectiva se guarda en rA una vez
realizada la operación de carga

Instrucciones de almacenamiento de enteros con indireccionamiento de registro


base y registro índice

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

Las instrucciones con indireccionamiento de registro base y registro índice se usan


principalmente en dos contextos:

o Para acceder a elementos de un array por índice. En este caso rA puede


apuntar a la base del array, y rB contener el desplazamiento respecto al
principio del array.
o Para mantener punteros a las variables de un segmento. En este caso rA
contiene la dirección de memoria de comienzo del segmento y rB contiene el
lo16(offset) de la dirección de memoria de la variable a acceder.

Pág 49
www.macprogramadores.org

4.5. Carga y almacenamiento de bloques de bytes

PowerPC, además de las instrucciones de carga/almacenamiento que hemos visto,


dispone de operaciones que permiten cargar/almacenar muchos bytes a la vez.

Estas instrucciones lo que hacen es cargar muchos datos de memoria a los registros
indicados en la instrucción.

Vamos a estudiar estas instrucciones divididas en dos grupos:

1) Instrucciones de carga y almacenamiento de multiples words.

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)

Ambas instrucciones utilizan un indireccionamiento de registro base con índice


inmediato.

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.

Si el registro usado para indireccionar (r2 en el ejemplo anterior) hubiera estado


dentro del rango rD..r31 se sobrescribe con el valor leído de memoria.

Pág 50
www.macprogramadores.org

2) Instrucciones de carga y almacenamiento de multiples bytes (caracteres)

Estas instrucciones, al igual que antes, nos permiten leer un bloque de memoria, pero
a diferencia de antes:

o El número de bytes a leer no tiene porque ser múltiplo de 4


o La dirección de memoria donde empezamos a leer no tiene porque ser
múltiplo de 4

Las cuatro instrucciones de que disponemos son:

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

El uso del registro XER se explica en 5.2

Por ejemplo, supongamos que tenemos una cadena de 12 caracteres en la dirección de


memoria saludo y la queremos copiar en la dirección de memoria mensaje (la
cual tiene un tamaño máximo de 255 caracteres). Podríamos realizar la copia
rápidamente así:

.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

Para simplificar algunas operaciones de programación el lenguaje ensamblador de


PowerPC define una serie de mnemonics2 (palabras fáciles de recordar) que son
instrucciones que equivalen realmente a otra instrucción ensamblador, pero que
permiten recordar una operación cuyo nombre no evoca al propósito de la operación
que queremos realizar.

A lo largo del estudio de los distintos grupos de instrucciones ensamblador, vamos a


acabar con una sección dedicada a los mnemonics que existen para operaciones
comunes.

El primer mnemonic que vamos a comentar es:

mr rD,rS equivale a or rD,rS,rS

Este mnemonic lo que hace es copiar el contenido de rS a rD.

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.

PowerPC no dispone de ninguna operación de copia de datos entre registros, porque el


mnemonic mr se implementa como una instrucción or.

En PowerPC tampoco existe ninguna instrucción que cargue un número en registro.


De hecho, como comentamos antes sería imposible diseñar una operación que cargara
32 bits, ya que todas las instrucciones en PowerPC ocupaban 32 bits, pero si que sería
posible diseñar una operación que cargara 16 bits, usando los otros 16 bits para
codificar la instrucción.

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:

li rD,valor equivale a addi rD,0,valor


lis rD,valor equivale a addis rD,0,valor

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

Y si lo fueramos a hacer con los nuevos mnemonics podríamos poner:

numero = 1768937;
lis r2,ha16(numero)
li r2,lo16(numero)

Pero esto no funciona como esperamos ya que en realidad hemos hecho:

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.

Luego, es importante recordar que la forma correcta de cargar un número de 32 bits


en registro no es la dada anteriormente sino esta:

numero = 1768937;
lis r2,ha16(numero)
addi r2,r2,lo16(numero)

En la que addi suma al valor ya guardado en r2 el valor de lo16(numero)

O bien esta otra:

numero = 1768937;
lis r2,hi16(numero)
ori r2,r2,lo16(numero)

En la que usamos hi16(numero) en vez de ha16(numero), con lo que para


cargar la parte baja usamos ori, que en vez de sumar pone los bits de la parte baja.

El último mnemonic que vamos a comentar de momento, es:

la rD,d(rA) equivale a addi rD,rA,d

Este mnemonic se utiliza cuando en rA tenemos los 16 bits altos de la dirección de un


segmento y queremos obtener la dirección de variables de ese segmento como un
desplazamiento d respecto a esa dirección base, por ejemplo:

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

5. Instrucciones de trabajo con enteros


Las instrucciones de trabajo con enteros cogen sus operandos de los GPRs y depositan
el resultado de la operación en un GPR.

A no ser que se especifique lo contrario estas instrucciones tratan a los operandos


como números con signo. En caso contrario se indica en el propio nombre de la
instrucción, por ejemplo, mulhwu (MULtiply High Word Unsigned) o divwu
(DIVide Word Unsigned) son instrucciones que trabajan con números sin signo.

Para el estudio de las instrucciones de trabajo con enteros las hemos dividido en los
siguientes grupos:

o Instrucciones aritméticas con enteros


o Instrucciones de comparación de enteros
o Instrucciones lógicas con enteros
o Instrucciones de desplazamiento y rotación de enteros

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.

5.1. El registro CR (Condition Register)

El registro CR sirve para reflejar el resultado de ejecutar ciertas operaciones y


proporciona un mecanismo para realizar comprobaciones en el caso de los saltos.

Los bits de este registro están agrupados en campos de 4 bits llamados


CR0,CR1,...,CR7 tal como muestra la siguiente figura:

CR0 CR1 CR2 CR3 CR4 CR5 CR6 CR7


0 3 4 7 8 11 12 15 16 19 20 23 24 27 28 31

En cualquier momento el valor de este registro puede ser leído con la instrucción:

mfcr rD /* Move From Condition register */

Que copia el contenido del registro CR en el registro rD

También podemos modificar el contenido de cualquiera de los campos del CR


usando:

mtcrf mascara,rS /* MoveTo Condition Register Fields */

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:

mascara=255 ; 1111 1111


mtcrf mascara,r2

Si queremos modificar sólo los campos CR6 y CR7 de CR haríamos:

mascara=3 ; 0000 0011


mtcrf mascara,r2

Y si sólo queremos modificar CR0 haríamos:

mascara=128 ; 1000 0000


mtcrf mascara,r2

El principal uso de los campos de CR es almacenar el resultado de ejecutar


operaciones de comparación como por ejemplo cmp, las cuales veremos más a fondo
en el apartado 5.4, y cuyos resultados se utilizan para tomar decisiones, como por
ejemplo, los saltos.

Las instrucción cmp tiene el formato:

cmp CRF,L,rA,rB

donde CRF es el número del campo de CR donde depositar el resultado, L en las


arquitecturas de 64 bits indica si los registros se tratan como números de 32 bits o de
64 bits, pero en las máquinas de 32 bits siempre debe valer 0, y rA, rB son los
registros cuyos valores vamos a comparar.

Luego si queremos almacenar el resultado de comparar r5 y r6 en el campo CR2


podemos hacer:

cmp 2,0,r5,r6

El significado de los bits del campo destino después de realizar la operación es el de


la siguiente tabla:

Significado de los bits de los campos de CR en las instrucciones de comparación

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

Si no se especifica campo de CR, por defecto va al CR0. Por desgracia el campo L es


obligatorio indicarlo, aunque para las arquitecturas de 32 bits siempre deba valer 0.

Por ejemplo, si queremos saber si es igual el contenido de los registros r5 y r6


haríamos:

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

Aquí guardamos el resultado de la operación de comparación en CR0 (obsérvese que


no hemos indicado el campo de CR, pero si L) y después sacamos ese campo a r2,
para ver si el bit que indica la igualdad (bit 2) está activo.

El hecho de disponer de 8 campos distintos en CR nos permite almacenar los


resultados de distintas comparaciones, cada una de ellas en un campo distinto.
Además como veremos en el Capítulo 3 esto permite acelerar la ejecución de
instrucciones al reducir las dependencias entre ellas en el pipeline.

Otra forma de modificar el registro CR es como resultado de ejecutar una instrucción


en la que se ha pedido reflejar el resultado de una operación con enteros. Estas
instrucciones modifican el registro CR0 y se caracterizan porque su nombre acaba en
punto (.), como por ejemplo addi., addic. ó andis.

El valor que se guarda en CR0 viene dado por la siguiente tabla:

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]

Una característica de PowerPC que no encontramos en arquitecturas más antiguas


como x86 es que la mayoría de las instrucciones no modifican los bits de condición,
con lo que se consigue acelerar el rendimiento al desaparecer dependencias entre
instrucciones.

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.

Un buen ejemplo es cuando vamos a realizar una comparación con el resultado de la


operación aritmética.

Pág 57
www.macprogramadores.org

Por ejemplo, si queremos sumar el contenido de los registros r5 y r6 y saber si el


resultado es un cero haríamos:

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.

5.2. El registro XER

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.

La siguiente figura muestra las partes en que se divide este registro:

SO OV CA 0 0000 0000 0000 0000 0000 0 Byte count


0 1 2 24 25 31

La siguiente tabla describe el significado de cada uno de estos bits:

Bit Nombre Descripción


0 SO Summary Overflow. Este bit se activa cada vez que una instrucción
produce un overflow, y queda activo hasta que se desactiva
explícitamente usando mtspr (indicando a XER como registro a
modificar) o mcrxr
1 OV Overflow. Indica que la última instrucción a producido un overflow
2 CA Carry, Indica que la última instrucción a producido un acarreo

Pág 58
www.macprogramadores.org

3-24 - Reservado. Siempre vale 0


25-31 Byte Este campo muestra el número de bytes transferidos durante la
count ejecución de una instrucción lswx (Load String Word indeXed) o
stswx (Store String Word indeXed)

Lo que muestra este registro es el resultado de ejecutar la instrucción en su totalidad,


no el de ejecutar alguna de sus partes, por ejemplo la instrucción adde (ADD
Extended) calcula la suma de los tres operandos y fija los bits de XER en base a la
operación completa, no en base a la operación entre dos de sus tres operandos.

PowerPC diferencia entre acarreo y overflow, considerando al overflow un error


mientras que al acarreo se le considera sólo un indicador de que a la parte alta de la
suma hay que sumarle 1.

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).

El registro XER se considera un SPR (Special Purpose Register). Cuando avancemos


más veremos que existen muchos más SPR, cada uno de ellos lleva un número
asociado. A XER se le considera el SPR número 1 (SPR1) y para leerlo/modificarlo
usamos dos instrucciones que nos permiten acceder a los SPR que son:

mfspr rD,SPR /* Move From Special Purpose Register */


mtspr SPR,rS /* Move To Special Purpose Register */

Por ejemplo, si queremos leer el contenido del registro XER y pasarlo a r5 haríamos:

mfspr r5,1 ; 1 indica SPR1

Y si ahora queremos ver si está activo el bit CA (CArry) haríamos:

andis r5,r5,0x2000 ; Desactiva todos los bits menos


; el bit de CA

En el apartado 5.7.4 veremos mnemonics que nos permiten acceder al registro XER
de forma más cómoda.

Es importante diferenciar entre el bit OV que indica un overflow en la última


instrucción y el bit SO que es un bit que se activa cuando alguna instrucción produce
un overflow y se queda activo hasta que lo desactivamos (a un bit que cuando se
activa ya no se desactiva en la siguiente instrucción se le llama un bit de retención).
Esto nos permite saber si a lo largo de una secuencia de operaciones aritméticas una
de ellas ha producido un overflow.

Pág 59
www.macprogramadores.org

La forma correcta de desactivarlo sería:

mfspr r3,1 ; Cogemos en r3 el valor de XER


lis r4,0x7FFFF ; r4 es la máscara que desactiva
; el bit SO
ori r4,r4,0xFFFF
and r3,r3,r4 ; Aplicamos la máscara
mtspr 1,r3 ; Modificamos XER
mtcrxr 0 ; En CR0[CA],CR0[OV],CR0[SO] copiamos XER

Aquí estamos usando la instrucción:

mtcrxr CRF /* Move to CR XeR */

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.

5.3. Instrucciones aritméticas


5.3.1. Instrucciones aritméticas de suma

La siguiente tabla resume las instrucciones aritméticas de suma de enteros que existen
en PowerPC:

Instrucciones aritméticas de suma de enteros

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.

Para la suma con acarreo de r3+r5 vamos a usar la instrucción:

addc r7,r3,r5

Que pone la suma r3+r5 en r7, y activa el bit XER[CA] si hay acarreo.

Después para sumar r2+r4 usamos la instrucción

addeo r6,r2,r4

Que suma r2+r4+XER[CA], y deposita la suma en r6. Además la instrucción tiene


detección de overflow, que significa que activa el bit XER[OV] si se produce
desbordamiento en la suma.

Este desbordamiento se podría usar después para dar un mensaje de error.

/* Descripción: Programa que suma dos números de 64 bits


* Escrito por: Fernando López Hernández
*/

.data // Segmento de datos


.comm C,8 ; Reserva 8 bytes sin inicializar

.text // Segmento de código


.const // Seccion (__TEXT,__const)
A: .long 30 ; Parte alta de la variable A
.long 0xFFFFFFFF ; Parte baja de la variable A
B: .long 50 ; Parte alta de la variable B
.long 0xFF ; Parte baja de la variable B
.text // Sección (__TEXT,__text)
.globl _main
_main:
.align 2
// Cargamos A en r2||r3
lis r10,ha16(A)
lwz r2,lo16(A)(r10)
lwz r3,lo16(A+4)(r10)
// Cargamos B en r4||r5
lis r10,ha16(B)
lwz r4,lo16(B)(r10)
lwz r5,lo16(B+4)(r10)
// Calculamos la suma en r6||r7

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

Otra instrucción relacionada con acarreos es:

addze rD,rA /* ADD Zero extended */

Que calcula la suma rA+XER[CA] y la deposita en rD

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.

Para ello vamos a guardar el primer operando en r3||r4||r5||r6 y el segundo en


r7||r8||r9||r10||r11, y vamos a calcular la suma en
r12||r13||r14||r15.

La siguiente figura muestra la forma de utilizar las instrucciones:

r3 r4 r5 r6

r7 r8 r9 r10

addzeo r3 addzeo r4 addzeo r5


addo r11,r3,r7 addc r12,r4,r8 addc r13,r5,r9 addc r14,r6,r10

/* Descripción: Programa que suma dos números de 128 bits


* Escrito por: Fernando López Hernández
*/

.data // Segmento de datos


.comm C,16 ; Reserva 16 bytes sin incializar

.text // Segmento de código


.const // Seccion (__TEXT,__const)
A: .long 1 ; Parte alta de la variable A
.long 1 ; Parte alta de la variable A
.long 1 ; Parte baja de la variable A
.long 1 ; Parte baja de la variable A
B: .long 1 ; Parte alta de la variable B

Pág 63
www.macprogramadores.org

.long 0xFFFFFFFF ; Parte alta de la variable B


.long 0xFFFFFFFF ; Parte baja de la variable B
.long 0xFFFFFFFF ; Parte baja de la variable B
.text // Sección (__TEXT,__text)
.globl _main
_main:
.align 2
// Cargamos A en r3||r4||r5||r6
lis r20,ha16(A)
lwz r3,lo16(A)(r20)
lwz r4,lo16(A+4)(r20)
lwz r5,lo16(A+8)(r20)
lwz r6,lo16(A+12)(r20)
// Cargamos B en r7||r8||r9|r10
lis r20,ha16(B)
lwz r7,lo16(B)(r20)
lwz r8,lo16(B+4)(r20)
lwz r9,lo16(B+8)(r20)
lwz r10,lo16(B+12)(r20)
// Calculamos la suma en r11||r12||r13||14
addc r14,r6,r10
addzeo r5,r5
addc r13,r5,r9
addzeo r4,r4
addc r12,r4,r8
addzeo r3,r3
addo r11,r3,r7
// Guardamos el resultado que tenemos
// en r11||r12||r13||r14 en C
lis r20,ha16(C)
stw r11,lo16(C)(r20)
stw r12,lo16(C+4)(r20)
stw r13,lo16(C+8)(r20)
stw r14,lo16(C+12)(r20)
blr

La suma se va haciendo registro a registro de derecha a izquierda. Sumamos usando


addc con el fin de hacer un acarreo al siguiente registro, el cual lo recoge con
addzeo, la cual incrementa 1 al registro sólo si hay acarreo. La última instrucción de
suma (addo) es la única que no produce acarreo, sino que en vez de esto provoca un
overflow en caso de que haya acarreo. Este overflow se podría detectar y actuar en
consecuencia.

Obsérvese que hemos utilizado addzeo (ADD Zero Extended Overflow) y no


addze (ADD Zero Extended), esto es así porque otra cosa que podría pasar es que el
registro al que estamos pasando el acarreo (p.e. r5) este lleno, es decir, valga
0x7FFFFFFF (todos los bits a 1 menos el de signo), en cuyo caso al sumarle 1 se
produce un overflow, que el programa debería de detectar y corregir
convenientemente pasando el acarreo al siguiente registro. Nosotros, para simplificar

Pág 64
www.macprogramadores.org

el programa, y debido a que todavía no hemos estudiado las sentencias de bifurcación


no lo vamos a hacer, aunque queda explicada la necesidad de hacerlo.

5.3.2. Instrucciones aritméticas de resta

La siguiente tabla resume las instrucciones aritméticas de resta de enteros que existen
en PowerPC:

Instrucciones aritméticas de resta de enteros

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

Igual que subfmeo, sólo que CR0 se actualiza tal


como explicamos en el apartado 5.1
subfze rD,rA,rB (SUBtract From Zero Extended) Calcula
~rA+XER[CA] y lo deposita en rD.
subfze. rD,rA,rB (SUBtract From Zero Extended) Igual que subfze,
sólo que CR0 se actualiza tal como explicamos en el
apartado 5.1
subfzeo rD,rA,rB (SUBtract From Zero Extended with Overflow) Igual
que subfze, sólo que XER[OV] se pone a 1 si hay
overflow
subfzeo. rD,rA,rB (SUBtract From Zero Extended with Overflow) Igual
que subfzeo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1

Al igual que en la suma, a todos los registros que reciben como operandos estas
instrucciones se les considera números con signo.

La operación rB-rA también se puede escribir como rB+~rA+1, con lo que


podríamos implementar una resta a base de sumas.

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.

5.3.3. Instrucciones de negación aritmética

La negación aritmética lo que obtiene es el complemento a 2 de un número, es decir el


mismo número cambiado de signo. Por ejemplo el negado de 3 es -3. Formalmente la
operación de complemento a 2 se define como ~rA+1

La siguiente tabla muestra las instrucciones de negación aritmética que existen:

Instrucciones de negación aritmética con enteros

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

5.3.4. Instrucciones aritméticas de multiplicación

La siguiente tabla muestra las operaciones de multiplicación que existen en PowerPC:

Instrucciones aritméticas de multiplicación de enteros

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

La siguiente figura muestra un ejemplo de como se calcula el producto de dos


números de 32 bits y como calcularlo usando las operaciones de PowerPC.

01010101 10101010 01010101 10101010 (rA)


x 11111111 00000000 11111111 00000000 (rB)
————————————————————————————————————
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
01010101 10101010 01010101 10101010
—————————————————————————————————————————————————————————————————————————
01010101 01010101 00000000 10101001 00000001 01010100 01010110 00000000

mulhw rH,rA,rB mullw rL,rA,rB

5.3.5. Instrucciones aritméticas de división

La siguiente tabla muestra las instrucciones de división de enteros de que dispone


PowerPC:

Instrucciones aritméticas de división de enteros

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

5.4. Instrucciones de comparación de enteros

Estas instrucciones comparan números y depositan el resultado de la comparación en


uno de los campos de CR.

El principal uso que se da a las instrucciones de comparación es la toma de decisiones


en instrucciones de salto, las cuales estudiaremos en el apartado 6.

A continuación se muestran las instrucciones de comparación de que dispone


PowerPC. El operando CRFD es el campo de CR donde depositar el resultado de la
comparación. El valor concreto que depositan en este campo se explica en el apartado
5.1.
Instrucciones de comparación de enteros

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.

Todas las instrucciones de comparación permiten omitir el operando CRFD, en cuyo


caso el resultado se deposita en CR0. Por desgracia no podemos omitir el campo L,
que en las máquinas de 32 bits siempre debe valer 0.

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

Si omitimos el campo CRFD el resultado se deposita el campo CR0:

cmp 0,r3,r4

Aunque L=0 lo hemos tenido que dar.

5.5. Instrucciones lógicas con enteros

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:

Instrucciones que realizan un and lógico con enteros

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

andc rD,rA,rB (AND with Complement) Realiza un and lógico entre


rA y ~rB (complemento a 1 de rB) y el resultado lo
deposita en rD
andc. rD,rA,rB (AND with Complement) Igual que andc, sólo que
CR0 se actualiza tal como explicamos en el apartado
5.1
nand rD,rA,rB (No AND) Al resultado del and 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)

En PowerPC no existen las respectivas operaciones andi. y andis. sin punto(.),


con lo que estas operaciones siempre modifican el campo CR0.

Instrucciones que realizan un or lógico con enteros

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)

Otras instrucciones lógicas con enteros

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

eqv rD,rA,rB (Equivalent) Realiza un xor lógico entre rA y rB y el


resultado se le hace un complemento a 1 y se guarda en
rD. Es decir, en rD se guarda ~(rA^rB)
extsb rD,rS (EXTended Sign Byte) El byte bajo de rS se copia en
el byte bajo de rD, el resto de los bits de rD se rellenan
con el bit de signo del byte cogido de rS, (el bit 24), es
decir, se extiende el signo de byte cogido.
extsb. rD,rS (EXTended Sign Byte) Igual que extsb, sólo que CR0
se actualiza tal como explicamos en el apartado 5.1
extsh rD,rS (EXTended Sign Half-word) El half-word bajo de rS se
copia en el half-word bajo de rD, el resto de los bits de
rD se rellenan con el bit de signo del half-word cogido
de rS, (el bit 16), es decir, se extiende el signo de half-
word cogido.
extsh. rD,rS (EXTended Sign Half-word) Igual que extsh, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
cntlzw rD,rS (CouNT Leading Zeros Word) La cuenta de bits 0
consecutivos de rS empezando a contar por la
izquierda se guarda en rD. Esta cuenta va de 0 a 32
ambos inclusive.
cntlzw. rD,rS (CouNT Leading Zeros Word) Igual que cntlzw, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1

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.

5.6. Instrucciones de rotación y desplazamiento con


enteros

Las operaciones de rotación y desplazamiento de bits son más complicadas en


PowerPC que en otras arquitecturas, pero una vez que se entienden, son especialmente
potentes.

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.

5.6.1. Instrucciones de desplazamiento con enteros

La siguiente tabla muestra las operaciones de desplazamiento de enteros que existen:

Pág 72
www.macprogramadores.org

Instrucciones de desplazamiento con enteros

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.

Por ejemplo si hacemos:

lis r3,0xFFFF ; r3=0xFFFF0000


srawi r2,r3,4 ; r2=0xFFFFF000
srawi r2,r3,15 ; r2=0xFFFFFFFE
srawi r2,r3,16 ; r2=0xFFFFFFFF

Sin embargo, en los desplazamientos sin signo el bit de signo no es distinto de los
demás.

Por ejemplo si hacemos:

lis r3,0xFFFF ; r3=0xFFFF0000


li r4,4
srw r2,r3,r4 ; r2=0x0FFFF000
li r4,15
srw r2,r3,r4 ; r2=0x0001FFFE

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.

5.6.2. Instrucciones de rotación con enteros

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.

P.e. si tenemos MB=5 y ME=28 la máscara sería:

5 28

0000 1111 1111 1111 1111 1111 1111 1000


A esta máscara se le hace un and binario con el resultado de ejecutar la rotación y este
es el valor final que se obtiene.

Las operaciones de rotación de que dispone PowerPC son:

Instrucciones de rotación con enteros

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

rlwnm, sólo que CR0 se actualiza tal como


explicamos en el apartado 5.1
rlwimi rD,rD,N,MB,ME (Rotate Left Word Immediate then Mask Insert) El
contenido de rS se rota a la izquierda tantas veces
como diga N, del resultado se insertan los bits
indicados por los unos de la máscara en sus
respectivas posiciones del registro rD, dejando los
anteriores bits con el valor anterior que tuvieran en
rD
rlwimi. rD,rD,N,MB,ME (Rotate Left Word Immediate then Mask Insert)
Igual que rlwimi, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1

Obsérvese que sólo existen operaciones de rotación a la izquierda, y no existen sus


correspondientes operaciones de rotación a la derecha, esto es porque siguiendo el
principio de las arquitecturas RISC de proporcionar un juego de operaciones mínimas,
las operaciones de rotación n bits a la derecha se pueden conseguir haciendo 32-n
rotaciones a la izquierda.

Un ejemplo de como se utiliza rlwinm sería este: Supongamos que tenemos el


registro rS con el valor:

rS = 00000000 00000000 00000000 00000001

Y ejecutamos las operaciones:

rlwinm rD,rS,2,0,10

Obtendremos 00000000 00000000 00000000 00000100, pero como la


mascara delimita los valores que van desde MB=0 a ME=10, los elementos que caigan
fuera de este rango valdrán 0 y en rD obtenemos todos los bits a 0:

rD=00000000 00000000 00000000 00000000

Sin embargo si ejecutamos:

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:

rD=00000000 00000000 00000000 00000100

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:

rD = 11111111 11111111 11111111 11111111


rS = 00000000 00000000 00000000 00000001

Pág 75
www.macprogramadores.org

Y hacemos:

rlwimi rD,rS,2,0,10

Obtenemos rD=00000000 00111111 11111111 11111111

Y si hacemos:

rlwimi rD,rS,2,0,31

Obtenemos rD=00000000 00000000 00000000 00000100

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 para la resta con un operando inmediato

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:

Mnemonic para resta de dos registros operandos

Mnemonics Equivale a Descripción


sub rD,rA,rB subf rD,rB,rA Calcula rA-rB y lo deposita en
rD
sub. rD,rA,rB subf. rD,rB,rA Igual que sub pero dejando el
CR0 el resultado de la
comparación tal como se explicó
en el apartado 5.1
subo rD,rA,rB subfo rD,rB,rA Igual que sub sólo que activa el
bit XER[OV] si hay overflow
subo. rD,rA,rB subfo. rD,rB,rA Igual que subo pero dejando el
CR0 el resultado de la
comparación tal como se explicó
en el apartado 5.1
Pág 76
www.macprogramadores.org

comparación tal como se explicó


en el apartado 5.1
subc rD,rA,rB subfc rD,rB,rA Calcula rA-rB y lo deposita en
rD, y si hay acarreo pone
XER[CA] a 1
subc. subfc. rD,rB,rA Igual que sub. pero dejando el
rD,rA,rB CR0 el resultado de la
comparación tal como se explicó
en el apartado 5.1
subco subfco rD,rB,rA Igual que subc sólo que activa el
rD,rA,rB bit XER[OV] si hay overflow
subco. subfco. rD,rB,rA Igual que subco pero dejando el
rD,rA,rB CR0 el resultado de la
comparación tal como se explicó
en el apartado 5.1

5.7.2. Mnemonics para las operaciones de comparación

Recuérdese que las operaciones de comparación de PowerPC tienen la forma:

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:

Operación Mnemonic Equivalente a


Compare Word Immediate cmpwi CRF,rA,SIMM cmpi CRF,0,rA,SIMM
Compare Word cmpw CRF,rA,rB cmpi CRF,0,rA,rB
Compare Logical Word cmplwi cmpli CRF,0,rA,SIMM
Immediate CRF,rA,SIMM
Compare Logical Word cmplw CRF,rA,rB cmpli CRF,0,rA,rB

Los siguientes símbolos se pueden usar en lugar de sus valores numéricos:

Mnemonics para símbolos numéricos

Símbolo Valor Descripción


cr0 0 Campo CR0 de CR
cr1 1 Campo CR1 de CR
cr2 2 Campo CR2 de CR
cr3 3 Campo CR3 de CR
cr4 4 Campo CR4 de CR
cr5 5 Campo CR5 de CR
cr6 6 Campo CR6 de CR
cr7 7 Campo CR7 de CR

Pág 77
www.macprogramadores.org

Estos símbolos se pueden usar en las instrucciones de comparación cmp, cmpi,


cmpl y cmpli, así como en los mnemonics de comparación cmpw, cmpwi, cmplw
y cmplwi. Por ejemplo, en vez de poner:

cmpw 2,rA,rB

Podemos poner:

cmpw cr2,rA,rB

Que queda mucho más claro.

5.7.3. Mnemonics para operaciones de desplazamiento y


rotación

Las operaciones de desplazamiento y rotación de PowerPC proporcionan una forma


general de manipular el contenido de los registros, pero pueden resultar difíciles de
entender o de saber como se utilizan.

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:

o Extract. Nos permite sacar de un registro un campo de n bits empezando por


una posición b. Estos bits se copian en un registro destino alineados a la
izquierda o a la derecha y poniendo a 0 todos los demás bits.
o Insert. Elegimos un campo de n bits del registro origen justificado a la
izquierda o a la derecha, e insertamos este campo de bits en el registro destino
empezando por la posición b. Todos los demás bits del registro destino quedan
sin cambios.
o Rotate. Rota el contenido de un registro a la izquierda o a la derecha, pero sin
usar máscara.
o Shift. Desplazamiento lógico (sin bit de signo) del contenido de un registro a
la izquierda o a la derecha.
o Clear. Borra los n bit más a la izquierda o a la derecha de un registro
o Clear left and Shift left. Borra los b bits más a la izquierda del registro,
después desplaza los bits del registro n posiciones a la izquierda. Esta
operación se suele usar para escalar un valor que actúa como índice (y que
sabemos que no es negativo) al ancho de un elemento del array.

Instrucciones de rotación y desplazamiento

Operación Mnemonic Equivalente a


EXTract and Left extlwi rD,rS,n,b rlwinm rD,rS,b,0,n-1
Justify Word (n>0)
Immediate
EXTract and Right extrwi rD,rS,n,b rlwinm rD,rS,b+n,32-
Justify Word (n>0) n,31
Immediate
Pág 78
www.macprogramadores.org

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

Shift Left Word slwi rD,rS,n (n<32) rlwlnm rD,rA,n,0,31-n


Immediate
Shift Right Word srwi rD,rS,n (n<32) rlwlnm rDmrS,32-n,n,31
Immediate
CLeaR Left Word clrlwi rD,rS,n rlwinm rD,rS,0,n,31
Immediate (n<32)
CLeaR Right Word clrrwi rD,rS,n rlwinm rD,rS,0,0,31-n
Immediate (n<32)
CLeaR Left and Shift clrlslwi rD,rS,b,n rlwinm rD,rS,n,b-n,31-
Left Word Immediate (n≤b≤31) n

Todas estas instrucciones disponen de su equivalente versión con punto (.) que
modifican el contenido de CR0 como explicamos en el apartado 5.1

5.7.4. Mnemonics para acceder al registro XER

Aunque disponemos de las instrucciones mtspr (Move To SPR) y mfspr (Move


From SPR), que nos permiten acceder al registro XER como registro SPR1 que es,
también existen mnemonics que nos permiten acceder a este registro:

mtxer rS equivale a mtspr 1,rS


mfxer rD equivale a mfspr rD,1

5.7.5. Otros mnemonics

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:

nop equivale a or 0,0,0

Lo que hace realmente es un or binario al contenido de r0 consigo mismo.

Otro mnemonic nos permite hacer un complemento a 1 de los bits de un registro:

not rD,rS equivale a nor rD,rS,rS

Pág 79
www.macprogramadores.org

5.8. Operaciones comunes con enteros

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.

5.8.1. Valor absoluto

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.

El valor absoluto de un número se puede calcular como:

abs(a) = (a>=0) ? a : (0-a)

Es decir, si el número es positivo sería el mismo número a, y si es negativo sería 0-a

El algoritmo que vamos a explicar nos devuelve:

Si (a≥0) => a
Si (a<0) => complemento2(a)

Para ello vamos a dar los siguientes pasos:

1. Calcular b de forma que:

Si (a≥0) => b = 00000000 00000000 00000000 00000000


Si (a<0) => b = 11111111 11111111 11111111 11111111

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)

3. Calcular d como: a=c-b

De esta forma:

Si (a≥0) => d=c-0


Si (a<0) => d=c+1

Obsérvese que si a<0 entonces d=c-(-1) => d=complemento1(a)-(-1)


=> d=complemento1(a)+1 => d=complemento2(a)

Si esto lo pasamos a ensamblador tenemos:

; 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

Al acabar de ejecutar este programa r6 contiene abs(r3).

Pág 80
www.macprogramadores.org

Por ejemplo, supongamos que a=-6 veamos como opera el programa con los bits:

r3(-6) 11111111 11111111 11111111 11111010


r4(-1) 11111111 11111111 11111111 11111111 srawi r4,r3,31
r5(+5) 00000000 00000000 00000000 00000101 xor r5,r4,r3
r6(+6) 00000000 00000000 00000000 00000110 sub r6,r5,r4

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

5.8.2. Máximo y mínimo de un número sin signo

Vamos a ver ahora como podríamos calcular el máximo y mínimo de 2 números


positivos sin usar sentencias condicionales.

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

El siguiente programa muestra un ejemplo de como calcular min(a,b). Para ello


vamos a aprovechar el hecho de que la resta de dos operandos produce un acarreo si el
sustraendo es mayor que el minuendo.

; 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)

En concreto, los pasos del algoritmo son los siguientes:

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.

2. Aprovechamos el acarreo de la operación anterior para calcular d, de forma que d


valga 0 (todos los bits a 0) si la diferencia anterior fue positiva, o -1 (todos los bits a
1) si la diferencia anterior fue negativa. Para ello usamos la instrucción subfe tal
como se muestra en el programa anterior.

Pág 81
www.macprogramadores.org

3. Calculamos en otra variable e la diferencia entre b-a de la forma:

Si (b-a≥0) => e=0


Si (b-a<0) => e=b-a

Para ello usamos la operación and entre c y d

4. Calculamos el mínimo como a+e ya que:

Si (b-a≥0) => min(a,b)=a+0 => min(a,b)=a


Si (b-a<0) => min(a,b)=a+e => min(a,b)=a+(b-a)=b

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)

Es decir, ahora la regla que calcula e es al revés:

Si (b-a≥0) => e=b-a


Si (b-a<0) => e=0

Con lo que en el paso 4 obtenemos el otro número, es decir:

Si (b-a≥0) => max(a,b)=a+e => min(a,b)=a+(b-a)=b


Si (b-a<0) => min(a,b)=a+0 => min(a,b)=a

5.8.3. Máximo y mínimo de un número con signo

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:

0000 0001 (+1) 1111 1111 (-1)


1000 0001 (129) 0111 1111 (127)

Teniendo en cuenta esta apreciación, este algoritmo calcula el min(a,b)


convirtiendo los números con signo a y b a números sin signo, para ello cambia el bit
de signo a cada número y interpreta el resultado como un número sin signo.

A partir de aquí el algoritmo a aplicar es el de cálculo del mínimo de números sin


signo, que vimos antes.

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

5.8.4. Resto de una división

La mayoría de las computadoras y lenguajes de programación truncan el resultado de


una división a la parte entera, descartando el resto.

Sean n el dividendo, d el divisor, c el cociente y r el resto, la operación de división


en un ordenador se define como:

n = d*c+r

Donde:

Si (n≥0) => 0≤r<|d|


Si (n<0) => -|d|<r≤0

Pág 83
www.macprogramadores.org

Ejemplos de división serían:

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.

La única operación de división que puede producir un desbordamiento es -231/-1, ya


que el número -231 en 32 bits se representa como 0x80000000, mientras que el
positivo más grande que podemos representar en 32 bits es 231-1=0x7FFFFFFF. Si se
produce este caso excepcional y la división la hacemos usando divw el resultado es 0
(ya que no puede representar el número), mientras que si la hacemos usando divwo
el resultado es también 0 y activa el overflow.

A continuación se muestra el algoritmo que nos permite calcular el resto de una


división suponiendo que conozcamos el cociente.

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

rT acaba conteniendo el resto de la división rN%rD.

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

5.8.5. División entre una constante entera

La operación de división de enteros en PowerPC es considerablemente más lenta que


la suma, resta o multiplicación de enteros. Cuando el divisor es una constante,
podemos acelerar el proceso realizando la división mediante desplazamientos a la
derecha cuando el divisor sea múltiplo de 2, o bien mediante multiplicaciones por un
“magic number”, cuando se trate de otro divisor.

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.

División con signo entre una potencia de 2

Si el divisor es una potencia de 2, es decir, d=2k para 1≤k≤31, la división entera se


puede realizar como:

srawi rC,rN,rK
addze rC,rC

Donde rN contiene el dividendo, y rC contendrá el cociente de dividir rN/d, siendo


d=2rK.

Obsérvese que si el número es positivo, el desplazamiento a la derecha siempre divide


entre dos.
Por ejemplo si tenemos:

r3=9 00000000 00000000 00000000 00001001


r3=4 00000000 00000000 00000000 00000100 srawi r3,r3,1

Pero si el número es negativo entonces un desplazamiento a la derecha sólo divide


entre dos si el número es par. Por ejemplo:

r3=-4 11111111 11111111 11111111 11111100


r3=-2 11111111 11111111 11111111 11111110 srawi r3,r3,1

Si el número es impar nos sale uno más de lo esperado. Por ejemplo:

r3=-5 11111111 11111111 11111111 11111011


r3=-3 11111111 11111111 11111111 11111101 srawi r3,r3,1

Este trozo de programa aprovecha el hecho de que la instrucción srawi de PowerPC


activa el bit de acarreo si rN contiene un número negativo e impar, y se desplazan uno
o más bits, es decir, rK≥1, con lo que la instrucción addze del programa anterior
arregla este problema.

División con signo entre un número que no sea potencia de 2

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.

A continuación se muestran tres ejemplos de como se haría la división con los


divisores 3, 5 y 7, respectivamente. Estos ejemplos también muestran como se
obtendría el resto con una simple resta de d*c al dividendo n.

Algoritmo de la división entre 3

lis rM,0x5555 ; Cargamos el magic number en rM


ori rM,rM,0x5556; m=0x55555556 = (232+2)/3
mulhw rC,rM,rN ; c=floor(m*n/232)
srwi rT,rN,31 ; Resta 1 a c si n es negativo
add rC,rC,rT ; rC contiene el cociente
mulli rT,rC,3 ; Calcula del resto como r=n-c*3
sub rT,rN,rT ; rT contiene el resto

Algoritmo de la división entre 5

lis rM,0x6666 ; Cargamos el magic number en rM


ori rM,rM,0x6667 ; m=0x66666667 = (233+3)/5
mulhw rC,rM,rN ; c=floor(m*n/232)
srawi rC,rC,1 ; c=floor(c/2)
srwi rT,rN,31 ; Resta 1 a c si n es negativo
add rC,rC,rT ; rC contiene el cociente
mulli rT,rC,5 ; Calcula del resto como r=n-c*5
sub rT,rN,rT ; rT contiene el resto

Algoritmo de la división entre 7

lis rM,0x9249 ; Cargamos el magic number en rM


ori rM,rM,0x2493 ; m=0x92492493 = (234+5)/7 - 232
mulhw rC,rM,rN ; c=floor(m*n/232)
add rC,rC,rN ; c=floor(m*n/232)+n
srawi rC,rC,2 ; c=floor(c/4)
srwi rT,rN,31 ; Resta 1 a c si n es negativo
add rC,rC,rT ; rC contiene el cociente
mulli rT,rC,7 ; Calcula del resto como r=n-c*7
sub rT,rN,rT ; rT contiene el resto

El método general de calculo del cociente c es:

1. Multiplicar el dividendo n por el magic number m


2. Obtener los 32 bits altos del producto y desplazarlo a la derecha un número de
veces comprendido entre 0 y 31
3. Añadir 1 si n es negativo

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.

Además, en el caso de d=-7 podemos ahorrarnos la negación si usamos el siguiente


algoritmo:

lis rM,0x6DB6 ; Cargamos el magic number en rM


ori rM,0xDB6D ; m=0x6DB6DB6D = -(234+5)/7 + 232
mulhw rC,rM,rN ; c=floor(m*n/232)
sub rC,rC,rN ; c=floor(m*n/232)-n
srawi rC,rC,2 ; c=floor(c/4)
srwi rT,rC,31 ; Añade 1 a c si n es negativo
add rC,rC,rT ; rC contiene el cociente
mulli rT,rC,-7 ; Calculo del resto como r=n-c*(-7)
sub rT,rN,rT ; rT contiene el resto

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).

El magic number usado como multiplicador al dividir entre -d es casi siempre el


negativo de magic number de d, es decir, -m (p.e. para d=7, teníamos que
m=92492493, con lo que para d=-7 tenemos que m=-1*92492493
=0x6DB6DB6D). Las únicas excepciones a esta regla son d=3 y d=715.827.883

La siguiente tabla muestra los magic number y desplazamientos para los números más
comunes.

Pág 87
www.macprogramadores.org

d (decimal) m (hexadecimal) desplazamiento


-5 9999 9999 1
-3 5555 5555 1
-2k 7FFF FFFF k-1
1 - -
2k 8000 0001 k-1
3 5555 5556 0
5 6666 6666 1
6 2AAA AAAB 0
7 9249 2493 2
9 38E3 8E39 1
10 6666 6667 2
11 2E8B A2E9 1
12 2AAA AAAB 1
25 51EB 851F 3
125 1062 4DD3 3

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.

5.8.6. División de 64 bits en máquinas de 32 bits

Aunque PowerPC dispone de instrucciones que nos permiten obtener números de 64


bits como el producto de números de 32 bits, no dispone de ninguna instrucción que
nos permita dividir números de 64 bits. En este apartado vamos a hacer un programa
que nos permite dividir números de 64 bits con o sin signo. El algoritmo de división
de números sin signo que vamos a usar se comenta con más detalle en el apéndice A.

Una vez el lector entienda el algoritmo puede modificar el cociente y resto


convenientemente para realizar divisiones de números de 64 bits con signo. En este
caso es importante contemplar el caso de -263/(-1), donde el resultado esta indefinido.

El programa lo vamos a hacer en un fichero llamado divide64.s. Para referirnos


a los registros vamos a usar definiciones #define tal como explicábamos en el
apartado 3.4, que nos permiten dar nombres a los registros para recordar su utilidad
más fácilmente, de acuerdo a la siguiente tabla:

identificador Registro Descripción


dvdh r3 (DiVidenDo High) 32 bits bajos del dividendo
dvdl r4 (DiVidenDo Low) 32 bits altos del dividendo
dvsh r5 (DiVideSor High) 32 bits bajos del divisor
dvsl r6 (DiVideSor Low) 32 bits altos del divisor
coch r7 (COCiente High) 32 bits bajos del cociente
cocl r8 (COCiente Low) 32 bits altos del cociente
resh r9 (RESto High) 32 bits bajos del resto
resl r10 (RESto Low) 32 bits altos del resto

Pág 88
www.macprogramadores.org

ceros_dvd r11 Número de ceros a la izquierda del dividendo


ceros_dvs r12 Número de ceros a la izquierda del divisor
rep r13 Repeticiones del bucle de desplazamiento a la izquierda
tmp1 r14 Para cálculos temporales
tmp2 r15 Para cálculos temporales
tmp3 r16 Para cálculos temporales

El dividendo se deposita en los registros dvdh:dvdl que forman el registro de 64


bits dvd. El divisor se deposita en dvsh:dvsl que forman el registro de 64 bits
dvs. Al acabar el algoritmo en coch:cocl se depositara el cociente y en
resh:resl el resto.

La operación se realiza sobre el registro de 128 bits res:dvd. Cada iteración incluye
los siguientes pasos:

1. Desplazar la combinación res:dvd 1 bit a la izquierda. Esto carga un 1 en el


bit menos significativo de res cuando el bit más significativo de dvd sea 1, o
un 0 en el bit menos significativo de res en caso contrario.
2. Restar a res el divisor dvs. Esto calcula la resta parcial de la división.
3. Si el resultado es negativo, no modificamos res y insertamos un cero en el bit
bajo de coc
4. Si el resultado es positivo ponemos el resultado en res, y insertamos un uno
en el bit bajo de coc
5. Si el número de iteraciones es menor al ancho de dvd, volvemos al paso 1

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.

// Nombramos los registros


#define dvdh r3
#define dvdl r4
#define dvsh r5
#define dvsl r6
#define coch r7
#define cocl r8
#define resh r9
#define resl r10
#define ceros_dvd r11
#define ceros_dvs r12
#define rep r13
#define tmp1 r14
#define tmp2 r15
#define tmp3 r16

.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

// Contamos el número de 0 a la izquierda


// del dividendo
cntlzw ceros_dvd,dvdh
cntlzw tmp1,dvdl
cmpwi cr0,dvdh,0
bne cr0,eti1 ; Si (dvdh!=0) hay ceros_dvd ceros
addi ceros_dvd,tmp1,32 ; Si (dvdh==0)
; hay ceros_dvd=32+tmp1 ceros
eti1:
// Contamos el número de 0 a la izquierda del
divisor
cntlzw ceros_dvs,dvsh
cntlzw tmp1,dvsl
cmpwi cr0,dvsh,0
bne cr0,eti2 ; Si (dvsh!=0) hay ceros_dvs ceros
addi ceros_dvs,tmp1,32 ; Si (dvsh==0)
; hay ceros_dvs=32+tmp1 ceros
eti2:
// Determina el desplazamiento necesario para
// minimizar el número de iteraciones
cmpw cr0,ceros_dvs,ceros_dvs
bgt cr0,eti9 ; Si (dvs>dvd) cociente = 0
li rep,64
sub rep,rep,ceros_dvd ; Repeticiones del bucle de
; desplazamiento a la
; izquierda

// Desplazamos el dvd a la izquierda


// tantas veces como ceros a la izquierda tenga
// if (ceros_dvd>=32)
cmpwi ceros_dvd,32
blt eti3 ; si (ceros_dvd<32) goto eti3
// (Cuerpo if) Copiamos dvdl en dvdh
// y desplazamos convenientemente
mr dvdh,dvdl
lis dvdl,0
subi ceros_dvd,ceros_dvd,32
slw dvdh,dvdh,ceros_dvd
b eti5

Pág 90
www.macprogramadores.org

eti3:// (Cuerp else) Desplazamos a la izquierda


// ceros_dvd veces a dvdh:dvdl
cmpwi ceros_dvd,0 ; Si (ceros_dvd==0) goto eti5
beq eti5
mtctr ceros_dvd ; Fijamos el contador del
; bucle
eti4:add dvdh,dvdh,dvdh ; Desplazamos uno a
; izquierda sumando
addc dvdl,dvdl,dvdl ; Si acarrea lo pasamos
; a dvdh
addze dvdh,dvdh
bdnz eti4
eti5:// Empezamos el bucle de desplazamiento
lis coch,0
lis cocl,0
lis resh,0
lis resl,0
mtctr rep ; Fijamos contador del bucle
eti6:// 1. Desplazar la combinación res:dvd 1 bit
// a la izquierda
addc dvdl,dvdl,dvdl
adde dvdh,dvdh,dvdh
adde resl,resl,resl
adde resh,resh,resh
// 2. Restar a res el divisor dvs. Esto calcula
// la resta parcial de la división.
lis tmp3,0
subc tmp1,resl,dvsl
subfe tmp2,dvsh,resh
subfe tmp3,tmp3,tmp3
cmpwi tmp3,0
beq eti7 ; Si(tmp3==0) => res>=dvs
// (res<dvs) 3. Si el resultado es negativo,
// no modificamos res
// y insertamos un cero en el bit bajo de coc
addc cocl,cocl,cocl
adde coch,coch,coch
b eti8
eti7:// (res>=dvs)
//4. Si el resultado es positivo ponemos el
// resultado en res y insertamos un uno en
// el bit bajo de coc
mr resl,tmp1
mr resh,tmp2
addc cocl,cocl,cocl
adde coch,coch,coch
ori cocl,cocl,1
eti8:// 5. Si el número de iteraciones es menor al
// ancho de dvd, volvemos al paso 1
bdnz eti6
b eti10

Pág 91
www.macprogramadores.org

eti9:// Cociente==0 (dvs>dvd)


lis coch,0
lis cocl,0
mr resh,dvdh
mr resl,dvdl

eti10:// Guardamos los registros en memoria


// con stswi (Store String Word Immediate)
lis r2,ha16(c)
addi r2,r2,lo16(c)
stswi coch,r2,16

// 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.

6.1. Tipos de calculo de la dirección de salto de una


instrucción

Los saltos de las instrucciones de bifurcación pueden ser condicionales o


incondicionales. Si son condicionales se utiliza el registro CR para tomar la decisión
de si hacer o no el salto.

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.

Las instrucciones de bifurcación de salto relativo son instrucciones en las que la


dirección de salto se calcula respecto al IP (Instruction Pointer) actual, sumándole o
restándole una determinada cantidad. Al ser esta cantidad un número menor de 32 bits
si que se puede incrustar como operando inmediato en la instrucción de salto. Como
en la práctica la mayoría de los saltos se suelen hacer a direcciones cercanas a la
posición actual del IP estas instrucciones resultan muy útiles.

Recuérdese que las instrucciones de PowerPC siempre estaban alineadas al tamaño de


palabra, con lo que los dos últimos bits de la dirección de destino siempre deben de
valer 0. Las instrucciones de bifurcación relativas aprovechan esta característica para
no tener que codificar dentro de la instrucción estos dos últimos bits, sino que
aprovechan para en su lugar codifican otros 2 bits de más peso.

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

Las instrucciones de bifurcación tienen los siguientes tipos de cálculo de la dirección


destino:

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

A continuación vamos a describir como funciona cada uno de ellos.

6.1.1. Instrucciones de salto relativo

Las instrucciones de salto relativo generan la dirección de la siguiente instrucción a


ejecutar usando el campo LI de la instrucción. A este campo se le concatena al final
dos bits con 0 y se le extiende el signo, y este valor se suma al IP lo cual nos da la
dirección efectiva de salto de la instrucción.

Las instrucciones de salto relativo siempre deben tener el bit de la posición 30 AA


(Absolute Address) a 0, y el bit de la posición 31 LK (LinK) puede estar activo, en
cuyo caso se guarda la dirección siguiente a la instrucción de salto en el registro LR.
El uso de este registro lo explicaremos en el apartado 6.1.7.

La siguiente figura muestra el proceso de cálculo de la dirección de salto en las


instrucciones de salto relativo:

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

Las instrucción de salto relativo de que dispone PowerPC se resumen en la siguiente


tabla:

Pág 94
www.macprogramadores.org

Instrucciones de salto relativo

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

6.1.2. Instrucciones de salto absoluto

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.

Como en una instrucción de 32 bits no se pueden codificar los 32 bits de la dirección


de salto, se codifican sólo 24 bits en el campo LI, y después se extienden a 32 bits
concatenando 2 bits con cero al final y rellenando los bits que quedan delante con
ceros.

La siguiente figura muestra el proceso de cálculo de la dirección absoluta:


0 5 6 29 30 31
18 LI AA LK
(Codificación de la instrucción)

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.

Las instrucciones de salto absoluto siempre deben tener el bit de la posición 30 AA


(Absolute Address) a 1, y el bit de la posición 31 LK (LinK) puede estar activo, en
cuyo caso se guarda la dirección siguiente a la instrucción de salto en el registro LR.
El uso de este registro lo explicaremos en el apartado 6.1.7.

Las instrucciones de salto absoluto de que dispone PowerPC se resumen en la


siguiente tabla:

Pág 95
www.macprogramadores.org

Instrucciones de salto absoluto

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

El destino de esta instrucción se indica con una etiqueta, de la cual el ensamblador


coge los bits de la posición 6 a la 29 y los codifica en el campo LI de la instrucción.
Por ejemplo, podemos hacer:

ba fin
········
fin: blr

6.1.3. Las instrucciones de salto condicional

Las instrucciones de salto condicional tienen una codificación de acuerdo con la


siguiente figura:

0 5 6 10 11 15 16 30 31

OpCode BO BI AA LK

OpCode identifica la instrucción que vamos a codificar.

BI (Branch Input) especifica los bits de CR usados como condición a evaluar para el
salto de acuerdo a la siguiente tabla:

Configuración del operando BI

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

12 01100 Para enteros rA<SIMM o rA<UIMM o rA<rB


16 10000 Para punto flotante fA<fB
20 10100
24 11000
28 11100
9 01001 CRn[1] Mayor que:
13 01101 Para enteros rA>SIMM o rA>UIMM o rA>rB
17 10001 Para punto flotante fA>fB
21 10101
25 11001
29 11101
10 01010 CRn[2] Igual:
14 01110 Para enteros rA=SIMM o rA=UIMM o rA=rB
18 10010 Para punto flotante fA=fB
22 10110
26 11010
30 11110
11 01011 CRn[3] Summary Overflow o floating Point Unordered
15 01111
19 10011
23 10111
27 11011
31 11111

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.

También, como explicamos en el apartado 5.1 el campo CR1 lo usaban las


operaciones en punto flotante.

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.

BO (Branch Output) especifica la acción a realizar por la instrucción de salto cuando


se cumpla la condición dada por BI de acuerdo a la siguiente tabla:

Pág 97
www.macprogramadores.org

Configuración del operando BO

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

Básicamente estos 5 bits codifican 6 posibles actuaciones:

o Decrementar el registro CTR


o Comprobar si CRT es 0
o Comprobar si CTR no es cero
o Comprobar si la condición es verdadera
o Comprobar si la condición es falsa
o Predicción de salto. Lo veremos en el Capítulo 3

Los otros dos campos de la instrucción codificada son:

AA (Absolute Address), indica si se trata de un salto a una dirección relativa (AA=0) o


absoluta (AA=1)

LK (LinK), indica si antes de saltar, se copia (LK=1) o no se copia (LK=0), la


dirección de la siguiente instrucción a la de salto en registro LR, esto como veremos
en el apartado 6.1.7 sirve para poder retornar de la llamada.

6.1.4. Instrucciones condicionales de salto relativo

Estas instrucciones realizan un salto relativo si se cumple la condición. El


funcionamiento exacto de la instrucción se muestra en la siguiente figura:

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

Las instrucciones de salto condicional relativo de que dispone PowerPC se resumen


en la siguiente tabla:

Instrucciones condicionales de salto relativo

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
······························

Aquí BI vale 0, que significa en la comparación almacenó en CR0 un “menor que”,


es decir, que r2<5, que es la condición que pedía el enunciado del ejemplo y BO vale
12=01100 que significa que salte si la condición es verdadera.

6.1.5. Instrucciones condicionales de salto absoluto

La siguiente figura muestra el funcionamiento de las instrucciones condicionales de


salto absoluto:

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.

Las instrucciones condicionales de salto absoluto que existen en PowerPC son:

Pág 100
www.macprogramadores.org

Instrucciones condicionales de salto absoluto

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

6.1.6. Instrucciones condicionales de salto al Count Register

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).

El CTR es un registro especial (SPR), en concreto el SPR9, y para leerlo/modificarlo


usamos dos instrucciones que nos permiten acceder a los SPR, que como vimos en el
apartado 5.2 son:

mfspr rD,SPR /* Move From Special Purpose Register */


mtspr SPR,rS /* Move To Special Purpose Register */

La siguiente figura muestra el funcionamiento de esta instrucción.

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:

Instrucciones condicionales de salto absoluto

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

6.1.7. Instrucciones condicionales de salto al Link Register

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.

La siguiente figura muestra el funcionamiento de este tipo de instrucciones. Éstas


también tienen el bit LK con el significado habitual de guardar el valor de la siguiente
instrucción a la de salto en LR, lo cual se hace cuando este bit está a 1. En la práctica
esta opción no se usa cuando retornamos de una subrutina ya que en ese caso no
solemos guardar la dirección de la siguiente instrucción al retorno. Sin embargo esta
opción se puede usar si ponemos en LR la dirección de una subrutina a la que
queremos llamar.

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:

Instrucciones condicionales de salto al Link Register

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:

mfspr rD,SPR /* Move From Special Purpose Register */


mtspr SPR,rS /* Move To Special Purpose Register */

Pág 103
www.macprogramadores.org

6.2. Mnemonics

Vamos a empezar con mnemonics típicos para las operaciones de bifurcación.

6.2.1. Mnemonics para saltos incondicionales

Para los saltos incondicionales existen cuatro mnemonics:

Mnemonics de salto incondicional

Mnemonic Descripción Equivale a


blr (Branch to LR) Salta a la dirección de memoria bclr 20,0
almacenada en el registro LR
blrl (Branch to LR and Link) Igual que blr sólo que bclrl 20,0
en LR se almacena la dirección de memoria de la
siguiente instrucción a la instrucción de salto.
bctr (Branch to CTR) Salta a la dirección de memoria bcctr 20,0
almacenada en el registro CTR
bctrl (Branch to CTR and Link) Igual que bctr sólo bcctrl 20,0
que en LR se almacena la dirección de memoria
de la siguiente instrucción a la instrucción de
salto.

blr es un mnemonic que ya hemos usado muchas veces para retornar de una función
main().

Ninguno de estos mnemonics reciben operandos ya que la dirección de salto estará ya


almacenada en los registros LR o CTR.

6.2.2. Mnemonics para saltos condicionales

Debido a la complejidad de codificar los operandos BI y BO de las instrucciones de


salto condicional, se han creado una serie de mnemonics que se describen en las
siguientes tablas:

Mnemonics de salto condicional sin actualización de LR

Instrucción a que equivale


Condición del salto bc bca bctr bcctr
Salto si la condición se cumple bt bta btlr btctr
Salto si la condición no se cumple bf bfa bflr bfctr

Mnemonics de salto condicional con actualización de LR

Instrucción a que equivale


Condición del salto bc bca bctr bcctr
Salto si la condición se cumple btl btla btlrl btctrl

Pág 104
www.macprogramadores.org

Salto si la condición no se cumple bfl bfla bflrl bfctrl

Estos mnemonics no reciben el operando BO, pero sí que tienen que recibir 2
operandos:

o El operando BI con la condición a evaluar


o La dirección de salto si se cumple la condición

Es decir, estas instrucciones tienen la forma:

MNEMONIC BI, ETIQUETA

Por ejemplo podemos hacer:

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:

Símbolo Valor Descripción


lt 0 Less Than
gt 1 Greater Than
eq 2 EQual
so 3 Summary Overflow
un 3 UNordered

Por ejemplo en el ejemplo anterior podríamos haber hecho:

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

Y bf usaría el campo cr0 para comprobar la condición.

También se han hecho mnemonics que no reciben ni el operando BI, ni el operando


BO:

Pág 105
www.macprogramadores.org

Mnemonics de salto condicional sin actualización de LR

Instrucción a que equivale


Condición del salto bc bca bclr bcctr
Branch if less than blt blta bltlr blrctr
Branch if less than or equal ble blea blelr blectr
Branch if equal beq beqa beqlr beqctr
Branch if greater than or equal bge bgea bgelr bgectr
Branch if greater than bgt bgta bgtlr bgctr
Branch if not less than bnl bnla bnllr bnlctr
Branch if not equal bne bnea bnelr bnectr
Branch if not greater than bng bnga bnglr bngctr
Branch if summary overflow bso bsoa bsolr bsoctr
Branch if not summary overflow bns bnsa bnslr bnsctr
Branch if unordered bun buna bunlr bunctr
Branch if not unordered bnu bnua bnulr bnuctr

Mnemonics de salto condicional con actualización de LR

Instrucción a que equivale


Condición del salto bcl bcla bclrl bcctrl
Branch if less than bltl bltla bltlrl bltctrl
Branch if less than or equal blel blela blelrl blectrl
Branch if equal beql beqla beqlrl beqctrl
Branch if greater than or equal bgel bgela bgelrl bgectrl
Branch if greater than bgtl bgtla bgtlrl bgctrl
Branch if not less than bnl bnlla bnllrl bnlctrl
Branch if not equal bnel bnela bnelrl bnectrl
Branch if not greater than bngl bngla bnglrl bngctrl
Branch if summary overflow bsol bsola bsolrl bsoctrl
Branch if not summary overflow bnsl bnsla bnslrl bnsctrl
Branch if unordered bunl bunla bunlrl bunctrl
Branch if not unordered bnul bnula bnulrl bnuctrl

La siguiente tabla de abreviaturas ayuda a entender y recordar las instrucciones


anteriores:

Pág 106
www.macprogramadores.org

Tabla de abreviaturas para los mnemonics de comparación

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.

Todas estas instrucciones reciben como primer operando el campo de CR a


comprobar, y como segundo operando la dirección de salto. El primero de los
operandos se puede omitir, en cuyo caso se supone que es el CR0.

Es decir el formato general de estos mnemonics es:

MNEMONIC [CRF,] ETIQUETA

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
············

6.2.3. Mnemonics para acceder a los registros CR, CTR y LR

CTR y LR son registros especiales (en concreto SPR9 y SPR8 respectivamente), que
comentamos que podíamos acceder a ellos con las instrucciones:

mfspr rD,SPR /* Move From Special Purpose Register */


mtspr SPR,rS /* Move To Special Purpose Register */

Pág 107
www.macprogramadores.org

Además existen mnemonics que nos permiten acceder a ellos más fácilmente:

Mnemonics para acceso a los registro CTR y LR

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:

Mnemonics para acceder a un bit del registro CR

Mnemonic Descripción Equivalente a


crset B CR SET creqv B,B,B
crclr B CR CLeaR crxor B,B,B
crmove B1,B2 CR MOVE cror B1,B2,B2
crnot B1,B2 CR NOT crnor B1,B2,B2

Donde B es el bit que queremos modificar.

6.3. Implementación en ensamblador de las sentencias de


control de flujo más conocidas del lenguaje C

En esta sección vamos a detallar como se implementarían en ensamblador cada una de


la sentencias de control de flujo del lenguaje C.

6.3.1. Condicional simple y doble

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.

Por ejemplo, si queremos codificar el ensamblador la sentencia de control de flujo


siguiente:

if (a>0)
{
// Hacer esto
}
else if (a<0)
{
// Hacer lo otro
}

Pág 108
www.macprogramadores.org

else
{
// Hacer lo de mas allá
}

Haríamos algo así:

cmpwi rA,0 ; rA contiene el valor de a


ble else_if
if:
; Hacer esto
······················
b fin_if
else_if:
beq else
; Hacer lo otro
······················
b fin_if
else:
; Hacer lo de mas allá
······················
fin_if:
······················

Al no indicar a cmpwi campo de CR con el que trabajar, por defecto estamos


trabajando con CR0.

6.3.2. Condicional múltiple

La condicional múltiple en C se representa por la sentencia switch, y puede


implementarse de muchas formas: secuencias de if-else, tablas de salto, tablas
hash, progresión aritmética, algoritmos de búsqueda en árboles binarios o ternarios,
test de rango, combinaciones, etc. Nosotros vamos a ver tres formas típicas.

Imaginemos que tenemos la sentencia de control de flujo switch siguiente:

switch (x)
{
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
// Hacer algo
}

La podemos codificar en ensamblador mediante una serie de varios if-else así:

Pág 109
www.macprogramadores.org

lis r2,ha16(segmento) ; r2 apunta a los 16 bits


; altos del segmento
lwz r3,lo16(x)(r2) ; Cargamos x en r3
cmpwi cr0,r3,10
beq cr0,etiq10 ;if (x==10) goto etiq10
cmpwi cr0,r3,11
beq cr0,etiq11 ;if (x==11) goto etiq11
cmpwi cr0,r3,12
beq cr0,etiq12 ;if (x==12) goto etiq12
cmpwi cr0,r3,13
beq cr0,etiq13 ;if (x==13) goto etiq13
cmpwi cr0,r3,14
beq cr0,etiq14 ;if (x==14) goto etiq14
cmpwi cr0,r3,15
beq cr0,etiq15 ;if (x==15) goto etiq15
b fuera
etiq10:
etiq11:
etiq12:
etiq13:
etiq14:
etiq15:
;Hacer algo
··············
fuera:

También lo podemos implementar como un test de rango así:

lis r2,ha16(segmento) ; r2 base del segmento


lwz r3,lo16(x)(r2) ; Cargamos x en r3
subi r4,r3,10 ; r4 = r3-10
cmpli cr3,r4,5 ; Comparación logica (r4,5)
bgt cr3,fuera ; if r4<0 or r4>5
; Hacer algo
················
fuera:

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

Por ejemplo para codificar un switch así:

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
·················
}

Haríamos un programa en ensamblador tal que así:

lis r2,ha16(segmento) ; r2 apunta a base del segmento


lwz r3,lo16(x)(r2) ; Cargamos x en r3
lis r7,ha16(tabla) ; Carga la direccion de tabla en r7
addi r7,r7,lo16(tabla)
slwi r4,r3,2 ; Multiplica por 4 (bytes/entrada tabla)
lwzx r5,r7,r4 ; r5 = tabla[x]
mtctr r5 ; Carga el CounT Register
bctr ; Branch to CounT Register

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.

6.4. Los bucles

Recuérdese que en el Capitulo 1 comentábamos que PowerPC no es una máquina


100% RISC en el sentido de que se le habían añadido instrucciones para operaciones
comunes que aunque no eran estrictamente necesarias, ayudaban a reducir el tamaño
del programa y a ejecutar operaciones comunes más rápido. Una de estas son las
instrucciones pensadas para bucles, especialmente los bucles con contador, las cuales
ejecutan más rápido que si implementáramos el bucle con instrucciones condicionales
normales.

En el apartado 6.1.3 vimos, aunque no usamos, que el operando BO tenia formas en


las que decrementaba el registro CRT en cada comprobación, este decremento es el
que se recomienda usar en los bucles con contador, ya que se consigue mejor
rendimiento que las operaciones de restar/comprobar normales del PowerPC.

Pág 111
www.macprogramadores.org

6.4.1. Mnemonics para bucles

Para las instrucciones de salto en los bucles se han creado los siguientes mnemonics:

Mnemonics para bucles sin actualización de LR

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

Mnemonics para bucles con actualización de LR

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

6.4.2. Bucle do-while

Vamos a empezar viendo como se implementa un bucle do-while, imaginemos que


queremos calcular la suma de los 100 primeros números, es decir 1+2+3+...+100. Para
ellos podríamos hacer un bucle en C así:

int acumulador = 0;
int contador = 1;
do
{
acululador += contador;
contador++;
}
while (contador<=100);

Este programa le podemos pasar a ensamblador tal que así:

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:

Aunque al ser la condición un contador es preferible usar el registro CTR de esta


manera:

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.

6.4.3. Bucle while

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.

lis r2,ha16(texto) ; Carga la dirección de texto en r2


addi r2,r2,lo16(texto)
lis r3,0 ; r3 es el contador
bucle:
lbzu r4,1(r2) ; Lee caracter en r4, incrementa r2
cmpwi cr3,r4,0
beq fuera
add r3,r3,1 ; Incrementamos r3
b bucle ; Repetimos
fuera: ; r3 contendrá la cuenta de
; caracteres sin contar el 0

6.4.4. Bucle for

Por último vamos a implementar un bucle for en ensamblador. Este tiene


básicamente estos cuatro pasos:

for (inicialización;condición;actualización)
{
cuerpo
}

Los cuales se detallan en el diagrama:

Inicialización

No
Actualización Condición Fin

Cuerpo

En caso de que el bucle deba repetirse un determinado número de veces conocido


antes de empezar el bucle podemos usar el registro CTR. Al igual que explicamos
antes podemos mantener un contador aparte en un GPR si necesitásemos usar el
contador para los cálculos.

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.

El programa en ensamblador le vamos a hacer en un fichero llamado primo.s:

.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

6.5. Operaciones lógicas con los bits del registro CR

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.

Las operaciones de este tipo de que dispone PowerPC son:

Operaciones lógicas con campos del registro 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

La principal utilidad de estas instrucciones es hacer operaciones lógicas con los


resultados de comparaciones que aparezcan en una expresión. Por ejemplo
imaginemos que queremos pasar a ensamblador este trozo de programa C:

if (a>5 && b>3){


// Haz algo
}

Pág 116
www.macprogramadores.org

Podemos realizar las comparaciones relacionales con cmpwi, depositar cada


resultado en un campo distinto de CR y luego usar crand para comprobar ambos
resultados:

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.

Como los bits de CR están ordenados así:

CR0 CR1 CR2 CR3 CR4 CR5 CR6 CR7


0 3 4 7 8 11 12 15 16 19 20 23 24 27 28 31

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. Instrucciones de trabajo con números en punto


flotante
En este aparatado vamos a estudiar las instrucciones de que dispone PowerPC para
ejecutar operaciones con números en punto flotante. Durante su estudio vamos a
suponer que el lector ya conoce la representación de números en punto flotante tal
como está definida en el estándar IEEE 754. Si el lector no conoce este sistema de
numeración, o no lo recuerda adecuadamente, le recomendamos que lea el apartado A
antes de continuar.

7.1. Introducción

La arquitectura de PowerPC dispone de un procesador de números en punto flotante


que cumple estrictamente con el estándar IEEE 754. El procesador soporta
directamente un subconjunto de las operaciones descritas en el IEEE 754 debiéndose
implementar las demás operaciones por software.

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.

7.2. Los registros de punto flotante

El procesador de PowerPC dispone de 32 registros destinados al trabajo con números


en punto flotante llamados FPR (Floating Point Registers). Cada uno de estos
registros tiene 64 bits con lo que pueden almacenar un número en formato doble. Para
referirnos a estos registros desde el lenguaje ensamblador usaremos los nombres f0 a
f31.

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:

o Si leemos un dato de memoria en formato doble, éste se pasa directamente a


un registro FPR, mientras que cuando leemos un dato de memoria en formato
simple este se transforma a formato doble antes de almacenarse en el registro.

o Cuando pasamos un dato de un registro FPR a memoria este se puede


almacenar en memoria en formato doble directamente. También podemos
pasar un dato de un registro FPR en formato doble a memoria en formato

Pág 118
www.macprogramadores.org

simple, en cuyo la instrucción transforma el dato de formato doble a simple


antes de pasarlo a memoria.

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.

CR (Condition Register) es el mismo registro que usan las instrucciones de trabajo


con enteros o las de bifurcación, y que también lo utiliza el sistema de punto flotante.
El campo CR1 del registro CR se puede usar para reflejar el resultado de ejecutar
operaciones de punto flotante con punto (p.e. fadd.), al igual que pasaba con el
campo CR0 en las instrucciones de trabajo con enteros. También podemos usar
cualquiera de los campos del registro CR para reflejar el resultado de las
comparaciones en punto flotante.

7.3. El registro FPSCR

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.

Asignación de bits en el registro FPSCR

Campo Bit Nombre Descripción Bit


retenido
0 0 FX Floating Point Exception Summary. Cualquier Sí
excepción en punto flotante, activa
implícitamente este bit.
1 FEX Floating Point Enabled Exception Summary. No
Cualquier excepción habilitada que se
produzca, activa este bit.

Pág 119
www.macprogramadores.org

2 VX Floating Point Invalid Operation Exception No


Summary. Este bit indica la ocurrencia de
cualquiera de las Invalid Operation Exception.
3 OX Floating Point Overflow Exception. Activo Sí
cuando se produce un overflow
1 4 UX Floating Point Underflow Exception. Activo Sí
cuando se produce un underflow
5 ZX Floating Point Zero Divide Exception. Activo Sí
cuando se produce una división entre cero
6 XX Floating Point Inexact Exception. Indica que Sí
se ha tenido que hacer un redondeo.
7 VXSNAN Floating Point Invalid Operation Exception Sí
for Signaling NAN. Se produce cuando uno
de los operandos es un signaling NaN.
2 8 VXISI Floating Point Invalid Operation Exception Sí
for Infinite Sustract Infinite. Se activa cuando
pedimos calcular ∞-∞.
9 VXIDI Floating Point Invalid Operation Exception Sí
for Infinite Divide Infinite. Se produce
cuando pedimos calcular ∞/∞.
10 VXZDZ Floating Point Invalid Operation Exception Sí
for Zero Divide Zero. Se produce cuando
pedimos calcular 0/0.
11 VXIMZ Floating Point Invalid Operation Exception Sí
for Infinite Multiply Zero. Se produce cuando
pedimos calcular ∞*0.
3 12 VXVC Floating Point Invalid Operation Exception Sí
for Invalid Compare. Se produce cuando
intentamos comparar números sin relación de
orden.
13 FR Floating Point Fraction Rounded. Se pone a 1 No
si la última instrucción de redondeo o
conversión incrementó la fracción, sino se
pone a 0.
14 FI Floating Point Fraction Inexact. La última No
instrucción necesitó de redondeo.
15 FPRF Floating Point Result Class Descriptor. Las No
instrucciones aritméticas, de redondeo y de
conversión deben encender este bit para
indicar el resultado de acuerdo a la tabla del
apartado 7.3.3
4 16 Floaintg Point Less Than or Negative No
17 Floating Point Greater Than or Positive No
18 Floating Point Equal or Zero No
19 Floating Point Unordered or NaN No
5 20 - Reservado -

Pág 120
www.macprogramadores.org

21 VXSOFT Floaintg Point Invalid Operation Exception Sí


for Software Request. Permite que el
programa cause una excepción que esté
asociada a una instrucción de punto flotante.
P.e. puede ser usada por un programa que
calcula la raíz cuadrada de un número, si el
operando de entrada es negativo. Esto permite
emular instrucciones no implementadas en
hardware
22 VXSQRT Floating Point Invalid Operation Exception Sí
for Invalid Square Root.
23 VXCVI Floating Point Invalid Operation Exception Sí
for Invalid Integer Convert
6 24 VE Floating Point Invalid Operation Exception -
Enable
25 OE Floating Point Overflow Exception Enable -
26 UE Floating Point Underflow Exception Enable -
27 ZE Floating Point Zero Divide Exception Enable -
7 28 XE Floating Point Inexact Exception Enable -
29 NI Floating Point no IEEE mode. Si activamos -
este bit, los resultados no cumplen con el
estándar IEEE 754 y los demás bits de FPSCR
pueden tener significados distintos a los
indicados aquí. El funcionamiento que tendría
el procesador sería dependiente del modelo y
debe consultarse el manual de usuario del
microprocesador usado.
30 RN Floating Point Rounding Control: -
31 00 - Redondeo al más cercano (por defecto)
01 - Redondeo a cero
10 - Redondeo a +∞
11 - Redondeo a -∞

7.3.1. Instrucciones para acceder a los bits de registro


FPSCR

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:

Instrucciones para acceso a los bits de FPSCR

Instrucción Descripción Nivel acceso


mffs fD (Move From FPSCR) El contenido de Registro
mffs. fD FPSCR se deposita en los bits 32-63 de
fD. El contenido de los bits 0-31 de fD
queda indefinido.

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.

7.3.2. Los flags de excepción

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.

En concreto estos cinco flags de excepción son:

1. VX (Invalid Operation). Este flag se activa cuando se ejecuta cualquier


instrucción de punto flotante con operandos no válidos. Para concretar más la causa
de la excepción tenemos los flags:
o VXSNAN (Invalid Operation Signaling NaN). Se activa cuando uno de los
operandos es un signaled NaN.
o VXISI (Invalid Operation Infinite Sustract Infinite). Se activa cuando pedimos
calcular ∞-∞.
o VXIDI (Floating Point Invalid Operation Exception for Infinite Divide
Infinite). Se produce cuando pedimos calcular ∞/∞.
o VXZDZ (Floating Point Invalid Operation Exception for Zero Divide Zero).
Se produce cuando pedimos calcular 0/0.

Pág 122
www.macprogramadores.org

o VXIMZ (Floating Point Invalid Operation Exception for Infinite Multiply


Zero). Se produce cuando pedimos calcular ∞*0.
o VXVC (Floating Point Invalid Operation Exception for Invalid Compare). Se
produce cuando intentamos comparar números sin relación de orden.

2. OX (Overflow Exception). Se produce cuando el número calculado es tan grande


que no se puede representar en el formato utilizado y hay que representarlo como ∞.

3. UX (Underflow Exception). Se produce cuando el número calculado es tan


pequeño que no se puede representar en el formato utilizado y hay que representarlo
como 0.

4. ZX (Zero Exception). Se produce cuando intentamos dividir entre 0.

5. XX (Inexact Exception). Se activa cuando las últimas instrucciones de punto


flotante produjeron un redondeo (es un bit de retención). Para concretar más sobre el
redondeo se usan estos otros dos flags:
o FI (Fraction Inexact). Se activa cuando la última instrucción de punto flotante
produjo un redondeo (no es de retención).
o FR (Fraction rounded). Si el redondeo se hizo hacia arriba se pone a 1, si se
hizo hacia abajo se pone a 0.
El siguiente diagrama muestra más claramente cuando se activa cada uno de estos
bits:

No ¿Redondeo? Sí

FI<–0
FR<–0 FI<–1

Incrementa ¿Fracción Decrementa


da incrementada? da

FR<–1 FR<–0

Pág 123
www.macprogramadores.org

Los flags de habilitación de excepción

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:

Flag Tipo de excepción que activa


VE Invalid Operation Exception
OE Overflow Exception
UE Underflow Exception
ZE Zero Divide Exception
XE Inexact Exception

Los flags de resumen

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 flags de resumen son:

o FX (Floating Point Exception Summary). Cualquier excepción en punto


flotante activa implícitamente este bit.
o FEX (Floating Point Enabled Exception Summary). Cualquier excepción
habilitada que se produzca activa este bit.

FX se activa aunque los flags de habilitación de excepción estén deshabilitados, pero


en este caso no se activarán los flags que indican la causa de la excepción. FEX se
activa sólo si se a producido alguna excepción para la que sus flags de habilitación de
excepción estaban activos.

7.3.3. Los bits de condición y el bit de clase

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).

La siguiente tabla muestra como se deben interpretar estos bits.

Pág 124
www.macprogramadores.org

Floating Point Result Flags FPSCR[FPRF]

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í:

fadd f0,f1,f2 ; Fija los valores de FPSCR[15-19]


; de acuerdo al tipo de f0
mcrfs 2,3 ; Copia los bits FPSCR[12-15] a CR2
mcrfs 3,4 ; Copia los bits FPSCR[16-19] a CR3
bun 3,infinito ; Si el bit 3 de CR3 es 1
; el resultado es infitito o NaN
beq 3,cero ; Si CR3[2]=1
b normal ; Es un número normalizado
; o denormalizado
infinito:
bt 11,NaN ; Si BI=11, es decir, CR2[3]=1
; (Summary Overflow o Floating Point
; Unordered) f0 es un Quiet NaN
; f0 es infinito
NaN:
; f0 es NaN
cero:
; f0 es 0
normal:
bt 11,denormal ; Si BI=11, es decir, CR2[3]=1
; (Summary Overflow o Floating Point
; Unordered) indica que f0
; es un número denormalizado
; f0 es un número normalizado
denormal:
; f0 es un número denormalizado

En el programa pasamos los bits FPSCR[15-19] al registro CR para comprobar su


valor y decidir la clase del resultado de la suma de acuerdo a la tabla anterior.

Pág 125
www.macprogramadores.org

7.3.4. Los bits de redondeo

Los bits 30 y 31 de FPSCR sirven para indicar el modo de redondeo a aplicar de


acuerdo a la siguiente tabla:

Bit 30 Bit 31 Modo redondeo


0 0 Al más cercano
0 1 A cero
1 0 A +∞
1 1 A -∞

7.4. El registro CR

Si usamos las instrucciones con punto (.), el resultado de su ejecución además de


almacenarse en el registro FPSCR se almacena en el campo CR1 del registro CR.
Después podemos comprobar este campo para obtener información sobre el valor
obtenido.

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

7.5. Manejo de traps

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.

Por defecto Mac OS X usa la opción de los flags de habilitación de excepción


[pendiente comprobarlo] que se considera la más adecuada, pero si el diseñador de un
sistema lo considera oportuno (p.e. por razones de compatibilidad hacia atrás como
pasa en Linux [pendiente comprobarlo]) puede usar traps. Para ello debe encender los
registros FE0 y FE1 del registro MSR, que es un registro de superusuario que
comentaremos en el Capítulo [pendiente], con lo cual sólo puede ser modificado por
el sistema operativo.

La siguiente tabla describe los valores que pueden tomar los flags FE0 y FE1:

FE0 FE1 Descripción


0 0 Ignore Exceptions Mode. Las excepciones en punto flotante no invocan
a un handle de traps
0 1 Imprecise Nonrecoverable Mode. Cuando se produce una excepción de
punto flotante, se llama al handle de traps de la excepción. Podría no ser
posible identificar la instrucción o dato que causó la excepción porque
los datos de la instrucción que provocó la excepción pueden ser usados
por otras instrucciones de punto flotante que se estén ejecutando.
1 0 Imprecise Recoverable Mode. Cuando se produce una excepción de
punto flotante, se llama al handle de traps de la excepción. Siempre es
posible saber la instrucción y dato que produjo la instrucción porque los
datos de la instrucción que provocó la excepción no pueden ser usados
por otras instrucciones de punto flotante que se estén ejecutando.
1 1 Precise Mode. El sistema nunca ejecuta concurrentemente dos
instrucciones de punto flotante, con lo que siempre es posible saber la
instrucción que produjo la excepción. Este modo sólo debe ser usado en
depuración ya que puede degradar mucho el rendimiento del sistema.

Pág 127
www.macprogramadores.org

El uso de traps lo veremos en el Capítulo [pendiente], el resto del capítulo


supondremos que los traps están desactivados.

7.6. Instrucciones de carga y almacenamiento

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:

o Instrucciones de acceso con indireccionamiento de registro base e índice


inmediato
o Instrucciones de acceso con indireccionamiento de registro base y registro índice

Véase el apartado 4.4 para una mejor descripción de estos modos de


indireccionamiento.

En concreto las instrucciones de acceso con indireccionamiento de registro base e


índice inmediato son:

Instrucciones de carga de números en punto flotante con indireccionamiento de


registro base e índice inmediato

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

Instrucciones de almacenamiento de números en punto flotante con


indireccionamiento de registro base e índice inmediato

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

dirección de memoria apuntada por d(rA)


sfsu fD,d(rA) (Store Floating-point Single with Update) Igual a sfs, sólo
que rA se actualiza con el valor de d(rA) después de
escribir la memoria
sfd fD,d(rA) (Store Floating-point Double) El contenido de fD se guarda
en memoria, en el doble-word apuntado por d(rA)
sfdu fD,d(rA) (Store Floating-point Double with Update) Igual a sfd,
sólo que rA se actualiza con el valor de d(rA) después de
escribir la memoria

Y las instrucciones de acceso con indireccionamiento de registro base y registro


índice son:

Instrucciones de carga de números en punto flotante con indireccionamiento de


registro base y registro índice

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

Instrucciones de almacenamiento de números en punto flotante con


indireccionamiento de registro base y registro índice

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

7.7. Instrucciones aritméticas

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.

La operación de multiplicación-suma se proporciona con el fin de poder realizar


operaciones de multiplicación seguidas de una suma con un único redondeo, lo cual
proporciona más precisión que hacer dos redondeos.

La siguiente tabla describe más concretamente las instrucciones:

Instrucción Descripción Precisión


fadd fD,fA,fB (Floating Add) En fD obtenemos la suma Doble
fadd. fD,fA,fB fA+fB
fadds fD,fA,fB (Floating Add Single) En fD obtenemos la Simple
fadds. fD,fA,fB suma fA+fB
fsub fD,fA,fB (Floating Substract) En fD obtenemos el Doble
fsub. fD,fA,fB valor de fA-fB
fsubs fD,fA,fB (Floating Sustract Single) En fD obtenemos Simple
fsubs. fD,fA,fB el valor de fA-fB
fmul fD,fA,fB (Floating Multiply) En fD obtenemos el Doble
fmul. fD,fA,fB producto fA*fB
fmuls fD,fA,fB (Floating Multiply Single) En fD Simple
fmuls. fD,fA,fB obtenemos el producto fA*fB
fmadd fD,fA,fB,fC (Floating Multiply-Add) En fD obtenemos Doble
fmadd. fD,fA,fB,fC fA*fB+fC
fmadds fD,fA,fB,fC (Floating Multiply-Add Single) En fD Simple
fmadds. fD,fA,fB,fC obtenemos fA*fB+fC
fmsub fD,fA,fB,fC (Floating Multiply-Substract) En fD Doble
fmsub. fD,fA,fB,fC obtenemos fA*fB-fC
fmsubs fD,fA,fB,fC (Floating Multiply-Subtract Single) En fD Simple
fmsubs. fD,fA,fB,fC obtenemos fA*fB-fC
fnmadd fD,fA,fB,fC (Floating Negative Multiply-Add) En fD Doble
fnmadd. fD,fA,fB,fC obtenemos -(fA*fB+fC)
fnmadds fD,fA,fB,fC (Floating Negative Multiply-Add Single) En Simple
fnmadds. fD,fA,fB,fC fD obtenemos -(fA*fB+fC)
Pág 130
www.macprogramadores.org

fnmadds. fD,fA,fB,fC fD obtenemos -(fA*fB+fC)


fnmsub fD,fA,fB,fC (Floating Negative Multiply-Substract) En Doble
fnmsub. fD,fA,fB,fC fD obtenemos -(fA*fB-fC)
fnmsubs fD,fA,fB,fC (Floating Negative Multiply-Subtract Simple
fnmsubs. fD,fA,fB,fC Single) En fD obtenemos -(fA*fB-fC)
fdiv fD,fA,fB (Floating Divide) en fD obtenemos el Doble
fdiv. fD,fA,fB resultado de dividir fA/fB.
fdivs fD,fA,fB (Floating Divide Single) en fD obtenemos Simple
fdivs. fD,fA,fB el resultado de dividir fA/fB
fsqrt fD,fS (Floating SQuare RooT) en fD obtenemos Doble
fsqrt. fD,fS la raíz cuadrada de fS. Esta operación es
opcional
fsqrts fD,fS (Floating SQuare RooT Single) En fD Simple
fsqrts. fD,fS obtenemos la raíz cuadrada de fS. Esta
operación es opcional
fres fD,fS (Floating Reciprocal Estimate Simple) En Simple
fres. fD,fS fD obtenemos 1/fS. Esta operación es
opcional. No existe su correspondiente
operación para números de precisión doble
frsqrte fD,fS (Floating Reciprocal SQuare Root Estimate) Doble
frsqrte. fD,fS En fD obtenemos 1/sqrt(fS). Esta
operación es opcional. No existe su
correspondiente operación para números
con precisión simple
fsel fD,fA,fB,fC (Floating Select) El valor de fA se compara Doble
fsel. fD,fA,fB,fC con 0. Si fA es mayor o igual que 0, fB se
deposita en fD, sino fC se deposita en fD.
La comparación ignora el signo de 0 (+0 ó -
0). Esta operación es opcional.

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.

En la división decimal no se desperdicia el resto como pasa en la división entera, es


decir, si hacemos:

; 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

7.8. Instrucciones de conversión

IEEE 754 requiere que el sistema de numeración en punto flotante disponga de las
siguientes operaciones de conversión:

o De punto flotante a entero


o De entero a punto flotante
o De punto flotante a entero, con el resultado en punto flotante
o Entre todos los formatos de punto flotante que existan
o Entre punto flotante binario y punto flotante decimal

En ensamblador de PowerPC dispone de algunas de estas conversiones, debiéndose


implementar las demás conversiones por software.

PowerPC dispone de tres instrucciones 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.

Además de estas tres instrucciones, podemos realizar conversiones entre formatos


simple y doble utilizando las operaciones de carga/almacenamiento de datos en
memoria. En concreto para convertir de simple a doble podemos usar la instrucción
lfs (Load Floating-point Single) que carga un dato que tengamos en formato simple
en memoria en un dato de formato doble en registro. Para convertir de doble a simple
podemos usar la instrucción stfs (STore Floating-point Single) que almacena un
dato que tengamos en formato doble en un registro a formato simple en memoria.

Pág 132
www.macprogramadores.org

7.9. Instrucciones de comparación

Las operaciones de comparación en punto flotante comparan el contenido de dos


registros FPR (esta comparación considera que +0=-0).

La comparación puede ser de dos tipos:

o Con relación de orden. Si uno de los operandos es un quiet NaN se activa el


flag de excepción VXVC (suponiendo que el flag de habilitación de excepción
VE este activo).
o Sin relación de orden. Si uno de los operandos es un quiet NaN no activa
ningún flag de excepción.

Sea la comparación con o sin relación de orden, si se encuentra un signaling NaN se


activa el bit VXSNAN (suponiendo que el flag de habilitación de excepción VE este
activo).

En cualquier caso, en el campo de CR que hayamos especificado a la instrucción se


activaran los bits del campo de acuerdo a la siguiente tabla:

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.

Las instrucciones de comparación de que dispone PowerPC se detallan en la siguiente


tabla:

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

7.10. Instrucciones de movimiento de datos

Las instrucciones de movimiento de datos nos permiten copiar el contenido de un


registro FPR a otro, permitiéndonos modificar el signo durante la copia.

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

8. Incrustar código ensamblador en un programa C


8.1. Integración entre C y ensamblador

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

Siendo cyasm.c el programa anterior, veríamos que las instrucciones dentro de la


directiva asm aparecen en el programa ensamblador correspondiente.

8.2. Acceso a variables C desde ensamblador

Si usamos la directiva asm, un problema que acabaremos encontrándonos, es el de


cómo acceder desde ensamblador a las variables C de nuestro programa. Para ello
tenemos que conocer el name-mangling que utiliza C para sus variables. En C a todas
las variables globales y funciones se les asigna una etiqueta global que corresponde
con el nombre de la función o variable precedida por un guión bajo (_).

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

Este programa accede desde ensamblador a la variable G, usando el nombre _G y la


incrementa en 1.

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.

C++ utiliza un name-mangling distinto sobre todo a la hora de referirse a las


funciones, ya que al poder estar las funciones sobrecargadas, C++ almacena, además
del nombre de la función, los tipos de sus parámetros.

Por ejemplo si declaramos la función:

int suma(int a, int b);

El nombre que la da C es _suma

Sin embargo en C++ cuando tenemos las funciones sobrecargadas:

int suma(int a, int b);


int suma(int a, int b, int c);

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

8.3. Expresiones C como operandos de instrucciones


ensamblador

Desde las instrucciones ensamblador puestas en la directiva asm podemos usar


expresiones C como operandos de las instrucciones ensamblador. Esto permite una
mejor integración entre C y ensamblador, ya que podemos acceder a variables C que
estén guardadas en registros directamente, en vez de tener que leer un dato de
memoria y pasarlo a un registro o viceversa.

El siguiente ejemplo muestra como podemos dividir dos números usando la


instrucción ensamblador divw:

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;
}

Aquí después de la instrucción ensamblador se ponen los operandos de salida, y los


operandos de entrada separados por dos puntos (:). Si hay más de un operando se
pueden separar por comas. Si no hay operandos de salida se debe de poner dos veces
el símbolo de dos puntos. "=r" y "r" son lo que se llama constraints, y las
explicaremos en el siguiente apartado. La r significa que queremos trabajar con un
registro GPR, y en los operandos de salida es obligatorio usar el símbolo = para
indicar que es un operando de salida.

Después de las constraints se pone entre paréntesis la expresión C que queremos


asociar al operando. Aquí decimos expresión C, y no variable porque dentro del
paréntesis se pueden poner expresiones C arbitrarias como por ejemplo 2*a+b, y no
sólo el nombre de una variable. El compilador ya se encargará de buscar el registro
que contiene el resultado de evaluar esta expresión. Una restricción importante es que
los operandos de salida sólo pueden contener expresiones Lvalues (y el compilador
comprueba que sea así), mientras que los operandos de entrada pueden contener
cualquier expresión.

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.

8.3.1. Las constraints y los modificadores

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

Lo posibles modificadores que pueden acompañar a una constraint son:

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

Estos modificadores siempre e aplican al operando de salida. En caso de usarlos se


colocan delante de la constraint. P.e. “+r”

Como vimos, en PowerPC había instrucciones como:

addi rD,(rA|0),SIMM

En la que el segundo operando podía recibir o bien un 0, o bien un GPR de r1 a r31,


pero no el registro r0. De esta forma podíamos sumar 0 a SIMM y almacenar el
resultado en rD.

Esto puede producir problemas si intentamos compilar instrucciones como:

asm ( "addi %0,%1,1" : "=r" (H) : "r" (G));

Ya que si el compilador tiene la variable G en el registro r0, al compilar tenemos:

addi r3,r0,1

Y como acabamos de decir, r0 no puede ser usado como segundo operando.

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:

asm ( "addi %0,%1,1" : "=r" (H) : "b" (G));

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.

Además de poder referirnos a registros podemos referirnos a direcciones de memoria,


en cuyo caso usamos la constraint m. Por ejemplo, si hacemos:

asm ( "lwz r3,%0":/*sin salida*/:"m" (N) );

El compilador lo sustituye por:

Pág 138
www.macprogramadores.org

lis r9,r31,ha16(_N)
addi r9,lo16(_N)(r9)
lwz r3,0(r9)

Es decir, el compilador carga en un registro (r9 en nuestro caso) la dirección de


memoria de N y nos hace un indireccionamiento de registro base e índice inmediato a
la variable N.

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.

Por ejemplo este programa compilaría correctamente, pero al ejecutarlo no da el


resultado esperado:

#include <stdio.h>
int N=4;
int main () {
asm ( " addi %0,%0,1 " : "=r" (N) );
printf("N incrementada vale %i",N);
return 0;
}

La razón de fallo es que el compilador considera a %0 un parámetro de salida, con lo


que no lo carga de memoria, y el resultado del registro está indefinido.

Si queremos que un operando de salida disponga del valor correcto de la entrada


debemos marcarlo con la constraint + como muestra el siguiente ejemplo:

#include <stdio.h>
int N=4;
int main () {
asm ( " addi %0,%0,1 " : "+r" (N) );
printf("N incrementada vale %i",N);
return 0;
}

Ahora el programa si funcionaría correctamente, ya que en el registro asignado a %0


estará el valor correcto de N el cual luego será sobrescrito.

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.

En concreto, la forma correcta de hacerlo sería esta:

#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;
}

La constraint "0" puesta en el registro de entrada garantiza que el registro de entrada


y de salida sean el mismo, aunque, carga de memoria el valor del operando antes de
ejecutar la instrucción (y lo guarda después de ejecutarla).

Curiosamente para la expresión que representa el operando de entrada y de salida


podemos usar la misma expresión o expresiones distintas. La constraints "0” se
encarga de que el registro que usemos sea el mismo. Los dígitos como constraints sólo
se permiten como operandos de entrada, y deben de referirse a operandos de salida.

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:

asm ( " addi %0,%1,1 " : "=r" (N) :"r" (N) );

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.

asm ( "lwz r3,%0":/*sin salida*/:"m" (N) );

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:

asm ( "lwz r3,%0":/*sin salida*/:"m" (N): "r3" );

Aquí podemos proteger cuantos registros sea necesario poniendo su nombre entre
comillas y separándolos por coma.

También si nuestro programa ensamblador modifica el registro CR debemos de


indicarlo bajo el nombre “cc”, por ejemplo:

asm ( "lwz r3,%0 \n cmpwi r3,0 \n beq fin \n addi r3,r3,1


\n fin:" : /*sin salida*/ : "m" (N) : "r3", "cc" );

Además si nuestro programa ensamblador modifica la memoria de una forma


impredecible, debemos añadir "memory" a la lista de registros modificados.

Pág 140
www.macprogramadores.org

En principio, el compilador de C puede asignar el mismo registro a un operando de


entrada y de salida, y suponer que el dato de entrada será consumido antes de producir
la salida. Esto normalmente es cierto, pero si el programa ensamblador está formado
por varias instrucciones esto podría no ser cierto, ya que una instrucción posterior
podría intentar leer un registro que una instrucción anterior haya modificado. En este
caso debemos de poner al operando de salida la constraint "&” para evitar que se le
asigne el mismo registro que use un operando de entrada.

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.

Podemos prevenir el que el compilador aplique estas optimizaciones usando el


modificador volative:

asm volatile ( "lwz r3,%0":/*sin salida*/:"m" (N) );

8.3.2. Expresiones C en gcc 3.1

A partir de la versión 3.1 de gcc es posible especificar los operandos de entrada y de


salida usando nombres simbólicos, los cuales podemos usar luego dentro del código
ensamblador. Estos nombres se ponen entre corchetes delante de la constraint del
operando y nos podemos referir a ellos desde dentro del código ensamblador como
%[nombre], en vez de usar %n siendo n el número del parámetro. Es decir,
podemos hacer cosas como:

#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.

9.1. Tipos de datos

Mac OS X define los siguientes tipos de datos:

Tipo C o C++ Tamaño Rango Valores


(en bytes)
unsigned char 1 0 a 255
char 1 -128 a 127
signed char
unsigned short 2 0 a 65.535
short 2 -32.768 a 32.767
signed short
unsigned int 4 0 a 4.294.967.295
unsigned long
int 4 -2.147..483.648 a 2.147.483.647
signed int
long
signed long
bool 4 0 (falso) 1-4.294.967.295 (true)
unsigned long long 8 0 a 18.446.744.073.709.551.615
long long 8 -9.223.372.036.854.775.808 a
signed long long 9.223.372.036.854.775.807
float 4 Ver Apéndice A
double 8 Ver Apéndice A
long double 16 Ver Apéndice A
puntero 4 0 a 0xFFFFFFFF

Además de estos tipos la siguiente tabla muestra los tipos de datos para AltiVec.

Tipo C o C++ Tamaño Rango Valores


(en bytes)
vector unsigned char 16 (1 byte cada) 0 a 255
vector char 16 (1 byte cada) -128 a 127
vector signed char
vector unsigned 16 (2 bytes 0 a 65.535
short cada)
vector signed short 16 (2 bytes -32.768 a 32.767
cada)
vector unsigned int 16 (4 bytes 0 a 4.294.967.295
cada)
vector int 16 (4 bytes -2.147..483.648 a 2.147.483.647
cada)
Pág 142
www.macprogramadores.org

vector signed int cada)


vector bool char 16 (1 byte cada) 0 (falso) 1-255(true)
vector bool short 16 (2 bytes 0 (falso) 1-65.535 (true)
cada)
vector bool int 16 (4 bytes 0 (falso) 1-4.294.967.295(true)
cada)
vector float 16 (4 bytes Ver Apéndice A
cada)
vector pixel 16 (3 bytes formato de pixel 1/5/5
cada)

Para una descripción de AltiVec diríjase al Capítulo 3.

9.2. Mecanismo general de llamada a procedimientos

El interfaz entre dos procedimientos se define en términos de un procedimiento que


llama (caller) y un procedimiento llamado (callee). El caller computa los
parámetros que debe recibir el callee, los deposita en la pila, y llama al callee. El
callee recoge los parámetros, realiza unas operaciones y calcula un valor (o quizá no),
y retorna el control a la siguiente sentencia a la sentencia que le llamó.

9.3. Convención del uso de los registros

Los registros del procesador se clasifican en dedicados, volátiles y no volátiles. Los


registros dedicados tienen asignado un uso especifico que nosotros no deberíamos de
usar para otros propósitos. Los registros volátiles (también llamados de scratch) se
pueden usar para hacer cualquier cálculo en ellos y su contenido no se garantiza que
se mantenga tras llamar a una subrutina, con lo que nosotros tampoco tendremos que
preocuparnos de salvaguardar su valor. A estos registros también se les llama caller-
save registers, porque el procedimiento que llama es el que debe encargarse de
guardar su valor (si le interesa) antes de llamar a otro procedimiento. Por último, los
registros no volátiles son registros que podemos usar en cualquier momento, pero su
contenido debe de ser guardado antes de usarlos en el contexto del procedimiento
local, y restaurados antes de abandonar el procedimiento. A estos procedimientos
también se les llama callee-save registers porque es el procedimiento llamado quien
debe de guardar su valor antes de usarlos.

La siguiente tabla describe cual es la convención del uso de registros en Mac OS X.

Pág 143
www.macprogramadores.org

Grupo Registro Tipo Uso


registro
GPR GPR0 Volátil Usado normalmente para almacenar el LR
de retorno de la función
GPR1 Dedicado Puntero a la cima de la pila
GPR2 Volátil En Mac OS Classic era un registro
dedicado que se usaba como puntero a la
TOC (Table Of Content) o a la GOT
(Global Offset Table). Mac OS X usa un
esquema de direccionamiento diferente y
este registro es un registro de propósito
general como otro cualquiera.
GPR3-GPR10 Volátil Estos 8 registros se usan para pasar los
parámetros de las llamadas a funciones.
Una vez recibimos los parámetros se
pueden usar para scratch si se desea
GPR11 Volátil
GPR12 Volátil Contiene la dirección de salto cuando
llamamos a funciones de enlace dinámico.
Si no estamos llamado a una función de
enlace dinámico se usa como un registro
más
GPR13-GPR31 No volátil Sus valores se conservan entre llamadas a
procedimientos
FPR FPR0 Volátil Registro de scratch
FPR1-FPR13 Volátil Usado para paso de parámetros en punto
flotante. Una vez recibimos los parámetros
se pueden usar para scratch si se desea
FPR14-FPR31 No volátil Sus valores se conservan entre llamadas a
procedimientos
AltiVec v0-v2 Volátil Registros de scratch
v3-v13 Volátil Usado para el paso de parámetros de este
tipo
v14-v19 Volátil Registros de scratch
v20-v31 No volátil Sus valores se conservan entre llamadas a
procedimientos
vrsave No volátil Indica los vectores que deben ser
guardados en un cambio de contexto
SPR LR No volátil Almacena la dirección de retorno de la
rutina llamada
CTR Volátil Usado en bucles y saltos
XER Volátil Excepciones de punto fijo
FPSCR Volátil Excepciones de punto flotante
CR CR0-CR1 Volátil Para consultar el resultado de una
operación aritmética o para comparaciones
CR2-CR4 No volátil Para comparaciones
CR5-CR7 Volátil Para comparaciones

Pág 144
www.macprogramadores.org

9.4. Estructura de la pila

El modelo de programación de Mac OS X define que un proceso consta de un


segmento de código, un segmente de datos, donde se depositan las variables globales
y un segmento de pila por cada hilo activo, donde se depositan las variables locales al
hilo y con el que se lleva el control de las llamadas a funciones que haga el hilo.

La pila crece avanzando de direcciones altas a direcciones bajas, y para gestionarla se


usa un único puntero a la cima de la pila (stack pointer) almacenado, por convenio, en
el registro GPR1.

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.

La siguiente figura muestra la creación de un frame en la pila tras llamar a un


procedimiento.

Área de parámetros Área de parámetros


Caller Caller
Área de enlace Área de enlace
GPR1
Registros guardados
Nuevo
La pila crece hacia abajo

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.

9.4.1. Las áreas del frame

El frame esta dividido en las cuatro áreas que vamos a comentar a continuación:

Pág 145
www.macprogramadores.org

El área de parámetros. Se trata de un trozo de memoria donde el caller deposita los


parámetros que va a pasar al callee, es decir, en el frame de un procedimiento no están
sus parámetros sino los parámetros del procedimiento al que va a llamar. Después, el
procedimiento al que llame deberá apañárselas para acceder a los parámetros que
están en el frame del que le ha llamado.

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.

El área de registros guardados es donde un procedimiento debe almacenar el valor


de todos los registros no volátiles que vaya a usar. En caso de que el procedimiento
sólo use registros volátiles (que es lo habitual) este área medirá 0 bytes. La forma en
que el procedimiento almacene los registros se deja a elección del programador.

El área de variables locales es un área adicional de memoria que puede reservar el


procedimiento si este va a almacenar variables locales en memoria. Si todas las
variables locales se almacenan en registro este área medirá 0 bytes.

Pág 146
www.macprogramadores.org

9.5. Paso de control a un procedimiento

Cuando un procedimiento (caller) quiere pasar el control a otro (callee), en principio


lo único que tiene que hacer es usando la instrucción bl (Brach then Link), o alguna
de sus variantes, saltar a la dirección en la que se encuentra el procedimiento que
queremos ejecutar. Esta instrucción deposita en el registro LR la dirección de la
siguiente instrucción a la instrucción de salto, que es lo que llamamos la dirección de
retorno.

9.5.1. El prólogo y el epílogo

Cada procedimiento va a ser el responsable de crear y destruir su propio frame. Esta


acción se lleva a cabo por un trozo de programa llamado prólogo, que se deposita
antes del cuerpo de la rutina, y por otro llamado epílogo, que se encarga de destruir el
frame y que se pone después del cuerpo de la rutina.

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.

El prólogo es el que se encarga de crear en la pila un nuevo frame y de guardar todos


los valores que deban ser preservados. En concreto sus responsabilidades son:

o Si el registro LR va a ser modificado por una futura llamada a procedimiento,


éste debe ser guardado en el área de enlace de su caller.
o Si los campos no volátiles del registro CR (CR2-CR4) van a ser modificados,
el callee debe guardar el registro CR en el área de enlace de su caller
o Decrementar el puntero a pila para crear su nuevo frame. La especificación
dice que el puntero a frame siempre debe apuntar a direcciones múltiplos de
16, con lo que durante el decremento si es necesario se deja un padding con el
fin de cumplir esta condición.
o El callee guarda en su área de enlace el valor del puntero a pila tal como se lo
dió el caller.
o Guardar el valor de los registros no volátiles que vayan a ser modificados en el
área de registros guardados.

Obsérvese que un procedimiento puede saber en tiempo de compilación cuál es el


tamaño de su frame, para lo cual calcula la suma de cada una de las áreas que forman
el frame, tamaños que son todos conocidos en tiempo de compilación.

En la especificación se recomienda que el guardar el puntero a pila y decrementarlo se


haga en un sólo paso usando la instrucción:

stwu r1,-tamanoFrame(r1)

Pág 147
www.macprogramadores.org

La cual guarda el valor del registro r1 en la dirección apuntada por


-tamanoFrame(r1) y después decrementa el valor de r1.

A continuación se muestra un ejemplo de cómo se implementaría el prólogo.

.set tamanoFrame,16 ; Tamaño del frame


miFuncion:
; Prólogo
mflr r0 ; Recoge en r0 el LR
stw r0,8(r1) ; Guarda el LR en el área de enlace
; del caller
mfcr r0 ; Recoge en r0 el CR
stw r0,4(r1) ; Guarda el CR en el área de enlace
; del caller
stwu r1,-tamanoFrame(r1) ; Crea su frame
·········
·········

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.

Al final de ejecutar la función el epílogo destruye el frame y retorna el control al


caller. En concreto las responsabilidades del epílogo son:

o Restaurar el valor de los registros no volátiles guardados.


o Restaurar el valor del puntero a pila.
o Si se modificaron los campos no volátiles del registro CR, restaurarlo.
o Si se modificó el valor del registro LR, restaurarlo.
o Retornar a la dirección almacenada en LR.

A continuación se muestra un ejemplo de cómo implementar el epílogo.

.set tamanoFrame,16 ; Tamaño del frame


miFuncion:
·········
·········
; Epílogo
lwz r1,0(r1) ; Recoge el puntero a pila de su
; área de enlace con lo que el
; frame queda destruido
lwz r0,4(r1) ; Recoge el CR del área de enlace
; del caller
mtcrf 255,r0 ; Recupera el valor del CR
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 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;
}

Y lo vamos a compilar para generar su correspondiente código ensamblador con el


comando:

$ 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

Hemos comentado el código ensamblador generado para facilitar su comprensión.

Pág 150
www.macprogramadores.org

Vemos que el funcionamiento del compilador es básicamente el que hemos estudiado,


aunque en este caso vemos una pequeña desoptimización debida a que el registro r30
y r31 son continuamente guardados y recuperados en cada llamada a función sin
razón aparente. De hecho podríamos eliminar las instrucciones que manejan estos
registros y el programa seguiría funcionando exactamente igual.

Estas desoptimizaciones se deben a que no hemos pedido a cc ninguna optimización,


el lector puede ejecutar ahora el compilador con optimización así:

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

9.5.2. Los procedimientos terminales

Recuérdese que al estar el puntero a pila


situado en la cima de la pila (y crecer la
pila en direcciones decrecientes de
memoria) un procedimiento siempre
accede a direcciones con offset positivo
respecto al puntero a pila. Área de parámetros

Esto aunque habitual no es obligatorio, ya Área de enlace


que las posiciones que están más allá del
GPR1
puntero a pila, que es lo que se llama la
zona vacía, están sin usar y si el Zona
vacía
procedimiento lo desea también puede
usarlas.

Esto es lo que hacen los llamados


procedimientos terminales, que son
aquellos que no llaman a ningún otro procedimiento. En este caso, muchas veces,
estos procedimientos no crean un nuevo frame para ellos, sino que mantienen el
puntero a pila en la posición que lo tenia el caller, y si necesitan trabajar con variables
locales en memoria lo que hacen es usar la zona vacía.

El procedimiento terminal también guardaría el valor del CR en el área de enlace del


caller (si fuese necesario), pero al no tener que fijar un nuevo frame el trabajo
realizado por el prólogo y el epílogo es mínimo.

9.5.3. Paso de parámetros

El procedimiento de paso de parámetros que vamos a ver es el que define Mac OS X


para C. Para C++ y Objective-C estos mecanismos varian ligeramente.

Los parámetros se pasan depositando el procedimiento que llama los parámetros en su


área de parámetros y recogiéndolos el procedimiento llamado del área de parámetros
de su caller.

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.

Los parámetros se colocan en el área de parámetros con una alineación de 4 bytes, en


concreto:
o Si el parámetro mide 4 bytes se coloca a continuación sin más
o Si el parámetro es menor a 4 bytes (p.e. char o short), se alinea a 4 bytes
ocupando el parámetro la parte baja de la palabra de 4 bytes. El contenido de
los bytes más significativos queda sin definir.

Pág 152
www.macprogramadores.org

o Si el parámetro es mayor a 4 bytes (p.e. un struct o un array) se rellenan a


la derecha (posiciones altas) con un padding para ocupar un tamaño múltiplo
de 4 bytes.

Por ejemplo consideremos una rutina con el siguiente prototipo:

void hazAlgo(int pi1, float pf2,double pd3,short ps4,


double pd5, char pc6, short ps7, float pf8, int pi9);

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.

En principio esta sería la forma que tendría el área de parámetros, pero la


especificación define una optimización muy importante que dice que:

“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.

Sin embargo, la especificación es un poco caprichosa respecto a la forma correcta de


pasar estos parámetros en registros. Veamos que dice exactamente.

Pág 153
www.macprogramadores.org

En primer lugar la especificación dice que los parámetros correspondientes a las 8


primeras palabras se pasan en los registros GPR3 al GPR10. Aun así, el espacio del
área de parámetros que ocuparían estos parámetros en el área de parámetros (y que no
ocupan por pasarse en registros) debe quedar reservado (a pesar de no contener los
datos). Esto se hace por varias razones:

o Proporciona un espacio de memoria al callee para guardar el valor de los


registros si este tuviera que usar los registros con otros fines (p.e. para pasar
parámetros a una subrutina).
o Para simplificar la depuración algunos compiladores escriben los parámetros
en el área de parámetros de la memoria, esto permite al depurador ver el valor
de los parámetros con sólo leer de memoria.
o Las rutinas con un número variable de parámetros nunca usan los registros
sino que guardan los parámetros en memoria.

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

9.5.4. Funciones con un número variable de parámetros

En C podemos crear funciones que reciban un número variable de parámetros. Estas


funciones denotan la parte variable con una elipsis (...) al final de la lista de
parámetros. La función puede tener un número fijo de parámetros al principio, los
cuales suelen dar información sobre los parámetros que van después.

Un ejemplo de función de este tipo sería:

double suma(int n,...)


{
double s = 0.0;
double* p = (double*)(&n+1);
while (n>0)
{
s += p[n-1];
n--;
}
return s;
}

A la hora de ejecutarla la ejecutaríamos así:

double s = suma(3,4.5,6.7,3.2);

Pág 155
www.macprogramadores.org

En este caso todos los parámetros variables se almacenan en su correspondiente


posición del área de parámetros de la memoria, esto permite que recojamos estos
parámetros accediendo mediante un puntero a la zona de memoria que ocupan. En el
ejemplo anterior el puntero p se calcula como:

double* p = (double*)(&n+1);

Una vez tenemos el puntero al área de parámetros ya podemos leerlos.

9.5.5. Retorno de una función

Cuando una función retorna un valor, la forma de devolverlo depende del tipo del
valor retornado:

o Si es un valor escalar de 4 bytes se retorna en el registro GPR3 sin más.


o Si es un valor menor de 4 bytes (p.e. char o short) se retorna en la parte
baja del registro GPR3. El contenido de los bytes más significativos queda sin
definir.
o Los valores de tipo long long (8 bytes) se devuelven en GPR3:GPR4
conteniendo GPR3 los bytes más significativos.
o Los valores de tipo float y double se devuelven en el registro FPR1.
o Los valores de tipo long double (16 bytes) se devuelven en los registros
FPR1:FPR2 siendo la parte alta la que se coloca en FPR1.
o Los datos compuestos (p.e. estructuras y arrays) se almacenan en memoria
gestionada por el caller, y en la llamada el caller debe de indicar la dirección
de memoria donde desea obtener el retorno, poniendo en GPR3 un puntero a
esta zona de memoria. Al ser considerado GPR3 como un parámetro no se
garantiza su valor en el retorno. En consecuencia en ese tipo de funciones
GPR4 contendrá el primer parámetro de la función.

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.

Esta función ensamblador la vamos a poner en un fichero llamado factorial.s y


la vamos a llamar desde un programa C que vamos a hacer en el fichero
factorial.c

#include <stdio.h>

int factorial(int n);

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).

La función comprueba si durante la multiplicación hay acarreo, es decir, si mulhw da


un número distinto de cero, en cuyo caso es que el número calculado no cabe en un
registro de 32 bits y retorna 0 indicando que no se pudo calcular.

Pág 158
www.macprogramadores.org

APÉNDICE A: ARITMÉTICA BINARIA


En este apéndice se pretende hacer un repaso a todos los conceptos relacionados con
la representación de números, tanto en punto fijo como en punto flotante4, así como
los temas relacionados con la aritmética binaria.

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.

1. Técnicas básicas de aritmética entera


1.1. Números sin signo

Vamos a empezar viendo la aritmética con números binarios sin signo y en el


siguiente apartado comentaremos los aspectos de los números binarios con signo.

1.1.1. Suma con transmisión de acarreo

La suma de números binarios se realiza sumando los dígitos de la misma magnitud de


acuerdo a la siguiente tabla:

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

a (an-1...a1a0) y b (bn-1...b1b0) son los dígitos a sumar, s (sn-1...s1s0) es la


suma y c (cn-1...c1c0) el acarreo. Si hay acarreo en el nivel i el acarreo se pasa al
nivel i+1.

Por ejemplo para sumar 23 y 56, primero los pasamos a binario, y después aplicando
la regla anterior tenemos:

0001 0111 (23)


0011 1000 (56) +
–––––––––––––––––
0100 1111 (79)

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.

Si queremos hacer un sumador hardware que pueda calcular la suma de números de n


bits: an-1...a1a0 y bn-1...b1b0 necesitamos unos componentes llamados
semisumadores, y otros llamados sumadores completos.

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.

Al semisumador también se le denomina sumador (2,2), ya que toma 2 entradas y


produce 2 salidas. El sumador completo es un sumador (3,2) y se define por las
ecuaciones lógicas:

si = ai*(~bi)*(~ci) + (~ai)*bi*(~ci) + (~ai)*(~bi)*ci


+ ai*bi*ci
ci+1 = aibi + aici + bici

A la entrada al sumador ci se le denomina el acarreo de entrada, mientras que la


salida del sumador ci+1 es el acarreo de salida.

El problema principal a la hora de construir un sumador para números de n bits es


propagar los acarreos. La forma más obvia de resolver esto es con un sumador de
transmisión de acarreo (ripple-carry adder), que consta de n sumadores completos
tal como muestra la siguiente figura:
an-1 bn-1 an-2 bn-2 a1 b1 a0 b0
0

Sumador
Sumador
completo
Sumador
completo ······· Sumador
completo completo

cn sn-1 cn-1 sn-2 cn-3 c2 s1 c1 s0

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

1.1.2. Resta con petición de acarreo

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.

Según esto la tabla para la resta de números binarios sería la siguiente:

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:

0011 1000 (56)


0001 0111 (23) -
–––––––––––––––––
0010 0001 (33)

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:

an-1 bn-1 an-2 bn-2 a1 b1 a0 b0


1

Sumador
Sumador
completo
Sumador
completo ······· Sumador
completo completo

cn sn-1 cn-1 sn-2 cn-3 c2 s1 c1 s0

Pág 161
www.macprogramadores.org

Realmente aquí lo que estamos haciendo es:

1. Pasar el sustraendo a complemento a 2.


2. Sumar el minuendo al sustraendo en complemento a 2.
3. Descartar el overflow.

Por ejemplo si tenemos:

(minuendo) 14 1111
(sustraendo) 7 - 0111 -
ææ æææ
(diferencia) 7 0111

1. Pasamos el sustraendo a complemento a 2

Binario Complemento a 1 Complemento a 2


0111 1000 1001

2. Sumar el minuendo al sustraendo en complemento a 2

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.

Ahora podríamos construir un circuito sumador-restador como el siguiente:

an-2 bn-2 a0 b0 0 - Sumar


an-1 bn-1 a1 b1
1 - Restar
XOR
XOR XOR XOR

Sumador
Sumador
completo
Sumador
completo ······· Sumador
completo completo

cn sn-1 cn-1 sn-2 cn-3 c2 s1 c1 s0


Pág 162
www.macprogramadores.org

El circuito sumador-restador tiene una entrada adicional de control. Si esta entrada


está a 0, significa que queremos sumar, y las puertas XOR dejan pasar la misma
entrada que reciben. Si la entrada de control está a 1, significa que queremos restar,
con lo que las puertas XOR invierten la entrada y se activa el bit de acarreo.

1.1.3. Multiplicación en base 2

La multiplicación binaria es muy parecida a la multiplicación decimal, se colocan


multiplicando y multiplicador de forma que el multiplicando se multiplica por cada
uno de los bits del multiplicador convenientemente desplazado, y al final se suman.

Por ejemplo, para calcular 34*67 en binario, haríamos:

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

El algoritmo repite los siguientes dos pasos:

Pág 163
www.macprogramadores.org

1. Si el bit menos significativo de A es 1, entonces el registro B se suma con el


registro P; en caso contrario, el registro P se mantiene como está.
2. Los registros A y P se desplazan un bit a la derecha de forma que el bit menos
significativo de P se pasa al bit más significativo de A, y el bit menos
significativo de A, que no se vuelve a usar más en el algoritmo, se pierde.

Después de n pasos el producto aparece en los registros P:A, conteniendo A los bits
menos significativos.

1.1.4. División en base 2

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:

Se pode el dividendo a en el registro A, y el divisor b en el registro B. Después se


habilita un tercer registro P que inicialmente se pone a 0.

Al final de la ejecución del algoritmo A contendrá el cociente, y P el resto de la


división.

El algoritmo consiste en repetir n veces los siguientes pasos (n es el numero de bits


de los registros con los que estamos trabajando):

1. Desplazar la combinación P:A 1 bit a la izquierda. Esto carga un 1 en el bit


menos significativo de P cuando el bit más significativo de A sea 1, o un 0 en
el bit menos significativo de P en caso contrario.
2. Restar a P el divisor B. Esto calcula la resta parcial de la división.
3. Si el resultado es negativo, no modificamos P e insertamos un cero en el bit
bajo de A
4. Si el resultado es positivo ponemos el resultado en P, y insertamos un 1 en el
bit bajo de A
5. Si el número de iteraciones es menor a n, volvemos al paso 1

A continuación se muestra un ejemplo de como se ejecutaría la división: 65/15 usando


registros de 16 bits.

Pág 164
www.macprogramadores.org

Inicialmente tendremos:

A= 00000000 01000001 (65)


B= 00000000 00001111 (15)
P= 00000000 00000000 (0)

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:

A= 10000010 00000000 (65)


B= 00000000 00001111 (15)
P= 00000000 00000000 (0)

Luego los registros P:A acaban teniendo:

P:A= 00000000 00000000 10000010 00000000

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

P:A= 00000000 00000001 00000100 00000000


B= 00000000 00001111

B>P y no hacemos nada más.

11º paso:

Desplazamos

P:A= 00000000 00000010 00001000 00000000


B= 00000000 00001111

B>P y no hacemos nada más.

12º paso:

Desplazamos

P:A= 00000000 00000100 00010000 00000000


B= 00000000 00001111

B>P y no hacemos nada más.

Pág 165
www.macprogramadores.org

13º paso:

Desplazamos

P:A= 00000000 00001000 00100000 00000000


B= 00000000 00001111

B>P y no hacemos nada más.

14º paso:

Desplazamos

P:A= 00000000 00010000 01000000 00000000


B= 00000000 00001111

En este desplazamiento finalmente (P>B) con lo que calculamos P-B y lo guardamos


en P. Además ponemos un 1 en A.

Luego acabamos teniendo:

P:A= 00000000 00000001 01000000 00000001


B= 00000000 00001111

15º paso:

Desplazamos

P:A= 00000000 00000010 10000000 00000010


B= 00000000 00001111

B>P y no hacemos nada más.

16º paso:

Desplazamos

P:A= 00000000 00000101 00000000 00000100


B= 00000000 00001111

B>P y finalmente hemos acabado.

Ahora tenemos que el cociente es A=4 y que el resto está en P=5.

Obsérvese que el divisor hardware es muy parecido al multiplicador hardware. La


principal diferencia está en que el par de registros P:A se desplaza a la derecha cuando
se multiplica y a la izquierda cuando se divide. Si permitimos que el par de registros
P:A se puedan desplazar a la izquierda y a la derecha indistintamente, podemos
aprovechar el mismo hardware para hacer un multiplicador-divisor.

Pág 166
www.macprogramadores.org

1.2. Números con signo


1.2.1. Representación

Hay cuatro métodos para representar números con signo: signo-magnitud,


complemento a dos, complemento a uno y polarizado (biased). En el sistema de
signo-magnitud el bit de orden superior es el bit de signo, y los n-1 bits inferiores son
la magnitud del número. En el sistema de complemento a dos, un número y su
negativo suman 2n. En el complemento a uno, el negativo de un número se obtiene
negando cada bit. En el sistema polarizado, se toma una polarización fija de forma
que la suma de la polarización y el número que se está representando sea siempre no
negativo. Un número se representa primero sumándolo a la polarización y dicha suma
se codifica como un número ordinario sin signo.

Ejemplo: ¿Cuanto es -3 representado en cada uno de estos formatos?

La representación binaria de 3 es 0011. En signo-magnitud -3 seria 1011. En


complemento a dos 0011+1101=10000, luego sería 1101. En complemento a uno
negamos todos los bits y tenemos 1100. Usando una polarización de 8, 3 se representa
como 0011+1000=1011, es decir 3+8=11, y -3 se representa como 0101 ya que
-3+8=5.

Negar un número en complemento a 2 es fácil, sólo hay que pasarlo a complemento a


1 (negar todos sus bits) y luego sumarle 1.

Por ejemplo si queremos negar el 27 hacemos:

00011011 (27)
11100100 (complemento a 1)
1 +
————————
11100101 (-27)

Si ahora lo queremos volver a negar aplicamos el mismo procedimiento:

11100101 (-27)
00011010 (complemento a 1)
1 +
————————
00011011 (27)

1.2.2. Suma y resta de números en complemento a 2

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.

Primero empezamos calculando la representación de -17:

Pág 167
www.macprogramadores.org

00010001 (17)
11101110 (complemento a 1)
1 +
————————
11101111 (-17)

Y luego los sumamos:

00100010 (34)
11101111 (-17) +
————————
100010001 (17)

Vemos que la suma produce un acarreo en el bit de orden superior que simplemente
se descarta.

La resta de números en complemento a 2 también se hace como la resta de números


sin signo:

Por ejemplo para calcular 34-(-17) hacemos:

00100010 (+34)
11101111 (-17) -
————————
100110011 (+51)

El desbordamiento (overflow) se produce cuando el resultado de la operación no


cabe en la representación que se está utilizando. Para números sin signo detectar el
desbordamiento es fácil: se presenta justo cuando hay un acarreo de salida del bit más
significativo. Para el complemento a dos las cosas son más complicadas: el
desbordamiento se presenta, exactamente, cuando el acarreo de entrada del bit de
orden superior es diferente del acarreo de salida (que es descartado) del bit de orden
superior. En el ejemplo de la suma 34+(-17) anterior el acarreo de entrada del bit de
orden superior es 1 y el acarreo de salida también 1, con lo que no hay
desbordamiento, pero si calculamos:

(-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

1.3. Aspectos del sistema

Cuando se diseña un repertorio de instrucciones, hay una serie de cuestiones relativas


a la aritmética entera que es necesario aclarar:

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.

El problema del desbordamiento se complica por el hecho de que detectar el


desbordamiento es diferente dependiendo de si los operandos son enteros con o sin
signo.

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.

En PowerPC el enfoque que se ha seguido es el de que las instrucciones no producen


un trap, sino que activan un flag que indica que la excepción se ha producido. En las
instrucciones de punto fijo existen tres flag en el registro XER que indican que la
excepción se ha producido:

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

Una segunda cuestión está relacionada con la multiplicación. El resultado de la


multiplicación de dos números de n bits ¿deberá ser de 2n bits, o deberá devolver los
n bits de orden inferior, señalando desbordamiento si el resultado sobrepasa los n
bits?.

El argumento en favor de un resultado de n bits es que, virtualmente en todos los


lenguajes de alto nivel, la multiplicación es una operación cuyos argumentos son
variables enteras y cuyo resultado es una variable entera del mismo tipo. Por tanto no
hay forma de generar código que utilice un resultado de doble precisión. El argumento
en favor de 2n bits es que lo pueda utilizar una rutina, en lenguaje ensamblador, para
acelerar sustancialmente la multiplicación de enteros en múltiple precisión.

En PowerPC se ha buscado una solución intermedia y para multiplicar números de 32


bits usamos las instrucciones mullw (MULtiply Low Word) mulhw (MULtiply High
Word) que nos proporcionan la parte alta y baja de los 64 bits resultados del producto.
Si sólo nos interesa la parte baja (como es habitual) usamos sólo mullw.

2. Introducción al punto flotante


Se han inventado varias formas de representar números no enteros. Una de ellas es
utilizar punto fijo, es decir, utilizar aritmética entera e imaginar el punto binario en
algún sitio en medio del número. Sumar dos de tales números suele hacerse mediante
una suma entera, mientras que la multiplicación requiere algún desplazamiento extra.

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:

n = (signo) mantisa * 2exponente

Aunque esta es la fórmula que se usa para guardar un número en un ordenador, a la


que llamaremos punto flotante binario, nosotros también usaremos en nuestros
ejemplos otra representación a la que llamaremos punto flotante decimal:

n = (signo) mantisa * 10exponente

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.

Un ejemplo de representación en punto flotante decimal sería un número con un


signo negativo, una mantisa de 1,5 y un exponente de -2, lo cual está representando el
número: -1,5*10-2=-0,015.

Un ejemplo de representación en punto flotante binario sería un número con signo


positivo, una mantisa de 1.01b y un exponente de +10b, que pasado a decimal 1,01b
es el número 1,25d y +10b pasado a decimal es el +2, luego seria el número
1,25*22=5

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

Para simplificar la representación se a creado el concepto de número en punto flotante


normalizado, donde decimos que un número está normalizado si la mantisa tiene un
sólo dígito a la izquierda de la coma. P.e. -7,5*103 está normalizado, -75*102 no. En
binario también se normalizan los números. Por ejemplo 1,0110*2-11 está
normalizado, pero 1011,0*2-110 no lo está.

3. Formato de los datos en punto flotante


El estándar IEEE 754 especifica cuatro formatos para almacenar números en notación
de punto flotante: simple, doble, simple extendido, doble extendido. Todos los
formatos almacenan los tres campos que explicábamos antes, sólo que cada uno de
ellos tiene un tamaño mayor o menor que les da más o menos precisión.

A la hora de almacenar un número en punto flotante a éste se le hace una


modificación llamada empaquetamiento que consiste en:

o Normalizar la mantisa de forma que a la izquierda del punto binario aparezca


un sólo 1 y coger de esta sólo los bits que están a la derecha del punto binario,
que es a lo que se llama la fracción.
o Al exponente se le polariza (véase el apartado 1.2) con el fin de almacenar el
número como un número sin signo. A esto es a lo que se llama el exponente
polarizado, en contraposición al exponente que tenemos cuando
desempaquetamos el número que se llama exponente no polarizado. Como
polarización se utiliza 2n-1 siendo n el número de bits del campo destinado a
almacenar el exponente.

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?

Primero debemos de pasar la mantisa a notación binaria, con lo que tenemos:

mantisa = 150 = 10010110

Ahora debemos normalizar la mantisa para lo que transformamos en el número:


1,0010110 * 2-2, es decir, al mover la coma binaria 7 posiciones a la izquierda el
exponente aumenta en 7 unidades.

Ahora ya podemos representar mantisa y exponente en binario:

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:

fracción = 0010110 00000000 00000000 00000000


exponente polarizado = 01111110

Como el signo es positivo valdrá 0, con lo que la representación del número


empaquetado en una variable de tipo simple será:

signo exponente polarizado fracción


0 0111 1110 0010110 00000000 00000000 00000000
1b 8b 23b

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:

Formato IEEE Tipo C


simple float
doble double
doble extendido long double

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.

Tamaños de los campos de cada uno de los formatos del IEEE

Formato Signo Exponente Fracción


simple 1 8 23
doble 1 11 52
simple extendido 1 ≥10 ≥32
doble extendido 1 ≥16 ≥64

El formato simple extendido actualmente no lo implementa nadie, pero el doble


extendido si que está implementado, tanto por Intel, como por PowerPC y SPARC,
aunque la forma en que lo implementan varia de un microprocesador a otro.

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

signo exponente fracción

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:

sin usar signo exponente explicit leading fracción

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.

Valor especial Signo Exponente Fracción


0 0ó1 0 0
Número denormalizado 0ó1 0 cualquiera
+∞ 0 máximo 0
-∞ 1 máximo 0
NaN 0ó1 máximo !=0

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

3.1. Números denormalizados

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.

Por ejemplo, si encontramos el número simple empaquetado así:

signo exponente polarizado fracción


0 0000 0000 0010000 00000000 00000000 00000000
1b 8b 23b

Este número se interpreta como 0,001*2-128

Cuando un número es tan pequeño que ya no se puede representar como un número


normalizado se dice que el número de ha degradado (underflow).

¿Por qué el estándar permite almacenar números denormalizados en vez de


simplemente redondearlos a 0?, una razón es que de esta forma el programador puede
saber que se está acercando a un número “peligrosamente pequeño”, lo cual es
especialmente útil en el cálculo de algunos valores matemáticos como los límites. Si
por ejemplo nosotros hacemos un programa que busca calcular:

lim 1/x
x->∞

El programa podría determinar que hemos entrado en un número denormalizado y


detener un supuesto bucle. Esto es a lo que se llama el degradamiento gradual a
cero.

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

3.2. Números especiales

Otra peculiaridad del estándar IEEE 754 es que nos permite representar los valores
+∞ y -∞, para ello utiliza los siguientes patrones de bits especiales:

+∞ 0 1111111 0000000 000000000 00000000

-∞ 1 1111111 0000000 000000000 00000000

Es decir, el exponente se pone al máximo valor, la fracción se pone a cero y el bit de


signo indica si es +∞ ó -∞.

Cuando el resultado de un cálculo es tan grande que no se puede representar en el


formato utilizado se usa esta forma de devolver infinito.

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.

NaN 0 1111111 xxxxxxx xxxxxxxx xxxxxxxx

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.

Cuando realizamos una operación aritmética que produce un resultado no válido


obtenemos un signaling NaN. Si usamos este valor para ejecutar otra operación
aritmética este produce un quiet NaN. Esto es útil porque si hay un problema de
cálculo, la excepción sólo se producirá una vez.

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:

Decimal Hexadecimal Significado


1 0x01 Raíz cuadrada invalida (p.e. raíz de -1)
2 0x02 Suma invalida (p.e. (+∞)-(-∞))
4 0x04 División inválida (p.e. 0/0)
8 0x08 Multiplicación inválida (p.e. 0*∞)
9 0x09 Resto inválido (p.e. x%0)
17 0x11 Intento de convertir cadena ASCII inválida
21 0x15 Intento de crear un NaN con código cero
33 0x21 Parámetro inválido para una función trigonométrica
(p.e. sin(), cos(), tan())
34 0x22 Parámetro inválido para una función trigonométrica
inversa (p.e. acos(), asin(), atan())
36 0x24 Parámetro inválido para una función logarítmica (p.e.
log() o ln())
37 0x25 Parámetro inválido para una función exponencial (p.e.
exp())
38 0x26 Parámetro inválido para una función financiera
40 0x28 Parámetro inválido para una función hiperbólica inversa
(p.e. acosh() o asinh())
42 0x2A Parámetro inválido para una función gamma (p.e.
gamma() o lgamma())

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:

signaling NaN 0 1111111 0000000 xxxxxxxx 00000000

quiet NaN 1 1111111 1000000 xxxxxxxx 00000000

Pág 176
www.macprogramadores.org

3.3. Rangos máximos y mínimos en los números en punto


flotante

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.

Los rangos exactos se describen en la siguiente tabla:

Formato Mínimo Mínimo Máximo


denormalizado normalizado
Simple 3,5*10-46 5,8*10-39 1,7*1038
Doble 1,2*10-324 1,1*10-308 8,9*10307
Doble extendido 1,6*10-4966 1,6*10-4932 5,9*104931

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:

1,1111111 11111111 11111111 * 2126 ≈ 2127 = 1,7*1038

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:

1,0000000 00000000 00000000*2-127 = 5,8*10-39

Por último el número denormalizado más pequeño que se puede representar en


formato simple seria aquel que tiene el exponente a -128 y la mantisa con un cero a la
izquierda de la coma, y la fracción con todo ceros excepto el bit menos significativo
que estará a 1.

0,0000000 00000000 00000001*2-128 = 2-151 = 3,5*10-46

Pág 177
www.macprogramadores.org

4. El problema del redondeo en punto flotante


4.1. La precisión en punto flotante

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...

Esto provoca que al almacenar este número en formato simple (float) en un


ordenador y luego recuperarlo, en vez de volver a obtener el 183234,373 obtengamos
el 183234,3729999... Este problema se acentúa más cuando más grande es el número.

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

O sea, el número de elementos entre cada par 2n-1 y 2n es constante, aunque no su


separación, lo cual produce el efecto que indicado.

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

astrónomo al calcular la distancia de la tierra al sol, o la distancia entre galaxias,


donde unos miles de metros más o menos son inapreciables.

4.2. Error absoluto y relativo

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.

Se sabe que si un sistema de cálculo en punto flotante calcula correctamente los


valores (con precisión infinita), el error máximo que puede cometer es de 0,5 uud.
Esto se debe a que después de calcular un valor (con precisión infinita) debe
representarlo en punto flotante, con lo que el redondeo produce una perdida de
precisión máxima de 0,5 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.

4.3. Modos de redondeo

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.

El modo por defecto es el modo de redondeo al más cercano, que redondea a un


número par en caso de empate. Por ejemplo si tenemos una fracción de 3 dígitos
1,4015 se redondearía a 1,402

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

Flags RN Modo de redondeo


00 Redondeo al más cercano
01 Redondeo a 0
10 Redondeo a +∞
11 Redondeo a -∞

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

En un sistema segmentado (ver Capítulo 3 para una descripción de los sistemas


segmentados) se podrían intentar ejecutar las dos instrucciones concurrentemente,
donde la instrucción fdiv tarda más que la instrucción fadd, y si ahora se produjese
una excepción en una de ellas el gestor de traps tendría problemas para saber cual de
ellas ha producido la excepción.

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.

Las cinco excepciones que define IEEE 754 son:

Pág 181
www.macprogramadores.org

1. Invalid Operation. Ocurre si algún operando es inválido para la operación que


estamos realizando. Esto ocurre siempre que un operando sea un NaN. También
ocurre en los siguientes casos:

Operación Invalid Operation


Suma o resta Suma o resta de infinitos. P.e. (+∞)+(-∞)
Multiplicación 0*∞
División 0/0 ó ∞/∞
Resto x%y si y=0 ó x=±∞
Raíz cuadrada Con un operando negativo
Comparación Cuando los operandos son ±∞ o NaN

2. Underflow. Ocurre cuando el resultado de la operación es demasiado pequeño para


ser almacenado en el formato utizado. En este caso el número toma el valor 0 y activa
el flag de excepción.

3. Overflow. Ocurre cuando el número obtenido es demasiado grande para ser


almacenado en el formato utilizado. En este caso el número toma el valor ±∞ y activa
el flag de excepción.

4. Divide-by-zero. Ocurre cuando dividimos un número entre cero. También ocurre


cuando intentamos calcular el logaritmo de 0 que es -∞.

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. Suma en punto flotante


Hay dos diferencias entre la aritmética en punto flotante y la aritmética entera:
Debemos mantener un campo para el signo, otro para exponente y otro para la parte
fraccionaria, y el resultado de una operación en punto flotante, habitualmente, se ha
de redondear al número más cercano representable en el formato utilizado.

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:

El resultado de una operación aritmética entre dos números en punto flotante, ha de


ser el mismo que si primero se realizase la operación con precisión infinita, y después
se redondease ese resultado a un número representable en el formato que estemos
utilizando, usando el método de redondeo que este actualmente activo.

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.

En el caso de la suma, para obtener el resultado de la forma pedida lo único que


tenemos que hacer es añadir dos bits de guarda al final de los registros sumadores, y
un bit de retención (stricky bit). Veamos cómo se hace esto.

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:

El primer caso requiere redondeo debido al acarreo de salida a la izquierda. Por


ejemplo:

2,34*102
8,51*102 +
––––––––
10,85*102 ––––> Redondea a 10,8*102

El segundo caso requiere redondeo debido a exponentes desiguales, Por ejemplo:

2,34*102
2,56*100 +
––––––––
2,3656*102 ––––> Redondea a 2,37*102

De hecho es posible que se den a la vez ambas formas:

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.

La situación peor seria una situación como esta:

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.

6.2. El algoritmo de la suma

Las notaciones ei y mi se utilizan aquí para referirnos al exponente y la mantisa


desempaquetados del número en punto flotante ai. Suponiendo que los números en
punto flotante a1 y a2 no contengan valores especiales, el procedimiento básico para
sumarlos consta de cinco pasos:

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

durante la suma desplazar una posición a la derecha el resultado así obtenido y


aumentar en una unidad el exponente del resultado.
5. Redondear el resultado usando el modo de redondeo que esté activo para que
quepa en un registro de |m| bits.

Ejemplo: Vamos a ver como procede el algoritmo sobre un número en notación


flotante decimal con mantisa de 5 dígitos. Para ello usaremos los valores del ejemplo
anterior a1=4,5674*100 y a2=2,5001*10-4

En el paso 1 e1=0 y e2=-4 con lo que d=4 y no es necesario intercambiarlo.

En el paso 2 los dígitos quedan como:

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.

En el paso 3 añadimos el bit de retención a m2

m1= 4567400
m2= 0000251

En el paso 4 sumamos obteniendo:

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

Vemos que el resultado es el mismo que si hubiéramos usado un sumador de precisión


infinita y luego hubiéramos redondeado, con lo que el resultado es correcto.

El paso 4 involucra la suma de números con signo y magnitud, y en sí mismo tiene


tres pasos:

a) Convertir cualquier número negativo en su complemento a dos


b) Realizar una suma de |m|+4 bits en complemento a dos: |m|+3 bits de
magnitud y 1 bit de signo
c) Si el resultado es negativo, realizar otra complementación a dos para
volver a poner el resultado en la forma de signo y magnitud.

Pág 185
www.macprogramadores.org

7. Multiplicación en punto flotante


Vamos a ver cómo se haría la multiplicación en punto flotante suponiendo que los
operandos de entrada no contienen valores especiales.

La multiplicación en punto flotante es parecida a la multiplicación entera que vimos


en el apartado 1.1.3. Debido a que los números en punto flotante se almacenan en
forma de signo-magnitud, el multiplicador sólo necesita tratar con números sin signo.
Si las mantisas son números sin signo de |m| bits, entonces el producto puede tener
hasta 2|m| bits y se debe redondear a un número de |m| bits. Además de multiplicar las
mantisas se deben de sumar los exponentes.

Sean a1 y a2 los números a multiplicar de los cuales hemos desempaquetado las


mantisas m1 y m2 y los exponentes e1 y e2, el algoritmo que nos permite realizar la
multiplicación de números en punto flotante sería:

1. Usando el multiplicador del apartado 1.1.3 multiplicar las dos mantisas m1 y


m2 para obtener un producto de 2|m| bits en los registros P:A. Además los bits
que se pierden por la derecha de A según avanza el algoritmo, se les debe de
hacer un or binario con el bit de retención.
2. Al acabar el algoritmo se suma el bit de retención al P:A, lo cual será luego
útil para determinar el redondeo a aplicar.
3. Redondear el registro P:A de 2|m| bits a un registro de |m| bits usando el modo
de redondeo que este activo obteniendo así la nueva mantisa m3.
4. Para calcular el exponente resultado e3, se calcula como la suma de los
exponentes de los operandos de entrada e1 y e2.

Ejemplo: Multiplicar los números en punto flotante decimal a1=67,45*102 y


a2=34,98*100 usando una mantisa de |m|=4 bits

Primero multiplicamos las mantisas obteniendo el resultado en un registro de 8 bits:

67,45
x 34,98
–––––––––
2359,4010

Ahora redondeamos el número obteniendo la mantisa resultado m3=2359

El exponente resultado se obtiene como la suma de los exponentes de entrada e1 e2,


luego e3=2+0=2

Finalmente tenemos que el producto 67,45*102*34,98*100=2359*102

Pág 186
www.macprogramadores.org

8. División y resto en punto flotante


Podemos obtener el algoritmo de la división en punto flotante a partir del algoritmo de
la división de enteros que vimos en el apartado 1.1.4 de forma similar a como hemos
obtenido el algoritmo de multiplicación en punto flotante a partir del algoritmo de
multiplicación de enteros. Además este algoritmo nos proporciona el resto de la
división el cual es útil a la hora de hacer los redondeos.

Sea a1 el dividendo y a2 el divisor desempaquetados sus respectivas mantisas m1 y m2


y sus exponentes e1 y e2. El algoritmo para calcular a3=a1/a2 seria el siguiente:

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.

Ejemplo: Dados los números en punto flotante decimal a1=23,52*102 y a2=12,75*100


calcular a1/a2 usando una mantisa de |m|=4 bits.

Primero dividimos las mantisas:

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

Por último calculamos el exponente e3=e1-e2=2-0=2, con lo que finalmente tenemos


que el resultado de la división es:

(23,52*102) / (12,75*100) = 18,45*102

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

Si a ó b valen NaN, entonces se dice que a no mantiene una relación de orden


respecto a b, en caso contrario se cumple una de las otras tres condiciones: <,>,=

IEEE 754 también requiere que el sistema de numeración en punto flotante disponga
de las siguientes operaciones de conversión:

o De punto flotante a entero


o De entero a punto flotante
o De punto flotante a entero, con el resultado en punto flotante
o Entre todos los formatos de punto flotante que existan
o Entre punto flotante binario y punto flotante decimal

Estas conversiones puede proporcionarlas el propio sistema hardware (ensamblador) o


bien proporcionarse por software (librerías numéricas en C).

Pág 188

También podría gustarte