Aller au contenu principal

La pile

Ce que ce chapitre apporte

  • Expliquer pourquoi la pile part du haut de la mémoire et descend, alors que les données montent.
  • Empiler puis dépiler un mot avec les deux gestes qui conviennent, et relever la valeur de sp après chaque instruction.
  • Lire la figure de la pile case par case, et dire à quelle case le pointeur sp renvoie.
  • Réserver un cadre de plusieurs mots d'un seul mouvement, et y ranger par décalages.
  • Repérer un rééquilibrage oublié, et dire ce que le code qui suit lira à la place de ce qu'il attendait.

Les registres sont trente-deux, et ce nombre ne changera pas au cours du programme. Un calcul qui a besoin de retenir davantage de valeurs doit donc les poser quelque part, puis les reprendre. Les chapitres précédents ont montré où poser une donnée dont l'adresse est connue d'avance : une déclaration, un nom, une case. Reste tout le reste, c'est-à-dire l'essentiel : les valeurs dont un fragment de programme a besoin le temps de son exécution, et dont plus personne ne veut ensuite. Pour celles-là, la machine ne réserve pas de case nommée. Elle réserve une discipline, une seule, qui tient en deux instructions et qui porte un nom : la pile. Ce chapitre l'installe et la fait voir grandir, parce que le chapitre suivant en dépend entièrement.

Onze instructions, et pas une de plus

La pile ne demande aucune instruction dédiée, et c'est tout l'intérêt de ce qui suit : elle se construit avec ce qui est déjà là. Onze instructions servent dans les figures de ce chapitre, et une seule, mv, n'a encore servi nulle part ailleurs que dans le mémento. Les dix autres reviennent de loin, alors les voici rappelées d'un mot, groupées par ce qu'elles font.

Poser une valeur dans un registre : li t0, 42 y met une constante, mv t0, sp y recopie le contenu d'un autre registre.

Toucher la mémoire : sw t0, 0(sp) écrit les quatre octets de t0 à l'adresse contenue dans sp, lw t0, 0(sp) fait la lecture inverse, et la t0, mesures charge l'adresse d'une donnée déclarée.

Calculer : addi sp, sp, -4 ajoute une constante à un registre, add additionne deux registres, sub les soustrait, et slli t3, t1, 2 décale de deux bits vers la gauche, c'est-à-dire multiplie par quatre.

Décider : beq t1, t2, fin saute à l'étiquette fin si les deux registres sont égaux, et j empiler saute sans condition.

Ces familles sont celles du mémento du jeu d'instructions, où chaque écriture est donnée au complet, avec les variantes que ce chapitre laisse de côté.

Trente-deux cases, et tout le reste

Un processeur RISC-V a trente-deux registres, dont un qui vaut toujours zéro et deux ou trois qui ont déjà un emploi réservé. Il en reste donc moins de trente pour calculer. C'est beaucoup pour une addition, et très peu dès qu'un programme fait deux choses l'une après l'autre : le fragment qui vient ensuite a lui aussi besoin de registres, et il ne connaît pas ceux que le fragment précédent comptait garder.

Une première réponse consiste à déclarer des données nommées, comme au chapitre Ranger et reprendre, et à y ranger ce qui déborde. Elle échoue sur trois points. Une donnée déclarée occupe une adresse fixe, choisie à l'assemblage, alors qu'une valeur temporaire n'a besoin d'exister que pendant quelques instructions. Une donnée déclarée porte un nom, alors qu'il faudrait en inventer un par valeur intermédiaire et par fragment de code. Surtout, une donnée déclarée est unique : si le même fragment de code s'exécute deux fois sans que la première soit terminée, ce qui arrive dès qu'une fonction s'appelle elle-même, les deux exécutions se disputent la même case et l'une écrase l'autre.

