L'esempio che tratteremo riguarda l'inversione dell'ordine di un array di interi con un certo numero n di posizioni. Per portare a termine questo compito sfrutteremo due procedure, una procedura invert e una procedura swap che è usata da invert per scambiare due elementi.
Supponiamo di avere un array di 5 elementi:
Indici | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
Elementi | 11 | 12 | 13 | 14 | 15 |
Quello che vogliamo fare è utilizzare una funzione: invert(array, size)
che prede come parametri il riferimento in memoria dell'array e la sua dimensione (numero di elementi), nel nostro caso invert
prenderà come parametri un certo indirizzo in memoria e il numero 5.
invert
avrà due variabili (registri) che hanno come valore gli estremi dell'array, quindi 0 e 4 (non 5 perché sarebbe il sesto elemento), effettuerà poi un ciclo di questo tipo:
invert:
i = indiceSinistro
j = indiceDestro
while(i < j):
swap(array, i, j)
i = i + 1
j = j - 1
end while
la funzione invert
scambia gli elementi agli estremi dell'array, poi l'indice i incrementa di 1, mentre l'indice j decrementa di 1. In questo modo gli indici man mano si avvicineranno.
invert
resterà nel ciclo fino a che i non sarà almeno uguale a j, nel momento in cui i due indici si incontrano il ciclo termina e l'array sarà stato invertito.
invert
si serve della funzione swap
che prende l'array e gli indici degli elementi da scambiare.
In questo contesto ci rendiamo subito conto di due cose:
swap()
è una procedura fogliainvert()
non è una procedura fogliaa0-a7
sono utilizzati per passare parametri alle funzioni, in particolare il registro a0
è usato come valore di ritorno;s0-s11
sono registri preservati: chi chiama la procedura può assumere che tali registri rimarranno in tatti: la procedura chiamata infatti può usarli, ma solo dopo aver salvato il loro contenuto sullo stack;t0-t6
sono registri non preservati: chi chiama la procedura deve considerare che la procedura chiamata potrebbe utilizzare quei registri senza preoccuparsi di salvarne il contenuto dunque il chiamante deve preoccuparsi di salvare il loro contenuto nello stack.In genere una procedura foglia non deve preoccuparsi di salvare nulla, può usare i registri temporanei (t) senza preoccuparsi di salvarli, tuttavia potrebbe verificarsi che i registri non bastino per svolgere il compito e che servano altri registri, in tal caso potrebbe essere necessario salvare qualche registro s nello stack ed usarlo.
Invece una procedura non foglia, in molti casi è costretta a salvare nello stack e ripristinare dallo stack il contenuto dei registri usati.
In questo esempio affronteremo entrambe le tipologie di procedure.
swap(array, x, y)
a0
: avrà l'indirizzo in memoria dell'arraya1
: avrà il valore dell'indice sinistroa2
: avrà il valore dell'indice destroNOTA: gli elementi dell'array sono di tipo word
swap:
# a1, a2 sono gli indici
# a0 è l'indirizzo in memoria dell'array
# non ha senso copiare questi valori in dei registri
addi t0, zero, 4 # abbiamo a che fare con delle word (4 byte)
# moltiplichiamo gli indici per 4 (1 word)
mul a1, a1, t0
mul a2, a2, t0
# sommiamo all'indirizzo base dell'array l'indice moltiplicato per 4
# otterremo così l'indirizzo degli elementi che ci interessano
add t3, a0, a1 # t3 contiene l'indirizzo di a0[a1]
add t4, a0, a2 # t4 contiene l'indirizzo di a0[a2]
# adesso da quegli indirizzi dobbiamo caricare (load) i valori
lw t1, 0(t3)
lw t2, 0(t4)
# t5 fungerà da registro di appoggio per effettuare lo scambio
add t5, zero, t2
add t2, zero, t1
add t1, zero, t5
# salviamo i cambiamenti fatti nell'array
sw t2, 0(t4)
sw t1, 0(t3)
ret
Avanzamento degli elementi dell'array
Indici | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
Elementi | 11 | 12 | 13 | 14 | 15 |
Dimensione per cella | 1 word | 1 word | 1 word | 1 word | 1 word |
a0
è un indirizzo che punta alla base dell'array, per base intendiamo la 0-esima posizione.
Se voglio accedere alle terza posizione dell'array, quello che devo fare è scostarmi di tre posizioni, tuttavia bisogna ricordarsi che ciascuna posizione è una word (4 byte), quindi devo moltiplicare le posizioni di cui mi voglio scostare per 4 e sommarli all'indirizzo base:
addi t0, zero, 3 # metto 3 in t0
addi t1, zero, 4 # metto 4 in t1
mul t2, t0, t1 # moltiplico 4 per 3
add a0, a0, t2 # sommo il risultato della moltiplicazione ad a0
Tale valore mi restituisce l'indirizzo della posizione 3, a quel punto non mi resta che caricare quel valore dalla memoria: lw t3, 0(a0)
.
invert(array, size)
a0
: conterrà l'indirizzo dell'arraya1
: conterrà la dimensione dell'arrayinvert
è una funzione non foglia, il che vuol dire che chiamerà qualche funzione, sarà necessario salvare da qualche parte il return address (ra
), ci si accorge nel corso dello sviluppo quali registri vanno salvati nello stack, supponiamo di voler salvare a prescindere l'indirizzo dell'array e la size.
In questo caso se usassimo i registri t per svolgere le operazioni, sarebbe necessario mantenere aggiornato nello stack il valore di quel registro, per non perderlo (dato che gli indici aumentano o diminuiscono), mentre possiamo decidere anche di usare i registri s in modo tale da risparmiarci di salvare in corso d'opera il loro valore, poiché le funzioni chiamate devono mantenere in tatti tali registri. Tuttavia dobbiamo prima, appunto, salvare il loro valore nello stack (anche se sappiamo che nessuno li ha usati/userà).
Essendo chiamata un'altra funzione (che in questo caso non ritorna niente) il valore di a0
potrebbe essere sovrascritto, salviamo anche quello.
Consiglio: quando apriamo lo stack per un certo numero di valori, conviene scrivere sin da subito il recupero di tali valori dallo stack.
invert:
# estendiamo lo stack per: ra, s0, s1, a0
addi sp, sp, -32
sd ra, 0(sp)
sd a0, 8(sp)
sd s0, 16(sp)
sd s1, 24(sp)
add s0, zero, zero # indice sinsitro
add s1, zero, a1 # indice destro
addi s1, s1, -1 # indice destro - 1
while:
bge s0, s1, end
# carico dalla memoria l'indirizzo dell'array (a0) e lo metto in a0 per passarlo
# alla procedura swap come parametro
ld a0, 8(sp)
add a1, zero, s0 # passo l'indice sinistro come parametro
add a2, zero, s1 # passo l'indice destro come parametro
jal ra, swap # chiamata funzione swap
addi s0, s0, 1 # indice sinistro avanza
addi s1, s1, -1 # indice destro indietreggia
j while # jump all'etichetta while
end:
ld ra, 0(sp)
ld a0, 8(sp)
ld s0, 16(sp)
ld s1, 24(sp)
addi sp, sp, 32
ret