Les fonctions et la pile de mémoire
Une fonction est un bloc nommé d'instructions que vous pouvez utiliser plusieurs fois. Vous y sautez, elle exécute sa tâche, puis rend le contrôle à l'endroit d'où vous l'avez appelée. Cela vous permet d'écrire une logique une seule fois et de l'utiliser depuis de nombreux endroits au lieu de répéter les mêmes lignes dans tout votre programme.
Utilisez pour appeler une fonction : cela saute à l'étiquette et stocke l'adresse de retour dans ra. La fonction se termine par , qui saute en retour à l'adresse contenue dans ra. et sont des pseudo-instructions : des noms pratiques que l'assembleur développe en instructions réelles, et .
Les arguments sont placés dans a0, a1, et les registres qui suivent. La valeur de retour est placée dans a0. Cette organisation est la convention d'appel, l'accord partagé qui permet à des morceaux de code distincts de fonctionner ensemble.
li a0, 6 # argumentcall square # appelle, et ra se souvient où revenirli a7, 1 # square laisse son résultat dans a0ecallli a7, 10ecallsquare:mul a0, a0, a0 # a0 = a0 * a0ret # retourne à ra
Suivez l'appel. place l'argument 6 dans a0. saute à l'étiquette square et stocke l'adresse de retour dans ra. À l'intérieur de la fonction, calcule 6 * 6 = 36 et laisse le résultat dans a0, le registre utilisé pour les valeurs de retour. saute en retour à l'instruction juste après l'appel, où et affichent 36, puis le programme se termine.
L'appelant est sur le point d'appeler A.
caller exécute , ce qui enregistre automatiquement le point de retour vers caller dans le registre ra. Avant que A puisse exécuter , il rencontre un problème : l'instruction est sur le point d'écraser ra avec une nouvelle adresse de retour, détruisant celle dont A a besoin pour revenir à son point de départ. Pour corriger cela, A libère de la place sur la pile () et y sauvegarde le ra d'origine (). Il est maintenant sûr d'exécuter . B s'exécute et retourne avec un ordinaire, atterrissant de nouveau à l'intérieur de A. Enfin, A restaure le ra d'origine depuis la pile (), nettoie le pointeur de pile (), et son propre utilise cette valeur restaurée pour sauter avec succès vers caller. Si A avait sauté cette étape, l'instruction aurait écrasé définitivement ra, et le final de A sauterait au mauvais endroit au lieu de revenir à son point de départ.La pile
Comme il n'existe qu'une poignée de registres, les fonctions qui en appellent d'autres ont besoin d'espace de travail. La pile est de la mémoire que vous empruntez via le pointeur de pile sp : soustrayez pour faire de la place, stockez ce que vous devez conserver, puis restaurez-le avant .
La pile est une structure Last-In, First-Out (LIFO) : la valeur stockée le plus récemment est la première que vous récupérez. En RISC-V, la pile croît vers le bas dans la mémoire. Soustraire de sp réserve un nouvel espace utilisable en toute sécurité. Lorsque vous récupérez les valeurs, vous devez le faire dans l'ordre inverse de leur stockage, sinon le flux d'exécution du programme sera corrompu.
addi sp, sp, -4 # réserve la place pour un motsw ra, 0(sp) # sauvegarde l'adresse de retour# ... appeler d'autres fonctions ici ...lw ra, 0(sp) # la restaureaddi sp, sp, 4 # libère la placeret
Chaque / écrase ra, donc si votre fonction appelle une autre fonction, l'appel interne détruit votre adresse de retour et votre propre saute au mauvais endroit. Pour éviter cela, réserve 4 octets sur la pile, et y sauvegarde ra. Une fois les appels internes terminés, restaure l'adresse de retour, et rend l'espace avant .
ra avant , et ramenez toujours sp à la valeur qu'il avait au début. Une pile qui ne correspond pas à son état initial est la cause habituelle d'un plantage dans les programmes en assembleur.