Ce qu'il faut est donc une zone de mémoire où l'on prend de la place pour un moment, où l'on repose ce qu'on avait pris, et où la place reprise redevient disponible. Une seule règle suffit à organiser cela sans tenir aucune comptabilité : ce qui est rangé en dernier est repris en premier. Cette règle porte le nom de la chose, une pile, comme une pile d'assiettes dont on ne retire que celle du dessus.

Pile

Une pile est une zone de mémoire gérée par une seule règle : on ne prend et on ne rend de la place qu'à une extrémité, appelée le sommet. L'adresse de ce sommet tient dans un registre, sp, le pointeur de pile. Il n'y a pas d'instruction « empiler » ni « dépiler » dans le matériel : il y a un registre, des rangements, des chargements, et une convention que tout le monde respecte.

Une mémoire, deux occupants

La mémoire d'un programme est un seul grand tableau d'octets, partagé entre les données déclarées et la pile. Les données, elles, montent : la première déclaration se pose à l'adresse 0x1000, la suivante juste après, et ainsi de suite. Plus un programme déclare de données, plus loin vers le haut il arrive.

Si la pile montait elle aussi, il faudrait décider au départ où les données s'arrêtent et où la pile commence. Ce partage serait faux presque toujours : un programme qui déclare un gros tableau et n'empile presque rien perdrait de la place à un bout, et un programme qui déclare trois mots mais appelle des fonctions profondément imbriquées buterait sur une frontière posée trop bas, avec de la mémoire libre juste derrière.

Faire partir les deux occupants des deux bouts supprime la question. Les données montent depuis le bas, la pile descend depuis le haut, et la seule limite est leur rencontre. Aucune frontière n'est fixée à l'avance ; c'est l'usage réel qui décide du partage, programme par programme et instant par instant.

La figure suivante ne fait rien d'autre que regarder où en est le haut de la mémoire. Elle emploie mv t0, sp, la seule écriture que les chapitres précédents n'ont pas mise au travail : elle recopie un registre dans un autre, sans rien calculer. La colonne des instructions réelles montre d'ailleurs qu'elle n'existe pas vraiment, et que l'assembleur la remplace par addi t0, sp, 0, une addition de zéro. Copier un registre n'a pas mérité d'instruction propre.

RISC-V RV32Ipas 0 sur 2pc = 0x0

programme

  1. 0x0mv t0, spaddi t0, sp, 0
  2. 0x4addi t1, sp, -4

registres

sp0x2000
t00
t10

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Exécuter les deux instructions et lire la ligne sp du tableau des registres : elle vaut 8192, c'est-à-dire 0x2000, avant même la première instruction. t0 en reçoit la copie, et t1 l'adresse qui serait le sommet après un premier rangement.

Le sommet de départ vaut ici 0x2000, soit 8 192 en décimal. Ce n'est pas un nombre magique : c'est le réglage par défaut des figures de ce module, modifiable par une ligne pile:. Sur une machine réelle, l'adresse est bien plus haute et le système d'exploitation la choisit au lancement, mais le principe est identique.

Le programme suivant met les deux occupants dans la même figure : trois mesures déclarées en bas, un mot empilé en haut, et la soustraction qui mesure ce qui reste libre entre les deux.

RISC-V RV32Ipas 0 sur 5pc = 0x0

programme

  1. 0x0la t0, mesureslui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8addi sp, sp, -4
  4. 0xcsw t0, 0(sp)
  5. 0x10sub t1, sp, t0

registres

sp0x2000
t00
t10

mémoire

0x1000mesures10
0x100420
0x100830

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Aller jusqu'à la fin et comparer les deux blocs de la figure : les données occupent 0x1000, le mot empilé occupe 0x1ffc, et t1 annonce 4092, la distance qui sépare encore les deux occupants.

Quatre mille quatre-vingt-douze octets libres entre le dernier octet utilisé par les données et le sommet courant de la pile. Aucune ligne du programme n'a fixé cette valeur : elle est la conséquence de ce que le programme a réellement déclaré et réellement empilé.

