Ensamblador del PowerPC con Mac OS X

Transcripción

Ensamblador del PowerPC con Mac OS X
Ensamblador del
PowerPC con
Mac OS X
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Acerca de 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 cómo 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 sí que varían dependiendo del
sistema operativo en cuyo caso haremos una comparativa entre cómo 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 cómo se implementa un
determinado aspecto en PowerPC, y cómo 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.
¿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.
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
Pág 2
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 sí que se explican bastante
bien a lo largo del tutorial.
Cómo leer este tutorial
En principio el tutorial está pensado para ser leído de principio a fin siguiendo
el orden de los temas. 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 3
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
Nota legal
Este tutorial ha sido escrito por Fernando López Hernández para
macprogramadores.org y de acuerdo a las leyes internacionales sobre
propiedad intelectual, a la Directiva 2001/29/CE del Parlamento Europeo de
22 de mayo de 2001 y al artículo 5 de la Ley 22/1987 de 11 de Noviembre de
Propiedad Intelectual Española, el autor prohíbe la publicación de este
documento en cualquier otro servidor web, así como su venta, o difusión en
cualquier otro medio sin autorización previa.
Sin embargo el autor anima a todos los servidores web a colocar enlaces a
este documento. El autor también anima a cualquier persona interesada en
aprender a programar el PowerPC a bajarse o imprimirse este tutorial.
Madrid, Diciembre 2005
Para cualquier aclaración contacte con:
[email protected]
Pág 4
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Tabla de contenido
TEMA 1: Introducción al PowerPC
1 La arquitectura del PowerPC .............................................................. 10
2 Los entornos del PowerPC.................................................................. 12
3 Los registros del PowerPC .................................................................. 13
4 Byte ordering .................................................................................... 14
5 Alineación ......................................................................................... 16
TEMA 2: Empezando a programar
1 Herramientas necesarias .................................................................... 18
2 El programa mínimo .......................................................................... 21
3 El lenguaje ensamblador.................................................................... 23
3.1
Sintaxis del lenguaje ................................................................... 23
3.2
Elementos del lenguaje ............................................................... 24
3.2.1
Literales .............................................................................. 25
3.2.2
Identificadores..................................................................... 26
3.2.3
Las expresiones ................................................................... 27
3.2.4
El location counter ............................................................... 30
3.3
Las sentencias de asignación directa............................................ 31
3.4
Las definiciones .......................................................................... 32
4 Acceso a memoria ............................................................................. 33
4.1
Segmentación del programa ........................................................ 33
4.2
Las secciones ............................................................................. 34
4.2.1
Secciones del segmento de código ........................................ 34
4.2.2
Secciones del segmento de datos.......................................... 38
4.2.3
Crear nuevas secciones ........................................................ 39
4.2.4
Agrupar las secciones........................................................... 40
4.3
Indireccionamiento de memoria en las máquinas RISC.................. 41
4.4
Modos de indireccionamiento....................................................... 43
4.4.1
Indireccionamiento de registro base e índice inmediato .......... 44
4.4.2
Ejemplo............................................................................... 47
4.4.3
Acceso a memoria con actualización de registro ..................... 48
4.4.4
Uso del operador ha16() .................................................... 49
4.4.5
Indireccionamiento de registro base y registro índice ............. 51
4.5
Carga y almacenamiento de bloques de bytes .............................. 54
4.6
Mnemonics................................................................................. 56
5 Instrucciones de trabajo con enteros .................................................. 59
5.1
El registro CR (Condition Register) ............................................... 59
5.2
El registro XER............................................................................ 63
5.3
Instrucciones aritméticas............................................................. 65
5.3.1
Instrucciones aritméticas de suma ........................................ 65
5.3.2
Instrucciones aritméticas de resta ......................................... 70
5.3.3
Instrucciones de negación aritmética..................................... 72
5.3.4
Instrucciones aritméticas de multiplicación ............................ 72
5.3.5
Instrucciones aritméticas de división ..................................... 74
5.4
Instrucciones de comparación de enteros..................................... 75
Pág. 5
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
5.5
Instrucciones lógicas con enteros ................................................ 76
5.6
Instrucciones de rotación y desplazamiento con enteros ............... 78
5.6.1
Instrucciones de desplazamiento con enteros ........................ 79
5.6.2
Instrucciones de rotación con enteros ................................... 80
5.7
Mnemonics................................................................................. 83
5.7.1
Mnemonics para la resta....................................................... 83
5.7.2
Mnemonics para las operaciones de comparación................... 84
5.7.3
Mnemonics para operaciones de desplazamiento y rotación .... 85
5.7.4
Mnemonics para acceder al registro XER ............................... 86
5.7.5
Otros mnemonics ................................................................. 86
5.8
Operaciones comunes con enteros............................................... 87
5.8.1
Valor absoluto...................................................................... 87
5.8.2
Máximo y mínimo de un número sin signo ............................. 88
5.8.3
Máximo y mínimo de un número con signo ............................ 90
5.8.4
Resto de una división ........................................................... 91
5.8.5
División entre una constante entera ...................................... 92
5.8.6
División de 64 bits en máquinas de 32 bits ............................ 96
6 Instrucciones de bifurcación............................................................. 101
6.1
Tipos de cálculo de la dirección de salto de una instrucción......... 101
6.1.1
Instrucciones de salto relativo............................................. 102
6.1.2
Instrucciones de salto absoluto ........................................... 103
6.1.3
Las instrucciones de salto condicional.................................. 104
6.1.4
Instrucciones condicionales de salto relativo ........................ 107
6.1.5
Instrucciones condicionales de salto absoluto ...................... 108
6.1.6
Instrucciones condicionales de salto al Count Register.......... 109
6.1.7
Instrucciones condicionales de salto al Link Register ............ 110
6.2
Mnemonics............................................................................... 112
6.2.1
Mnemonics para saltos incondicionales ................................ 112
6.2.2
Mnemonics para saltos condicionales................................... 112
6.2.3
Mnemonics para acceder a los registros CR, CTR y LR .......... 116
6.3
Implementación en ensamblador de las sentencias de control de
flujo más conocidas del lenguaje C ...................................................... 116
6.3.1
Condicional simple y doble.................................................. 116
6.3.2
Condicional múltiple ........................................................... 117
6.4
Los bucles ................................................................................ 120
6.4.1
Mnemonics para bucles ...................................................... 120
6.4.2
Bucle do-while ................................................................... 121
6.4.3
Bucle while ........................................................................ 122
6.4.4
Bucle for............................................................................ 122
6.5
Operaciones lógicas con los bits del registro CR.......................... 124
7 Instrucciones de trabajo con números en punto flotante.................... 127
7.1
Introducción............................................................................. 127
7.2
Los registros de punto flotante .................................................. 127
7.3
El registro FPSCR ...................................................................... 128
7.3.1
Instrucciones para acceder a los bits de registro FPSCR........ 131
7.3.2
Los flags de excepción........................................................ 132
7.3.3
Los bits de condición y el bit de clase .................................. 134
7.3.4
Los bits de redondeo.......................................................... 136
Pág. 6
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
7.4
El registro CR ........................................................................... 136
7.5
Manejo de traps........................................................................ 137
7.6
Instrucciones de carga y almacenamiento .................................. 138
7.7
Instrucciones aritméticas........................................................... 140
7.8
Instrucciones de conversión ...................................................... 143
7.9
Instrucciones de comparación ................................................... 144
7.10
Instrucciones de movimiento de datos .................................... 145
8 Incrustar código ensamblador en un programa C .............................. 146
8.1
Integración entre C y ensamblador ............................................ 146
8.2
Acceso a variables C desde ensamblador.................................... 146
8.3
Expresiones C como operandos de instrucciones ensamblador..... 148
8.3.1
Las constraints y los modificadores ..................................... 149
8.3.2
Expresiones C en gcc 3.1.................................................... 153
9 Llamada a funciones........................................................................ 154
9.1
Tipos de datos.......................................................................... 154
9.2
Mecanismo general de llamada a procedimientos........................ 155
9.3
Convención del uso de los registros ........................................... 155
9.4
Estructura de la pila .................................................................. 157
9.4.1
Las áreas del frame............................................................ 158
9.5
Paso de control a un procedimiento ........................................... 159
9.5.1
El prólogo y el epílogo ........................................................ 159
9.5.2
Los procedimientos terminales ............................................ 164
9.5.3
Paso de parámetros ........................................................... 165
9.5.4
Funciones con un número variable de parámetros................ 168
9.5.5
Retorno de una función ...................................................... 169
9.6
Ejemplo ................................................................................... 169
APÉNDICE A: Aritmética binaria
1 Técnicas básicas de aritmética entera ............................................... 173
1.1
Números sin signo .................................................................... 173
1.1.1
Suma con transmisión de acarreo ....................................... 173
1.1.2
Resta con petición de acarreo ............................................. 175
1.1.3
Multiplicación en base 2...................................................... 177
1.1.4
División en base 2.............................................................. 178
1.2
Números con signo ................................................................... 181
1.2.1
Representación .................................................................. 181
1.2.2
Suma y resta de números en complemento a 2.................... 182
1.3
Aspectos del sistema................................................................. 183
2 Introducción al punto flotante .......................................................... 186
3 Formato de los datos en punto flotante ............................................ 188
3.1
Números denormalizados .......................................................... 191
3.2
Números especiales .................................................................. 192
Rangos máximos y mínimos en los números en punto flotante .............. 194
4 El problema del redondeo en punto flotante...................................... 196
4.1
La precisión en punto flotante ................................................... 196
4.2
Error absoluto y relativo ............................................................ 197
4.3
Modos de redondeo .................................................................. 198
5 Las excepciones .............................................................................. 199
Pág. 7
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
6
Suma en punto flotante ................................................................... 201
6.1
Redondeo................................................................................. 201
6.2
El algoritmo de la suma............................................................. 202
7 Multiplicación en punto flotante........................................................ 205
8 División y resto en punto flotante ..................................................... 206
9 Comparaciones y conversiones......................................................... 207
APÉNDICE B: La segmentación
1 ¿Que es la segmentación?................................................................ 209
2 Etapas multiciclo ............................................................................. 212
3 Los riesgos...................................................................................... 213
3.1
Riesgos estructurales ................................................................ 213
3.2
Riesgos por dependencia de datos ............................................. 214
3.3
Riesgos de control .................................................................... 217
3.4
Saltos sin resolver..................................................................... 218
3.5
Solución software a los saltos sin resolver .................................. 219
3.5.1
Estructura if....................................................................... 220
3.5.2
Estructura while ................................................................. 220
3.5.3
Estructura do-while ............................................................ 221
3.5.4
Estructura for .................................................................... 222
3.6
Solución hardware a los saltos sin resolver ................................. 222
3.7
La serialización ......................................................................... 226
4 Más allá de la segmentación ............................................................ 227
Pág. 8
Tema 1
Introducción al PowerPC
Sinopsis:
Este primer tema sirve para orientar al lector sobre el contenido del tutorial, y
ayuda a concretar una serie de conceptos básicos que serán necesario tener
claro para leer el resto del tutorial.
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
conceptuales 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 sería, 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, ¿cómo 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 de
reloj 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
Pág 10
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
(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 irá 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 (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 está 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 de 32 bits como de 64
bits para sus máquinas y para las últimas máquinas de Apple.
Apple por su parte compra los micros a Motorola (en concreto los G4
disponen de micros de la serie MPC74xx de Motorola y a IBM (los G5 son
microprocesadores que IBM ha hecho a Apple). Podemos consultar los micros
de que dispone Motorola aquí en [MICROMOTOROLA], y los micros de IBM
en [MICROIBM].
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 el sistema operativo Mac OS X. Téngase en cuenta que aunque
dispongamos de una máquina G5 con un microprocesador de 64 bits,
actualmente no se utilizan sus características adicionales con el fin de
mantener compatibilidad. En cualquier caso pasar de un ensamblador de 32
bits a uno de 64 bits resulta muy fácil ya que los juegos de instrucciones
fueron pensados con el fin de que fueran similares.
Pág 11
Ensamblador del PowerPC con Mac OS X
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 (Véase
Figura 1.1). 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:
OEA
VEA
UISA
Figura 1.1: Entornos del PowerPC
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, sólo se puede acceder a las operaciones
de UISA y VEA.
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.
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.
Pág 12
Ensamblador del PowerPC con Mac OS X
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), FloatingPoint 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 operandos
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
Pág 13
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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)
aparecen en la Tabla 1.1:
Tipo dato
char
short
int
Tamaño
(bytes)
1 byte
2 bytes
4 bytes
Rango Valores
-128..+127
-32.768..+32.767
-2.147.483.648..+ 2.147.483.647
Tabla 1.1: Tamaños típicos de variables enteras
En nuestro caso necesitaremos una variable de tipo int. En la Figura 1.2 se
muestra como se almacenaría esta variable en big-endian y en little-endian en
memoria.
Pág 14
Ensamblador del PowerPC con Mac OS X
4.1.1.1
Big-Endian
0000 0000
0
4.1.1.2
macprogramadores.org
0000 1001
1
0110 1010
2
1100 1011
3
0000 1001
2
0000 0000
3
Little-Endian
1100 1011
0
0110 1010
1
Figura 1.2: Almacenamiento de variables Big-Endian y Little-Endian en memoria
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 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, 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.
Pág 15
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tabla 1.2 muestra la alineación recomendada para cada tamaño de dato.
Operando
Longitud
byte
halfword
word
doubleword
1 byte
2 bytes
4 bytes
8 bytes
Dirección
alineada
xxxx
xxx0
xx00
x000
Tabla 1.2: Alinación recomendada para cada tamaño de dato
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 tienen un tamaño diferente. En PowerPC todas las
instrucciones ocupan 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 16
Tema 2
EMPEZANDO A PROGRAMAR
Sinopsis:
Con este tema pretendemos que el lector aprenda a manejar el juego de
instrucciones UISA, es decir, el modo usuario, que son las instrucciones
comunes que tiene este microprocesador.
En los siguientes temas (que actualmente estamos escribiendo) pretendemos
que el lector aprenda también a manejar operaciones UISA más avanzadas
así como a manejar los demás modos.
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
1 Herramientas necesarias
Vamos a empezar viendo qué herramientas de programación en ensamblador
existen para Mac OS X y como se pueden usar.
Los primero que vamos a necesitar es obtener las Development Tools que
podemos conseguir gratuitamente de la Apple Developer Connection (ADC) en
[DEVTOOLS]:
Dentro de estas herramientas encontramos el conocido compilador gcc de
GNU, que 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
como el del Listado 1.1:
#include <stdio.h>
#define MENSAJE "Hola mundo\n"
int main ()
{
printf(MENSAJE);
return 0;
}
Listado 1.1: Programa ensamblador mínimo
Y compilarlo desde la línea de comandos con:
$ gcc 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.
2.
3.
4.
Preprocesado
Generación del código ensamblado (compilación)
Generación del código objeto (ensamblado)
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
Pág 18
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
en nuestro caso es el comando de 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 gcc 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í:
$ gcc -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í:
$ gcc -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
$ gcc -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:
$ gcc -c saluda.s
En cualquier caso, para obtener el código objeto de un fichero en
ensamblador, gcc 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 Xcode que nos permite de
Pág 19
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Xcode.
Por último, la Tabla 2.1 muestra las extensiones de fichero que reconoce el
compilador de GNU, y para que se utiliza cada una.
Extensión Descripción
.c
Código fuente C
Qué hace con ellos gcc
Preprocesa, ensambla,
compila y enlaza
Preprocesa, ensambla,
compila y enlaza
.cpp
.cc
.cxx
.C
Código fuente C++
.m
Código fuente Objective-C
Preprocesa, ensambla,
compila y enlaza
.h
Ficheros de cabecera C, C++ o
Objective-C
Código C preprocesado (Si se lo
pasamos al compilador no lo
preprocesa)
Código C++ preprocesado (Si se lo
pasamos al compilador no lo
preprocesa)
Código ensamblador que no debe
ser preprocesado
Código ensamblador que debe ser
preprocesado
Archivo de código objeto
Librería de enlace estático
Librería de enlace dinámico
No usados directamente
.i
.ii
.s
.S
.o
.a
.so
Ensambla, compila y
enlaza
Ensambla, compila y
enlaza
Compila y enlaza
Preprocesa, compila y
enlaza
Enlaza
Enlaza
Enlaza
Tabla 2.1: Extensiones de fichero que reconoce el compilador de GNU
Pág 20
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
2 El programa mínimo
Ya que sabemos cómo se usa el compilador, vamos a escribir un programa
mínimo para ver cómo 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 ensamblador, los cuales se resumen en la Tabla 2.2.
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
Tabla 2.2: Tipos de comentarios en lenguaje ensamblador
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.
Pág 21
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
.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 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:
$ gcc basico.s -o basico
$ ./basico
Pág 22
Ensamblador del PowerPC con Mac OS X
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, sí 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 Tema 3.
Los o p e r a n d o s , 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
Pág 23
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
principio de línea si no existe la etiqueta, o bien poder un tabulador después
de la etiqueta.
Por ejemplo:
inicio:
mflr r0
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 se llamará sumaregistros.s y aparece en el Listado 2.1:
/* 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
Listado 2.1: Programa que suma el contenido de los registros
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 cuáles son los principales elementos que
componen el lenguaje ensamblador.
Pág 24
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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'
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: 0 x 4 5 , 0 x F 2 5 9 B 4 C 2 . 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...]]
flt_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:
Pág 25
Ensamblador del PowerPC con Mac OS X
Número
1.34
0.00045
2456
macprogramadores.org
Precisión
doble
doble
float
Representación
0d1.34e0
0d45.0e-4
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 claridad.
Por ejemplo:
F1:
F2:
D1:
D2:
.single 0r2456.0e0 ;
;
;
.single 0d2456.0e0 ;
.double 0d1.34e0
;
;
;
.double 0r1.34e0
;
Forma recomentable de reservar
memoria para un float de
32 bits
También reserva 32 bits
Forma recomentable para
reservar memoria para un
double de 64 bits
También 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
Pág 26
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Las etiquetas deben de estar precedidas por : cuando se declaran, pero no
cuando se usan. Por ejemplo:
inicio:
········
stwu r1,-80(r1) ; Declaración
········
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
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'
Pág 27
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tabla 2.3:
Operador
~
!
+
*
/
%
>>
<<
&
|
^
&&
||
<
>
<=
>=
==
!=
Nombre
Menos unário
Negado binario
Negado lógico
Descripción
El complemento a 2 de un número
Complemento a uno de un número
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 El resultado es el valor del primer operando
a 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
Desplazamiento El resultado es el valor del primer operando
a 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
Tabla 2.3: Operandos que pueden aparecer en una expresión del ensamblador
Las reglas de precedencia y asociatibidad de estos operadores también son
las mismas que en el lenguaje C.
Pág 28
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 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 está
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.
Pág 29
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
Por ejemplo:
.org 100, 0xFF
Rellena los siguientes 100 bytes con 0xFF
Pág 30
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 del Listado 2.1 lo podríamos haber hecho como
muestra el Listado 2.2:
// 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
Listado 2.2: Programa que usa sentencias de asignación directa
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 haberlo hecho como muestra el Listado 2.3:
Pág 31
Ensamblador del PowerPC con Mac OS X
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
Listado 2.3: Programa que usa .set en lugar de identificadores
3.4 Las definiciones
Las sentencias de asignación directa y las directivas .set sólo nos permiten
almacenar valores literales:
var1
var1
.set
.set
= 3 ; Correcto
= r3 ; Error ensamblado
var1 3; correcto
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 32
Ensamblador del PowerPC con Mac OS X
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 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 33
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
.data
············
············
.text
············
············
············
Cuando el enlazador recibe los ficheros de código objeto, éste 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 Tabla 2.4 resume las directivas usadas para cada tipo de sección que
puede contener el segmento de código:
Directiva
.text
Sección
(__TEXT,__text)
.const
.literal4
(__TEXT,__const)
(__TEXT,
__literal4)
Pág 34
Descripción
Almacena
código
de
programa
Variables constantes
Variables constantes de 4
bytes
Ensamblador del PowerPC con Mac OS X
.literal8
.cstring
.constructor
.destructor
.fvmlib_init0
.fvmlib_init1
macprogramadores.org
(__TEXT,
__literal8)
(__TEXT,
__cstring)
(__TEXT,
__constructor)
(__TEXT,
__destructor)
(__TEXT,
__ fvmlib_init0)
(__TEXT,__
fvmlib_init1)
.symbol_stub
(__TEXT,
__symbol_stub)
.picsymbol_stub (__TEXT,
__picsymbol_stub)
Variables constantes de 8
bytes
Cadenas de caracteres
constantes
Usada
sólo
por
los
constructores de C++
Usada
sólo
por
los
destructores de C++
Estas secciones las debe de
usar solamente el sistema de
memoria virtual de las
librerías de enlace dinámico.
Nosotros nunca debemos
poner nada aquí.
Usadas para llamar a
funciones de librerías de
enlace dinámico. Como
veremos más adelante
Tabla 2.4: Directivas para cada tipo de sección del segmento de código
.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.
Pág 35
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
.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 de la Tabla 2.5:
Directiva
.byte [valor]
Descripción
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
repeticiones,
diga repeticiones. El tamaño de la variable
tamaño, valor
puede ser 1,2 ó 4 según diga tamaño
.space
n_bytes, Pone el valor dado en valor tantas veces como
[valor]
diga n_bytes, o ceros si no damos valor
Tabla 2.5: Directivas para indicar la cantidad de memoria a reservar y valor inicial
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
Pág 36
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
.literal4, .literal8 o .cstring es que si el mismo valor aparece
varias 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
A2: .long
A3: .long
A4: .long
0
0
0
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í:
.const
A1: .long
A2: .long
A3: .long
A4: .long
0
0
0
0
Las demás directivas que aparecen en la tabla las comentaremos cuando
hayamos avanzado más.
Pág 37
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
4.2.2 Secciones del segmento de datos
La Tabla 2.6 resume las directivas usadas para cada tipo de sección que
puede contener el segmento de datos:
Directiva
.data
.static_data
.non_lazy_symbol
_pointer
Sección
(__DATA,__data)
(__DATA,__static_data)
(__DATA,
__nl_symbol_pointer)
.lazy_symbol
_pointer
(__DATA,
__la_symbol_pointer)
.dyld
(__DATA,__dyld)
.const_data
(__DATA,__const)
Descripción
Sección de datos regular
Almacena datos estáticos
El compilador guarda en
esta sección punteros a
símbolos
non-lazy
(excepto punteros a
funciones)
El compilador guarda en
esta sección punteros a
símbolos lazy.
Esta sección se usa para
el
enlazado
con
funciones de librerías de
enlace
dinámico.
Nosotros no debemos
usarla.
Para almacenar datos
constantes en librerías
de enlace dinámico
Tabla 2.6: Directivas usadas para cada tipo de sección del segmento de datos
.data Es la sección de datos regular donde se almacenan datos variables
a no ser que se especifique otra sección.
. s t a t i c _ d a t a 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 dos directivas de la Tabla 2.7, 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.
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.
Pág 38
Ensamblador del PowerPC con Mac OS X
Directiva
.comm etiqueta, tamaño
.lcomm etiqueta, tamaño
macprogramadores.org
Descripción
Reserva tamaño bytes y crea la etiqueta
global etiqueta que apunta a esta zona
de memoria sin inicializar.
Igual a .comm, sólo que la etiqueta es de
ámbito local, con lo que no es accesible
desde fuera del módulo
Tabla 2.7: Directivas para reservar memoria en las secciones del segmento de datos
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
.static_data
B:
.long 3
.comm C, 4
; Crea una variable de 4 bytes con
; un valor de 60 en la sección
; (__DATA,__data)
;
;
;
;
;
Crea una variable de 4 bytes con
un valor de 3 en la sección
(__DATA,__static_data)
Reserva 4 bytes sin inicializar
en la sección (__DATA,__static_data)
.text
; Ahora van las instrucciones en ensamblador
; en la sección regular del segmento de código
; (__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 sección regular
; del segmento de código (__TEXT,__text)
mtlr r0
lmw r30,-8(r1)
Listado 2.4: Ejemplo de reserva de memoria
4.2.3 Crear nuevas secciones
Siempre podemos pedir un cambio de sección usando la directiva .section
Pág 39
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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
··········
··········
Pág 40
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 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.
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 Tema 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
Pág 41
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 h a 1 6 ( e x p r e s i o n ) 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:
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.
Pág 42
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 r2, 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)
Aún queda por ver cuándo y cómo se usa ha16(), que lo vamos a ver en el
siguiente punto.
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.
Pág 43
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
Como hemos dicho, 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.
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
Pág 44
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
instrucciones que realizan operaciones aritméticas, pero no se puede usar
nunca para indireccionar.
En la Figura 2.1 se muestra cómo se genera la dirección efectiva en las
indirecciones de registro e índice inmediato.
0
56
10 11
Opcode rD/rS
Codificación de la instrucción
15 16
rA
0
d
15 16
Extensión de signo
¿rA=0?
Sí
31
d
0
+
No
GPR(rA)
GPR(rD/rS)
31
Direción efectiva
Store
Load
Memoria principal
Figura 2.1: Indirecciones de registro e índice inmediato
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.
La Tabla 2.8 y Tabla 2.9 muestran las principales instrucciones de acceso a
memoria que usan este modo de indireccionamiento:
Instrucción
lbz rD,d(rA)
lbzu rD,d(rA)
lhz rD,d(rA)
Descripción
(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
(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
(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
Pág 45
Ensamblador del PowerPC con Mac OS X
lhzu rD,d(rA)
lha rD,d(rA)
lhau rD,d(rA)
lwz rD,d(rA)
lwzu rD,d(rA)
macprogramadores.org
(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
(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
(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
(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
(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
Tabla 2.8: Instrucciones de carga de enteros con indireccionamiento de registro base e
índice inmediato
Instrucción
stb rS,d(rA)
stbu rS,d(rA)
sth rS,d(rA)
sthu rS,d(rA)
stw rS,d(rA)
stwu rS,d(rA)
Descripción
(STore Byte) El byte menos significativo de rS se
guarda en la posición de memoria dada por 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)
(STore Half-word) El half-word menos significativo de
rS se guarda en la posición de memoria dada por
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)
(STore Word) El valor de rS se guarda en la posición
de memoria dada por 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)
Tabla 2.9: Instrucciones de almacenamiento de enteros con indireccionamiento de registro
base e índice inmediato
Pág 46
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
4.4.2 Ejemplo
Como ejemplo el Listado 2.5 muestra un programa 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 // Sección (__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
Listado 2.5: Programa que calcula la suma de dos números almacenados en memoria
El anterior programa tiene tanto un segmento de datos (.data) como un
segmento de código (.text).
Pág 47
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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 S D
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 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 la Tabla 2.8 y Tabla 2.9 y se caracterizan
porque son iguales a las instrucciones de acceso a memoria normales, sólo
que su nombre acaba en u, por ejemplo en vez de llamarse lwz (Load Word
and Zero) se llaman lwzu (Load Word and Zero with Update).
Pág 48
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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
realidad 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
Pág 49
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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:
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 del Listado 2.5 para que use ha16() en vez de
hi16() como muestra el Listado 2.6.
Pág 50
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
/* 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 // Sección (__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 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
Listado 2.6: Suma de números con ha16() y hi16()
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.
Pág 51
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
La Figura 2.2 muestra cómo se genera la dirección efectiva en las
indirecciones de registro y registro índice.
0
56
10 11
Opcode rD/rS
Codificación de la instrucción
15 16
rA
0
20 21
rB
subopcode
15 16
31
0
31
GPR(rB)
¿rA=0?
Sí
0
+
No
GPR(rA)
GPR(rS/rS)
Direción efectiva
Store
Load
Memoria principal
Figura 2.2: Indirecciones de registro y registro indice
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.
Las Tabla 2.10 y Tabla 2.11 muestran las principales instrucciones que usan
este modo.
Instrucción
lbzx rD,rA,rB
lbzux rD,rA,rB
lhzx rD,rA,rB
Descripción
(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
(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
(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
Pág 52
Ensamblador del PowerPC con Mac OS X
lhzux rD,rA,rB
lhax rD,rA,rB
lhaux rD,rA,rB
lwzx rD,rA,rB
lwzux rD,rA,rB
macprogramadores.org
(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
(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
(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
(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
(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
Tabla 2.10: Instrucciones de carga de enteros con indireccionamiento de registro base y
registro índice
Instrucción
stbx rS,rA,rB
stbux rS,rA,rB
sthx rS,rA,rB
sthux rS,rA,rB
stwx rS,rA,rB
stwux rS,rA,rB
Descripción
(STore Byte indeXed) El byte menos significativo de
rS se guarda en la posición de memoria dada por
(rA|0)+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
(STore Half-word indeXed) El half-word menos
significativo de r S se guarda en la posición de
memoria dada por (rA|0)+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
(STore Word indeXed) El valor de rS se guarda en la
posición de memoria dada por (rA|0)+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
Tabla 2.11: Instrucciones de almacenamiento de enteros con indireccionamiento de registro
base y registro índice
Pág 53
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 r A
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.
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
lmw rD,d(rA)
stmw rS,d(rA)
Descripción
(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)
(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)
Tabla 2.12: Instrucciones de carga y almacenamiento de multiples words
Estas instrucciones aparecen en la Tabla 2.12 y 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:
Pág 54
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
.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.
2) Instrucciones de carga y almacenamiento de múltiples 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 aparecen en la Tabla 2.13:
Instrucción
lswi rD,rA,n
stswi rS,rA,n
lswx rD,rA,rB
stswi rS,rA,rB
Descripción
(Load String Word Indirect) Carga los n primeros
bytes a partir de la dirección de memoria dada por
( r A | 0 ) en los registros que van desde r D en
adelante
(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
(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
(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
Tabla 2.13: Instrucciones de carga y almacenamiento de múltiples bytes (caracteres)
Pág 55
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
El uso del registro XER se explica en el apartado 5.2
Por ejemplo, supongamos que tenemos una cadena de 12 caracteres en la
dirección de memoria etiquetada 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 número
; de bytes a leer
andi r3,r3,0
stswi r10,r2,r3 ; Pasa r10,r11,r12,r13 a memoria
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.
2
Aunque en castellano se pueden traducir por mnemónicos, hemos preferido mantener el
término inglés para facilitar la comprensión de la documentación
Pág 56
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 sí 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
lis rD,valor
equivale a
equivale a
addi rD,0,valor
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)
Y si lo fuéramos 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:
Pág 57
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 dirección base del 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 58
Ensamblador del PowerPC con Mac OS X
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
o
o
o
Instrucciones
Instrucciones
Instrucciones
Instrucciones
aritméticas con enteros
de comparación de enteros
lógicas con enteros
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 Figura 2.3:
CR0
0
CR1
34
CR2
78
11 12
CR3
CR4
15 16
CR5
19 20
CR6
23 24
CR7
27 28
31
Figura 2.3: Organización del registro CR en campos
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
Pág 59
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 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
Pág 60
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
El significado de los bits del campo destino después de realizar la operación
es el que aparece en la Tabla 2.14.
Bit
0
1
2
3
Descripción
El primer operando es menor al segundo
El primer operando es mayor al segundo
Los operandos son iguales
Summary Overflow (SO). Hubo un overflow. Este bit es una copia del
estado final del bit XER[SO]
Tabla 2.14: Significado de los bits de los campos de CR en las instrucciones de comparación
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 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 sí 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 se explica en el Apéndice B 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 tabla Tabla 2.15.
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.
Pág 61
Ensamblador del PowerPC con Mac OS X
Bit
0
1
2
3
macprogramadores.org
Descripción
El registro destino ha recibido un valor negativo
El registro destino ha recibido un valor positivo
El registro destino ha recibido un cero
Summary Overflow (SO). Hubo un overflow. Este bit es una copia del
estado final del bit XER[SO]
Tabla 2.15: Significado de los bits del campo de CR0 en las instrucciones con enteros
acabadas en punto (.)
Como veremos en el tema de pipelines del Tema 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.
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 Tabla 2.16.
Bit
4
5
6
7
Descripción
Floating-point
Floating-point
Floating-point
Floating-point
eXception (FX)
Enabled eXception (FEX)
inValid eXception (VX)
Overflow eXception (OX)
Tabla 2.16: Significado de los bits del campo de CR1 en las instrucciones en punto flotante
acabadas en punto (.)
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 el apartado 7.4 es el que
almacena los resultados de realizar operaciones en punto flotante.
Pág 62
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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, 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 Figura 2.4 muestra las partes en que se divide este registro:
SO OV CA
0
1
0 0000
0000
0000
0000
2
0000 0
Byte Count
24 25
31
Figura 2.4: Partes del registro XER
La Tabla 2.17 describe el significado de cada uno de estos bits:
Bit
0
Nombre
SO
1
OV
2
CA
3-24
25-31
Byte
count
Descripción
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
Overflow. Indica que la última instrucción a producido un
overflow
Carry, Indica que la última instrucción a producido un
acarreo
Reservado. Siempre vale 0
Este campo muestra el número de bytes transferidos
durante la ejecución de una instrucción lswx (Load String
Word indeXed) o stswx (Store String Word indeXed)
Tabla 2.17: significado de los bits del registro XER
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
Pág 63
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
addi no activa el bit de acarreo si se produce y addic sí 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 se explica en el
Apéndice B.
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.
La forma correcta de desactivarlo sería:
mfspr r3,1
lis r4,0x7FFFF
; Cogemos en r3 el valor de XER
; 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
Pág 64
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tabla 2.18 resume las instrucciones aritméticas de suma de enteros que
existen en PowerPC:
Instrucción
addi rD,rA,SIMM
addis rD,rA,SIMM
add rD,rA,rB
add. rD,rA,rB
addo rD,rA,rB
addo. rD,rA,rB
addic rD,rA,SIMM
addic. rD,rA,SIMM
addc rD,rA,rB
addc. rD,rA,rB
addco rD,rA,rB
addco. rD,rA,rB
adde rD,rA,rB
adde. rD,rA,rB
Descripción
(ADD Immediate) Calcula la suma de
(rA|0)+SIMM y la pone en rD.
(ADD Immediate Shift) Calcula la suma de
(rA|0)+(SIMM||0x0000) y la pone en rD
(ADD) La suma rA+rB se deposita en rD
(ADD) Igual que add, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
(ADD Overflow) Igual que add, sólo que XER[OV]
se pone a 1 si hay overflow
(ADD Overflow) Igual que addo, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
(ADD Immediate Carrying) Igual que addi sólo
que XER[CA] a se pone a 1 si hay acarreo
(ADD Immediate Carrying) Igual que addic sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
(ADD Carrying) Igual que add sólo que XER[CA]
se pone a 1 si hay acarreo
(ADD Carrying) Igual que addc sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
(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
(ADD Carrying with Overflow) Igual que addco
sólo CR0 se actualiza tal como explicamos en el
apartado 5.1
(ADD Extended) La suma rA+rB+XER[CA] se
pone en rD
(Add Extended) Igual que adde, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
Pág 65
Ensamblador del PowerPC con Mac OS X
addeo rD,rA,rB
addeo. rD,rA,rB
addme rD,rA
addme. rD,rA
addmeo rD,rA
addmeo. rD,rA
addze rD,rA
addze. rD,rA
addzeo rD,rA
addzeo. rD,rA
macprogramadores.org
(ADD Extended with Overflow) Igual que adde,
sólo que XER[OV] se pone a 1 si hay overflow
(ADD Extended with Overflow) Igual que addeo,
sólo que CR0 se actualiza tal como explicamos en
el apartado 5.1
(ADD Minus one Extended) La suma
rA+XER[CA]+0xFFFFFFFF se guarda en rD
(ADD to Minus one Extended) Igual que addme,
sólo que CR0 se actualiza tal como explicamos en
el apartado 5.1
(ADD to Minus one Extended with Overflow) Igual
que addme, sólo que XER[OV] se pone a 1 si hay
overflow
(ADD to Minus one Extended with Overflow) Igual
que addmeo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
(ADD to Zero Extended) La suma rA+XER[CA] se
deposita en rD
(ADD to Zero Extended) Igual que addze, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
(ADD to Zero Extended with Overflow) Igual que
addze, sólo que XER[OV] se pone a 1 si hay
overflow
(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
Tabla 2.18: Instrucciones aritméticas de suma de enteros
A todos los registros que reciben como operandos estas instrucciones se les
considera números con signo.
En el Listado 2.7 aparece un programa llamado sumalong.s3 que suma dos
números de 64 bits. Al tener los registros de PowerPC sólo 32 bits tenemos
que usar dos registros para almacenar un número. Imaginemos que
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 66
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
.long 0xFFFFFFFF ; Parte baja
B:
.long 50
; Parte alta
.long 0xFF
; Parte baja
.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)
Pág 67
de
de
de
de
la
la
la
la
variable
variable
variable
variable
A
A
B
B
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
lwz r4,lo16(B)(r10)
lwz r5,lo16(B+4)(r10)
// Calculamos la suma en r6||r7
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
Listado 2.7: Programa que suma dos números de 64 bits
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 aparece en el Listado 2.8) el cual suma 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 Figura 2.5 muestra la forma de utilizar las instrucciones:
r3
r4
r5
r6
r7
r8
r9
r10
addzeo r3
addo r11,r3,r7
addzeo r4
addc r12,r4,r8
addzeo r5
addc r13,r5,r9
addc r14,r6,r10
Figura 2.5: Uso de instrucciones que suman dos números de 128 bits
Pág 68
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
/* 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
.long 1
; Parte alta de la variable
.long 1
; Parte baja de la variable
.long 1
; Parte baja de la variable
B:
.long 1
; Parte alta de la variable
.long 0xFFFFFFFF ; Parte alta de la variable
.long 0xFFFFFFFF ; Parte baja de la variable
.long 0xFFFFFFFF ; Parte baja de la variable
.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
Listado 2.8: Programa que suma números de 128 bits
Pág 69
A
A
A
A
B
B
B
B
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 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 Tabla 2.19 resume las instrucciones aritméticas de resta de enteros que
existen en PowerPC:
Instrucción
subf rD,rA,rB
subf. rD,rA,rB
subfo rD,rA,rB
subfo. rD,rA,rB
subfc rD,rA,rB
subfc. rD,rA,rB
subfco rD,rA,rB
subfco. rD,rA,rB
Descripción
(SUBtract From) Calcula rB-rA y lo deposita en
rD.
(SUBtract From) Igual que subf, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
(SUBtract From with Overflow) Igual que subf,
sólo que XER[OV] se pone a 1 si hay overflow
(SUBtract From with Overflow) Igual que subfo,
sólo que CR0 se actualiza tal como explicamos en
el apartado 5.1
(SUBtract From Carring with Overflow) Calcula rBrA y lo deposita en rD, y si hay acarreo pone
XER[CA] a 1
(SUBtract From Carring with Overflow) Igual que
s u b f c , sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
(SUBtract From Carrying with Overflow) Igual que
subfc, sólo que XER[OV] se pone a 1 si hay
overflow
(SUBtract From Carrying with Overflow) Igual que
subfco, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
Pág 70
Ensamblador del PowerPC con Mac OS X
subfe rD,rA,rB
subfe. rD,rA,rB
subfeo rD,rA,rB
subfeo. rD,rA,rB
subfme rD,rA,rB
subfme. rD,rA,rB
subfmeo rD,rA,rB
subfmeo. rD,rA,rB
subfze rD,rA,rB
subfze. rD,rA,rB
subfzeo rD,rA,rB
subfzeo. rD,rA,rB
macprogramadores.org
(SUBtract From Extended) Calcula r B rA+XER[CA] y lo deposita en rD.
(SUBtract From Extended) Igual que subfe, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
(SUBtract From Extended with Overflow) Igual que
subfe, sólo que XER[OV] se pone a 1 si hay
overflow
(SUBtract From Extended with Overflow) Igual que
subfeo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
(SUBtract From Minus one Extended) Calcula
~rA+XER[CA]+0xFFFFFFFF y lo deposita en rD.
(SUBtract From Minus one Extended) Igual que
subfme, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
(SUBtract From Minus one Extended with
Overflow) Igual que subfme, sólo que XER[OV] se
pone a 1 si hay overflow
(SUBtract From Minus one Extended with
Overflow) Igual que subfmeo, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
(SUBtract From Zero Extended) Calcula
~rA+XER[CA] y lo deposita en rD.
(SUBtract From Zero Extended) Igual que subfze,
sólo que CR0 se actualiza tal como explicamos en
el apartado 5.1
(SUBtract From Zero Extended with Overflow)
Igual que subfze, sólo que XER[OV] se pone a 1
si hay overflow
(SUBtract From Zero Extended with Overflow)
Igual que subfzeo, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
Tabla 2.19: Instrucciones aritméticas de resta de enteros
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.
Pág 71
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tabla 2.20 muestra las instrucciones de negación aritmética que existen:
Instrucción
neg rD,rA
neg. rD,rA
nego rD,rA
nego. rD,rA
Descripción
(NEGate) Calcula el complemento a 2 de rA y lo
deposita en rD, es decir, calcula ~rA+1
(NEGate) Igual que neg, sólo que CR0 se actualiza
tal como explicamos en el apartado 5.1
(NEGate with Overflow) Igual que neg, sólo que
XER[OV] se pone a 1 si hay overflow
(NEGate) Igual que n e g o , sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
Tabla 2.20: Instrucciones de negación aritmética con enteros
5.3.4 Instrucciones aritméticas de multiplicación
La Tabla 2.21 muestra las operaciones de multiplicación que existen en
PowerPC.
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.
La Figura 2.6 muestra un ejemplo de como se calcula el producto de dos
números de 32 bits y como calcularlo usando las operaciones de PowerPC.
Pág 72
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
Figura 2.6: Cálculo del producto de dos números de 32 bits usando instrucciones PowerPC
Instrucción
mulli rD,rA,SIMM
mullw rD,rA,rB
mullw. rD,rA,rB
mullwo rD,rA,rB
Descripción
(MULtiply Low Immediate) Los 32 bits bajos del
producto rA*SIMM se depositan en rD.
(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
(MULtiply Low Word) Igual que mullw, sólo que
CR0 se actualiza tal como explicamos en el
apartado 5.1
(MULtiply Low Word with Overflow) Igual que
mullw, sólo que XER[OV] se pone a 1 si hay
overflow
Pág 73
Ensamblador del PowerPC con Mac OS X
mullwo. rD,rA,rB
mulhw rd,rA,rB
mulhw. rD,rA,rB
mulhwo rD,rA,rB
mulhwo. rD,rA,rB
mulhwu rD,rA,rB
mulhwu. rD,rA,rB
macprogramadores.org
(MULtiply Low Word with Overflow) Igual que
mullwo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
(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
(MULtiply High Word) Igual que mulhw, sólo que
CR0 se actualiza tal como explicamos en el
apartado 5.1
(MULtiply High Word with Overflow) Igual que
mulhw, sólo que XER[OV] se pone a 1 si hay
overflow
(MULtiply High Word with Overflow) Igual que
mulhwo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
(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
(MULtiply High Word Unsigned)
Igual que
mulhwu, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
Tabla 2.21: Instrucciones aritméticas de multiplicación de enteros
5.3.5 Instrucciones aritméticas de división
La Tabla 2.22 muestra las instrucciones de división de enteros de que dispone
PowerPC.
Instrucción
divw rD,rA,rB
divw. rD,rA,rB
divwo rD,rA,rB
divwo. rD,rA,rB
divwu rD,rA,rB
Descripción
(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.
(DIVide Word) Igual que divw, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
(DIVide Word with Overflow) Igual que divw, sólo
que XER[OV] se pone a 1 si hay overflow
(DIVide Word with Overflow) Igual que divwo,
sólo que CR0 se actualiza tal como explicamos en
el apartado 5.1
(DIVide Word Unsigned) Calcula la división con
números sin signo. El dividendo es rA, el divisor
es r B , y el cociente se deposita en rD. La
instrucción no nos d el resto de la operación.
Pág 74
Ensamblador del PowerPC con Mac OS X
divwu. rD,rA,rB
divwuo rD,rA,rB
divwuo. rD,rA,rB
macprogramadores.org
instrucción no nos d el resto de la operación.
(DIVide Word Unsigned) Igual que divwu, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
(DIVide Word Unsigned with Overflow) Igual que
divwu, sólo que XER[OV] se pone a 1 si hay
overflow
(DIVide Word Unsigned with Overflow) Igual que
divwuo, sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
Tabla 2.22: Instrucciones aritméticas de división de enteros
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.
En la Tabla 2.23 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.
Instrucción
cmpi CRFD,L,rA,SIMM
Descripción
(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 0 x 0 0 0 0 | | U I M M , 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
Tabla 2.23: Instrucciones de comparación de enteros
Pág 75
Ensamblador del PowerPC con Mac OS X
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 Tabla 2.24, Tabla 2.25 y Tabla 2.26 muestran las instrucciones lógicas con
enteros que existen en PowerPC.
Instrucción
andi. rD,rA,UIMM
andis. rD,rA,UIMM
Descripción
(AND Immediate) Realiza un and lógico entre rA y
0x0000||UIMM y lo deposita en rD
(AND Immediate Shifted) Realiza un and lógico
entre rA y UIMM||0x0000 y el resultado lo
deposita en rD
Pág 76
Ensamblador del PowerPC con Mac OS X
and rD,rA,rB
and. rD,rA,rB
andc rD,rA,rB
andc. rD,rA,rB
nand rD,rA,rB
macprogramadores.org
(AND) Realiza un and lógico entre rA y rB y el
resultado se deposita en rD
(AND) Igual que add, sólo que CR0 se actualiza
tal como explicamos en el apartado 5.1
(AND with Complement) Realiza un and lógico
entre r A y ~rB (complemento a 1 de rB) y el
resultado lo deposita en rD
(AND with Complement) Igual que andc, sólo que
CR0 se actualiza tal como explicamos en el
apartado 5.1
(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)
Tabla 2.24: Instrucciones que realizan un and lógico con enteros
En PowerPC no existen las respectivas operaciones andi. y andis. sin
punto(.), con lo que estas operaciones siempre modifican el campo CR0.
Instrucción
ori rD,rA,UIMM
oris rD,rA,UIMM
or rD,rA,rB
or. rD,rA,rB
orc rD,rA,rB
orc. rD,rA,rB
nor rD,rA,rB
Descripción
(OR Immediate) Realiza un or lógico entre rA y
0x0000||UIMM y lo deposita en rD
(OR Immediate Shifted) Realiza or lógico entre rA
y UIMM||0x0000 y el resultado lo deposita en rD
(OR) Realiza un or lógico entre rA y rB y el
resultado se deposita en rD
(OR) Igual que or, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
(OR with complement) Realiza un or lógico entre
rA y ~rB (complemento a 1 de rB) y el resultado
lo deposita en rD
(OR with Complement) Igual que orc, sólo que
CR0 se actualiza tal como explicamos en el
apartado 5.1
(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)
Tabla 2.25: Instrucciones que realizan un or lógico con enteros
Instrucción
xori rD,rA,UIMM
xoris rD,rA,UIMM
Descripción
(eXclusive OR Immediate) Realiza un xor lógico
entre rA y 0x0000||UIMM y lo deposita en rD
(eXclusive OR Immediate Shifted) Realiza xor
lógico entre rA y UIMM||0x0000 y el resultado lo
deposita en rD
Pág 77
Ensamblador del PowerPC con Mac OS X
xor rD,rA,rB
xor. rD,rA,rB
eqv rD,rA,rB
extsb rD,rS
extsb. rD,rS
extsh rD,rS
extsh. rD,rS
cntlzw rD,rS
cntlzw. rD,rS
macprogramadores.org
(eXclusive OR) Realiza un xor lógico entre rA y rB
y el resultado se deposita en rD
(eXclusive OR) Igual que xor, sólo que CR0 se
actualiza tal como explicamos en el apartado 5.1
(Equivalent) Realiza un xor lógico entre rA y rB y
el resultado se le hace un complemento a 1 y se
guarda en r D . Es decir, en r D se guarda
~(rA^rB)
(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.
(EXTended Sign Byte) Igual que extsb, sólo que
CR0 se actualiza tal como explicamos en el
apartado 5.1
(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.
(EXTended Sign Half-word) Igual que extsh, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
(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.
(CouNT Leading Zeros Word) Igual que cntlzw,
sólo que CR0 se actualiza tal como explicamos en
el apartado 5.1
Tabla 2.26: Otras instrucciones lógicas con enteros
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.
Pág 78
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tabla 2.27 muestra las operaciones de desplazamiento de enteros que
existen:
Instrucción
slw rD,rS,rC
srw rD,rS,rC
srawi rD,rS,C
srawi. rD,rS,C
sraw rD,rS,rC
sraw. rD,rS,rC
Descripción
(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
(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
(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
(Shift Right Algebraic Word Inmediate) Igual que
s r a w i , sólo que CR0 se actualiza tal como
explicamos en el apartado 5.1
(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
(Shift Right Algebraic Word) Igual que sraw, sólo
que CR0 se actualiza tal como explicamos en el
apartado 5.1
Tabla 2.27: Instrucciones de desplazamiento con enteros
Entre las operaciones se diferencia claramente entre los desplazamientos sin
signo (slw y srw), y desplazamientos con signo o algebraicos (srawi y
s r a w ). 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.
Pág 79
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Por ejemplo si hacemos:
lis r3,0xFFFF
srawi r2,r3,4
srawi r2,r3,15
srawi r2,r3,16
;
;
;
;
r3=0xFFFF0000
r2=0xFFFFF000
r2=0xFFFFFFFE
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
li r4,4
srw r2,r3,r4
li r4,15
srw r2,r3,r4
li r4,16
srw r2,r3,r4
; r3=0xFFFF0000
; r2=0x0FFFF000
; r2=0x0001FFFE
; r2=0x0000FFFF
Obsérvese que faltan las operaciones de desplazamiento a la izquierda con
signo, esto es porque hay mnemonics que equivalen a estas 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=4 y ME=28 la máscara sería:
4
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 se describen en la Tabla
2.28.
Pág 80
Ensamblador del PowerPC con Mac OS X
Instrucción
rlwinm rD,rS,N,MB,ME
rlwinm. rD,rS,N,MB,ME
rlwnm rD,rS,rN,MB,ME
rlwnm. rD,rS,rN,MB,ME
rlwimi rD,rD,N,MB,ME
rlwimi. rD,rD,N,MB,ME
macprogramadores.org
Descripción
(Rotate Left Word Immediate theN and with
Mask) El contenido de r S 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
(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
(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
(Rotate Left Word theN and with Mask) Igual
que rlwnm, sólo que CR0 se actualiza tal
como explicamos en el apartado 5.1
(Rotate Left Word Immediate then Mask
Insert) El contenido de r S 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 r D , dejando los
anteriores bits con el valor anterior que
tuvieran en rD
(Rotate Left Word Immediate then Mask
Insert) Igual que rlwimi, sólo que CR0 se
actualiza tal como explicamos en el apartado
5.1
Tabla 2.28: Instrucciones de rotación con enteros
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.
Pág 81
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Un ejemplo de cómo 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 sí 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
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
Pág 82
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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. En la Tabla 2.29 se detallan que mnemonics
hay para la resta inmediata.
Mnemonic
subi rD,rA,SIMM
subi. rD,rA,SIMM
subis rD,rA,SIMM
subis. rD,rA,SIMM
subic rD,rA,SIMM
subic. rD,rA,SIMM
Equivalente a
addi rD,rA,-SIMM
addi. rD,rA,-SIMM
addis rD,rA,-SIMM
addis. rD,rA,-SIMM
addic rD,rA,-SIMM
addic. rD,rA,-SIMM
Tabla 2.29: Mnemonic para la resta con un operando inmediato
Por otro lado las 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 como muestra la Tabla 2.30.
Mnemonics
sub rD,rA,rB
Equivale a
subf rD,rB,rA
sub. rD,rA,rB
subf. rD,rB,rA
subo rD,rA,rB
subfo rD,rB,rA
subo. rD,rA,rB
subfo. rD,rB,rA
subc
subfc rD,rB,rA
subc.
rD,rA,rB
rD,rA,rB subfc. rD,rB,rA
Pág 83
Descripción
Calcula rA-rB y lo deposita en
rD
Igual que sub pero dejando el
CR0 el resultado de la
comparación tal como se
explicó en el apartado 5.1
Igual que sub sólo que activa
el bit XER[OV] si hay overflow
Igual que subo pero dejando el
CR0 el resultado de la
comparación tal como se
explicó en el apartado 5.1
Calcula rA-rB y lo deposita en
r D , y si hay acarreo pone
XER[CA] a 1
Igual que sub. pero dejando el
CR0 el resultado de la
comparación tal como se
explicó en el apartado 5.1
Ensamblador del PowerPC con Mac OS X
subco
macprogramadores.org
rD,rA,rB subfco rD,rB,rA
subco. rD,rA,rB subfco.
rD,rB,rA
Igual que subc sólo que activa
el bit XER[OV] si hay overflow
Igual que subco pero dejando
el CR0 el resultado de la
comparación tal como se
explicó en el apartado 5.1
Tabla 2.30: Mnemonic para resta de dos registros operandos
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 Tabla 2.31:
Operación
Compare Word
Immediate
Compare Word
Compare Logical Word
Immediate
Compare Logical Word
Mnemonic
cmpwi CRF,rA,SIMM
Equivalente a
cmpi CRF,0,rA,SIMM
cmpw CRF,rA,rB
cmplwi
CRF,rA,SIMM
cmplw CRF,rA,rB
cmpi CRF,0,rA,rB
cmpli CRF,0,rA,SIMM
cmpli CRF,0,rA,rB
Tabla 2.31: Mnenonics de comparación para 32 bits
Los símbolos de la Tabla 2.32 se pueden usar en lugar de sus valores
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
Tabla 2.32: Mnemonics para símbolos numéricos
Pág 84
Ensamblador del PowerPC con Mac OS X
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 cómo se utilizan.
Para ayudar al programador se han creado una serie de mnemonics que
realizan las operaciones más comunes que aparecen en la Tabla 2.33. 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.
Todas estas instrucciones disponen de su equivalente versión con punto (.)
que modifican el contenido de CR0 como explicamos en el apartado 5.1
Pág 85
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Operación
EXTract and Left
Justify Word
Immediate
EXTract and Right
Justify Word
Immediate
INSert from Left
Word Immediate
INSert from Right
Word Immediate
ROTate Left Word
Immediate
ROTate Right Word
Immediate
ROT Left Word
Mnemonic
extlwi rD,rS,n,b
(n>0)
Equivalente a
rlwinm rD,rS,b,0,n-1
extrwi rD,rS,n,b
(n>0)
rlwinm rD,rS,b+n,32n,31
inslwi rD,rS,n,b
(n>0)
insrwi rD,rS,n,b
(n>0)
rotlwi rD,rS,n
rlwimi rD,rS,32b,b,(b+n)-1
rlwimi rD,rS,32b+n,b,b+n-1
rlwinm rD,rS,n,0,31
rotrwi rD,rS,n
rlwinm rD,rS,32-n,0,31
rotlw rD,rS,rN
rlwnm rD,rS,rN,0,31
Shift Left Word
Immediate
Shift Right Word
Immediate
CLeaR Left Word
Immediate
CLeaR Right Word
Immediate
CLeaR Left and
Shift Left Word
Immediate
slwi rD,rS,n (n<32)
rlwlnm rD,rA,n,0,31-n
srwi rD,rS,n (n<32)
rlwlnm rDmrS,32-n,n,31
clrlwi rD,rS,n
(n<32)
clrrwi rD,rS,n
(n<32)
clrlslwi rD,rS,b,n
(n≤b≤31)
rlwinm rD,rS,0,n,31
rlwinm rD,rS,0,0,31-n
rlwinm rD,rS,n,b-n,31n
Tabla 2.33: Instrucciones de rotación y desplazamiento
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
mfxer rD
equivale a
equivale a
mtspr 1,rS
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:
Pág 86
Ensamblador del PowerPC con Mac OS X
nop
macprogramadores.org
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
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 cómo 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:
Pág 87
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
srawi r4,r3,31 ; r4 = (r3<0)
xor r5,r4,r3
; r5 = (r3<0)
sub r6,r5,r4 ; r6 = (a<0) ?
el valor de a
? -1 : 0
? -a : a
(-a+1) : a
Al acabar de ejecutar este programa r6 contiene abs(r3).
Por ejemplo, supongamos que a=-6 veamos como opera el programa con los
bits:
r3(-6)
r4(-1)
r5(+5)
r6(+6)
11111111
11111111
00000000
00000000
11111111
11111111
00000000
00000000
11111111
11111111
00000000
00000000
11111010
11111111 srawi r4,r3,31
00000101 xor r5,r4,r3
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 cómo 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 sí 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.
;
;
subc r5,r4,r3 ;
subfe r6,r4,r4 ;
and r5,r5,r6
;
add r7,r3,r5
;
;
r3
r4
r5
r6
r5
r7
r7
=
=
=
=
=
=
=
a
b
r4-r3
(r4>r3)?0:-1
(r4>r3)?0:(r4-r3)
(r4>r3)?r3:r4
min(r3,r4)
Pág 88
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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, ó -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.
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)
;
;
subc r5,r4,r3 ;
subfe r6,r4,r4 ;
andc r5,r5,r6 ;
add r7,r3,r5
;
;
r3
r4
r5
r6
r5
r7
r7
=
=
=
=
=
=
=
a
b
r4-r3
(r4>r3)?0:-1
(r4>r3)?(r4-r3):0
(r4>r3)?r4:r3
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
Pág 89
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 análogo al del mínimo 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 contendrá la solución
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)
1000 0001 (129)
1111 1111 (-1)
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 análogo 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 contendrá la solución
Pág 90
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
Ejemplos de división serían:
n
7
-7
7
-7
d
3
3
-3
-3
c
2
-2
-2
2
r
1
-1
1
-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:
divw rT,rN,rD
mullw rT,rT,rD
sub rT,rN,rT
;
;
;
;
;
rN Dividendo
rD Divisor
c = n/d
c*d
r = n-c*d
rT acaba conteniendo el resto de la división rN%rD.
Pág 91
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
En las divisiones de número sin signo el algoritmo es similar, sólo que ahora
se usa divwu en vez de divw:
divwu rT,rN,rD
mullw rT,rT,rD
sub rT,rN,rT
;
;
;
;
;
rN Dividendo
rD Divisor
c = n/d
c*d
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.
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.
5.8.5.1
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
Pág 92
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
5.8.5.2
División con signo por 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.
Los detalles son complicados, especialmente para algunos divisores, como por
ejemplo el 7.
En el Listado 2.9, Listado 2.10 y Listado 2.11 se muestran tres ejemplos de
cómo se haría la división con los divisores 3, 5 y 7, respectivamente. Estos
ejemplos también muestran cómo se obtendría el resto con una simple resta
de d*c al dividendo n.
lis rM,0x5555
;
ori rM,rM,0x5556;
mulhw rC,rM,rN ;
srwi rT,rN,31
;
add rC,rC,rT
;
mulli rT,rC,3
;
sub rT,rN,rT
;
Cargamos el magic number en rM
m=0x55555556 = (232+2)/3
c=floor(m*n/232)
Resta 1 a c si n es negativo
rC contiene el cociente
Calcula del resto como r=n-c*3
rT contiene el resto
Listado 2.9: Algoritmo de la división entre 3
Pág 93
Ensamblador del PowerPC con Mac OS X
lis rM,0x6666
ori rM,rM,0x6667
mulhw rC,rM,rN
srawi rC,rC,1
srwi rT,rN,31
add rC,rC,rT
mulli rT,rC,5
sub rT,rN,rT
;
;
;
;
;
;
;
;
macprogramadores.org
Cargamos el magic number en rM
m=0x66666667 = (233+3)/5
c=floor(m*n/232)
c=floor(c/2)
Resta 1 a c si n es negativo
rC contiene el cociente
Calcula del resto como r=n-c*5
rT contiene el resto
Listado 2.10: Algoritmo de la división entre 5
lis rM,0x9249
ori rM,rM,0x2493
mulhw rC,rM,rN
add rC,rC,rN
srawi rC,rC,2
srwi rT,rN,31
add rC,rC,rT
mulli rT,rC,7
sub rT,rN,rT
;
;
;
;
;
;
;
;
;
Cargamos el magic number en rM
m=0x92492493 = (234+5)/7 - 232
c=floor(m*n/232)
c=floor(m*n/232)+n
c=floor(c/4)
Resta 1 a c si n es negativo
rC contiene el cociente
Calcula del resto como r=n-c*7
rT contiene el resto
Listado 2.11: Algoritmo de la división entre 7
El método general de cálculo 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
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.
Pág 94
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
ori rM,0xDB6D
mulhw rC,rM,rN
sub rC,rC,rN
srawi rC,rC,2
srwi rT,rC,31
add rC,rC,rT
mulli rT,rC,-7
sub rT,rN,rT
;
;
;
;
;
;
;
;
;
Cargamos el magic number en rM
m=0x6DB6DB6D = -(234+5)/7 + 232
c=floor(m*n/232)
c=floor(m*n/232)-n
c=floor(c/4)
Añade 1 a c si n es negativo
rC contiene el cociente
Cálculo del resto como r=n-c*(-7)
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
= 0 x 6 D B 6 D B 6 D ). Las únicas excepciones a esta regla son d = 3 y
d=715.827.883
La Tabla 2.34 muestra los magic number y desplazamientos para los números
más comunes.
d (decimal)
-5
-3
-2k
1
2k
3
5
6
7
9
10
11
12
m (hexadecimal)
9999 9999
5555 5555
7FFF FFFF
8000 0001
5555 5556
6666 6666
2AAA AAAB
9249 2493
38E3 8E39
6666 6667
2E8B A2E9
2AAA AAAB
Pág 95
desplazamiento
1
1
k-1
k-1
0
1
0
2
1
2
1
1
Ensamblador del PowerPC con Mac OS X
25
125
macprogramadores.org
51EB 851F
1062 4DD3
3
3
Tabla 2.34: Magic number y desplazamientos para los números más comunes
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].
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 aparece en
el Listado 2.12. Para referirnos a los registros vamos a usar definiciones
#define tal como explicábamos en el apartado 0, que nos permiten dar
nombres a los registros para recordar su utilidad más fácilmente, de acuerdo
a la Tabla 2.35:
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
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
Tabla 2.35: #define del programa
Pág 96
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 e insertamos un cero
en el bit bajo de coc
4. Si el resultado es positivo ponemos el resultado en res, e 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
d:
.long
.comm
.comm
2,1
1,0
c,8
r,8
;
;
;
;
Dividendo
Divisor
Cociente
Resto
.text
.globl _main
Pág 97
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
_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
eti3:// (Cuerp else) Desplazamos a la izquierda
// ceros_dvd veces a dvdh:dvdl
cmpwi ceros_dvd,0
; Si (ceros_dvd==0) goto eti5
Pág 98
Ensamblador del PowerPC con Mac OS X
beq eti5
mtctr ceros_dvd
eti4:add dvdh,dvdh,dvdh
addc dvdl,dvdl,dvdl
macprogramadores.org
;
;
;
;
;
;
Fijamos el contador del
bucle
Desplazamos uno a
izquierda sumando
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
eti9:// Cociente==0 (dvs>dvd)
lis coch,0
Pág 99
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
Listado 2.12: División de 64 bits en máquinas de 32 bits
Pág 100
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
6 Instrucciones de bifurcación
En esta sección vamos a comentar con qué 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 cálculo 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 sí 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.
Pág 101
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
Las instrucciones de bifurcación tienen los siguientes tipos de cálculo de la
dirección destino:
o
o
o
o
o
o
Salto
Salto
Salto
Salto
Salto
Salto
relativo
absoluto
condicional
condicional
condicional
condicional
relativo
absoluto
al Link Register
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.
Las instrucción de salto relativo de que dispone PowerPC se resumen en la
Tabla 2.36:
Instrucción
b D
bl D
Descripción
(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
(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
Tabla 2.36: Instrucciones de salto relativo
La Figura 2.7 muestra el proceso de cálculo de la dirección de salto en las
instrucciones de salto relativo:
Pág 102
Ensamblador del PowerPC con Mac OS X
0
macprogramadores.org
5 6
29 30 31
18
LI
AA LK
(Codificación de la instrucción)
0
5 6
29 30 31
Exten signo
LI
0
0 0
31
+
IP (Instruction Pointer)
0
31
Dirección de salto
Figura 2.7: Cálculo de la dirección de salto en las instrucciones de salto relativo
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.
0
5 6
29 30 31
18
LI
AA LK
(Codificación de la instrucción)
0
5 6
Exten signo
29 30 31
LI
0
0 0
31
Dirección de salto
Figura 2.8: Cálculo de la dirección de salto en las instrucciones de salto absoluto
Pág 103
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
La Figura 2.8 muestra el proceso de cálculo de la dirección absoluta.
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
Tabla 2.37:
Instrucción
ba D
Descripción
(Branch Absolute) Salta a la dirección dada en D.
Esta instrucción tiene AA=1 y LK=0
(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
bla D
Tabla 2.37: Instrucciones de salto absoluto
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
OpCode
10 11
BO
15 16
29 30 31
BI
AA LK
Figura 2.9: Codificación de las instrucciones de salto condicional
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 Tabla 2.38:
Pág 104
Ensamblador del PowerPC con Mac OS X
BI
Dec Bin
0 00000
Bit CRn
a evaluar
CR0[0]
1 00001
CR0[1]
2 00010
CR0[2]
3 00011
CR0[3]
4
5
6
7
8
12
16
20
24
28
9
13
17
21
25
29
10
14
18
22
26
30
11
15
19
23
27
31
00100
00101
00110
00111
01000
01100
10000
10100
11000
11100
01001
01101
10001
10101
11001
11101
01010
01110
10010
10110
11010
11110
01011
01111
10011
10111
11011
11111
CR1[0]
CR1[1]
CR1[2]
CR1[3]
CRn[0]
macprogramadores.org
Descripción
Negative (LT). El resultado de una instrucción con
punto (.) es negativo
Positive (GT). El resultado de una instrucción con
punto (.) es positivo
Zero (EQ). El resultado de una instrucción con
punto (.) es cero
Summary Overflow (SO). Copia del bit XER[SO]
de la anterior instrucción ejecutada
Copia de FPSCR[FX]
Copia de FPSCR[FEX]
Copia de FPSCR[VX]
Copia de FPSCR[OX]
Menor que:
Para enteros rA<SIMM o rA<UIMM o rA<rB
Para punto flotante fA<fB
CRn[1]
Mayor que:
Para enteros rA>SIMM o rA>UIMM o rA>rB
Para punto flotante fA>fB
CRn[2]
Igual:
Para enteros rA=SIMM o rA=UIMM o rA=rB
Para punto flotante fA=fB
CRn[3]
Summary Overflow o floating Point Unordered
Tabla 2.38: Configuración del operando BI
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 consultando los bits de este campo.
Pág 105
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 2.38 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 Tabla 2.39.
BO
0000y
0001y
001zy
0100y
0101y
011zy
1z00y
1z01y
1z1zz
Descripción
Decrementa el registro CTR y después salta si CTR≠0
es FALSE
Decrementa el registro CTR y después salta si CTR=0
es FALSE
Salta si la condición es FALSE
Decrementa el registro CTR y después salta si CTR≠0
es TRUE
Decrementa el registro CTR y después salta si CTR=0
es TRUE
Salta si la condición es TRUE
Decrementa el registro CTR y después salta si CTR≠0
Decrementa el registro CTR y después salta si CTR=0
Salta siempre
y la condición
y la condición
y la condición
y la condición
z Es un bit que se reserva para el futuro, y 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 explica en el
Apéndice B, en principio se puede dejar siempre a 0
Tabla 2.39: Configuración del operando BO
Básicamente estos 5 bits codifican 6 posibles actuaciones:
o
o
o
o
o
o
Decrementar el registro CTR
Comprobar si CRT es 0
Comprobar si CTR no es cero
Comprobar si la condición es verdadera
Comprobar si la condición es falsa
Predicción de salto. Se explica en el Apéndice B
Pág 106
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 a una
subrutina.
Las instrucciones de salto condicional se dividen en cuatro tipos que vamos a
explicar detallar a continuación.
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 Figura 2.10:
0
5 6
16
10 11
B0
15 16
29 30 31
BI
BD
AA LK
(Codificación de la instrucción)
¿Cumple
condición?
0
Sí
0
15 16
Extensión del signo
No
29 30 31
BD
AA LK
0
+
31
31
Instruction Pointer (IP)
Siguiente instrucción
0
31
Dirección de salto
Figura 2.10: Instrucciones condicionales de salto relativo
Las instrucciones de salto condicional relativo de que dispone PowerPC se
resumen en la Tabla 2.40:
Pág 107
Ensamblador del PowerPC con Mac OS X
Instrucción
bc BO,BI,D
bcl BO,BI,D
macprogramadores.org
Descripción
(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
(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
Tabla 2.40: Instrucciones condicionales de salto relativo
Por ejemplo, imaginemos que queremos hacer una operación sólo si el valor
del registro r2 es menor a 5, entonces haríamos:
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 Figura 2.11 muestra el funcionamiento de las instrucciones condicionales
de salto absoluto. 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.
Instrucción
bca BO,BI,D
bcla BO,BI,D
Descripción
(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
(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
Tabla 2.41: Instrucciones condicionales de salto absoluto
Pág 108
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Las instrucciones condicionales de salto absoluto que existen en PowerPC se
detallan en la Tabla 2.41.
0
5 6
16
10 11
B0
15 16
29 30 31
BI
BD
AA LK
(Codificación de la instrucción)
¿Cumple
condición?
0
Sí
0
15 16
0000 0000 0000 0000
No
29 30 31
BD
AA LK
31
Siguiente instrucción
0
31
Dirección de salto
Figura 2.11: Instrucciones condicionales de salto absoluto
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 2.12 muestra el funcionamiento de esta instrucción.
Pág 109
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Una vez puesta la dirección a la que queremos saltar en el registro CTR
podemos saltar a esta dirección con las instrucciones de PowerPC de la Tabla
2.42.
0
5 6
19
10 11
B0
15 16
BI
20 21
0000
29 30 31
528
LK
(Codificación de la instrucción)
¿Cumple
condición?
0
Sí
No
0
29
CTR (CounT Register)
30 31
31
||
Siguiente instrucción
00
0
31
Dirección de salto
Figura 2.12: Instrucciones condicionales de salto al Count Register
Instrucción
bcctr BO,BI
bcctrl BO,BI
Descripción
(Branch Conditional to CounT Register) Salta a la
dirección de memoria almacenada en el CTR. Esta
instrucción tiene LK=0
(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
Tabla 2.42: Instrucciones condicionales de salto absoluto
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.
Pág 110
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Queda por ver cómo 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 Figura 2.13 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.
0
5 6
19
10 11
B0
15 16
BI
20 21
29 30 31
0000
16
LK
(Codificación de la instrucción)
¿Cumple
condición?
0
Sí
No
0
29
LR (Link Register)
30 31
31
||
Siguiente instrucción
00
0
31
Dirección de salto
Figura 2.13: Instrucciones condicionales de salto al Link Register
Las instrucciones condicionales de salto al Link Register que tiene PowerPC se
resumen en la Tabla 2.43.
Instrucción
bclr BO,BI
bclrl BO,BI
Descripción
(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
(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
Pág 111
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
almacena la dirección de la siguiente instrucción a
la instrucción de salto. Esta instrucción tiene LK=1
Tabla 2.43: Instrucciones condicionales de salto al Link Register
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 mfspr y mtspr.
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 que se resumen en
la Tabla 2.44:
Mnemonic
blr
blrl
bctr
bctrl
Descripción
(Branch to LR) Salta a la dirección de
memoria almacenada en el registro LR
(Branch to LR and Link) Igual que blr sólo
que en LR se almacena la dirección de
memoria de la siguiente instrucción a la
instrucción de salto.
(Branch to CTR) Salta a la dirección de
memoria almacenada en el registro CTR
(Branch to CTR and Link) Igual que bctr
sólo que en LR se almacena la dirección de
memoria de la siguiente instrucción a la
instrucción de salto.
Equivale a
bclr 20,0
bclrl 20,0
bcctr 20,0
bcctrl 20,0
Tabla 2.44: Mnemonics de salto incondicional
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 la Tabla 2.45 y Tabla 2.46:
Pág 112
Ensamblador del PowerPC con Mac OS X
Condición del salto
Salto si la condición se cumple
Salto si la condición no se
cumple
macprogramadores.org
bc
bt
bf
Instrucción a que equivale
bca
bctr
bcctr
bta
btlr
btctr
bfa
bflr
bfctr
Tabla 2.45: Mnemonics de salto condicional sin actualización de LR
Condición del salto
Salto si la condición se cumple
Salto si la condición no se
cumple
bc
btl
bfl
Instrucción a que equivale
bca
bctr
bcctr
btla
btlrl
btctrl
bfla
bflrl
bfctrl
Tabla 2.46: Mnemonics de salto condicional con actualización de LR
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 2.47.
Símbolo Valor Descripción
lt
0
Less Than
gt
1
Greater Than
eq
2
EQual
so
3
Summary Overflow
un
3
UNordered
Tabla 2.47: Símbolos para el operando BI
Pág 113
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 como muestra la Tabla 2.48 y Tabla 2.49:
Condición del salto
Branch if less than
Branch if less than or equal
Branch if equal
Branch if greater than or equal
Branch if greater than
Branch if not less than
Branch if not equal
Branch if not greater than
Branch if summary overflow
Branch if not summary overflow
Branch if unordered
Branch if not unordered
Instrucción a que equivale
bc
bca
bclr
bcctr
blt
blta
bltlr
blrctr
ble
blea
blelr
blectr
beq
beqa
beqlr
beqctr
bge
bgea
bgelr
bgectr
bgt
bgta
bgtlr
bgctr
bnl
bnla
bnllr
bnlctr
bne
bnea
bnelr
bnectr
bng
bnga
bnglr
bngctr
bso
bsoa
bsolr
bsoctr
bns
bnsa
bnslr
bnsctr
bun
buna
bunlr
bunctr
bnu
bnua
bnulr
bnuctr
Tabla 2.48: Mnemonics de salto condicional sin actualización de LR
Condición del salto
Branch if less than
Branch if less than or equal
Branch if equal
Branch if greater than or equal
Branch if greater than
Branch if not less than
Branch if not equal
Branch if not greater than
Branch if summary overflow
Instrucción a que equivale
bcl
bcla
bclrl
bcctrl
bltl bltla bltlrl bltctrl
blel blela blelrl blectrl
beql beqla beqlrl beqctrl
bgel bgela bgelrl bgectrl
bgtl bgtla bgtlrl
bgctrl
bnl
bnlla bnllrl bnlctrl
bnel bnela bnelrl bnectrl
bngl bngla bnglrl bngctrl
bsol bsola bsolrl bsoctrl
Pág 114
Ensamblador del PowerPC con Mac OS X
Branch if not summary overflow
Branch if unordered
Branch if not unordered
macprogramadores.org
bnsl
bunl
bnul
bnsla
bunla
bnula
bnslrl
bunlrl
bnulrl
bnsctrl
bunctrl
bnuctrl
Tabla 2.49: Mnemonics de salto condicional con actualización de LR
La Tabla 2.50 ayuda a entender y recordar las instrucciones anteriores:
Abreviatura
lt
le
eq
ge
gt
nl
ne
ng
so
ns
un
nu
Descripción
Less Than
Less than or Equal
EQual
Greater than or Equal
Greater Than
Not Less
Not Equal
Not Greater than
Summary Overflow
Not Summary Overflow
UNordered (para comparaciones en punto flotante)
Not Unordered (para comparaciones en punto flotante)
Tabla 2.50: Abreviaturas para los mnemonics de comparación
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
············
Pág 115
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 */
Además existen mnemonics que nos permiten acceder a ellos más fácilmente
que se muestran en la Tabla 2.51:
Mnemonic
mfctr rD
mtctr rS
mflr rD
mtlr rS
Descripción
(Move From CTR) Copia el contenido de CTR en rD
(Move to CTR) Copia el contenido de rS en CTR
(Move From LR) Copia el contendo de LR en rD
(Move To LR) Copia el contenido de rS en CTR
Tabla 2.51: Mnemonics para acceso a los registro CTR y LR
También tenemos mnemonics que nos permiten encender, apagar, copiar e
invertir un determinado bit del registro CR que se resumen en la Tabla 2.52:
Mnemonic
crset B
crclr B
crmove B1,B2
crnot B1,B2
Descripción
CR SET
CR CLeaR
CR MOVE
CR NOT
Equivalente a
creqv B,B,B
crxor B,B,B
cror B1,B2,B2
crnor B1,B2,B2
Tabla 2.52: Mnemonics para acceder a un bit del registro CR
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 e 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 y otra parte.
Pág 116
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
}
else
{
// Hacer lo de más 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 más 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.
Pág 117
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 ifelse así:
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 lógica (r4,5)
Pág 118
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 (sin 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.
Por ejemplo para codificar un switch así:
switch (x)
{
case 0:
// Código del caso
case 1:
// Código del caso
case 2:
// Código del caso
case 3:
// Código del caso
case 4:
// Código del caso
case 5:
// Código del caso
·················
}
0
1
2
3
4
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 dirección 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
Pág 119
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tema 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 tenía
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.
6.4.1 Mnemonics para bucles
Para las instrucciones de salto en los bucles se han creado los mnemonics que
se resumen en la Tabla 2.53 y Tabla 2.54:
Equivale a
Condición de salto
bc
bca
bclr
bdnz
bdnza
bdnzlr
Decrementa CTR y salta si CTR≠0
Decrementa CTR y salta si CTR≠0 y la condición bdnzt bdnzta bdnztlr
es true
Decrementa CTR y salta si CTR≠0 y la condición bdnzf bdnzfa bdnzflr
es false
bdz
bdza
bdzlr
Decrementa CTR y salta si CTR=0
bdzta
bdztlr
Decrementa CTR y salta si CTR=0 y la condición bdzt
es true
bdzfa
bdzflr
Decrementa CTR y salta si CTR=0 y la condición bdzf
es false
Tabla 2.53: Mnemonics para bucles sin actualización de LR
Equivale a
bc
bca
bclr
Condición de salto
bdnzl
bdnzla
bdnzlrl
Decrementa CTR y salta si CTR≠0
Decrementa CTR y salta si CTR≠0 y la bdnztl bdnztla bdnztlrl
condición es true
Pág 120
Ensamblador del PowerPC con Mac OS X
Decrementa CTR
condición es false
Decrementa CTR y
Decrementa CTR
condición es true
Decrementa CTR
condición es false
macprogramadores.org
y salta si CTR≠0 y la bdnzfl bdnzfla bdnzflrl
salta si CTR=0
y salta si CTR=0 y la
bdzl
bdztl
bdzla
bdztla
bdzlrl
bdztlrl
y salta si CTR=0 y la
bdzfl
bdzfla
bdzflrl
Tabla 2.54: Mnemonics para bucles con actualización de LR
La Tabla 2.55 ayuda a recordar las abreviaturas usadas por estos mnemonics:
Abreviatura
t
f
d
z
nz
Descripción
True
False
Decrement
Zero
Not Zero
Tabla 2.55: Abreviaturas de los mnemonics para bucles
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 cómo se usan estos mnemonics
para cada uno de los bucles de C.
6.4.2 Bucle do-while
Vamos a empezar viendo cómo 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
Pág 121
Ensamblador del PowerPC con Mac OS X
fin:
macprogramadores.org
add r3,r3,1 ; r3++
cmpwi r3,100
ble do
; r3<=100
Aunque al ser la condición un contador es preferible usar el registro CTR de
esta manera:
li r2,0
li r3,1
li r4,100
mtctr r4
do: add r2,r2,r3
add r3,r3,1
bdnz do
fin:
;
;
;
;
;
;
;
r2 es el acumulador
r3 es el contador
Numero de repeticiones
Carga el CTR
r2 += r3
r3++
Hasta que CTR llege a 0
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.
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:
Pág 122
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
for (inicialización;condición;actualización)
{
cuerpo
}
Los cuales se detallan en la Figura 2.14:
Inicialización
Actualización
Condición
No
Fin
Sí
Cuerpo
Figura 2.14: Estructura de la sentencia for
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.
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:
Pág 123
Ensamblador del PowerPC con Mac OS X
.data
SD: .comm esprimo, 4
n:
.long 13
.text
.globl _main
_main:
lis r2,ha16(SD)
macprogramadores.org
; Aquí se deposita si es primo
; Número que queremos ver
; si es primo
; r2 apunta a la base del
; segmento de datos
lwz r3,lo16(n)(r2) ; r3 es el número 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
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 se muestran en la
Tabla 2.56:
Pág 124
Ensamblador del PowerPC con Mac OS X
Instrucción
crand CRBD,CRBA,CRBB
cror CRBD,CRBA,CRBB
crxor CRBD,CRBA,CRBB
crnand CRBD,CRBA,CRBB
crnor CRBD,CRBA,CRBB
creqv CRBD,CRBA,CRBB
crandc CRBD,CRBA,CRBB
crorc CRBD,CRBA,CRBB
mcrf CRD,CRS
macprogramadores.org
Descripción
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
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
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
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
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
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
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
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
Los 4 bits del campo CRS se copian en el
campo CRD
Tabla 2.56: Operaciones lógicas con campos del registro CR
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
}
Podemos realizar las comparaciones relacionales con cmpwi, depositar cada
resultado en un campo distinto de CR y luego usar crand para comprobar
ambos resultados:
Pág 125
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
cmpwi cr2,rA,5
cmpwi cr3,rB,3
crand 17,9,13
bng cr4,fin
; Haz algo
·············
fin: ·············
Hay que tener en cuenta que crand opera a nivel de bit de CR, no a nivel de
campo de CR, con lo que tenemos que decirle que bits origen leer y en que
bit destino depositarlo.
Como los bits de CR están ordenados como muestra la Figura 2.3, 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 126
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
7 Instrucciones de trabajo con números en
punto flotante
En este apartado 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 directamente 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.
Pág 127
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
o Cuando pasamos un dato de un registro FPR a memoria éste 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 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 él 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
2.15 muestra los principales campos del registro FPSCR y cual es su utilidad.
0
34
78
11 12
15 16
Flags de
exception
inválida
Flags de
summary
exception
Flags de
exception
19 20
23 24
Flags de
habilitación
de excepción
Códigos de
condición
27 28
31
Bits de
redondeo
Figura 2.15: Campos del registro FPSCR
La Tabla 2.57 describe detalladamente el propósito de cada uno de los bits
del registro FPSCR.
Pág 128
Ensamblador del PowerPC con Mac OS X
Campo Bit
0
1
2
3
macprogramadores.org
Nombre Descripción
0
FX
1
FEX
2
VX
3
OX
4
UX
5
ZX
6
XX
7
VXSNAN
8
VXISI
9
VXIDI
10
VXZDZ
11
VXIMZ
12
VXVC
Floating Point Exception Summary.
Cualquier excepción en punto flotante,
activa implícitamente este bit.
Floating Point Enabled Exception
Summary. Cualquier excepción
habilitada que se produzca, activa este
bit.
Floating Point Invalid Operation
Exception Summary. Este bit indica la
ocurrencia de cualquiera de las Invalid
Operation Exception.
Floating Point Overflow Exception.
Activo cuando se produce un overflow
Floating Point Underflow Exception.
Activo cuando se produce un underflow
Floating Point Zero Divide Exception.
Activo cuando se produce una división
entre cero
Floating Point Inexact Exception. Indica
que se ha tenido que hacer un
redondeo.
Floating Point Invalid Operation
Exception for Signaling NAN. Se
produce cuando uno de los operandos
es un signaling NaN.
Floating Point Invalid Operation
Exception for Infinite Sustract Infinite.
Se activa cuando pedimos calcular ∞∞.
Floating Point Invalid Operation
Exception for Infinite Divide Infinite. Se
produce cuando pedimos calcular ∞/∞.
Floating Point Invalid Operation
Exception for Zero Divide Zero. Se
produce cuando pedimos calcular 0/0.
Floating Point Invalid Operation
Exception for Infinite Multiply Zero. Se
produce cuando pedimos calcular ∞*0.
Floating Point Invalid Operation
Exception for Invalid Compare. Se
produce cuando intentamos comparar
números sin relación de orden.
Pág 129
Bit
retenido
Sí
No
No
Sí
Sí
Sí
Sí
Sí
Sí
Sí
Sí
Sí
Sí
Ensamblador del PowerPC con Mac OS X
4
5
6
7
13
FR
14
FI
15
FPRF
16
17
18
19
20
21
VXSOFT
22
VXSQRT
23
VXCVI
24
VE
25
OE
26
UE
27
ZE
28
XE
macprogramadores.org
Floating Point Fraction Rounded. Se
pone a 1 si la última instrucción de
redondeo o conversión incrementó la
fracción, sino se pone a 0.
Floating Point Fraction Inexact. La
última instrucción necesitó de
redondeo.
Floating Point Result Class Descriptor.
Las 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
Floaintg Point Less Than or Negative
Floating Point Greater Than or Positive
Floating Point Equal or Zero
Floating Point Unordered or NaN
Reservado
Floaintg Point Invalid Operation
Exception 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
Floating Point Invalid Operation
Exception for Invalid Square Root.
Floating Point Invalid Operation
Exception for Invalid Integer Convert
Floating Point Invalid Operation
Exception Enable
Floating Point Overflow Exception
Enable
Floating Point Underflow Exception
Enable
Floating Point Zero Divide Exception
Enable
Floating Point Inexact Exception Enable
Pág 130
No
No
No
No
No
No
No
Sí
Sí
Sí
-
Ensamblador del PowerPC con Mac OS X
29
NI
30
31
RN
macprogramadores.org
Floating Point no IEEE 754 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.
Floating Point Rounding Control:
00 - Redondeo al más cercano (por
defecto)
01 - Redondeo a cero
10 - Redondeo a +∞
11 - Redondeo a -∞
-
-
Tabla 2.57: Asignación de bits en el registro FPSCR
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 2.58 resume las instrucciones de que dispone PowerPC
para acceder a los bits de registro FPSCR.
Instrucción
mffs fD
mffs. fD
mtfsf FM,fD
mtfsf. FM,fD
mtcrfs CRFD,FS
Descripción
Nivel
acceso
(Move From FPSCR) El contenido de Registro
FPSCR se deposita en los bits 32-63
de fD. El contenido de los bits 0-31
de fD queda indefinido.
(Move To FPSCR Fields) Los bits 32- Registro
63 del registro f D se copian al
registro FPSCR bajo el control de la
máscara de campos F M , la cual
indica que campos se deben copiar.
FM puede tener hasta 8 bits de los
cuales los activos indican los
campos a copiar.
(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
Pág 131
Ensamblador del PowerPC con Mac OS X
mtfsfi FD,UIMM
mtfsfi. CRFD,UIMM
mtfsb0 BD
mtfsb0. BD
mtfsb1 BD
mtfsb1. BD
macprogramadores.org
excepción copiados (excepto FEX y
VX) son borrados en FPSCR
(Move To FPSCR Field Immediate) El Campo
contenido de UIMM se deposita en el
campo FD
(Move To FPSCR Bit 0) El bit de la Bit
posición BD del registro FPSCR es
borrado. Los bits FEX y VX no
pueden borrarse explícitamente
(Move To FPSCR Bit 1) El bit de la Bit
posición BD del registro FPSCR es
encendido. Los bits FEX y VX no
pueden encenderse explícitamente
Tabla 2.58: Instrucciones para acceso a los bits de FPSCR
Como es habitual, las instrucciones de la Tabla 2.58 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.
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.
Pág 132
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
La Figura 2.16 muestra más claramente cuando se activa cada uno de los bits
de XX:
¿Redondeo?
FI <- 0
FR <- 0
FI <- 1
¿Fración
incrementada?
FR <- 1
FR <- 0
Figura 2.16: Proceso de activación de los flags de excepción
7.3.2.1
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 Tabla 2.59:
Pág 133
Ensamblador del PowerPC con Mac OS X
Flag
VE
OE
UE
ZE
XE
macprogramadores.org
Tipo de excepción que activa
Invalid Operation Exception
Overflow Exception
Underflow Exception
Zero Divide Exception
Inexact Exception
Tabla 2.59: Flags de habilitación de excepción
7.3.2.2
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 Tabla 2.60 muestra cómo se deben interpretar estos bits.
Pág 134
Ensamblador del PowerPC con Mac OS X
Result Flags
(Bits 15-19)
C
< >
0
0
0
=
0
?
1
macprogramadores.org
Resultado para
una comparación
Resultado para
otra operación
No aplicable
0
0
0
0
0
1
0
0
0
1
1
0
0
1
1
0
0
0
1
0
0
0
0
0
0
0
1
0
1
1
1
1
0
0
0
1
1
0
0
0
Sin relación de
orden
== (Igual)
> (Mayor que)
No aplicable
< (Menor que)
No aplicable
Sin relación de
orden
== (Igual)
> (Mayor que)
1
1
0
0
0
< (Menor que)
+0
Número normalizado positivo
+∞
Número normalizado negativo
-∞
Quiet NaN
-0
Número denormalizado
positivo
Número denormalizado
negativo
Tabla 2.60: Floating Point Result Flags FPSCR[FPRF]
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
mcrfs 2,3
mcrfs 3,4
bun 3,infinito
;
;
;
;
;
;
;
;
;
beq 3,cero
b normal
infinito:
bt 11,NaN
Fija los valores de FPSCR[15-19]
de acuerdo al tipo de f0
Copia los bits FPSCR[12-15] a CR2
Copia los bits FPSCR[16-19] a CR3
Si el bit 3 de CR3 es 1
el resultado es infitito o NaN
Si CR3[2]=1
Es un número normalizado
o denormalizado
; 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
;
;
;
;
; f0 es un número
Si BI=11, es decir, CR2[3]=1
(Summary Overflow o Floating Point
Unordered) indica que f0
es un número denormalizado
normalizado
Pág 135
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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 Tabla 2.61:
Bit
30
0
0
1
1
Bit 31
0
1
0
1
Modo redondeo
Al más cercano
A cero
A +∞
A -∞
Tabla 2.61: Modo de redondeo
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 Tabla 2.62 muestra cuáles son los bits del registro FPSCR que se copian al
registro CR en caso de usar instrucciones con punto.
Bit
4
5
6
7
Descripción
Contiene el valor del bit FX del registro FPSCR que indica que alguna
excepción se ha producido
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
Contiene el valor del bit VX del registro FPSCR que indica que alguna
invalid exception se ha producido
Contiene el valor del bit OX del registro FPSCR que indica que se ha
producido un desbordamiento
Tabla 2.62: Bits del registro FPSCR copiados al registro CR
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:
Pág 136
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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
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 esté 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
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) 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
Tema [pendiente], con lo cual sólo puede ser modificado por el sistema
operativo.
La Tabla 2.63 describe los valores que pueden tomar los flags FE0 y FE1:
Pág 137
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
Tabla 2.63: Valores que pueden tomar los flags FE0 y FE1
El uso de traps lo veremos en el Tema [pendiente], el resto del tema
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
Pág 138
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 aparecen en la Tabla 2.64 y Tabla 2.65:
Instrucción
lfs fD,d(rA)
lfsu fD,d(rA)
lfd fD,d(rA)
lfdu fD,d(rA)
Descripción
(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
(Load Floating-point Single with Update) Igual a lfs,
sólo que r A se actualiza con el valor de d ( r A )
después de leer la memoria
(Load Floating-point Double) El doble-word en la
dirección de memoria d(rA) se carga en fD
(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
Tabla 2.64: Instrucciones de carga de números en punto flotante con indireccionamiento de
registro base e índice inmediato
Instrucción
sfs fD,d(rA)
sfsu fD,d(rA)
sfd fD,d(rA)
sfdu fD,d(rA)
Descripción
(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)
(Store Floating-point Single with Update) Igual a sfs,
sólo que r A se actualiza con el valor de d ( r A )
después de escribir la memoria
(Store Floating-point Double) El contenido de fD se
guarda en memoria, en el doble-word apuntado por
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
Tabla 2.65: Instrucciones de almacenamiento de números en punto flotante con
indireccionamiento de registro base e índice inmediato
Y las instrucciones de acceso con indireccionamiento de registro base y
registro índice aparecen en la Tabla 2.66 y Tabla 2.67:
Instrucción
lfsx fD,rA,rB
Descripción
(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
Pág 139
Ensamblador del PowerPC con Mac OS X
lfsux fD,rA,rB
lfdx fD,rA,rB
lfdux fD,rA,rB
macprogramadores.org
carga en fD convertido a punto flotante de precisión
doble
(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
(Load Floating-point Double indeXed) El doble-word en
la dirección de memoria (rA|0)+rB se carga en fD
(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
Tabla 2.66: Instrucciones de carga de números en punto flotante con indireccionamiento de
registro base y registro índice
Instrucción
sfsx fD,rA,rB
sfsux fD,rA,rB
sfdx fD,rA,rB
sfdux fD,rA,rB
Descripción
(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
(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
(Store Floating-point Double indeXed) El contenido de
fD se guarda en memoria, en el doble-word apuntado
por (rA|0)+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
Tabla 2.67: Instrucciones de almacenamiento de números en punto flotante con
indireccionamiento de registro base y registro índice
7.7 Instrucciones aritméticas
PowerPC dispone de las operaciones aritméticas que propone el estándar
IEEE 754:
o
o
o
o
o
o
o
Suma
Resta
Multiplicación
Multiplicación-suma
División
Raíz cuadrada (opcional)
Redondeo a entero (opcional)
Pág 140
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tabla 2.68 describe más concretamente las instrucciones:
Instrucción
fadd fD,fA,fB
fadd. fD,fA,fB
fadds fD,fA,fB
fadds. fD,fA,fB
fsub fD,fA,fB
fsub. fD,fA,fB
fsubs fD,fA,fB
fsubs. fD,fA,fB
fmul fD,fA,fB
fmul. fD,fA,fB
fmuls fD,fA,fB
fmuls. fD,fA,fB
fmadd fD,fA,fB,fC
fmadd. fD,fA,fB,fC
fmadds fD,fA,fB,fC
fmadds. fD,fA,fB,fC
fmsub fD,fA,fB,fC
fmsub. fD,fA,fB,fC
fmsubs fD,fA,fB,fC
fmsubs. fD,fA,fB,fC
fnmadd fD,fA,fB,fC
fnmadd. fD,fA,fB,fC
fnmadds fD,fA,fB,fC
fnmadds. fD,fA,fB,fC
fnmsub fD,fA,fB,fC
fnmsub. fD,fA,fB,fC
fnmsubs fD,fA,fB,fC
fnmsubs. fD,fA,fB,fC
fdiv fD,fA,fB
fdiv. fD,fA,fB
fdivs fD,fA,fB
fdivs. fD,fA,fB
Descripción
(Floating Add) En fD obtenemos la
suma fA+fB
(Floating Add Single) En fD
obtenemos la suma fA+fB
(Floating Substract) En fD obtenemos
el valor de fA-fB
(Floating Sustract Single) En fD
obtenemos el valor de fA-fB
(Floating Multiply) En fD obtenemos
el producto fA*fB
(Floating Multiply Single) En fD
obtenemos el producto fA*fB
(Floating Multiply-Add) En fD
obtenemos fA*fB+fC
(Floating Multiply-Add Single) En fD
obtenemos fA*fB+fC
(Floating Multiply-Substract) En fD
obtenemos fA*fB-fC
(Floating Multiply-Subtract Single) En
fD obtenemos fA*fB-fC
(Floating Negative Multiply-Add) En
fD obtenemos -(fA*fB+fC)
(Floating Negative Multiply-Add
Single) En fD obtenemos (fA*fB+fC)
(Floating Negative Multiply-Substract)
En fD obtenemos -(fA*fB-fC)
(Floating Negative Multiply-Subtract
Single) En fD obtenemos -(fA*fBfC)
(Floating Divide) en fD obtenemos el
resultado de dividir fA/fB.
(Floating Divide Single) en fD
obtenemos el resultado de dividir
fA/fB
Pág 141
Precisión
Doble
Simple
Doble
Simple
Doble
Simple
Doble
Simple
Doble
Simple
Doble
Simple
Doble
Simple
Doble
Simple
Ensamblador del PowerPC con Mac OS X
fsqrt fD,fS
fsqrt. fD,fS
fsqrts fD,fS
fsqrts. fD,fS
fres fD,fS
fres. fD,fS
frsqrte fD,fS
frsqrte. fD,fS
fsel fD,fA,fB,fC
fsel. fD,fA,fB,fC
macprogramadores.org
fA/fB
(Floating SQuare RooT) en fD
obtenemos la raíz cuadrada de fS.
Esta operación es opcional
(Floating SQuare RooT Single) En fD
obtenemos la raíz cuadrada de fS.
Esta operación es opcional
(Floating Reciprocal Estimate Simple)
En fD obtenemos 1/fS. Esta
operación es opcional. No existe su
correspondiente operación para
números de precisión doble
(Floating Reciprocal SQuare Root
Estimate) En fD obtenemos
1/sqrt(fS). Esta operación es
opcional. No existe su
correspondiente operación para
números con precisión simple
(Floating Select) El valor de fA se
compara 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.
Doble
Simple
Simple
Doble
Doble
Tabla 2.68: Instrucciones aritméticas
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 se explica en el Apéndice B,
degradan más el rendimiento del programa.
Pág 142
Ensamblador del PowerPC con Mac OS X
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
o
o
o
o
De punto flotante a entero
De entero a punto flotante
De punto flotante a entero, con el resultado en punto flotante
Entre todos los formatos de punto flotante que existan
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 que aparecen en la
Tabla 2.69:
Instrucción
frsp fD,fS
frsp. fD,fS
fctiw fD,fS
fctiw. fD,fS
fctiwz fD,fS
fctiwz. fD,fS
Descripción
(Floating Round to Single Precision) Redondea el
dato almacenado en fS al número más cercano que
pueda ser representado en formato simple, y los
guarda en fD (en formato doble)
(Floating Convert To Integer Word) El número
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.
(Floating Convert To Integer Word round toward
Zero) El 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.
Tabla 2.69: Instrucciones de conversión
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 143
Ensamblador del PowerPC con Mac OS X
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 esté 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 esté activo).
En cualquier caso, en el campo de CR que hayamos especificado a la
instrucción, se activarán los bits del campo de acuerdo a la Tabla 2.70:
Bit
0
1
2
3
Significado
fA < fB
fA > fB
fA = fB
fA ? fB (unordered)
Tabla 2.70: Reglas de activación de los campos del registro CR
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
Tabla 2.71:
Instrucción
fcmpo CRFD,fA,fB
fcmpu CRFD,fA,fB
Descripción
(Floating CoMPare Ordered) Compara f A 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.
(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.
Tabla 2.71: Instrucciones de comparación
Pág 144
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
7.10Instrucciones 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 y se detallan en la Tabla 2.72.
Instrucción
fmr fD,fS
fmr. fD,fS
fneg fD,fS
fneg. fD,fS
fabs fD,fS
fabs. fD,fS
fnabs fD,fS
fnabs. fD,fS
Descripción
(Floating Move Register) Copia el contenido de fS a
fD
(Floating Negate) Copia el contenido de fS a fD y
cambia el signo durante la copia
(Floating ABSolute value) Copia el contenido de fS a
fD y pone el bit de signo a 0 durante la copia
(Floating Negate ABSolute value) Copia el contenido
de fS a fD y pone el bit de signo a 1 durante la copia
Tabla 2.72: Instrucciones de movimiento de datos
Pág 145
Ensamblador del PowerPC con Mac OS X
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:
$ gcc -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)" );
Pág 146
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
printf("La variable incrementada es %i",G);
return 0;
}
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 le 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
Pág 147
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 cómo podemos dividir dos números usando la
instrucción ensamblador divw:
#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.
Pág 148
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
8.3.1 Las constraints y los modificadores
Cada operando lleva asociadas unas constraints que indican el tipo del
operando según la Tabla 2.73:
Constraint
r
b
f
m
i
F
X
Significado
Registro GPR
Registro GPR distinto de r0
Registro FPR
Referencia a memoria
Operando inmediato entero. Son literales cuyo valor es
conocido en tiempo de compilación
Operando inmediato en punto flotante de doble precisión. Son
literales cuyo valor es conocido en tiempo de compilación
Se acepta un operando de cualquier tipo: registro, dirección de
memoria o operando inmediato.
Tabla 2.73: Constraints de una expresión C
Lo posibles modificadores que pueden acompañar a una constraint están en
la Tabla 2.74:
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
Tabla 2.74: Modificadores de una constraint
Estos modificadores siempre se 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));
Pág 149
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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:
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;
}
Pág 150
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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;
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
Pág 151
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 usado por el programa C.
asm ( "lwz r3,%0":/*sin salida*/:"m" (N) );
Para evitar esto, podemos avisar al compilador de que la 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 sean necesarios poniendo su
nombre entre comillas y separándolos por comas.
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.
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 a s m 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,
Pág 152
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 por qué
coincidir con los nombres de las variables C a las que los asociamos.
Pág 153
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
9 Llamada a funciones
Vamos a ver ahora cuál 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 tipos de datos de la Tabla 2.75:
Tipo C o C++
unsigned char
char
signed char
unsigned short
short
signed short
unsigned int
unsigned long
int
signed int
long
signed long
bool
unsigned long long
long long
signed long long
float
double
long double
puntero
Tamaño
(en bytes)
1
1
Rango Valores
2
2
0 a 65.535
-32.768 a 32.767
4
0 a 4.294.967.295
4
-2.147..483.648 a 2.147.483.647
4
8
8
0 (falso) 1-4.294.967.295 (true)
0 a 18.446.744.073.709.551.615
-9.223.372.036.854.775.808 a
9.223.372.036.854.775.807
Ver Apéndice A
Ver Apéndice A
Ver Apéndice A
0 a 0xFFFFFFFF
4
8
16
4
0 a 255
-128 a 127
Tabla 2.75: Tipos de datos de Mac OS X
Además de estos tipos la Tabla 2.76 muestra los tipos de datos para AltiVec.
Tipo C o C++
Tamaño
(en bytes)
vector
u n s i g n e d 16 (1 byte
char
cada)
vector char
16 (1 byte
vector signed char
cada)
vector
u n s i g n e d 16 (2 bytes
short
cada)
Pág 154
Rango Valores
0 a 255
-128 a 127
0 a 65.535
Ensamblador del PowerPC con Mac OS X
vector signed short
vector unsigned int
vector int
vector signed int
vector bool char
vector bool short
vector bool int
vector float
vector pixel
macprogramadores.org
16 (2 bytes
cada)
16 (4 bytes
cada)
16 (4 bytes
cada)
16 (1 byte
cada)
16 (2 bytes
cada)
16 (4 bytes
cada)
16 (4 bytes
cada)
16 (3 bytes
cada)
-32.768 a 32.767
0 a 4.294.967.295
-2.147..483.648 a 2.147.483.647
0 (falso) 1-255(true)
0 (falso) 1-65.535 (true)
0 (falso) 1-4.294.967.295(true)
Ver Apéndice A
formato de pixel 1/5/5
Tabla 2.76: Tipos de datos de AltiVec
9.2 Mecanismo general de llamada a
procedimientos
La 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 pasa el control 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
modificarlos 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 modificarlos.
Pág 155
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
La Tabla 2.77 describe cual es la convención del uso de registros en Mac OS X
Grupo Registro
GPR
FPR
AltiVec
SPR
GPR0
Tipo
registro
Volátil
GPR1
GPR2
Dedicado
Volátil
GPR3-GPR10
Volátil
GPR11
GPR12
Volátil
Volátil
GPR13-GPR31
No volátil
FPR0
FPR1-FPR13
Volátil
Volátil
FPR14-FPR31
No volátil
v0-v2
v3-v13
Volátil
Volátil
v14-v19
v20-v31
Volátil
No volátil
vrsave
No volátil
LR
No volátil
CTR
Volátil
Uso
Usado normalmente para almacenar el
LR de retorno de la función
Puntero a la cima de la pila
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.
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
Contiene la dirección de salto cuando
llamamos a funciones de enlace
dinámico. Si no estamos llamando a
una función de enlace dinámico se
usa como un registro más
Sus valores se conservan entre
llamadas a procedimientos
Registro de scratch
Usado para paso de parámetros en
punto flotante. Una vez recibimos los
parámetros se pueden usar para
scratch si se desea
Sus valores se conservan entre
llamadas a procedimientos
Registros de scratch
Usado para el paso de parámetros de
este tipo
Registros de scratch
Sus valores se conservan entre
llamadas a procedimientos
Indica los vectores que deben ser
guardados en un cambio de contexto
Almacena la dirección de retorno de la
rutina llamada
Usado en bucles y saltos
Pág 156
Ensamblador del PowerPC con Mac OS X
CR
XER
FPSCR
CR0-CR1
Volátil
Volátil
Volátil
CR2-CR4
CR5-CR7
No volátil
Volátil
macprogramadores.org
Excepciones de punto fijo
Excepciones de punto flotante
Para consultar el resultado de una
operación aritmética o para
comparaciones
Para comparaciones
Para comparaciones
Tabla 2.77: Convención de uso de los registros de Mac OS X
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 segmento 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.
Área de parámetros
Área de parámetros
Caller
Área de enlace
La pila crece hacia abajo
GPR1
Caller
Nuevo
frame
Área de enlace
Registros guardados
Variables locales
Callee
Área de parámetros
Área de enlace
GPR1
Figura 2.17: Creación de un frame en la pila
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.
Pág 157
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
La Figura 2.17 muestra la creación de un frame en la pila tras llamar a un
procedimiento. Como vamos a ver, 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 está dividido en las cuatro áreas que vamos a comentar a
continuación:
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 esta área se la considera volátil en el sentido de que una vez que llamamos
a un procedimiento, éste 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 está 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 un
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.
Pág 158
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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) esta
á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 éste va a almacenar variables locales en
memoria. Si todas las variables locales se almacenan en registro esta área
medirá 0 bytes.
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 qué acciones hay
que llevar a cabo y no en qué orden deben de ejecutarse.
Pág 159
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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)
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
miFuncion:
; Prólogo
mflr r0
stw r0,8(r1)
; Tamaño del frame
; Recoge en r0 el LR
; 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
·········
·········
Pág 160
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
En este ejemplo suponemos que no existen variables locales ni hay que
guardar registros no volátiles luego necesitamos crear un frame de 12 bytes
para el área de enlace, pero como la especificación dice que los frames
siempre deben de tener un tamaño múltiplo de 16, creamos un frame de 16
bytes con un padding de 4 bytes.
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
o
o
o
o
Restaurar el valor de los registros no volátiles guardados.
Restaurar el valor del puntero a pila.
Si se modificaron los campos no volátiles del registro CR, restaurarlo.
Si se modificó el valor del registro LR, restaurarlo.
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
miFuncion:
·········
·········
; Epílogo
lwz r1,0(r1)
lwz r0,4(r1)
mtcrf 255,r0
lwz r0,8(r1)
mtlr r0
blr
; Tamaño del frame
;
;
;
;
;
;
;
;
;
;
Recoge el puntero a pila de su
área de enlace con lo que el
frame queda destruido
Recoge el CR del área de enlace
del caller
Recupera el valor del CR
Recoge el LR del área de enlace
del caller
Fija la dirección de retorno
Retorna
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 gcc de Mac OS X actúa
como aquí hemos explicado. Para ello vamos a crear un fichero llamado
llamadas.c que aparece en el Listado 2.13:
void funcion_b(void)
{
}
void funcion_a()
{
funcion_b();
Pág 161
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
}
int main ()
{
funcion_a();
return 0;
}
Listado 2.13: Ejemplo de funciones
Y lo vamos a compilar para generar su correspondiente código ensamblador
con el comando:
$ gcc -S llamadas.s
Usando la versión 2.95.2 del compilador obtenemos una salida como la del
Listado 2.14 (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:
; 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:
Pág 162
Ensamblador del PowerPC con Mac OS X
; Epílogo
lwz r1,0(r1)
lwz r0,8(r1)
;
;
;
;
;
;
;
mtlr r0
lmw r30,-8(r1)
blr
.align 2
.globl _main
_main:
; Prólogo
mflr r0
stmw r30,-8(r1) ;
;
stw r0,8(r1)
;
;
stwu r1,-80(r1) ;
;
mr r30,r1
;
; Llamada
bl _funcion_a
; Epílogo
li r3,0
;
main()
b L8
L8:
lwz r1,0(r1)
;
;
lwz r0,8(r1)
;
;
mtlr r0
lmw r30,-8(r1) ;
blr
;
macprogramadores.org
Recoge el puntero a pila del
área de enlace del caller
Recoge el LR del área de enlace
del caller
Fija la dirección de retorno
Recupera los valores de r30 y r31
Retorna
Guarda r30 y r31 en su área de
registros guardados
Guarda LR en el área de enlace
del caller
Crea su frame de 80 bytes
(5*16 bytes)
Pone el puntero a pila en r30
Pone un 0 en el retorno de la
Recupera el puntero a pila del
área de enlace de su caller
Recupera el LR del área de enlace
de su caller
Restaura los registros r30 y r31
Retorna
Listado 2.14: Código ensamblador generado por gcc
Hemos comentado el código ensamblador generado para facilitar su
comprensión.
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 gcc ninguna
optimización, el lector puede ejecutar ahora el compilador con optimización
así:
Pág 163
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
$ gcc -S -O3 llamadas.c
Obteniendo el código del Listado 2.15:
.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
Listado 2.15: Código ensamblador generado por gcc optimizado
Que como se puede apreciar a simple vista es un programa mucho más
pequeño y optimizado.
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.
Esto aunque habitual no es obligatorio, ya que las posiciones que están más
allá del puntero a pila, que es lo que se llama la zona vacía (véase Figura
2.18), están sin usar y si el 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 tenía el caller, y si necesitan
trabajar con variables locales en memoria lo que hacen es usar la zona vacía.
Pág 164
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
Área de parámetros
Caller
Área de enlace
9.5.3 Paso de parámetros
GPR1
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 varían ligeramente.
Zona
vacía
Los parámetros se pasan depositando el Figura 2.18: Zona vacía en
los procedimientos terminales
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.
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 cómo se colocan estos bytes en el área de parámetros, primero
convertimos los parámetros en una estructura tal que así:
Pág 165
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
struct Parametros
{
int pi1;
float pf2;
double pd3;
short ps4;
double pd5;
char pc6;
short ps7;
float pf8;
short ps9;
};
+44
+40
+36
ps9
pf8
ps7
+32
pc6
+28
pd5
+20
Esta estructura sirve como plantilla
para construir el área de parámetros
de la pila. El elemento que acaba en
la dirección de memoria más baja es
pi1, y a partir de ahí van ocupando
direcciones de memoria positivas a lo
largo del área de memoria,
respetando las reglas de padding que
hemos dado. Luego la organización
exacta de los parámetros sería la de
la Figura 2.19.
ps4
+16
pd3
+8
+4
0
pf2
pi1
Zona vacía
Figura 2.19: Ejemplo de organización
de parámetros en la pila
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.
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:
Pág 166
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
+44
+40
+36
ps9
pf8
ps7
+32
pc6
+28
GPR8
ps4
+16
pd3
+8
+4
0
GPR10
GPR9
pd5
+20
FPR4
FPR3
GPR7
GPR6
GPR5
pf2
GPR4
pi1
GPR3
FPR2
FPR1
Zona vacía
Figura 2.20: Ejemplo de organización real de parámetros en la pila
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.
Pág 167
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Figura 2.20.
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.
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);
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.
Pág 168
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 que aparece en el Listado 2.16, y la vamos a llamar desde un
programa C que vamos a hacer en el fichero factorial.c que aparece en
el Listado 2.17.
.set tamanoFrame,16
.text
.align 2
.globl _factorial
_factorial:
; Prólogo
mflr r0
stw r0,8(r1)
stwu r1,-tamanoFrame(r1)
;
;
;
;
Pág 169
Recoge en r0 el LR
Guarda el LR en el área
de enlace del caller
Crea su frame
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
; 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
Listado 2.16: Llamada a una función C
Como la función se vuelve a llamar a sí 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 170
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
#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);
return 0;
}
Listado 2.17: Función llamada desde ensamblador
Pág 171
Apéndice A
Aritmética binaria
Sinopsis:
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.
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
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 Tabla A.1:
ai
0
0
1
1
0
0
1
1
bi
0
1
0
1
0
1
0
1
ci
0
0
0
0
1
1
1
1
si
0
1
1
0
1
0
0
1
ci+1
0
0
0
1
0
1
1
1
Tabla A.1: Proceso de suma de números binarios
a (an-1...a1a0) y b (b n-1...b1b0) son los dígitos a sumar, s (sn-1...s1s0)
es la suma y c (c n-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)
Vemos que en el quinto bit empezando por la derecha ha habido acarreo, al
igual que en el sexto bit, y éste 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: a n-1...a1a0 y b n-1...b1b0 necesitamos unos
componentes llamados semisumadores, y otros llamados sumadores
completos.
Pág 173
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
El semisumador toma dos bits ai y b i 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 Figura A.1:
an-1 bn-1
an-2 bn-2
Sumador
completo
cn
sn-1
a1
Sumador
completo
cn-1
b1
ao
Sumador
completo
sn-2
cn
sn-1
bo
0
Sumador
completo
c1
Figura A.1: Sumador de transmisión de acarreo
El bit menos significativo entra por el sumador más a la derecha. Vemos que
la salida del i-ésimo sumador alimenta el acarreo del sumador (i+1)-ésimo.
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 174
Ensamblador del PowerPC con Mac OS X
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 el proceso de resta de números binarios sería el de la Tabla A.2:
ai
0
0
1
1
0
0
1
1
bi
0
1
0
1
0
1
0
1
ci
0
0
0
0
1
1
1
1
si
0
1
1
0
1
0
0
1
ci+1
0
1
0
0
1
1
0
1
Tabla A.2: Proceso de resta de números binarios
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.
an-1 bn-1
an-2 bn-2
Sumador
completo
cn
sn-1
a1
Sumador
completo
cn-1
b1
ao
Sumador
completo
sn-2
Figura A.2: Circuito restador
Pág 175
cn
sn-1
bo
Sumador
completo
c1
1
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 el bit de acarreo de más a la derecha como
muestra la Figura A.2.
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:
14
7 
7
(minuendo)
(sustraendo)
(diferencia)
1111
0111 
0111
1. Pasamos el sustraendo a complemento a 2
Binario
Complemento a 1
0111
Complemento a 2
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
0 - Suma
1 - Resta
an-1 bn-1
an-2 bn-2
a1
b1
A0
b0
xor
xor
xor
xor
Sumador
completo
Sumador
completo
Sumador
completo
Sumador
completo
cn
sn-1
cn-1
sn-2
Figura A.3: Circuito sumador-restador
Pág 176
cn
sn-1
c1
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 de la Figura
A.3. 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 Figura A.4. 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:
Pág 177
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Desplazamiento
P
A
∑
1 bit
n bits
B
Figura A.4: Circuito multiplicador
El algoritmo repite los siguientes dos pasos:
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 la Figura A.5:
Desplazamiento
P
A
∑
1 bit
n bits
B
Figura A.5: Circuito divisor
Pág 178
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Para calcular la división a/b el algoritmo más sencillo procede de la siguiente
forma:
Se pone 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 número
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.
Inicialmente tendremos:
A= 00000000 01000001
B= 00000000 00001111
P= 00000000 00000000
(65)
(15)
(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
B= 00000000 00001111
P= 00000000 00000000
(65)
(15)
(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.
Pág 179
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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ág 180
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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.
Pág 181
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Ejemplo: ¿Cuánto es -3 representado en cada uno de estos formatos?
La representación binaria de 3 es 0011. En signo-magnitud -3 sería 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
11100100
1
————————
11100101
(27)
(complemento a 1)
+
(-27)
Si ahora lo queremos volver a negar aplicamos el mismo procedimiento:
11100101
00011010
1
————————
00011011
(-27)
(complemento a 1)
+
(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:
00010001
11101110
1
————————
11101111
(17)
(complemento a 1)
+
(-17)
Y luego los sumamos:
00100010 (34)
11101111 (-17) +
————————
100010001 (17)
Pág 182
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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ág 183
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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, que se detallan
en la Tabla A.3, en el registro XER que indican que la excepción se ha
producido:
Flag
SO
OV
CA
Descripción
(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
(OVerflow) Indica si la última operación aritmética produjo
overflow
(Carry) Indica si la última operación aritmética produjo acarreo
Tabla A.3: Flags de excepción en PowerPC
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 explica en el Apéndice B.
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á
Pág 184
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 a 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.
Pág 185
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 ésta 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 sería el número 1,25*22=5
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 n o r m a l i z a d o, donde decimos que un número está
Pág 186
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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á.
Pág 187
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 tienen 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 ésta 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)
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
Pág 188
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Como el signo es positivo valdrá 0, con lo que la representación del número
empaquetado en una variable de tipo simple será la que se muestra en la
Figura A.6:
Signo Exponente polarizado
0
0111 1110
1b
8b
Fracción
0010110 00000000 00000000
23b
Figura A.6: Representación de un número simple
De los cuatro formatos definidos por el IEEE 754, 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 tipos de datos de la
Tabla A.4:
Formato IEEE 754
Simple
Doble
Doble extendido
Tipo C
float
double
long double
Tabla A.4: Tipos de datos C para números un punto flotante
El tamaño de los campos para los formatos simple y doble está estandarizado
por el IEEE 754 y son los que se muestran el la Tabla A.5, pero el tamaño de
los campos para los formatos simple extendido y doble extendido no están
estandarizados por el IEEE 754, sino que el IEEE754 sólo da unos tamaños
mínimos para cada campo.
Formato
simple
doble
simple
extendido
doble extendido
Signo
1
1
1
Exponente Fracción
8
23
11
52
≥10
≥32
1
≥16
≥64
Tabla A.5: Tamaños de los campos de cada uno de los formatos del IEEE 754
El formato simple extendido actualmente no lo implementa nadie, pero el
doble extendido si que está implementado, tanto por Intel, como por
Pág 189
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
PowerPC y SPARC, aunque la forma en que lo implementan varía 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)
como muestra la Figura A.7:
Signo Exponente
1b
Fracción
15b
112b
16 B
Figura A.7: Representación de los double extendidos en PowerPC y SPARC
Intel, sin embargo, lo implementa en un número de 96 bits (12 bytes) como
el que muestra la siguiente figura:
Sin usar
16b
Explicit
Signo Exponente leading
1b
15b
Fracción
1b
63b
12 B
Figura A.8: Representación de los double extendidos por Intel
La especificación de Intel dice que de los 12 bytes sólo se usan 10 bytes
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 Tabla A.6, y que comentaremos en los siguientes apartados.
Valor especial
0
Número denormalizado
+∞
-∞
NaN
Signo
0ó1
0ó1
0
1
0ó1
Exponente
0
0
máximo
máximo
máximo
Tabla A.6: Representación de valores especiales
Pág 190
Fracción
0
cualquiera
0
0
!=0
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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.
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 754 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 que muestra la
Figura A.9:
Signo Exponente
0
0000 0000
1b
8b
Fracción
0010000 00000000 00000000
23b
Figura A.9: Ejemplo de número denormalizado
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->∞
Pág 191
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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].
3.2 Números especiales
Otra peculiaridad del estándar IEEE 754 es que nos permite representar los
valores +∞ y -∞, para ello utiliza los patrones de bits especiales de la Figura
A.10:
+∝
0
1111 1111
000000 00000000 00000000
-∝
1
1111 1111
000000 00000000 00000000
Figura A.10: Representación de +∝ y -∝
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 tal como muestra
la Figura A.11, con lo que más que haber un número NaN, hay una familia
completa de NaN.
NaN
0
1111 1111
xxxxxxx xxxxxxxx xxxxxxxx
Figura A.11: Representación de NaN
El valor NaN se propaga entre las operaciones aritméticas, de forma que si
uno de los operandos de una operación aritmética es NaN, el resultado de la
operación también será NaN.
Pág 192
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 la excepción.
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.
Los NaN toman distintos valores en su parte fraccionaria indicando la causa
del error. La Tabla A.7 muestra los valores que puede tomar el campo de la
fracción:
Decimal
1
2
4
8
9
17
21
33
Hexadecimal
0x01
0x02
0x04
0x08
0x09
0x11
0x15
0x21
34
0x22
36
0x24
37
0x25
38
40
0x26
0x28
42
0x2A
Significado
Raíz cuadrada invalida (p.e. raíz de -1)
Suma invalida (p.e. (+∞)-(-∞))
División inválida (p.e. 0/0)
Multiplicación inválida (p.e. 0*∞)
Resto inválido (p.e. x%0)
Intento de convertir cadena ASCII inválida
Intento de crear un NaN con código cero
Parámetro inválido para una función
trigonométrica (p.e. sin(), cos(), tan())
Parámetro inválido para una función
trigonométrica inversa (p.e. acos(), asin(),
atan())
Parámetro inválido para una función logarítmica
(p.e. log() o ln())
Parámetro inválido para una función exponencial
(p.e. exp())
Parámetro inválido para una función financiera
Parámetro inválido para una función hiperbólica
inversa (p.e. acosh() o asinh())
Parámetro inválido para una función gamma (p.e.
gamma() o lgamma())
Tabla A.7: Significado de la parte fraccionaria de un NaN
Pág 193
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 A.12:
Signaling NaN
0
1111 1111
000000 xxxxxxxx 00000000
Quiet NaN
1
1111 1111
100000 xxxxxxxx 00000000
Figura A.12: Representación signaling NaN y quiet NaN
3.3 Rangos máximos y mínimos en los números en
punto flotante
Para acabar de ver el formato de los números en punto flotante 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 Tabla A.8:
Formato
Simple
Doble
Doble extendido
Mínimo
denormalizado
3,5*10-46
1,2*10-324
1,6*10-4966
Mínimo
normalizado
5,8*10-39
1,1*10-308
1,6*10-4932
Máximo
1,7*1038
8,9*10307
5,9*104931
Tabla A.8: Rangos de los distintos tipos de datos en punto flotante
Vamos a explicar cómo se calculan estos rangos. Sólo vamos a ver cómo 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
sería:
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 sería:
Pág 194
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 sería 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 195
Ensamblador del PowerPC con Mac OS X
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 sí 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 Figura A.13.
0 2-1 20
21
22
23
Figura A.13: Distribución de los números en punto flotante
Pág 196
24
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 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 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.
Pág 197
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
4.3 Modos de redondeo
El estándar IEEE 754 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
Tabla A.9.
Flags RN
00
01
10
11
Modo de redondeo
Redondeo al más cercano
Redondeo a 0
Redondeo a +∞
Redondeo a -∞
Tabla A.9: Modos de redondeo
Pág 198
Ensamblador del PowerPC con Mac OS X
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 Apéndice B 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.
Pág 199
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Además estos flag pueden ser flags retenidos (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:
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 casos descritos en la Tabla A.10:
Operación
Suma o resta
Multiplicación
División
Resto
Raíz cuadrada
Comparación
Invalid Operation
Suma o resta de infinitos. P.e. (+∞)+(-∞)
0*∞
0/0 ó ∞/∞
x%y si y=0 ó x=±∞
Con un operando negativo
Cuando los operandos son ±∞ o NaN
Tabla A.10: Causas de una Invalid Operation
2. Underflow. Ocurre cuando el resultado de la operación es demasiado
pequeño para ser almacenado en el formato utilizado. 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 200
Ensamblador del PowerPC con Mac OS X
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 homogénea posible en distintas plataformas, el estándar del IEEE 754 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 esté 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:
Pág 201
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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:
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 sería 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:
Pág 202
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 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
Pág 203
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
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 204
Ensamblador del PowerPC con Mac OS X
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 esté 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 205
Ensamblador del PowerPC con Mac OS X
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 sería 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
esté 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 206
Ensamblador del PowerPC con Mac OS X
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
o
o
o
a<b
a>b
a=b
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
o
o
o
o
De punto flotante a entero
De entero a punto flotante
De punto flotante a entero, con el resultado en punto flotante
Entre todos los formatos de punto flotante que existan
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 207
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Apéndice B
La segmentación
Sinopsis:
Este apéndice está pensado para lectores que desconozcan en que consiste la
segmentación, la cual se menciona repetidamente en distintos contextos de
los temas anteriores.
Actualmente todos los microprocesadores de alto rendimiento que se fabrican
son segmentados. A continuación vamos a ver en que consiste la
segmentación y las ventajas de rendimiento que introduce.
Pág 208
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
1 ¿Que es la segmentación?
La s e g m e n t a c i ó n
( p i p e l i n e s ) es un técnica usada por los
microprocesadores más avanzados (la mayoría de los procesadores actuales:
Pentium, SPARC, PowerPC, Alpha,...) por la cual se solapa la ejecución de
varias instrucciones.
En las máquinas segmentadas una instrucción se divide en varias etapas o
segmentos, cada una de las cuales corresponde con un ciclo de reloj. De
esta forma la segmentación ejecuta las instrucciones como si fuera una
cadena de montaje, donde en cada ciclo de reloj se ejecuta una etapa de la
instrucción.
Lo importante de esta división en etapas es que nos permite tener varias
instrucciones ejecutándose a la vez, aunque cada una de ellas en una etapa
distinta, como si se tratase de una cadena de montaje de automóviles, donde
hay varios automóviles fabricándose en etapas distintas.
Aunque con variaciones dependiendo del microprocesador, las principales
etapas en que se descompone una instrucción segmentada suelen ser:
•
•
•
•
•
Fetch (FE). La instrucción que está en la dirección de memoria del IP
se recoge de memoria al micro. Además incrementa el IP para que
apunte a la siguiente instrucción
Decode (DE). Decodifica la instrucción.
Dispatch (DI). Lee los operandos de los registros indicados en la
instrucción, o la dirección de memoria indicada en la instrucción, para
pasarlos a la unidad funcional que corresponda.
Execute (EX). La operación indicada por la instrucción se ejecuta en
la unidad funcional que corresponda.
Write Back (WB). El resultado de ejecutar la instrucción se escribe
en los registros o en la memoria.
Normalmente el micro dispone de varias unidades funcionales, que son las
partes del microprocesador donde se realizan las operaciones indicadas en las
instrucciones. Aunque el número y tipo de unidades funcionales de un micro
dependen de la implementación, de forma general podemos decir que un
micro dispone de los siguientes tipos de unidades funcionales:
•
•
•
Memory Unit. Se encarga de los accesos a memoria y de los saltos.
Fixed-Point Unit. Se encarga de las operaciones con enteros.
Floating-Point Unit. Se encarga de las operaciones con decimales.
Además normalmente las unidades funcionales están segmentadas con el fin
de que puede haber varias instrucciones usando la unidad funcional.
Segmentar las unidades funcionales es muy costoso, y hay microprocesadores
Pág 209
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
que no las segmentan, en cuyo caso esa unidad funcional sólo puede ser
usada por una instrucción a la vez.
De cada uno de los tres tipos de unidades funcionales que existen, un micro
suele disponer de más de una, con el fin de que varias instrucciones puedan
estar ejecutando en distintas unidades funcionales del mismo tipo, aunque si
la unidad funcional está segmentada, esto podría no ser tan necesario.
Visto esto podríamos representar la ejecución simultánea de instrucciones en
un micro segmentado tal como muestra la Figura B.1:
Instrucción
Instrucción
Instrucción
Instrucción
Instrucción
Instrucción
i
i+1
i+2
i+3
i+4
1
FE
2
DE
FE
3
DI
DE
FE
Ciclo de reloj
4
5
6
EX
WB
DI
EX
WB
DE
DI
EX
FE
DE
DI
FE
DE
7
8
9
WB
EX
DI
WB
EX
WB
Figura B.1: Ejecución simultánea de instrucciones en un micro segmentado
En este caso suponemos que el microprocesador tiene 5 pipes, de aquí viene
el nombre de pipelines, que a veces se da a la segmentación.
Aunque cada instrucción necesita 5 ciclos de reloj, la ejecución simultánea de
las instrucciones nos permiten que cada instrucción tenga un tiempo medio
de ejecución de 1 ciclo. Esto se formaliza de la siguiente forma:
Llamamos latencia, al tiempo (normalmente medido en ciclos de reloj)
necesario para ejecutar una instrucción.
Llamamos caudal (throughput) al número de instrucciones ejecutadas por
unidad de tiempo (también medido en ciclos de reloj).
Obsérvese que en el ejemplo anterior aunque la latencia de una instrucción es
de 5 ciclos, el caudal es de 1 ciclo, ya que en 5 ciclos hemos ejecutado 5
instrucciones. El lector debe de ser consciente de que en la Figura B.1 se
tardan 9 ciclos por el hecho de que el micro está “arrancando” y “parando”,
pero en circunstancias normales el micro permanece con los 5 pipes llenos.
Vemos que la segmentación incrementa la productividad de las instrucciones,
pero no aumenta el tiempo de ejecución de una instrucción individual, de
hecho la decrementa un poco debido a dos factores:
Por un lado la duración de todas las etapas no es exactamente la misma, sino
que hay etapas que tardarían menos en terminarse que otras, pero el diseño
del procesador obliga a utilizar como ciclo de reloj el tiempo de la etapa más
larga, con lo que es muy importante que las etapas estén perfectamente
Pág 210
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
equilibradas, pero en la práctica esto no siempre es posible y se suelen
producir reducciones en el rendimiento que no suelen superar, digamos, un
10%.
Por otro lado entre las etapas hay que dejar un pequeño tiempo de reajuste
de los cerrojos (latchs) que conectan las distintas etapas que es lo que se
llama el sesgo de reloj (clock skew).
Como ejemplo de esto último vamos a estudiar la mejora de rendimiento que
conseguimos en una máquina gracias a la segmentación. Supongamos que
tenemos una máquina no segmentada con 5 pasos por instrucción con
tiempos: 50ns, 50ns, 60ns, 50ns, 50ns. Suponer que debido al sesgo de reloj,
segmentar la máquina añade 5ns de gasto en cada etapa de la ejecución.
¿Qué incremento de rendimiento se ganará con la segmentación de esa
máquina en una máquina que disponga de 5 pipes?
La Figura B.2 (a) muestra la ejecución de instrucciones sin segmentación y la
Figura B.2 (b) la ejecución de las mismas instrucciones con segmentación.
260
260
260
50 50 60 50 50 50 50 60 50 50 50 50 60 50 50
Instrucción 1
Instrucción 2
Instrucción 3
(a) Ejecución no segmentada
455
Instrucción 1
Instrucción 2
Instrucción 3
65 65 65 65 65
65 65 65 65 65
65 65 65 65 65
(b) Ejecución segmentada
Figura B.2: Ejecución de instrucciones con y sin segmentación
Vemos que sin segmentar, el tiempo medio de ejecución de una instrucción
es de:
50ns + 50ns + 60ns + 50ns + 50ns = 260ns
Luego 5 instrucciones se ejecutan en 5*260ns=1300ns
Mientras que en una máquina segmentada (suponiendo que no estamos
arrancando o parando) el tiempo de ejecución de 5 instrucciones es:
5*65ns=325ns. Con lo que la mejora de rendimiento es de:
Pág 211
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
R=1300ns/325ns=4
Es decir se consigue un rendimiento 4 veces superior en la máquina
segmentada, y no las 5 veces teóricas que veíamos al principio.
2 Etapas multiciclo
No es práctico exigir que todas las etapas de una instrucción se ejecuten en
un solo ciclo de reloj, como por ejemplo en las operaciones de punto flotante.
Hacer esto significaría aceptar un reloj lento.
En la práctica, una etapa de una instrucción puede duran varios ciclos de
reloj, por ejemplo las operaciones de suma y resta de entero se ejecutan en
un solo ciclo EX, mientras que la multiplicación en coma flotante suele
consumir hasta 5 ciclos EX, y la división hasta 20 ciclos EX.
Luego la ejecución de una instrucción de multiplicación de punto flotante se
podría representar así:
FE DE DI EX EX EX EX EX WB
Obsérvese que esto hace que la unidad funcional de punto flotante
permanezca ocupada durante todos los ciclos que dura la etapa EX,
impidiendo que otra instrucción la use. Para evitarlo, se suelen aplicar dos
soluciones: Disponer de varias unidades funcionales de punto flotante, o bien,
segmentar la unidad funcional para que puedan entrar varias instrucciones de
punto flotante a tiempos distintos.
El mismo problema le encontramos en las instrucciones que acceden a
memoria, en las que cuando el dato accedido está en caché, la etapa de
lectura de memoria DI, o la de escritura en memoria WB se completan en un
solo ciclo, pero si el dato no esta en caché y hay que ir a memoria principal,
estas etapas pueden consumir más de un ciclo.
FE DE DI DI DI EX WB
En este caso la unidad funcional de memoria también necesita estar duplicada
o segmentada para poder permitir a varias instrucciones trabajar en esta
unidad.
Pág 212
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
3 Los riesgos
Su instinto es correcto si encuentra difícil pensar que la segmentación es tan
simple como esto, porque no lo es. En esta sección vamos a comentar unos
problemas que surgen en la segmentación llamados riesgos (hazards), que
impiden que se ejecute la siguiente instrucción del flujo de instrucciones
durante su ciclo de reloj designado. Los riesgos reducen el rendimiento de la
velocidad ideal lograda con la segmentación. Hay tres tipos de riesgos:
1 . Riesgos estructurales. Surgen de conflictos con los recursos
disponibles, cuando el hardware no puede soportar todas las
combinaciones posibles de instrucciones en ejecución simultánea.
2. Riesgos por dependencia de datos. Surgen cuando una instrucción
depende de los resultados de una instrucción anterior, de forma que
una tiene que esperar al resultado de la otra.
3. Riesgos de control. Surgen de la segmentación de los saltos y otras
instrucciones que cambian el IP.
Los riesgos en la segmentación pueden hacer necesario detenerla. Una
detención en una máquina segmentada requiere, con frecuencia, que
prosigan algunas instrucciones mientras que se retardan otras. Normalmente,
cuando una instrucción está detenida, todas las instrucciones posteriores a
esta instrucción también se detienen. Las instrucciones anteriores a la
instrucción detenida pueden continuar, pero no se cogen instrucciones nuevas
durante la detención. Veremos algunos ejemplos de cómo operan las
detenciones en esta sección. ¡No se preocupe, no son tan complejas como
puede parecer!
3.1 Riesgos estructurales
Los riesgos estructurales se producen cuando diferentes instrucciones
acceden simultáneamente a los mismos recursos. Para evitarlo hay que
segmentar las unidades funcionales y duplicar los recursos. Si alguna
combinación de instrucciones no es posible ejecutarla simultáneamente, se
produce un riesgo estructural, y el microprocesador detiene a la última
instrucción que emitió hasta que el riesgo desaparece.
Por ejemplo, un recurso que suele producir riesgos estructurales son los
puertos de memoria, muchas máquinas tienen un único puerto de memoria
con lo que si una instrucción accede a memoria (lo cual puede llevar varios
ciclos) y otra instrucción también intenta acceder a memoria, ésta última
queda detenida hasta que la primera acaba.
Pág 213
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Instucción
Instrucción
Instrucción
Instrucción
Instrucción
Instrucción
i
i+1
i+2
i+3
i+4
1
FE
2
DE
FE
3
DI
DE
FE
4
EX
DI
DE
FE
Ciclo de reloj
5
6
7
WB
EX
WB
DI
EX
WB
DE
stall DI
FE
DE
8
9
10
EX
DI
WB
EX
WB
Figura B.3: Ejemplo de stall en ejecución segmentada
En la Figura B.3 se muestra una detención (stall) que se produce cuando
en el sexto ciclo de reloj la instrucción i+1 está haciendo un almacenamiento
en memoria (stw) mientras que la instrucción i+3 intenta hacer una carga de
memoria (lwz), como ambas comparten el puerto de datos la segunda tiene
que esperar, lo cual retrasa un ciclo a todas las instrucciones que siguen a la
instrucción i+3.
¿Por qué permite el diseñador riesgos estructurales?. Hay dos razones: Para
reducir el coste de fabricación y para reducir la latencia de una unidad
funcional, ya que la segmentación de algunas unidades funcionales aumentan
mucho su latencia porque sus etapas están muy desequilibradas, y si la
segmentamos debemos de hacer que cada etapa dure lo que un ciclo de reloj.
Si los riesgos estructurales no se presentan con frecuencia, puede no merecer
la pena el coste de evitarlos. Además con frecuencia merece más la pena
diseñar una unidad funcional no segmentada, pero con una latencia menor.
3.2 Riesgos por dependencia de datos
Los riesgos por dependencia de datos se presentan cuando la segmentación
cambia el orden de acceso a los operandos respecto al orden secuencial
normal que se produciría si no hubiera segmentación. Los riesgos se pueden
producir tanto durante el acceso por parte de dos instrucciones a los registros
como durante el acceso a memoria.
Considérese la ejecución de estas dos instrucciones:
add r1,r2,r3
sub r4,r1,r5
La instrucción sub tiene como dato de entrada r1, que es el dato de salida
de la instrucción add. Como muestra la Figura B.4, la instrucción add escribe
el valor de r1 en la etapa WB, mientras que la instrucción sub lee el valor del
registro en la etapa DI.
Pág 214
Ensamblador del PowerPC con Mac OS X
Instrucción
add
macprogramadores.org
1
2
3
Ciclo de reloj
4
5
6
FE
DE
DI
EX
WB
FE
DE
DI
EX
sub
lee
7
8
escri
WB
Figura B.4: Problema en la ejecución segmentada de instrucciones
Luego si no hacemos nada para impedirlo, sub leerá un dato erróneo. En este
caso, el microprocesador tiene que detener a la instrucción sub hasta que el
dato este disponible, como muestra la siguiente Figura B.5.
Instrucción
add
1
2
3
Ciclo de reloj
4
5
6
FE
DE
DI
EX
WB
FE
DE
stall
stall
sub
7
8
EX
WB
escri
DI
lee
Figura B.5: Solución para los riesgos en la ejecución segmentada de instrucciones
Lo cual provoca una perdida de dos ciclos de reloj.
Para reducir el efecto de los riesgos de dependencia de datos, una técnica
muy usada por los microprocesadores es el adelantamiento de datos
(forwaring o bypassing), que consiste en que el resultado de una
instrucción se envía directamente a otra instrucción sin necesidad de
almacenar ese dato el registro o memoria. De esta forma la instrucción
detenida puede leer antes este dato y perder menos ciclos de reloj.
Por ejemplo, en le caso anterior, el microprocesador se podría cablear para
que la salida de EX de la primera instrucción pasase directamente a la entrada
de DI de la segunda instrucción sin esperar a la etapa WB de la primera
instrucción, de esta forma, como muestra la Figura B.6, se ganaría un ciclo de
reloj.
Instrucción
add
sub
1
2
3
Ciclo de reloj
4
5
6
FE
DE
DI
EX
WB
FE
DE
stall
DI lee
7
8
escri
EX
WB
Figura B.6: Adelantamiento de datos en la ejecución segmentada
Los riesgos por dependencia de datos pueden clasificarse en tres grupos,
dependiendo del orden de los acceso de lectura/escritura en las instrucciones:
•
RAW (Read After Write). La instrucción j trata de leer un operando
antes de que una instrucción anterior i lo haya escrito, con lo que j
Pág 215
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
tomaría el valor antiguo. Este es el tipo de riesgo de dependencia de
datos más común y es el que aparece en el ejemplo anterior.
add r1,r2,r3
sub r4,r1,r5
Para evitar este tipo de riesgos se recomienda siempre que sea posible
intercalar una instrucción sin dependencias en medio. Por ejemplo, en
nuestro ejemplo podemos intercalar una instrucción sin dependencias
así:
add r1,r2,r3
add r6,r7,r8
sub r4,r1,r5
•
WAW (Write After Write). La instrucción j intenta escribir un
operando antes de que sea escrito por otra instrucción anterior i. Este
riesgo es menos común ya que implica que la instrucción i gaste más
ciclos de reloj que la instrucción j, ya que si ambas consumieran el
mismo número de ciclos i siempre escribiría (llegaría a la etapa WB)
antes de escribir j.
Por ejemplo si tenemos:
divf fr2,fr3,fr4
addf fr2,fr5,fr6
Al ser más rápida addf que divf, aunque addf empezase después,
escribiría en fr2 antes de que divf.
Para evitar este tipo de riesgos basta con renombrar los operandos:
divf fr2,fr3,fr4
addf fr7,fr5,fr6
•
WAR (Write After Read). La instrucción j intenta escribir un
operando antes de que sea leído por una instrucción anterior i, con lo
que i leerá un valor erróneo.
Este es el tipo más raro de dependencias, y para que se produzcan la
instrucción j debe de llegar a su etapa WB (escribir resultados) antes
de que la instrucción anterior i llegue a su etapa DI (leer operandos).
A las dependencia de tipo WAR también se las llama
a n t i d e p e n d e n c i a s . Al igual que la dependencia WAW, las
antidependencias son falsas dependencias que no se deben a una
Pág 216
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
dependencia real de datos, sino a un conflicto de recursos y se puede
eliminar renombrando los registros. Por ejemplo si tenemos:
lwz r2,0(r3)
addi r3,r4,r5
Si hay un fallo de caché, la instrucción lwz se puede retrasar durante
varios ciclos, los cuales aprovecha addi para adelantarse en su
escritura.
Para solucionar la antidependencia renombramos los registros así:
lwz r2,0(r3)
addi r4,r5,r6
Es evidente que el caso RAR (Read After Read) no es un riesgo, ya que
ambas instrucciones leen el mismo dato sin modificarlo.
Afortunadamente todos los riesgos de dependencia de datos se pueden
comprobar durante la etapa DE, y si existe un riesgo la instrucción es
detenida antes de ser emitida.
3.3 Riesgos de control
Los saltos provocan retrasos en la segmentación mayores a los que provocan
los riesgos estructurales o de dependencia de datos, ya que la dirección
efectiva de un salto no se conoce hasta la fase WB, que es la que modifica el
IP, lo cual da lugar un retraso considerable en la segmentación tal como
muestra la Figura B.7:
Instrucción
addi
b
subi
1
FE
2
DE
FE
Ciclo de reloj
4
5
6
EX
WB
DI
EX
WB
stall
stall
stall
3
DI
DE
FE1
7
8
FE
DE
Figura B.7: Retraso producido por un salto
Obsérvesele que en 1 la instrucción de la posición IP+4 se carga, pero al
decodificar b en la etapa DE y ver que es un salto se detiene y en el ciclo 7 se
vuelve a leer la instrucción que está en la dirección destino del salto.
Para evitar retrasos tan grandes se utiliza la técnica del adelantamiento de
datos (véase el apartado 3.2), la cual nos permite adelantar la dirección
efectiva de salto en la etapa DE, tal como muestra la Figura B.8:
Pág 217
Ensamblador del PowerPC con Mac OS X
Instrucción
addi
b
subi
1
FE
2
DE
FE
3
DI
DE
FE1
macprogramadores.org
Ciclo de reloj
4
5
6
EX
WB
DI
EX
WB
FE
DE
DI
7
8
EX
WB
Figura B.8: Ejemplo de adelantamiento de datos en los saltos
Obsérvese que con un correcto cableado para el adelantamiento de datos
podemos conseguir perder un sólo ciclo en lugar de 4 ciclos.
3.4 Saltos sin resolver
Los saltos se pueden dividir en tres categorías:
•
•
•
Saltos incondicionales
Saltos condicionales, los cuales seleccionan la siguiente instrucción a
ejecutar entre 2 alternativas dependiendo de si se cumple o no una
condición que se encuentra en uno de los campos del registro CR
Saltos multidestino, son saltos en los que, al igual que los
condicionales, evalúan una condición puesta en un campo de CR, pero
la dirección de destino del salto es también variable (estará en el
registro LR o CTR), con lo que puede haber muchos destinos.
Se dice que un salto está sin resolver (unresolved) cuando o bien la
condición, o bien la dirección destino del salto no se conocen cuando se va a
ejecutar el salto.
Los saltos sin resolver nunca se producen en los saltos incondicionales, pero
sí que se producen en los demás tipos de saltos, veamos un ejemplo:
lis r2,ha16(dato)
li r2,lo16(dato)
lwz r4,0(r2)
cmpwi cr0,r3,0
beq alla
aqui:
subi r4,r4,1
alla:
addi r4,r4,1
El orden de ejecución de las instrucciones segmentadas es ahora el que se
muestra en la Figura B.9:
Pág 218
Ensamblador del PowerPC con Mac OS X
Instrucción
1
FE
lwz
cmpwi
beq
2
3
DE DI
FE DE
FE1
macprogramadores.org
Ciclo de reloj
4
5
6
7
8
EX
WB
DI
EX
WB
stall stall FE
DE DI
stall stall stall stall FE
9
10
11 12
EX
DE
WB
DI EX
WB
Figura B.9: Orden de ejecución de las instrucciones del ejemplo
Aun con adelantamientos de datos acabamos teniendo un retraso de 4 ciclos
de reloj, esto es así porque la instrucción cmpwi no calcula la condición del
registro CR hasta la etapa WB. El primer adelantamiento evita que beq tenga
que esperar a la etapa WB de cmpwi, el segundo adelantamiento permite que
la etapa DE de beq actualice el IP para poder recoger la siguiente instrucción
ejecutar.
En definitiva, los saltos sin resolver enlentecen mucho la ejecución de
instrucciones segmentadas, y teniendo en cuenta que entre el 11% y el 18%
de las instrucciones de un programa suelen ser saltos, el retraso global es
considerable.
A continuación vamos a comentar varias soluciones que evitan estos
problemas.
3.5 Solución software a los saltos sin resolver
Una primera solución es que el programador planifique las instrucciones,
es decir, que coloque las instrucciones en un orden que evite que los saltos
estén sin resolver cuando llegue el momento de ejecutarlos.
Por ejemplo, en le programa anterior podemos adelantar la instrucción cmpwi
así:
cmpwi cr0,r3,0
lis r2,ha16(dato)
li r2,lo16(dato)
lwz r4,0(r2)
beq alla
aqui:
subi r4,r4,1
alla:
addi r4,r4,1
Ahora cuando se fuera a ejecutar la instrucción beq, el salto estaría resuelto
tal como muestra la Figura B.10:
Pág 219
Ensamblador del PowerPC con Mac OS X
Instrucción
cmpwi
lis
li
lwz
beq
1
FE
2
DE
FE
3
DI
DE
FE
4
EX
DI
DE
FE
macprogramadores.org
Ciclo de reloj
5
6
7
8
9
10
WB
EX WB
DI EX WB
DE DI EX WB
FE DE DI EX WB
FE1 FE DE DI EX
11
12
WB
Figura B.10: Ejecución con salto resuelto
En este caso sólo se perdería 1 ciclo de reloj en el sexto ciclo de la última
instrucción, en vez de perderse 4 ciclos de reloj, como pasaba cuando el salto
estaba sin resolver.
Vamos a ver ejemplos de cómo resolver el problema de los saltos sin resolver
en las estructuras de control de flujo más conocidas.
3.5.1 Estructura if
En las estructuras if y if-else normalmente, como muestra la Figura B.11,
siempre vamos a poder intercalar instrucciones entre la comparación y el
salto:
Salto sin resolver
Solución
·············
instrucciones1
·············
cmpw cr0,r2,0
beq alla
·············
instrucciones2
·············
cmpw cr0,r2,0
·············
instrucciones1
·············
beq alla
·············
instrucciones2
·············
Figura B.11: Intercalar instrucciones entre la comparación y el salto
3.5.2 Estructura while
En los bucles while (también llamados 0-n) la solución no siempre es
posible, ya que pasa por intercalar instrucciones del bloque
instrucciones2 entre la operación de comparación y de salto, tal como
muestra la Figura B.12.
El poder adelantar estas instrucciones o no depende de los efectos laterales
que esto implique, es decir, sólo podemos adelantar instrucciones que si
finalmente el salto es efectivo no modifiquen el estado de las variables del
programa, esto sería cierto si las variables o registros con los que han
Pág 220
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
trabajado las instrucciones son sólo de uso interno al bucle. La Figura B.12
muestra un ejemplo de adelantamiento de instrucciones en un bucle while.
Salto sin resolver
·············
instrucciones1
·············
inicio: cmpw cr0,r2,0
beq fin
·············
instrucciones2
·············
b inicio
fin:
Solución
·············
instrucciones1
·············
inicio: cmpw cr0,r2,0
·············
instrucciones2
·············
beq fin
·············
instrucciones2
·············
b inicio
fin:
Figura B.12: Adelantar instrucciones en un bucle while
3.5.3 Estructura do-while
En las estructuras do-while (o 1-n) la solución pasa por adelantar la
comparación, realizándola lo antes posible en el cuerpo del bucle, pero al
igual que antes esto no siempre es posible, ya que para poder realizar la
comparación debemos de conocer el resultado que vamos a comparar, que
normalmente se calcula dentro del bucle. La Figura B.13 muestra un ejemplo
de adelantamiento de instrucciones en un bucle while
Salto sin resolver
Solución
·············
instrucciones1
·············
inicio:
·············
instrucciones1
·············
inicio:
·············
instrucciones2
·············
cmpw cr0,r2,0
beq inicio
fin:
fin:
Figura B.13: Adelantar instrucciones en un bucle while
Pág 221
·············
instrucciones2
·············
cmpw cr0,r2,0
·············
instrucciones2
·············
beq inicio
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
3.5.4 Estructura for
En estas estructuras se pueden eliminar los saltos sin resolver usando saltos
condicionales en función del registro CTR, que son saltos que siempre están
resueltos.
·············
instrucciones1
·············
mtctr r5
inicio:
·············
instrucciones2
·············
bdnz inicio
fin:
Esto es posible siempre que el número de repeticiones se conozca antes de
meternos en el bucle.
Típicamente el contador de un bucle for no suele contar hacia atrás hasta el
0, sino que suele contar desde 1 a n, aun así podemos usar un bucle cuya
condición de salida esté en el contador, y llevar otra variable contador aparte:
li r3,1 ; Desde 1
li r4,n ; Hasta n
subi r5,r4,1
mtctr r5 ; Fijamos el contador hasta 0
inicio:
·············
instrucciones2
·············
add r3,r3,1
bdnz inicio
fin:
3.6 Solución hardware a los saltos sin resolver
La planificación de instrucciones es muy efectiva para resolver el problema de
los saltos sin resolver, pero no siempre es posible: o bien porque el
programador no la hace, o bien porque la lógica del programa no permite
hacerla. En estos casos todavía podemos aprovecharnos de soluciones
hardware, como las que vamos ver.
Cuando el procesador encuentra una instrucción de salto, escanea los pipes
en ejecución para determinar si alguna de las instrucciones que se están
ejecutando puede modificar el estado del campo CR usado, o bien de los
registros LR y CTR si estos están en uso por la instrucción de salto, si no es
así el salto se resuelve inmediatamente, pero si se encuentra dependencia, el
salto se considera sin resolver, y el hardware en vez de detenerse lo que hace
Pág 222
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
es que ejecuta especulativamente una de las ramas. Después cuando la
condición del salto se resuelve, si la predicción es correcta, la ejecución
simplemente continúa, y sino el procesador debe volver al estado en el que
estaba cuando se inicio la ejecución especulativa, y coger el otro camino.
Para evitar la penalización en tiempo que supone restaurar los registros
cuando el camino cogido no es el correcto los procesadores suelen usar los
llamados shadow registers, que son registros en los que se van guardando
los resultados de las instrucciones cuando se está ejecutando de forma
especulativa, de forma que si al final la predicción es correcta su contenido se
copia a los registros reales, y sino su contenido se descarta.
Los algoritmos que usa el procesador para decidir si llevar a cabo o no el salto
se clasifican en dos grupos:
1. Algoritmos de predicción estáticos. Consisten en que el programador
codifica la instrucción de salto indicando si es más probable que el salto sea
efectivo o si no, para ello utiliza el bit y del operando BO que veíamos en el
Tema 2 y que volvemos a reproducir en la Tabla B.1:
BO
0000y
0001y
001zy
0100y
0101y
011zy
1z00y
1z01y
1z1zz
Descripción
Decrementa el registro CTR y
es FALSE
Decrementa el registro CTR y
es FALSE
Salta si la condición es FALSE
Decrementa el registro CTR y
es TRUE
Decrementa el registro CTR y
es TRUE
Salta si la condición es TRUE
Decrementa el registro CTR y
Decrementa el registro CTR y
Salta siempre
después salta si CTR≠0 y la condición
después salta si CTR=0 y la condición
después salta si CTR≠0 y la condición
después salta si CTR=0 y la condición
después salta si CTR≠0
después salta si CTR=0
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 a que no se realice
Tabla B.1: Configuración del operando BO
El campo y se activa cuando el programador ve más probable que el salto se
lleve a cabo que no, y se deja a cero para indicar que no se lleve a cabo el
salto especulativamente.
Por defecto se recomienda dejarlo a 0, ya que es más fácil para el procesador
especular que el salto no se realizará.
Pág 223
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Este bit es especialmente útil a la hora de implementar bucles, ya que los
bucles tienen una mayor probabilidad de repetirse que de no hacerlo, luego si
nosotros estamos implementando un bucle podemos indicar al procesador
nuestra opinión usando este bit.
Por ejemplo para implementar el bucle do-while anterior podríamos hacerlo
así:
·············
instrucciones1
·············
inicio:
fin:
·············
instrucciones2
·············
cmpw cr0,r2,0
bc 13,2,inicio
El operando BO vale 13=0b01101 donde hemos activado el bit y, ya que el
salto es más probable que se repita. El operando BI vale 2=0b00010
indicando que queremos comprobar la igualdad a 0 del registro r2 en el
campo cr0.
Otra forma de indicar en las instrucciones de salto si creemos más probable
que se produzcan o no es añadir al nombre de la instrucción un - (más
probable que no se realice) o un + (más probable que si se realice). Por
ejemplo:
bucle:
cmpwi r3,100
beq+ bucle
Indica que lo más probable es que se realice el salto.
2. Algoritmos de predicción dinámicos. Consisten en que es el
procesador el que decide si ejecutar especulativamente el salto o no. Aunque
inicialmente el procesador puede hacer caso al bit de predicción depositado
por el programador, en microprocesadores más avanzados el propio
procesador puede usar mecanismos para decidir si el salto especulativo que
va a dar es el más correcto.
Básicamente existen dos técnicas de predicción dinámica:
Branch Target Address Cache (BTAC). El procesador almacena la
dirección destino de los últimos saltos realizados en una memoria caché, de
forma que si esa instrucción de salto se vuelve a intentar ejecutar otra vez, el
procesador busca la instrucción en su cache y mira a ver que ocurrió la vez
anterior para tomar ese camino. De esta forma si el salto especulativo tiene
éxito se puede conseguir una pérdida de 0 ciclos.
Pág 224
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Las BTAC son tablas en las que se almacena la dirección de la instrucción de
salto y su dirección de salto efectivo más o menos de la forma que se
muestra en la Figura B.14:
Dirección de la
instrucción de
salto
Dirección a la que
salto la última vez
Figura B.14: Estructura de una Branch target Address Cache
Branch History Tables. El procesador mantiene un registro de las últimas
acciones realizadas por una instrucción. Estas tablas suelen tener la forma de
la Figura B.15:
Dirección de la
instrucción de salto
Estado
Figura B.15: Estructura de una Branch History Table
Donde a cada instrucción de salto se le asocian 2 bits en el campo de estado.
Los cuatro estados de los 2 bits asociados a la instrucción de salto pueden
tomar los valores:
00
01
10
11
-
Strongly Taken
Weakly taken
Weakly Not taken
Strongly Not Taken
La siguiente Figura B.16 muestra la relación entre estos 4 estados:
T
NT
Strongly
Not
Taken
NT
T
Weakly
Not
Taken
NT
Figura B.16: Estado de los bits de salto
Pág 225
T
Weakly
Taken
NT
Strongly
Taken
T - Taken NT- Not taken
T
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Lo importante es que para que el salto pase de efectivo a no efectivo, deben
de producirse dos fallos consecutivos, lo cual se hace así porque normalmente
los bucles se llevan a cabo un número de veces hasta que la condición se deja
de cumplir, pero sí otro vez pasase el flujo del programa por el bucle, el bucle
seguiría considerándose en el mismo estado (aunque weakly) con lo que si
ahora el bucle se repite, que es lo más probable, el estado vuelve al estado
strongly, en el que permanece durante todas las repeticiones. De esta forma
se consigue que los saltos de los bucles se predigan siempre correctamente,
excepto cuando la condición de repetición se deja de cumplir.
Una última solución hardware a los saltos sin resolver que están empezando a
usar los procesadores más avanzados es el scheduling, que consiste en que
el procesador puede pasar las instrucciones a ejecución en orden distinto al
que están escritas en el programa. El procesador puede decidir cambiar el
orden de ejecución de las instrucciones en base a dos criterios: El primero es
que el procesador puede leer adelantadamente varias instrucciones en el
llamado fetch buffer y decide si pasar las instrucciones a ejecución en el
orden que están llegando o bien adelantar alguna de ellas si encuentra que
esa instrucción va a producir una detención. El segundo criterio es que el
procesador puede detectar dependencias, y si las encuentra detiene una
instrucción aunque deja pasar a otras con las que no hay dependencias.
3.7 La serialización
Para mantener al procesador y a la memoria en un estado consistente con el
modelo de ejecución secuencial, en ciertas situaciones el procesador se ve
obligado a serializar la ejecución de una instrucción entera, deteniendo la
ejecución de todas las demás instrucciones hasta que esta acaba.
Esto ocurre por ejemplo cuando hay más de una unidad funcional de punto
fijo donde recursos comunes no duplicados como el registro XER van a ser
actualizados. Por esta razón las instrucciones que modifican este registro
llevan un nombre especial como addc o addo, las cuales pueden ejecutar
considerablemente más lento que la instrucción que no modifica este registro
(add). Lo mismo ocurre con las instrucciones con punto (.) como por
ejemplo add., que al escribir en le campo CR0 puede necesitar ejecutar
secuencialmente.
Para evitar detenciones de este tipo debido a un conflicto en el campo CR0,
es recomendable que los saltos cercanos utilicen otros campos (CR1,...CR7)
para evaluar sus condiciones.
Pág 226
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
4 Más allá de la segmentación
Se denomina procesadores supersegmentados a los procesadores donde la
segmentación es más profunda (pueden llegar a tener 10 etapas, en lugar de
las 5 antes descritas), y en las que todas las unidades funcionales se
encuentran segmentadas.
Este término no debe de ser confundido con el de procesador superescalar,
que es un procesador capaz de emitir varias instrucciones en el mismo ciclo
de reloj, normalmente de 2 a 4 instrucciones sin embargo si las instrucciones
del flujo de instrucciones son dependientes, o no cumplen ciertos criterios,
sólo se emitirá la primera instrucción de la secuencia. La mayoría de los
PowerPC actuales son superescalares.
Actualmente están surgiendo máquinas, como por ejemplo Itanium de Intel a
las que se llama VLIW (Very Long Instruction Word) que se caracterizan
porque una instrucción está formada por la unión de varias instrucciones (3
en concreto en el caso de Itanium) las cuales se agrupan en lo que llaman un
b u n d l e , y las 3 se emiten a la vez. Lo importante de agrupar las
instrucciones en bundles es que el compilador puede colocar en cada bundle
instrucciones que no tengan dependencias entre sí con el fin de facilitar su
ejecución simultánea sin detenciones. Esta solución consigue mejor
rendimiento que las máquinas superescalares tradicionales, a cambio de
aumentar la complejidad de la programación, ya que el programador tiene
que pensar en grupos de instrucciones (bundles) más que en instrucciones
secuenciales.
Por último están las llamadas máquinas vectoriales, que usan a la vez
ambas técnicas. Habitualmente son supersegmentadas, y tienen potentes
operaciones vectoriales que se pueden considerar equivalentes a emitir
múltiples operaciones independientes.
Pág 227
Ensamblador del PowerPC con Mac OS X
macprogramadores.org
Referencias
[DEVTOOLS] Herramientas de desarrollo de Apple
http://developer.apple.com/tools/index.html
[MICROIBM] Microprocesadores de IBM
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)
[MICROMOTOROLA] Microprocesadores de Motorola
http://www.motorola.com/SPS/PowerPC/teksupport
/teklibrary/
[UNDERFLOW] “Underflow and the Reliability of Numerical Software”, James
Demmel, y “Combatting the effect of Underflow and Overflow in
determining Real Roots of Polynomials” de S. Linnainmaa.
[WARREN] Changing Division by a constant to Multiplication in Two’s
Complement Arithmetic. Warren, Henry S., Jr., IBM Research Report:
RC 18601 [1992].
Pág 228

Documentos relacionados