Laboratori 5: Coma flotant i compilació separada
Lliuraments
Cal lliurar les respostes als exercicis següents al campus virtual abans de l’inici de la sessió de laboratori:
| Exercici | Nom i format del fitxer |
|---|---|
| Exercici 32.1 | s5_1_1.s i s5_1_2.s |
| Exercici 32.2 | s5_2_1.md i s5_2_1.s |
| Exercici 32.3 | s5_3_1.s |
| Exercici 32.4 | s5_4_1.md |
Lectura prèvia
Abans de fer la sessió, cal haver llegit i entès els continguts següents:
| Concepte | On trobar-ho |
|---|---|
| Format IEEE 754 de precisió simple: signe, exponent esbiaixat, fracció | Secció 5.1.3, RISC-V 5.1 |
| El bit ocult | Essencial 5.1 |
| Codificació i decodificació de nombres en coma flotant | Exemple 5.2, Exemple 5.3 |
| Modes d’arrodoniment | Secció 5.1.7 |
Desplaçaments lògics i aritmètics (sll, srl, sra, slli, srli, srai) |
RISC-V 3.1, RISC-V 3.2 |
Operacions lògiques bit a bit i de comparació (and, or, slt) |
RISC-V 3.3, RISC-V 3.5 |
| Subrutines: pas de paràmetres i retorn de resultats | Secció 3.9.2, RISC-V 3.17 |
| Subrutines multinivell: registres segurs i bloc d’activació | Secció 3.9.4, RISC-V 3.22 |
Compilació separada i directiva .globl |
Secció 3.10, RISC-V 3.24 |
32.1 Compilació separada a RARS
En un projecte real, el codi es divideix en mòduls independents que es compilen i assemblen per separat (Secció 3.10). Cadascun viu en el seu propi fitxer font, declara com a globals les etiquetes que han de ser visibles des d’altres mòduls, i referencia les etiquetes externes sense necessitat de conèixer-ne els detalls interns.
RARS reprodueix aquest flux de manera directa: amb l’opció Settings → Assemble all files currently open activada, l’assemblador processa tots els fitxers oberts alhora i resol automàticament les referències entre ells, assumint la funció combinada d’assemblador i enllaçador.
.globl
Qualsevol etiqueta referenciada des d’un altre fitxer ha de declarar-se amb .globl al fitxer on es defineix. Sense aquesta declaració, l’etiqueta és invisible per als altres mòduls i RARS finalitza amb un error de símbol no resolt. Només s’hi declaren els símbols que altres mòduls necessiten: el programa principal no en porta, perquè cap fitxer no hi fa referència i RARS no el cerca per nom (vegeu l’aprofundiment següent).
Un cop oberts tots els fitxers i activada l’opció Assemble all files currently open, assembleu el projecte. Al panell Labels (o Symbol Table) de RARS hi apareixeran totes les etiquetes globals dels mòduls. Comproveu que les etiquetes esperades hi són i que les seves adreces s’han resolt correctament.
L’execució comença a la primera instrucció del segment .text (Secció 2.3.1). Quan s’assemblen diversos fitxers alhora, aquell .text és el combinat, i l’ordre en què s’hi encadenen els mòduls el decideix RARS: amb Assemble all files currently open, el primer que s’assembla és el del fitxer que té la pestanya activa en prémer F3.
Per tant, la pestanya activa ha de ser la del fitxer que conté el programa principal. Si és la d’un fitxer de subrutines, el programa arrenca dins d’una subrutina.
RARS no té enllaçador separat: assembla i resol les referències entre mòduls en una sola passada, de manera que el que es tria aquí és l’ordre d’assemblatge (el procés que ho fa en dues fases és a Secció 3.10.4).
⚠️ L’assemblatge no falla si l’ordre és el dolent. La consola diu operation completed successfully en tots dos casos, i l’error només apareix en executar:
Assemble: assembling s5_1_2.s, s5_1_1.s
Assemble: operation completed successfully.
Assemble: assembling s5_1_1.s, s5_1_2.s
Assemble: operation completed successfully.
L’execució, en canvi, sí que els distingeix:
-- program is finished running (0) --
Go: running s5_1_1.s
Error in : Instruction load access error
Go: execution terminated with errors.
La primera pista és el nom del fitxer: Go: running s5_1_1.s diu que RARS ha començat pel fitxer de la subrutina, i ho diu abans del missatge d’error. Si el programa s’atura amb un error d’accés a instrucció just en començar, cal mirar aquella línia abans que res.
A la solució de l’exercici hi ha els dos segments de text comparats instrucció per instrucció, per a qui vulgui veure què hi canvia.
Exercici 32.1 (s5_1_1.s i s5_1_2.s) Traduïu a RV32I el programa C del Codi 32.1, repartint el codi en dos fitxers tal com s’indica:
s5_1_1.s: conté únicament la subrutinaabs.s5_1_2.s: conté el programa principal, que cridaabsi escriu el resultat aresultat.
abs i programa principal.
Tingueu en compte:
absés uninivell: determineu si cal algun registre segur.- El programa principal és multinivell: determineu quins valors, si n’hi ha, travessen la crida i cal preservar.
- Obriu els dos fitxers a RARS, activeu Settings → Assemble all files currently open i assembleu. Comproveu que
absapareix al panell Labels.
Comproveu que, en acabar, la variable global resultat conté 7, i que conté 0 si el programa principal crida abs(0).
Solució 32.1. Anàlisi de registres segurs.
abs és uninivell: no fa cap crida, de manera que no hi ha cap registre en risc de ser destruït; tots els temporals van a registres t. BA buit.
El programa principal és multinivell (crida abs), però no necessita cap registre segur ni bloc d’activació: no hi ha cap valor generat abans de la crida que s’usi després (el resultat d’abs s’usa immediatament en tornar), i com que és el punt d’entrada i mai no torna a cap invocant (acaba amb ecall), tampoc cal preservar ra. BA buit.
s5_1_1.s
s5_1_2.s
Obriu s5_1_1.s i s5_1_2.s a RARS, activeu Settings → Assemble all files currently open i assembleu. Al panell Labels hi ha d’aparèixer abs amb la seva adreça: és l’únic símbol declarat .globl, i per això és l’únic visible entre mòduls. Amb abs(0), el salt bge es pren i es retorna el valor directament.
L’ordre d’assemblatge, vist al segment de text. Feu File → Dump Memory… (Ctrl-D) amb el segment .text i el format Text/Data Segment Window: escriu en un fitxer el llistat del segment de text, i permet comparar els dos ordres de la pestanya activa (RISC-V 32.1). Per línia d’ordres, és dump .text SegmentWindow <fitxer>:
Correcte — pestanya activa: s5_1_2.s
Address Code Basic Line Source
0x00400000 0xff900513 addi x10,x0,0xfffffff9 6 li a0, -7
0x00400004 0x01c000ef jal x1,0x0000001c 7 jal ra, abs
0x00400008 0x0fc10297 auipc x5,0x0000fc10 9 la t0, resultat
0x0040000c 0xff828293 addi x5,x5,0xfffffff8
0x00400010 0x00a2a023 sw x10,0(x5) 10 sw a0, 0(t0)
0x00400014 0x05d00893 addi x17,x0,0x0000005d 12 li a7, 93
0x00400018 0x00000513 addi x10,x0,0 13 li a0, 0
0x0040001c 0x00000073 ecall 14 ecall
0x00400020 0x00055463 bge x10,x0,0x00000008 8 bge a0, zero, positiu
0x00400024 0x40a00533 sub x10,x0,x10 9 neg a0, a0
0x00400028 0x00008067 jalr x0,x1,0 11 ret
Incorrecte — pestanya activa: s5_1_1.s
Address Code Basic Line Source
0x00400000 0x00055463 bge x10,x0,0x00000008 8 bge a0, zero, positiu
0x00400004 0x40a00533 sub x10,x0,x10 9 neg a0, a0
0x00400008 0x00008067 jalr x0,x1,0 11 ret
0x0040000c 0xff900513 addi x10,x0,0xfffffff9 6 li a0, -7
0x00400010 0xff1ff0ef jal x1,0xfffffff0 7 jal ra, abs
0x00400014 0x0fc10297 auipc x5,0x0000fc10 9 la t0, resultat
0x00400018 0xfec28293 addi x5,x5,0xffffffec
0x0040001c 0x00a2a023 sw x10,0(x5) 10 sw a0, 0(t0)
0x00400020 0x05d00893 addi x17,x0,0x0000005d 12 li a7, 93
0x00400024 0x00000513 addi x10,x0,0 13 li a0, 0
0x00400028 0x00000073 ecall 14 ecall
Dues coses que el bolcat no diu tot sol:
- Amb l’ordre incorrecte, la primera instrucció de
.textés de la subrutinaabs(bge a0, zero, positiu). És allà on RARS posa el PC, de manera que el programa comença per la primera instrucció d’abs, amba0sense inicialitzar. - La mateixa instrucció font canvia de codificació.
jal ra, absés0x01c000efen un cas i0xff1ff0efen l’altre —un salt endavant i un enrere—, i ella t0, resultatpassa d’addi x5,x5,-8aaddi x5,x5,-20per arribar a la mateixa adreça. Són desplaçaments relatius al PC, corregits segons on ha quedat cada mòdul: és la correcció d’adreces de Secció 3.10.4, aquí visible en dos bolcats.
32.2 Subrutina descompon
En aquesta sessió es desenvolupa un programa que converteix un nombre real codificat en coma fixa al format de coma flotant IEEE 754 de precisió simple (Secció 5.1.3). El programa s’estructura en dues subrutines (descompon i compon) i un programa principal repartits en dos fitxers, aplicant la compilació separada de la secció anterior.
El format de coma fixa que s’usa en aquesta sessió té 32 bits: el bit 31 conté el signe (0 per als positius) i els bits 30–0 representen la magnitud en base 2, amb 19 bits de part entera (bits 30–12) i 12 bits de part fraccionària (bits 11–0):
\[\text{valor}(A) = (-1)^{A_{31}} \times \sum_{i=0}^{30} A_i \cdot 2^{i-12}\]
El format de coma flotant IEEE 754 de precisió simple (RISC-V 5.1) té 32 bits: 1 bit de signe (bit 31), 8 bits d’exponent esbiaixat amb biaix 127 (bits 30–23) i 23 bits de fracció (bits 22–0), amb bit ocult implícit (Essencial 5.1).
El programa de conversió s’organitza en dos passos:
main.c
cflotant es declara com a int, no float: el valor que es construeix és el patró de bits de la representació IEEE 754, no un nombre que el compilador hagi de convertir. Si es declarés float, l’assignació convertiria el valor enter a coma flotant en lloc de reinterpretar-ne els bits.
La subrutina descompon extreu de cfixa els tres camps que necessita IEEE 754: el signe, la fracció (els 23 bits que segueixen el bit ocult de la mantissa normalitzada) i l’exponent esbiaixat amb biaix 127 (8 bits):
El Codi 32.2 descriu l’algorisme bit a bit: alguns dels desplaçaments sobre valors amb signe (cf << 1, cf >> 8) queden fora de la definició estricta del llenguatge C, per això en assemblador es fa servir srli, el desplaçament lògic, que és el que descriu l’algorisme. (Aquí srai donaria el mateix resultat: la màscara 0x7FFFFF esborra els 8 bits que entren per l’esquerra.)
L’algorisme treballa en tres fases. Primer, separa el signe de la magnitud: codifica el signe a *s i elimina el bit de signe de cf desplaçant tots els bits una posició a l’esquerra. En aquest punt la coma implícita ha avançat una posició i se situa entre els bits 13 i 12 (el bit 12 és ara el primer bit fraccionari). Segon, si la magnitud no és zero, normalitza: inicialitza l’exponent a 18 (que correspon a situar la coma a la dreta del bit 31) i desplaça cf a l’esquerra iterativament fins que el bit 31 sigui 1, decrementant l’exponent a cada iteració. Tercer, alinea la mantissa normalitzada als 23 bits de menys pes fent un desplaçament a la dreta de 8 posicions i elimina el bit ocult (posició 23) amb una màscara 0x7FFFFF; finalment, suma 127 a l’exponent per codificar-lo en forma esbiaixada.
Exercici 32.2 (s5_2_1.md i s5_2_1.s) Traduïu a RV32I la subrutina descompon. El fitxer s5_2_1.s ha de contenir únicament la subrutina, sense cap programa principal.
Abans d’escriure cap instrucció, responeu al fitxer s5_2_1.md:
- Determineu si
descomponés uninivell o multinivell i justifiqueu-ho. - Assigneu cada paràmetre (
cf,*s,*e,*m) i la variable local (exp) a un registre i justifiqueu l’elecció. - Dibuixeu el bloc d’activació amb els desplaçaments de cada element.
Comproveu que, amb cfixa = 0x87D18A00, descompon deixa signe = 0x00000001, exponent = 0x0000008D (141 en decimal) i mantissa = 0x007A3140.
Solució 32.2. Pas 1. Uninivell o multinivell.
descompon és uninivell: no fa cap crida. No cal preservar ra.
Pas 2. Assignació de registres.
Els paràmetres arriben per convenció ABI: a0 = cf, a1 = @s, a2 = @e, a3 = @m. Cap valor travessa una frontera de crida, de manera que tots els temporals van a registres t. BA buit.
| Variable | Registre |
|---|---|
cf (es modifica al llarg de la subrutina) |
t0 |
exp |
t1 |
@s, @e, @m (paràmetres: adreces de destinació dels resultats) |
a1, a2, a3 |
Pas 3. Bloc d’activació.
descompon és uninivell i totes les variables s’assignen a registres: BA buit.
s5_2_1.s
.globl descompon
.text
# descompon(int cf, int *s, int *e, int *m)
# a0 = cf, a1 = @s, a2 = @e, a3 = @m
descompon:
# *s = (cf < 0)
slt t2, a0, zero # t2 = 1 si cf < 0, 0 si no
sw t2, 0(a1) # *s = t2
# cf = cf << 1 (elimina el bit de signe)
slli t0, a0, 1 # t0 = cf << 1
# if (cf == 0) exp = 0
li t1, 0 # exp = 0 (cas zero)
beqz t0, desa # si cf == 0, salta a desar
# else: exp = 18
li t1, 18
# while (cf >= 0): normalitza
while:
blt t0, zero, fiwhile # si bit 31 és 1 (cf < 0), fi del bucle
slli t0, t0, 1 # cf = cf << 1
addi t1, t1, -1 # exp--
j while
fiwhile:
# cf = (cf >> 8) & 0x7FFFFF
# srli (desplaçament lògic): els 8 bits que entren per l'esquerra són
# 0. Amb srai, que hi propagaria el bit 31 (aquí, 1), el resultat seria
# el mateix: la màscara 0x7FFFFF esborra aquests bits i el bit ocult.
srli t0, t0, 8
li t2, 0x7FFFFF
and t0, t0, t2 # elimina el bit ocult i bits superiors
# exp = exp + 127
addi t1, t1, 127
desa:
sw t1, 0(a2) # *e = exp
sw t0, 0(a3) # *m = cf
retPer provar descompon de manera aïllada, creeu temporalment un programa principal mínim al fitxer s5_3_1.s (el de la secció següent) que declari les variables globals i faci la crida:
s5_3_1.s
Obriu s5_2_1.s i s5_3_1.s a RARS amb Assemble all files currently open i executeu: en acabar, els valors de signe, exponent i mantissa es llegeixen al panell del segment de dades.
32.3 Subrutina compon i programa complet
La subrutina compon fa el pas invers: rep els tres camps separats i els combina en un únic valor de 32 bits en format IEEE 754 de precisió simple. El signe s’ha de desplaçar al bit 31, l’exponent als bits 30–23, i la fracció (paràmetre mantissa) ja ocupa els bits 22–0:
compon.c
Els tres camps no se superposen (cada un ocupa exclusivament els seus bits), de manera que la combinació amb or és directa sense necessitat de màscares.
Exercici 32.3 (s5_3_1.s) Substituïu el programa principal temporal de la secció anterior pel programa complet. El fitxer s5_3_1.s ha de contenir compon i el programa principal, que crida descompon (definida a s5_2_1.s) i compon en seqüència i escriu el resultat a cflotant.
Abans d’escriure cap instrucció:
- Determineu si
componés uninivell o multinivell i justifiqueu-ho. - Determineu si el programa principal és uninivell o multinivell. Identifiqueu quins valors es generen abans d’una crida i s’usen després, i assigneu-los a registres segurs.
- Dibuixeu el bloc d’activació del programa principal.
Comproveu que, en acabar, cflotant = 0xC6FA3140, i que, amb els valors inicials de cfixa següents, pren el valor esperat:
cfixa |
Valor decimal | cflotant esperat |
|---|---|---|
0x00000000 |
\(0{,}0\) | 0x00000000 |
0x80000000 |
\(-0{,}0\) | 0x80000000 |
0x0000CC00 |
\(12{,}75\) | 0x414C0000 |
Solució 32.3. Pas 1. compon: uninivell o multinivell.
compon és uninivell: no fa cap crida. Tots els temporals van a registres t; BA buit.
Pas 2. El programa principal: registres segurs.
El programa principal és multinivell: crida descompon i després compon. Els valors generats abans de la primera crida que s’usen després són les adreces de signe, exponent i mantissa, necessàries per llegir els resultats de descompon abans de cridar compon: calen registres segurs per a elles. ra, en canvi, no necessita preservar-se: és el punt d’entrada i mai no torna a cap invocant (acaba amb ecall), de manera que el valor que hi deixa cada jal ra, ... es pot sobreescriure lliurement en la crida següent.
| Dada | Registre |
|---|---|
@signe |
s0 |
@exponent |
s1 |
@mantissa |
s2 |
Pas 3. Bloc d’activació del programa principal.
El programa principal no necessita bloc d’activació, tot i usar registres segurs: com que és el punt d’entrada i no torna mai a cap invocant, no hi ha cap valor previ de s0–s2 (ni de ra) que calgui preservar i restaurar. BA buit.
s5_3_1.s
.data
signe: .word 0
exponent: .word 0
mantissa: .word 0
cfixa: .word 0x87D18A00
cflotant: .word 0
.text
la s0, signe
la s1, exponent
la s2, mantissa
# descompon(cfixa, &signe, &exponent, &mantissa)
la a0, cfixa
lw a0, 0(a0)
mv a1, s0
mv a2, s1
mv a3, s2
jal ra, descompon
# compon(signe, exponent, mantissa)
lw a0, 0(s0) # a0 = signe
lw a1, 0(s1) # a1 = exponent
lw a2, 0(s2) # a2 = mantissa
jal ra, compon # a0 = cflotant
la t0, cflotant
sw a0, 0(t0) # cflotant = a0
li a7, 93
li a0, 0
ecall
# compon(int signe, int exponent, int mantissa)
# a0 = signe, a1 = exponent, a2 = mantissa
# retorna el valor IEEE 754 de precisió simple a a0
compon:
slli t0, a0, 31 # t0 = signe << 31
slli t1, a1, 23 # t1 = exponent << 23
or t0, t0, t1 # t0 = (signe << 31) | (exponent << 23)
or a0, t0, a2 # a0 = t0 | mantissa
retObriu s5_2_1.s i s5_3_1.s a RARS amb Assemble all files currently open i executeu. Per als casos límit, canvieu el valor inicial de cfixa a s5_3_1.s i torneu a assemblar els dos fitxers abans d’executar.
32.4 Errors de precisió
El programa de conversió desenvolupat en aquesta sessió és correcte per a la majoria de valors, però en alguns casos introdueix una pèrdua de precisió inevitable. En aquest exercici se n’analitzen les causes i les condicions en què es produeix.
Exercici 32.4 (s5_4_1.md) Responeu les preguntes següents sobre el programa de conversió de coma fixa a coma flotant:
Quina condició ha de complir el valor inicial de
cfixaperquè es produeixi pèrdua de precisió en la conversió? Raoneu la resposta en termes del nombre de bits significatius de cada format.Indiqueu un valor concret de
cfixaper al qual es produeixi pèrdua de precisió. Indiqueu també el valor decflotantresultant i expliqueu quins bits s’han perdut.En quina instrucció concreta del codi C de
descompones produeix la pèrdua de precisió?Quin dels modes d’arrodoniment (Secció 5.1.7) aplica aquest programa de conversió? Justifiqueu-ho.
El format de coma fixa d’aquesta sessió té un rang limitat. Indiqueu un nombre positiu que estigui dins del rang del format IEEE 754 de precisió simple però fora del rang del format de coma fixa. Indiqueu també quina és la potència de 2 més petita que compleix aquesta condició.
Solució 32.4. Pregunta 1.
El format de coma fixa té 31 bits de magnitud; el format IEEE 754 de precisió simple té 24 bits significatius (23 de fracció més el bit ocult). Després de la normalització, l’operació cf >> 8 descarta els 8 bits de menys pes de la mantissa normalitzada (posicions 0–7). Es produeix pèrdua de precisió quan algun d’aquests 8 bits és significatiu (és a dir, val 1), cosa que passa sempre que la magnitud original tingui bits significatius en posicions prou baixes per caure en aquest rang un cop normalitzada.
De manera equivalent i exacta: sigui \(b_{\text{alt}}\) la posició del bit 1 de més pes de la magnitud i \(b_{\text{baix}}\) la posició del bit 1 de menys pes. Es produeix pèrdua de precisió si i només si \(b_{\text{alt}} - b_{\text{baix}} + 1 \geq 25\), és a dir, si la magnitud «ocupa» 25 bits o més entre el seu bit significatiu més alt i el més baix (només 24 d’aquests bits caben al format IEEE 754: el bit ocult més els 23 de fracció).
Pregunta 2.
Un exemple clar és cfixa = 0x7FFFFFFF (magnitud màxima: tots els bits 30–0 a 1, valor \(\approx 2^{19}\)). Després de cf = cf << 1, cf = 0xFFFFFFFE; el bit 31 ja és 1, de manera que el bucle de normalització no itera i exp es manté a 18. El desplaçament cf = (cf >> 8) & 0x7FFFFF dona cf = 0x007FFFFF (la fracció resultant). L’exponent codificat és exp = 18 + 127 = 145 = 0x91. El valor cflotant resultant és 0x48FFFFFF: els 7 bits significatius de menys pes de la magnitud original (tots amb valor 1), més el 0 introduït pel desplaçament inicial cf << 1, s’han descartat, i el nombre representat és lleugerament inferior al valor original.
Pregunta 3.
La pèrdua de precisió es produeix a la instrucció:
El desplaçament a la dreta de 8 posicions descarta els 8 bits de menys pes de la mantissa normalitzada. Si algun d’aquests bits era significatiu, la informació es perd de manera irreversible.
Pregunta 4.
El programa aplica el mode d’arrodoniment a zero (round toward zero): el desplaçament >> 8 descarta els bits de menys pes sense cap ajust (truncament).
Pregunta 5.
El rang del format de coma fixa d’aquesta sessió és aproximadament \([-(2^{19}-2^{-12}),\ +(2^{19}-2^{-12})]\), és a dir, el valor positiu màxim és lleugerament inferior a \(2^{19} = 524\,288\). Qualsevol nombre positiu \(\geq 2^{19}\) és dins del rang IEEE 754 de precisió simple però fora del rang de coma fixa. El menor nombre potència de 2 que compleix aquesta condició és:
\[2^{19} = 524\,288\]