Empiler : réserver, puis écrire

Empiler un mot demande deux instructions, toujours les mêmes, toujours dans cet ordre.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0addi sp, sp, -4
  2. 0x4sw t0, 0(sp)
  3. 0x8li t0, 0addi t0, zero, 0

registres

sp0x2000
t042

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Exécuter les trois instructions en surveillant sp et le bloc « pile » de la figure : sp passe de 0x2000 à 0x1ffc, une case apparaît à 0x1ffc et reçoit 42, et la remise à zéro de t0 qui suit ne la touche pas.

La première instruction réserve : elle retire quatre à sp, qui passe de 0x2000 à 0x1ffc. Rien n'est écrit, mais quatre octets viennent d'être déclarés occupés. La seconde écrit dans la case ainsi réservée, à l'adresse que sp désigne maintenant.

L'ordre des deux n'est pas indifférent. Écrire d'abord à -4(sp) puis déplacer sp donnerait le même contenu, mais il existerait un instant, entre les deux instructions, où un mot serait écrit en dehors de la zone réservée. Sur une machine de bureau ce n'est qu'un détail de style ; sur une machine qui interrompt un programme pour en traiter un autre, l'interruption qui survient à cet instant écrase le mot. La règle est donc : réserver d'abord, écrire ensuite.

La troisième instruction du programme est là pour la démonstration. Une fois le mot empilé, t0 peut servir à autre chose : c'est exactement la raison d'être de la manœuvre.

Dépiler : lire, puis rendre

Reprendre le mot demande les deux mêmes instructions, à l'envers.

RISC-V RV32Ipas 0 sur 5pc = 0x0

programme

  1. 0x0addi sp, sp, -4
  2. 0x4sw t0, 0(sp)
  3. 0x8li t0, 7addi t0, zero, 7
  4. 0xclw t0, 0(sp)
  5. 0x10addi sp, sp, 4

registres

sp0x2000
t042

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Exécuter jusqu'au bout et regarder t0 : il vaut 42, puis 7, puis de nouveau 42. À la dernière instruction, sp revient à 0x2000 et le bloc « pile » de la figure redevient vide.

Le chargement reprend la valeur, l'addition rend la place. Là encore l'ordre compte, et pour la même raison : rendre la place avant d'avoir lu laisserait un instant pendant lequel la valeur à lire se trouve en dehors de la zone réservée.

À la fin de ce programme, sp vaut de nouveau 0x2000 et la figure n'affiche plus aucune case de pile. Le mot 42 est pourtant toujours écrit à l'adresse 0x1ffc : dépiler n'efface rien, cela déclare seulement la place disponible. Le prochain empilement écrira par-dessus.

Les deux gestes, et leur symétrie

Empiler un mot, c'est addi sp, sp, -4 puis sw registre, 0(sp). Le dépiler, c'est lw registre, 0(sp) puis addi sp, sp, 4. Un fragment de code qui empile trois mots doit dépiler trois mots. La machine ne le vérifie pas : c'est celui qui écrit le code qui répond de l'équilibre.

Voir la pile grandir

Trois empilements de suite, et la figure montre trois cases là où il n'y en avait aucune.

RISC-V RV32Ipas 0 sur 9pc = 0x0

programme

  1. 0x0addi sp, sp, -4
  2. 0x4sw t0, 0(sp)
  3. 0x8addi sp, sp, -4
  4. 0xcsw t1, 0(sp)
  5. 0x10addi sp, sp, -4
  6. 0x14sw t2, 0(sp)
  7. 0x18lw t3, 0(sp)
  8. 0x1clw t4, 4(sp)
  9. 0x20lw t5, 8(sp)

registres

sp0x2000
t011
t122
t233
t30
t40
t50

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Avancer instruction par instruction et regarder le bloc « pile » s'allonger vers le bas, à 0x1ffc puis 0x1ff8 puis 0x1ff4, avec la marque sp toujours sur la case la plus basse. Les trois derniers chargements ramènent 33, 22 et 11 sans déplacer sp.

