Le terminal de paiement planté
Le terminal de paiement d’une pharmacie se bloque pendant un encaissement. Son rapport d’erreur ne donne qu’une ligne : la dernière instruction exécutée, sw a1, 16(sp).
Le terminal ne contient aucun code source, seulement des instructions. Quelqu’un qui sait les lire peut dire ce que faisait le programme au moment de l’arrêt.
À la fin de cette leçon
Remarque
Je peux lire un court programme RISC-V.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Registre | une petite mémoire rapide dans le processeur | a0 |
| Immédiat | un nombre écrit dans l’instruction elle-même | 93 |
| Chargement | une instruction qui copie une valeur de la mémoire vers un registre | lw |
| Rangement | une instruction qui copie un registre vers la mémoire | sw |
| Branchement | une instruction qui peut sauter ailleurs dans le programme | bne |
| Étiquette | un nom donné à un endroit du programme | boucle: |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Trente-deux registres, chacun son rôle
Un processeur RISC-V de 64 bits a 32 registres de 64 bits, nommés x0 à x31. L’assembleur donne à la plupart un nom qui dit leur usage.
x0 s’appelle zero : il vaut toujours 0, et ce qu’on y écrit est perdu. Les registres a portent les entrées d’une fonction et sa réponse. Les registres t gardent des valeurs de travail de courte durée.
Exemple travaillé : les registres de la leçon
| Numéro | Nom | Usage habituel |
|---|---|---|
| x0 | zero | Vaut toujours 0 |
| x2 | sp | Le pointeur de pile : l’adresse de la pile en mémoire |
| x5 à x7 | t0 à t2 | Valeurs de travail |
| x10, x11 | a0, a1 | Entrées d’une fonction, et sa réponse dans a0 |
| x17 | a7 | Le service que ecall demande à Linux |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Aller dans le dossier d’architecture
cd ~/archAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Trois formes d’instruction
La plupart des instructions nomment d’abord le registre de destination, puis l’origine des entrées.
sub a0, a0, a1 soustrait deux registres. addi t0, t0, -1 ajoute à un registre un immédiat, un nombre écrit dans l’instruction. li a0, 5 semble être une troisième sorte. C’est un raccourci : pour un petit nombre, l’assembleur écrit addi a0, zero, 5.
Chargements et rangements touchent la mémoire
Les registres sont peu nombreux : les données vivent en mémoire. RISC-V ne calcule que sur des registres. Un chargement copie de la mémoire vers un registre, un rangement fait l’inverse.
lw a4, 8(a5) charge un mot de 4 octets à l’adresse contenue dans a5, plus 8. Le loop.c du labo remplit un tableau puis l’additionne : gcc doit donc charger et ranger.
Compiler loop.c en assembleur RISC-V
riscv64-linux-gnu-gcc -O2 -march=rv64gc -S loop.cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Trouver les chargements et le rangement
grep -E 'lw|sw' loop.sAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
La boucle colonne par colonne
grep -A 4 'L9:' loop.sAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Exemple travaillé : le C à côté du RISC-V
| C dans loop.c | RISC-V | Ce que fait l’instruction |
|---|---|---|
| grid[r][c] = 1; | sw a3,0(a5) | Écrit a3, qui vaut 1, à l’adresse contenue dans a5 |
| sum = sum + grid[r][c]; | lw a4,0(a5) | Lit 4 octets à l’adresse de a5 et les met dans a4 |
| sum = sum + grid[r][c]; | add a3,a3,a4 | Ajoute le nombre au total, dans les registres |
| la ligne r suivante | add a5,a5,a1 | Avance l’adresse de a1, qui vaut 16384 octets |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Erreur fréquente
Attention
Beaucoup pensent que add peut additionner un nombre pris directement en mémoire. En RISC-V, c’est impossible. Seuls les chargements et les rangements touchent la mémoire. Une valeur est chargée, travaillée dans les registres, puis rangée. C’est pourquoi la somme demande lw, puis add.
Un branchement fait une boucle
Un branchement compare deux registres et saute si le test réussit. beq saute s’ils sont égaux, bne s’ils sont différents.
Une étiquette comme boucle: nomme l’endroit où sauter. Le programme suivant calcule 5 fois 3 sans multiplier. t0 part de 5, a0 reçoit 3 à chaque tour, et bne t0, zero, boucle revient en arrière tant que t0 n’est pas nul.
Écrire une boucle en RISC-V
printf '.globl _start\n_start:\n li a0, 0\n li t0, 5\nboucle:\n addi a0, a0, 3\n addi t0, t0, -1\n bne t0, zero, boucle\n li a7, 93\n ecall\n' > fois.sAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
L’assembler
riscv64-linux-gnu-as fois.s -o fois.oAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Faire l’édition de liens
riscv64-linux-gnu-ld fois.o -o foisAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Le lancer dans l’émulateur
qemu-riscv64 ./foisAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lire le résultat
echo $?Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Le relire sans raccourcis
riscv64-linux-gnu-objdump -d -M no-aliases foisAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Pour aller plus loin : une puissance de deux
Écrivez un programme RISC-V qui calcule 2 puissance 5 en doublant : partez de 1 dans a0 et faites add a0, a0, a0 cinq fois dans une boucle.
Essayez-le d’abord dans votre interpréteur. Écrivez-le ensuite avec printf, comme fois.s, puis construisez-le et lancez-le dans le labo. Le code de sortie doit être 32.