Funzioni e stack
Una funzione è un blocco denominato di istruzioni che puoi usare più di una volta. Ci salti, essa svolge il suo compito e restituisce il controllo al punto da cui l'hai chiamata. Questo ti permette di scrivere una parte di logica una sola volta e usarla da molti punti invece di ripetere le stesse righe in tutto il programma.
Usa per chiamare una funzione: salta all'etichetta e memorizza la posizione di ritorno in ra. La funzione termina con , che salta indietro all'indirizzo in ra. e sono pseudo-istruzioni: nomi comodi che l'assembler espande nelle istruzioni effettive, e .
Gli argomenti vengono inseriti in a0, a1 e nei registri che seguono. Il valore di ritorno viene inserito in a0. Questa disposizione è la convenzione di chiamata, l'accordo condiviso che permette a parti separate di codice di lavorare insieme.
li a0, 6 # argomentocall square # chiama, e ra ricorda dove tornareli a7, 1 # square lascia il suo risultato in a0ecallli a7, 10ecallsquare:mul a0, a0, a0 # a0 = a0 * a0ret # torna a ra
Segui la chiamata. inserisce l'argomento 6 in a0. salta all'etichetta square e memorizza l'indirizzo di ritorno in ra. All'interno della funzione, calcola 6 * 6 = 36 e lascia il risultato in a0, il registro usato per i valori di ritorno. salta indietro all'istruzione subito dopo la chiamata, dove e stampano 36, e poi il programma termina.
Il chiamante sta per chiamare A.
caller esegue , che salva automaticamente il punto di ritorno a caller nel registro ra. Prima che A possa eseguire , incontra un problema: l'istruzione sta per sovrascrivere ra con un nuovo indirizzo di ritorno, distruggendo quello che A necesita per tornare a casa. Per risolvere questo, A libera spazio sullo stack () e vi salva il ra originale (). Ora è sicuro eseguire . B viene eseguito e ritorna con un normale , atterrando di nuovo dentro A. Infine, A ripristina il ra originale dallo stack (), ripulisce il puntatore dello stack (), e il suo stesso usa quel valore ripristinato per saltare con successo indietro a caller. Se A avesse saltato questo passaggio, l'istruzione avrebbe sovrascritto permanentemente ra, e il finale di A sarebbe saltato nel posto sbagliato invece di tornare a casa.Lo stack
I registri disponibili sono pochi, quindi le funzioni che ne chiamano altre hanno bisogno di spazio temporaneo. Lo stack è memoria che si prende in prestito tramite il puntatore allo stack sp: sottrarre per fare spazio, memorizzare ciò che deve essere conservato, ripristinarlo prima di .
Lo stack è una struttura Last-In, First-Out (LIFO): il valore memorizzato più di recente è il primo che recuperi. In RISC-V, lo stack cresce verso il basso nella memoria. Sottrarre da sp riserva nuovo spazio che è sicuro usare. Quando riprendi i valori, devi farlo nell'ordine inverso rispetto a come li hai memorizzati, altrimenti il flusso di esecuzione del programma verrà corrotto.
addi sp, sp, -4 # crea spazio per una parolasw ra, 0(sp) # salva l'indirizzo di ritorno# ... chiama altre funzioni qui ...lw ra, 0(sp) # la ripristinaaddi sp, sp, 4 # libera lo spazioret
Ogni / sovrascrive ra, quindi se la tua funzione chiama un'altra funzione, la chiamata interna distrugge il tuo indirizzo di ritorno e il tuo salta nel posto sbagliato. Per evitarlo, riserva 4 byte sullo stack, e vi salva ra. Dopo che le chiamate interne sono terminate, ripristina l'indirizzo di ritorno, e restituisce lo spazio prima di .
ra prima di , e riporta sempre sp al valore che aveva all'inizio. Uno stack che non corrisponde al suo stato iniziale è la causa abituale di un crash nei programmi in assembly.