La figure affiche les cases du sommet courant jusqu'au sommet de départ, et signale d'une marque celle que sp désigne. Le mot empilé en dernier, 33, se trouve à l'adresse la plus basse, et c'est celui que lw t3, 0(sp) ramène. Le premier empilé, 11, se trouve tout en haut, à 8(sp), et il faudrait dépiler les deux autres pour l'atteindre par le sommet. Voilà la règle du dernier arrivé, premier sorti, non plus énoncée mais lue sur des adresses.

Les trois derniers chargements montrent au passage qu'une pile n'interdit pas de regarder plus bas que le sommet. Un décalage constant à partir de sp donne accès à n'importe quelle case déjà réservée, sans rien déplacer. C'est ce qui rend l'écriture suivante possible.

Réserver un cadre d'un seul mouvement

Quand un fragment de code sait combien de mots il lui faut, il n'a aucune raison de déplacer sp une fois par mot. Une seule soustraction réserve le tout, et les rangements se font par décalages.

RISC-V RV32Ipas 0 sur 5pc = 0x0

programme

  1. 0x0addi sp, sp, -12
  2. 0x4sw t0, 8(sp)
  3. 0x8sw t1, 4(sp)
  4. 0xcsw t2, 0(sp)
  5. 0x10addi sp, sp, 12

registres

sp0x2000
t011
t122
t233

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Exécuter la première instruction seule : sp descend d'un coup de 0x2000 à 0x1ff4, et trois cases vides apparaissent d'un seul mouvement. Continuer et vérifier que les trois rangements touchent 0x1ffc, 0x1ff8 et 0x1ff4, dans cet ordre.

Cette zone réservée en une fois porte un nom, le cadre. Elle contient tout ce dont un fragment de code a besoin le temps de son exécution, et elle disparaît d'un seul addi sp, sp, 12 à la fin. Le chapitre suivant montrera que c'est exactement la forme que prend une fonction : un cadre à l'entrée, un cadre rendu à la sortie, et entre les deux un code qui travaille par décalages à partir de sp.

Un détail de lecture mérite l'attention, parce qu'il surprend au premier abord. Le décalage 0 désigne l'adresse la plus basse du cadre, et le décalage le plus grand l'adresse la plus haute. Les cases se remplissent donc vers le bas quand les décalages diminuent, ce qui est la conséquence directe d'une pile qui descend.

Le multiple de quatre n'est pas une préférence

Les déplacements de sp se font par multiples de quatre, parce qu'un sw et un lw exigent une adresse multiple de quatre. Un cadre de trois mots occupe douze octets, pas dix. Un cadre d'un seul octet utile occupe quand même quatre octets. Ce n'est pas du gaspillage : c'est le prix d'un accès mémoire qui fonctionne.

Ce que coûte un rééquilibrage oublié

Le programme suivant contient une faute qui ne déclenche aucune erreur, et c'est justement ce qui la rend intéressante. Un premier fragment empile une valeur qu'il compte retrouver plus tard. Un second fragment empile deux mots et n'en dépile qu'un. Le premier reprend ensuite la main.

RISC-V RV32Ipas 0 sur 10pc = 0x0

programme

  1. 0x0addi sp, sp, -4
  2. 0x4sw t3, 0(sp)
  3. 0x8addi sp, sp, -4
  4. 0xcsw t0, 0(sp)
  5. 0x10addi sp, sp, -4
  6. 0x14sw t1, 0(sp)
  7. 0x18lw t2, 0(sp)
  8. 0x1caddi sp, sp, 4
  9. 0x20lw t4, 0(sp)
  10. 0x24addi sp, sp, 4

registres

