Una funzione è un frammento di codice che riceve degli argomenti e calcola un risultato.
- ha un indirizzo di partenza
- riceve uno o più argomenti(mettiamo dei valori nei registri)
- svolge un calcolo
- ritorna un risultato
- torna all’istruzione seguente a quella che l’ha chiamata
STRUTTURA DI UNA FUNZIONE
- per chiamare una funzione:
jal <etichetta>
- registra nel registro$ra
la posizione dell’istruzione successiva ($ra <- PC+4
) e cambia il Program Counter per iniziare l’esecuzione del corpo della funzione (PC <- etichetta
) - per uscire dalla funzione e continuare l’esecuzione del chiamante:
jr $ra
salta all’indirizzo messo prima in$ra
- per passare valori alla funzione:
- registri per passare alla funzione (fino a 4 valori a 32 bit o 2 a 64 bit) -
$a0, $a1, $a2, $a3
- per restituire dalla funzione (fino a 2 valori a 32 bit o 1 a 64 bit) -
$v0, $v1
- registri per passare alla funzione (fino a 4 valori a 32 bit o 2 a 64 bit) -
preservare il contenuto dei registri
Conviene preservare il contenuto dei registri usati dalla funzione e ripristinarlo:
- meno vincoli alla funzione chiamante
- utile nelle funzioni che chiamano altre funzioni, che rischiano di perdere valori (sicuramente quello di
$ra
) - “elimina” il limite di spazio per i dati posto dai soli 4 registri
$a0-3
Le informazioni da preservare hanno un ciclo di vita caratteristico, dovuto al nidificarsi delle chiamate delle funzioni:
-
salvo stato prima di chiamata 1
- salvo stato prima di chiamata 2
- …
- ripristino stato prima di chiamata 2
-
ripristino stato prima di chiamata 1
Questo è il comportamento di uno stack, in cui aggiungere un elemento (push) e togliere l’ultimo inserito (pop).
Viene realizzata con un vettore di cui si tiene l’indirizzo dell’ultimo elemento occupato nel registro $sp (Stack Pointer).
esempio chiamate nidificate:
- main chiama foo che chiama bar
- foo ha bisogno di
$s0, $s1, $s2
- bar ha bisogno di
$s0, $s1
Visto che foo sa che ha bisogno dei 3 registri + il link di
$ra
, prima di chiamare bar, foo va a scrivere$s0, $s1, $s2
e$ra
sullo stack.
- Salto a bar, salvo i valori di
$s0, $s1
prima di sporcarli, la funzione fa quello che deve fare, torna a$ra
(ovvero foo), e ora il programma deve riscrivere nei registri tutto quello che ha salvato prima nello stack.
uso dello stack - push e pop
Lo stack si trova nella parte “alta” della memoria e cresce verso il basso.
Supponiamo di voler salvare e ripristinare il registro $ra
.
push
- si decrementa lo
$sp
della dimensione dell’elemento(in genere una word) - si memorizza l’elemento nella posizione 0
$sp
pop
- si legge l’elemento dalla posizione 0
$sp
- si incrementa o
in una funzione
All’inizio della funzione:
- allocare su stack abbastanza word da contenere i registri da preservare
- salvare su stack i registri, ad offset multipli di 4 rispetto a
$sp
nota bene
conviene allocare tutto lo spazio assieme per avere offset che restano costanti durante tutta l’esecuzione della funzione
All’uscita della funzione:
- ripristinare da stack i registri salvati agli stessi offset usati precedentemente
- disallocare da stack lo stesso spazio allocato in precedenza
- tornare alla funzione chiamante
addi $sp, $sp, -12
sw $ra, 8($sp)
sw $a0, 4($sp)
sw $a1, 0($sp)
# corpo della funzione
lw $a1, 0($sp)
- lo stack pointer parte da 1000 e scende, quindi faccio spazio per 3 word con -12
stack frame / activation record
La stack è usata anche per:
- comunicare ulteriori argomenti (oltre
$a0..
) e ulteriori risultati (oltre$v0, $v1
) - allocare variabili locali alla procedura.
Lo stack frame o activation record:
- è allocato sullo stack prima della chiamata della funzione e rilasciato subito dopo
$sp
- punta alla fine del record di attivazione (varia allocando dati dinamicamente)$fp
(frame pointer) - punta all’inizio del record di attivazione (resta fisso durante l’esecuzione della funzione)