sp0x2000
t0100
t1200
t20
t37
t40

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Aller jusqu'à la fin et relever deux choses : t4 vaut 100 là où 7 était attendu, et sp s'arrête à 0x1ffc au lieu de 0x2000. Reculer ensuite jusqu'au seul « addi sp, sp, 4 » du programme, alors qu'il en fallait deux.

Le mot 7 est toujours en mémoire, intact, à l'adresse 0x1ffc. Personne ne l'a effacé. Simplement, sp ne le désigne plus : il pointe une case plus bas, celle du 100 abandonné par le fragment fautif, et le chargement final ramène donc 100. Aucune instruction n'a été refusée, aucun message n'a été affiché, et le programme se termine normalement avec une valeur fausse.

Le bouton « Modifier le programme » permet de réparer cette faute sans quitter la page : ajouter une ligne addi sp, sp, 4 juste avant lw t4, 0(sp), et le programme se réassemble à la frappe. Le fragment fautif rend alors les deux mots qu'il avait pris, t4 reprend la valeur 7 attendue, et sp revient à 0x2000. Une instruction manquait, et elle manquait à un endroit que rien ne signalait.

La seconde conséquence est plus insidieuse encore. À la fin, sp vaut 0x1ffc et non 0x2000 : quatre octets de pile sont perdus pour toujours. Un fragment qui dérive ainsi de quatre octets à chaque exécution et qui tourne dans une boucle fait descendre la pile sans fin, jusqu'à ce qu'elle rejoigne les données.

Deux façons de casser la pile

La première est signalée. Déplacer sp d'une quantité qui n'est pas multiple de quatre suffit à rendre le rangement suivant impossible.

RISC-V RV32Ipas 0 sur 1pc = 0x0

programme

  1. 0x0addi sp, sp, -2
  2. 0x4sw t0, 0(sp)

registres

sp0x2000
t05

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Aller à la fin : sp descend à 0x1ffe, et la figure s'arrête sur le message « sw veut une adresse multiple de 4, et 0x1ffe ne l'est pas ». Reculer d'un pas pour voir l'instruction responsable, qui est la précédente et non celle qui échoue.

L'instruction fautive n'est pas celle qui échoue. Le sw est correct ; c'est le addi d'avant qui a mis sp dans un état où plus aucun rangement de mot ne peut réussir. C'est une situation générale en assembleur, et la raison d'être du bouton qui recule : l'endroit où un programme s'arrête est rarement l'endroit où il s'est trompé.

La seconde façon n'est signalée par rien du tout. Il suffit que la pile descende assez pour atteindre les données.

RISC-V RV32Ipas 0 sur 4pc = 0x0

programme

  1. 0x0addi sp, sp, -4
  2. 0x4sw t0, 0(sp)
  3. 0x8addi sp, sp, -4
  4. 0xcsw t1, 0(sp)

registres

sp0x1010
t0111
t1222

mémoire

0x1000mesures10
0x100420
0x100830

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Ici la pile démarre à 0x1010, juste au-dessus des données, pour rendre la collision visible en deux empilements. Aller à la fin et regarder la troisième mesure dans le bloc des données : elle vaut 222 au lieu de 30, et aucune erreur n'a été signalée.

La ligne pile: 0x1010 rapproche artificiellement les deux occupants, parce qu'il faudrait un millier d'empilements pour provoquer la même chose avec le réglage habituel. Le mécanisme, lui, est exactement celui d'un programme réel : le second empilement écrit à l'adresse 0x1008, qui est le troisième mot du tableau mesures. La mesure 30 devient 222. Le tableau n'a pas été déclaré trop court, le programme n'a pas dépassé un indice, et pourtant sa troisième valeur est fausse.

Rien ne surveille la frontière

La mémoire n'est qu'un tableau d'octets, et aucune instruction ne demande la permission d'écrire. Sur une machine réelle, le système d'exploitation pose au bas de la pile une zone interdite d'accès, et le programme qui la touche est arrêté net : c'est l'erreur de segmentation. Cette protection attrape la pile qui descend d'un coup, pas les cas où les deux zones se recouvrent progressivement. Un débordement de pile détecté est une chance ; un débordement silencieux est la règle.

Un travail complet : renverser une suite

La pile ne sert pas qu'à ranger des valeurs en attente. Sa règle du dernier arrivé, premier sorti est un outil de calcul à elle seule. Le programme suivant lit un tableau de quatre valeurs, empile chacune, puis les ressort une à une dans un second tableau : l'ordre se trouve inversé sans qu'aucune instruction n'ait eu à s'en occuper.

RISC-V RV32Ipas 0 sur 82pc = 0x0

programme

  1. 0x0la t0, suitelui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8li t1, 0addi t1, zero, 0
  4. 0xcli t2, 4addi t2, zero, 4
  5. 0x10beq t1, t2, preparer
  6. 0x14slli t3, t1, 2
  7. 0x18add t4, t0, t3
  8. 0x1clw t5, 0(t4)
  9. 0x20addi sp, sp, -4
  10. 0x24sw t5, 0(sp)
  11. 0x28addi t1, t1, 1
  12. 0x2cj empilerjal zero, empiler
  13. 0x30la t0, renverselui t0, 1
  14. 0x34addi t0, t0, 16
  15. 0x38li t1, 0addi t1, zero, 0
  16. 0x3cli t2, 4addi t2, zero, 4
  17. 0x40beq t1, t2, fin
  18. 0x44lw t5, 0(sp)
  19. 0x48addi sp, sp, 4
  20. 0x4cslli t3, t1, 2
  21. 0x50add t4, t0, t3
  22. 0x54sw t5, 0(t4)
  23. 0x58addi t1, t1, 1
  24. 0x5cj viderjal zero, vider

registres

sp0x2000
t00
t10
t20
t30
t40
t50

mémoire

0x1000suite3
0x10041
0x10084
0x100c1
0x1010renverse0
0x10140
0x10180
0x101c0

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Avancer jusqu'à ce que la première boucle s'achève : la pile compte alors quatre cases, de 0x1ff0 à 0x1ffc. Aller ensuite à la fin et lire le second tableau dans le bloc des données, qui vaut 1, 4, 1, 3 quand le premier vaut 3, 1, 4, 1.

Le parcours du tableau reprend le calcul d'adresse du chapitre Tableaux et adresses : slli t3, t1, 2 multiplie l'indice par quatre, l'addition à l'adresse de base donne l'adresse de l'élément. La nouveauté tient en quatre instructions, deux pour empiler et deux pour dépiler, et c'est tout ce qu'il faut pour renverser une suite de longueur quelconque.

Au dernier pas, sp est revenu à 0x2000. Les deux boucles empilent et dépilent le même nombre de mots, la pile est donc rendue dans l'état où elle a été trouvée. C'est la propriété que le chapitre suivant va exiger de chaque fonction.

Un dernier exercice de lecture

Le programme suivant échange le contenu de deux registres en passant par la pile, sans faire appel à un troisième registre. Prévoir, avant de lancer la figure, l'adresse touchée par chacun des deux rangements, puis les valeurs de t0 et de t1 à la fin.

RISC-V RV32Ipas 0 sur 6pc = 0x0

programme

  1. 0x0addi sp, sp, -8
  2. 0x4sw t0, 4(sp)
  3. 0x8sw t1, 0(sp)
  4. 0xclw t0, 0(sp)
  5. 0x10lw t1, 4(sp)
  6. 0x14addi sp, sp, 8

registres

sp0x2000
t01
t12

Rien n'a encore été exécuté. Le compteur ordinal pointe la première instruction, en 0x0.

Prévoir d'abord, puis exécuter pas à pas : le premier rangement touche 0x1ffc et le second 0x1ff8, et les deux chargements les reprennent en sens inverse. Vérifier pour finir que sp est revenu à 0x2000.
La réponse

Le cadre de huit octets s'ouvre à 0x1ff8, donc 4(sp) désigne 0x1ffc et 0(sp) désigne 0x1ff8. La valeur 1 part en 0x1ffc, la valeur 2 en 0x1ff8. Les deux chargements croisent les décalages : t0 reprend ce qui se trouve en 0(sp), soit 2, et t1 ce qui se trouve en 4(sp), soit 1. L'échange n'a demandé aucun registre supplémentaire, seulement quatre accès mémoire, et sp retrouve 0x2000 à la dernière instruction. Une pile ne sert donc pas seulement à mettre de côté : les décalages en font une zone de travail à part entière.

Vérification rapideon peut se reprendre

1.Pourquoi la pile part-elle du haut de la mémoire et descend-elle ?

2.Quel couple d'instructions empile le contenu de t0 ?

3.Après trois empilements d'un mot chacun depuis 0x2000, où se trouve le mot empilé en premier ?

4.Un fragment de code empile deux mots et n'en dépile qu'un. Que se passe-t-il ?

5.Que provoque addi sp, sp, -2 suivi d'un sw ?

6.Un programme écrit sur la pile jusqu'à atteindre la zone des données. Que signale la machine ?

La méthode

  1. Compter les mots nécessaires avant d'écrire la première instruction, puis réserver le cadre d'un seul addi sp, sp, -4n, plutôt que de déplacer sp une fois par valeur.
  2. Écrire le rééquilibrage en même temps que la réservation : taper la ligne addi sp, sp, +4n immédiatement après avoir tapé la ligne négative, et remplir le corps entre les deux.
  3. Ranger par décalages constants à partir de sp, le décalage 0 pour la case la plus basse, et noter en commentaire ce que contient chaque décalage du cadre.
  4. Vérifier l'alignement : tout déplacement de sp est un multiple de quatre, sans exception.
  5. Relever sp au début et à la fin d'un fragment dans la figure : les deux valeurs doivent être identiques. Un écart signale un rééquilibrage manquant, même quand le résultat affiché semble correct.
  6. Chercher la faute en amont de l'arrêt : reculer d'un pas depuis l'instruction qui échoue, car c'est presque toujours celle qui a placé sp ou une adresse dans un état impossible.

Synthèse

  • La pile existe parce que les registres sont trente-deux et qu'un programme a besoin de retenir plus de valeurs que cela, pour une durée courte, sans leur donner de nom ni d'adresse fixe.
  • Les données montent depuis 0x1000, la pile descend depuis 0x2000. Faire partir les deux occupants des deux bouts de la mémoire évite de fixer une frontière à l'assemblage, et laisse l'usage réel décider du partage.
  • Empiler, c'est réserver puis écrire : addi sp, sp, -4 puis sw. Dépiler, c'est lire puis rendre : lw puis addi sp, sp, 4. Les deux ordres sont imposés, pour qu'aucun mot ne soit jamais écrit ou lu en dehors de la zone réservée.
  • La figure affiche la pile case par case, du sommet courant au sommet de départ, avec une marque sur la case que sp désigne. Le dernier mot empilé occupe l'adresse la plus basse et se lit à 0(sp).
  • Un cadre se réserve d'un seul mouvement, addi sp, sp, -12 pour trois mots, et se remplit par décalages, le décalage 0 désignant la case la plus basse.
  • Un rééquilibrage oublié ne déclenche aucune erreur : sp reste décalé, le code suivant lit la case du voisin, et la pile perd quelques octets à chaque passage.
  • Rien ne surveille la frontière entre la pile et les données. Une pile qui descend trop loin écrase les données en silence, et un déplacement de sp qui n'est pas multiple de quatre rend impossible le rangement suivant.

La pile est maintenant un outil. Appeler en fait le cœur du langage machine : une fonction appelée doit savoir où revenir, et cette adresse de retour se range, comme le reste, sur la pile.