Aller au contenu principal

Ranger et reprendre

Ce que ce chapitre apporte

  • Désigner une case de mémoire par son adresse, et lire une adresse écrite en hexadécimal.
  • Charger une valeur de la mémoire vers un registre, et ranger un registre en mémoire, en passant par une adresse chargée au préalable.
  • Choisir la largeur d'un accès entre lw, lh et lb, et distinguer leurs variantes signées de leurs variantes non signées.
  • Expliquer pourquoi une adresse mal alignée fait échouer un chargement, et reconnaître le message qui le signale.
  • Déterminer dans quel ordre les octets d'un mot sont posés en mémoire, et le vérifier octet par octet.

Un processeur calcule vite et retient peu. Trente-deux registres suffisent à tenir trois ou quatre résultats intermédiaires, et pas davantage : une trame réseau, un relevé de mesures, une phrase, rien de tout cela n'y entre. Ce qui dépasse vit ailleurs, dans la mémoire, et ne monte dans le processeur que le temps d'un calcul avant de redescendre. Ce chapitre installe ce va-et-vient. Trois instructions y suffisent, et elles sont posées d'abord seules ; les variantes de largeur et de signe, qui ne changent rien au geste, ne viennent qu'ensuite. Il amène au passage deux notations dont aucune documentation technique ne se prive, l'écriture hexadécimale et l'ordre des octets, et une contrainte qui fait échouer des programmes entiers, l'alignement.

Les instructions de ce chapitre

Huit instructions nouvelles apparaissent dans les pages qui suivent, et c'est beaucoup. Elles n'arrivent pas ensemble. Les trois premières portent tout ce que ce chapitre enseigne et occupent sa première moitié ; les cinq autres n'en sont que des variantes, même geste, largeur ou interprétation différente.

InstructionFormeCe qu'elle fait
lala rd, nomCharge dans rd l'adresse de la donnée nommée, sans rien lire en mémoire.
lwlw rd, dec(rs)Charge dans rd le mot de quatre octets rangé à l'adresse rs plus dec.
swsw rs2, dec(rs1)Range les quatre octets de rs2 à l'adresse rs1 plus dec.
lblb rd, dec(rs)Charge un octet et remplit le reste du registre en recopiant le bit de poids fort de cet octet.
lbulbu rd, dec(rs)Charge un octet et remplit le reste du registre avec des zéros.
lhlh rd, dec(rs)Charge deux octets et remplit le reste en recopiant leur bit de poids fort.
lhulhu rd, dec(rs)Charge deux octets et remplit le reste avec des zéros.
sbsb rs2, dec(rs1)Range le seul octet de poids faible de rs2 à l'adresse rs1 plus dec.

rd désigne le registre qui reçoit, rs un registre source, et dec un nombre ajouté à l'adresse au moment de l'accès. Ces noms sont des abréviations, et les lire comme telles évite de les apprendre un par un : l pour charger, s pour ranger, puis la largeur, w pour un mot de quatre octets, h pour un demi-mot de deux, b pour un octet. Le u final, quand il est là, annonce une lecture non signée. lbu est donc le chargement d'un octet non signé, et rien d'autre. Les programmes s'appuient par ailleurs sur li, add et addi, posées au premier chapitre.

Trente-deux cases, et le reste du monde

Le décompte du premier chapitre mérite d'être pris au sérieux. Trente-deux registres de trente-deux bits, dont un qui vaut toujours zéro : la totalité de ce qu'un processeur RISC-V tient sous la main représente cent vingt-quatre octets. Un nom de fichier un peu long n'y tient pas.

La mémoire est l'autre pièce, démesurée en comparaison. C'est un tableau d'octets numérotés à partir de zéro, et le numéro d'un octet s'appelle son adresse : la mémoire n'a pas de noms, elle a des numéros.

Entre les deux pièces, la règle est stricte. Le processeur calcule uniquement sur les registres. Il n'existe aucune instruction qui additionne deux cases de mémoire, ni même qui en compare deux. Ce qui existe, ce sont deux opérations de transport : charger, qui recopie de la mémoire vers un registre, et ranger, qui recopie d'un registre vers la mémoire. Tout programme un peu sérieux passe donc son temps à faire descendre des valeurs, calculer, et remonter le résultat.

Cette contrainte vient du matériel : un registre est physiquement à côté de l'unité de calcul, et la mémoire est loin. Loin se mesure en cycles, et l'écart entre les deux explique une bonne part des programmes lents.

Adresse

Une adresse est le numéro d'un octet dans la mémoire. Elle ne dit rien de ce que cet octet contient, ni de sa taille : c'est l'instruction employée qui décide de lire un octet, deux ou quatre à partir de là. Une adresse est elle-même un nombre de trente-deux bits, et tient donc dans un registre, ce qui est exactement ce qui rend les tableaux et les chaînes possibles.

Charger l'adresse, puis lire à travers

Le geste central de ce chapitre se fait en deux temps, et jamais en un seul.

RISC-V RV32Ipas 0 sur 5pc = 0x0

programme

  1. 0x0la t0, mesurelui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8lw t1, 0(t0)
  4. 0xcaddi t1, t1, 1
  5. 0x10sw t1, 0(t0)

registres

sp0x2000
t00
t10

mémoire

0x1000mesure42

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

Exécuter jusqu'au bout en surveillant la zone mémoire de la figure : la case nommée « mesure » contient 42 au départ et 43 à la fin, et la ligne du bas signale les deux accès, une lecture puis une écriture, à la même adresse.

La ligne données: mesure = 42 pose un mot de quatre octets en mémoire et lui donne un nom. Ce nom n'existe que pour qui écrit le programme : à l'exécution, il ne reste qu'une adresse, et la figure l'affiche, 0x1000.

la t0, mesure charge l'adresse de mesure dans t0. Rien n'est lu en mémoire à ce stade : t0 reçoit un numéro, 4096 en décimal, 0x1000 en hexadécimal. La colonne des instructions réelles montre d'ailleurs que la est une pseudo-instruction, remplacée ici par lui t0, 1, qui pose la valeur 4096 sans toucher à la mémoire.

lw t1, 0(t0) charge enfin le mot. L'écriture 0(t0) se lit « à l'adresse contenue dans t0, plus zéro ». Le nombre devant les parenthèses est un décalage, ajouté à l'adresse au moment de l'accès. C'est lui qui permettra plus tard de parcourir un tableau sans recalculer l'adresse à chaque élément.

sw t1, 0(t0) fait le trajet inverse. L'ordre des opérandes surprend : le registre nommé en premier est ici la source, et la destination est la case mémoire. C'est la seule famille d'instructions où le premier nom n'est pas celui qui reçoit.

Le décalage est l'endroit le plus instructif où porter la main. Ouvrir « Modifier le programme » et remplacer lw t1, 0(t0) par lw t1, 4(t0) : la lecture se déplace à l'adresse 0x1004, quatre octets plus loin que la case déclarée, t1 reçoit donc 0 au lieu de 42, puis 1, et la case « mesure » finit à 1. L'adresse chargée dans t0 n'a pas bougé d'un octet ; seul le nombre écrit devant la parenthèse a changé.

« lw t1, mesure » est refusé, et c'est volontaire

Cette écriture, acceptée par beaucoup d'assembleurs, cache le point que ce chapitre enseigne. La figure la refuse avec un message qui rappelle la marche à suivre : charger d'abord l'adresse avec la, puis lire à travers le registre. La raison est qu'une adresse de trente-deux bits ne tient pas dans une instruction de trente-deux bits ; un assembleur qui accepte lw t1, mesure fabrique en réalité deux instructions dans le dos de celui qui écrit, et l'habitude se paie au premier débogage.

Une adresse s'écrit en hexadécimal

0x1000, 0x3004, 0x7ffe4a20 : les adresses ne s'écrivent jamais en décimal, dans aucune documentation, aucun message d'erreur, aucun débogueur. La raison est typographique avant d'être mathématique.

Un octet vaut de 0 à 255. En décimal, il occupe une, deux ou trois positions, ce qui rend toute colonne illisible. En hexadécimal, chaque chiffre représente exactement quatre bits, donc un octet occupe toujours deux chiffres, de 00 à ff. Une adresse de trente-deux bits, soit quatre octets, en occupe toujours huit. Un vidage de mémoire devient une grille régulière, et le découpage en octets se lit à l'œil.

Les seize chiffres vont de 0 à 9 puis de a à f, où a vaut dix et f quinze. Le préfixe 0x n'appartient pas au nombre : il annonce seulement la base.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0li t0, 0x2Aaddi t0, zero, 42
  2. 0x4li t1, 42addi t1, zero, 42
  3. 0x8li t2, 0b101010addi t2, zero, 42

registres

t00
t10
t20

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

Comparer les trois valeurs finales dans le tableau des registres : elles valent toutes 42. Regarder ensuite la colonne des instructions réelles, qui écrit les trois constantes de la même façon.

Les trois écritures désignent le même nombre, et la machine n'en garde aucune trace : les instructions réelles affichées sont addi t0, zero, 42 trois fois de suite. Une base est une notation pour les humains, pas une propriété d'une valeur. Reste à savoir où les données atterrissent.

RISC-V RV32Ipas 0 sur 6pc = 0x0

programme

  1. 0x0la t0, alui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8la t1, blui t1, 1
  4. 0xcaddi t1, t1, 4
  5. 0x10la t2, clui t2, 1
  6. 0x14addi t2, t2, 8

registres

t00
t10
t20

mémoire

0x1000a7
0x1004b8
0x1008c9

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

Relever les trois adresses obtenues dans t0, t1 et t2, puis les comparer à celles que la zone mémoire affiche à côté des trois noms : 0x1000, 0x1004 et 0x1008.

Les trois déclarations se suivent à partir de 0x1000, chacune occupant quatre octets, d'où un écart de quatre entre deux adresses voisines. La figure affiche 4096, 4100 et 4104 dans les registres et les mêmes valeurs en hexadécimal dans la zone mémoire : un registre contient un nombre, et l'hexadécimal n'est que la façon dont toute documentation l'écrit.

Ce que coûte un aller-retour

Reste à voir pourquoi les registres existent, plutôt que de tout garder en mémoire.

RISC-V RV32Ipas 0 sur 12pc = 0x0

programme

  1. 0x0la t0, mesureslui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8lw t1, 0(t0)
  4. 0xclw t2, 4(t0)
  5. 0x10add t1, t1, t2
  6. 0x14lw t2, 8(t0)
  7. 0x18add t1, t1, t2
  8. 0x1clw t2, 12(t0)
  9. 0x20add t1, t1, t2
  10. 0x24la t3, totallui t3, 1
  11. 0x28addi t3, t3, 256
  12. 0x2csw t1, 0(t3)

registres

sp0x2000
t00
t10
t20
t30

mémoire

0x1000mesures12
0x10045
0x1008-3
0x100c20
0x1100total0

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

Suivre t1, qui passe par 12, 17, 14 puis 34, et surveiller t2, qui sert quatre fois de suite pour quatre valeurs différentes. Vérifier à la fin que la case « total » contient 34.

Quatre valeurs additionnées, et deux registres ont suffi. t1 accumule, t2 sert de case de passage et se fait écraser à chaque tour : ses valeurs successives sont 5, puis -3, puis 20. Une seule adresse a été chargée, 0x1000, et les quatre éléments ont été atteints par les décalages 0, 4, 8 et 12. Ce schéma est le point de départ du parcours de tableau, au chapitre consacré aux tableaux et aux adresses.

La déclaration données: total @ 0x1100 = 0 montre l'autre possibilité : une adresse imposée par qui écrit, au lieu d'un placement automatique à la suite des précédentes. C'est ce que fait un système d'exploitation quand il décide où poser les zones d'un programme, et c'est exactement le mécanisme qui, au chapitre suivant, placera une lampe à une adresse choisie.

Le compte des instructions mérite un regard. Trois additions ont demandé quatre chargements, et sur une machine réelle un accès qui rate les mémoires intermédiaires coûte des centaines de fois le prix d'une addition. Tout l'art d'un compilateur consiste à garder dans les trente-deux registres ce dont le calcul a besoin, et à n'écrire en mémoire qu'au dernier moment ; quand il n'y parvient pas, il déverse des valeurs temporaires en mémoire, et cela se voit dans le code produit.

L'alignement, et l'erreur qui le signale

Une contrainte encadre enfin tout accès large, et elle tombe sans prévenir : l'adresse d'un mot doit être un multiple de quatre, celle d'un demi-mot un multiple de deux. La démonstration la plus claire consiste à violer la règle.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0la t0, nlui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8addi t0, t0, 1
  4. 0xclw t1, 0(t0)

registres

t00
t10

mémoire

0x1000n16909060

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

Exécuter jusqu'à l'arrêt : la figure s'interrompt au troisième pas et affiche « lw veut une adresse multiple de 4, et 0x1001 ne l'est pas ». Reculer d'un pas pour voir l'instruction qui a décalé l'adresse.

L'adresse 0x1000 était valable, 0x1001 ne l'est plus. La cause tient au matériel : la mémoire est câblée par blocs de quatre octets, et un mot à cheval sur deux blocs demanderait deux accès au lieu d'un. Plutôt que de payer ce coût en silence, l'architecture refuse.

La ligne attendu: erreur en tête du programme sert à dire que cet échec est voulu. Le contrôle vaut dans les deux sens : un programme déclaré fautif qui s'exécuterait normalement serait signalé lui aussi, de sorte qu'une démonstration d'erreur ne peut pas cesser silencieusement de démontrer.

Une adresse mal alignée vient presque toujours d'un calcul

Personne n'écrit une adresse impaire à la main. Elle apparaît quand un programme avance dans une zone d'octets et tente d'y lire un mot, ou quand un décalage exprimé en octets est ajouté là où un décalage exprimé en éléments était attendu. Le symptôme est brutal et immédiat, ce qui est une chance : il désigne l'instruction fautive au lieu de laisser un résultat faux se propager.

Lire moins qu'un mot

Le deuxième temps du chapitre commence ici, et il n'apporte aucun geste nouveau. Charger l'adresse, puis lire à travers : la marche à suivre reste celle de la et de lw. Seule la largeur de l'accès change, et c'est le nom de l'instruction qui la porte. lw lit un mot de quatre octets, lh un demi-mot de deux, lb un seul octet, à la même adresse et avec la même écriture dec(registre).

Un accès étroit laisse cependant une question ouverte, que lw ne posait pas. Un octet chargé dans un registre doit remplir trente-deux bits. Il reste vingt-quatre bits à décider, et le jeu d'instructions ne tranche pas : il propose les deux réponses, et les distingue par un u final.

RISC-V RV32Ipas 0 sur 4pc = 0x0

programme

  1. 0x0la t0, octetlui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8lb t1, 0(t0)
  4. 0xclbu t2, 0(t0)

registres

sp0x2000
t00
t10
t20

mémoire

0x1000octet240

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

Comparer les deux registres à la fin : t1 vaut -16 et t2 vaut 240, pour deux lectures du même octet à la même adresse.

lb traite l'octet comme un nombre signé : son bit de poids fort étant à un, il recopie ce bit dans les vingt-quatre positions restantes, et le registre contient -16. lbu traite le même octet comme un nombre non signé : il complète par des zéros, et le registre contient 240. Le motif de bits lu en mémoire est identique dans les deux cas ; seule l'interprétation change, et elle est choisie par le nom de l'instruction.

Une modification du programme suffit à faire disparaître l'écart. Remplacer 0x000000F0 par 0x0000007F sur la ligne données: et relancer : les deux registres valent alors 127. Le bit de poids fort de l'octet est passé à zéro, lb n'a plus rien de gênant à recopier, et les deux instructions rendent la même valeur. C'est donc bien le motif de bits, et lui seul, qui les sépare.

La même paire existe pour deux octets.

RISC-V RV32Ipas 0 sur 4pc = 0x0

programme

  1. 0x0la t0, demilui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8lh t1, 0(t0)
  4. 0xclhu t2, 0(t0)

registres

sp0x2000
t00
t10
t20

mémoire

0x1000demi65486

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

Relever les deux résultats, -50 et 65486, et vérifier que la zone mémoire n'affiche qu'une seule valeur déposée : la différence vient de l'instruction, pas de la donnée.

Le choix se fait sur la nature de la donnée. Un relevé de température qui peut descendre sous zéro se lit avec lb ; un compteur, un code, un octet de trame se lisent avec lbu. Se tromper donne une valeur juste dans la moitié des cas, ce qui est la pire des situations : le défaut n'apparaît que sur les données dont le bit de poids fort est à un.

Un lb prélève un octet parmi les quatre d'un mot, et encore faut-il savoir lequel : l'ordre dans lequel ces quatre octets sont posés en mémoire n'a rien d'évident, et c'est l'affaire de la section suivante.

Quatre octets, et dans quel ordre

Un mot de trente-deux bits occupe quatre octets consécutifs. Reste une question que rien n'impose : lequel des quatre est posé en premier. Les deux réponses existent dans l'industrie, et RISC-V, comme x86, choisit de commencer par l'octet de poids faible. Cette convention s'appelle le petit-boutisme.

RISC-V RV32Ipas 0 sur 6pc = 0x0

programme

  1. 0x0la t0, nlui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8lb t1, 0(t0)
  4. 0xclb t2, 1(t0)
  5. 0x10lb t3, 2(t0)
  6. 0x14lb t4, 3(t0)

registres

sp0x2000
t00
t10
t20
t30
t40

mémoire

0x1000n16909060

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

Avancer pas à pas et relever les quatre valeurs lues, dans l'ordre : t1 reçoit 4, t2 reçoit 3, t3 reçoit 2, t4 reçoit 1. Vérifier au passage les quatre adresses de la ligne d'accès mémoire, 0x1000 à 0x1003.

Le mot écrit 0x01020304 se lit comme n'importe quel nombre : 01 est son octet de poids fort, 04 son octet de poids faible. En mémoire, l'ordre est inversé, l'octet 04 occupant l'adresse 0x1000 et l'octet 01 l'adresse 0x1003. La figure le montre sans commentaire possible : quatre valeurs, 4 puis 3 puis 2 puis 1. Ce détail devient central dès qu'un programme lit autre chose que sa propre mémoire.

RISC-V RV32Ipas 0 sur 6pc = 0x0

programme

  1. 0x0la t0, tramelui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8lbu t1, 0(t0)
  4. 0xclbu t2, 1(t0)
  5. 0x10lhu t3, 0(t0)
  6. 0x14lw t4, 0(t0)

registres

sp0x2000
t00
t10
t20
t30
t40

mémoire

0x1000trame168496141

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

Lire les quatre valeurs obtenues à la fin, 13, 12, 3085 et 168496141, et vérifier que les trois premières se retrouvent dans l'écriture hexadécimale de la quatrième, affichée dans la zone mémoire.

Trois accès à la même adresse 0x1000, trois résultats différents, parce que trois largeurs différentes. L'octet vaut 13, soit 0x0d. Les deux octets valent 3085, soit 0x0c0d : l'octet d'adresse 0x1001, celui qui vaut 0c, est devenu le poids fort. Le mot entier vaut 168 496 141, soit 0x0a0b0c0d.

C'est très exactement le problème que rencontre quiconque lit une trame réseau. Les protocoles de l'Internet rangent leurs champs dans l'ordre inverse, l'octet de poids fort en premier, et une documentation de protocole précise toujours lequel des deux ordres elle emploie. Un champ de deux octets lu naïvement dans un mot reçu du réseau donne une valeur qui n'a aucun sens, et ce genre d'erreur ne se voit pas : le programme ne plante pas, il calcule faux.

L'ordre des octets n'est visible qu'en lisant octet par octet

Tant qu'un programme écrit un mot et le relit en entier, la convention est invisible : elle s'annule. Elle apparaît dès qu'un accès étroit se pose sur une donnée large, ce qui est précisément ce que fait tout code qui décode un format, une trame ou un fichier binaire. Lire un mot en quatre lb est le geste qui révèle la convention, et le seul.

Écrire moins qu'un mot

Le rangement offre les mêmes largeurs que le chargement, avec une question de moins : il n'y a rien à interpréter, donc pas de variante signée. sw écrit les quatre octets d'un registre, sh ses deux octets de poids faible, et sb son seul octet de poids faible. L'écriture ne touche que les octets concernés, et laisse les voisins intacts.

RISC-V RV32Ipas 0 sur 5pc = 0x0

programme

  1. 0x0la t0, nlui t0, 1
  2. 0x4addi zero, zero, 0
  3. 0x8li t1, 0xFFaddi t1, zero, 255
  4. 0xcsb t1, 1(t0)
  5. 0x10lw t2, 0(t0)

registres

sp0x2000
t00
t10
t20

mémoire

0x1000n16909060

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

Regarder la ligne d'accès mémoire au quatrième pas : une seule adresse est touchée, 0x1001. Relever ensuite t2, qui vaut 16973572, et le comparer à la valeur de départ.

La valeur de départ vaut 0x01020304, la valeur finale 16 973 572, soit 0x0102ff04. Le deuxième octet en partant de la droite a changé, passant de 03 à ff, et les trois autres sont intacts. C'est ce que fait tout pilote qui modifie un champ d'un registre matériel sans perturber les champs voisins, et c'est le sujet des deux chapitres qui suivent.

Vérification rapideon peut se reprendre

1.Que contient t0 après la t0, mesure ?

2.Dans sw t1, 0(t0), quel est le rôle de t1 ?

3.Un mot vaut 0x01020304. Que lit lb à la première de ses quatre adresses ?

4.Le même octet est lu par lb puis par lbu, à la même adresse. Pourquoi les deux registres diffèrent-ils ?

5.Pourquoi lw refuse-t-il l'adresse 0x1001 ?

6.Pourquoi un compilateur cherche-t-il à garder les valeurs dans les registres ?

7.Combien de chiffres hexadécimaux faut-il pour écrire une adresse de trente-deux bits ?

La méthode

  1. Poser la donnée avec une ligne données:, en lui donnant un nom, et laisser la figure afficher l'adresse obtenue plutôt que de la supposer.
  2. Charger l'adresse d'abord, avec la, dans un registre qui lui sera réservé, et ne plus le modifier tant que la donnée sert.
  3. Lire à travers ce registre avec 0(registre), puis employer le décalage pour atteindre les éléments voisins sans recalculer l'adresse.
  4. Choisir la largeur avant de choisir le signe : d'abord w, h ou b selon la taille réelle de la donnée, puis la variante non signée pour un code ou un octet de trame, et la variante signée pour une mesure qui peut être négative.
  5. Vérifier l'ordre des octets dès qu'un accès étroit se pose sur une donnée large, en lisant les octets un par un et en comparant à l'écriture hexadécimale affichée.
  6. Contrôler l'alignement avant tout lw dont l'adresse a été calculée, et lire le message d'erreur jusqu'au bout : il donne l'adresse fautive, donc le calcul à reprendre.
  7. Compter les accès mémoire d'un morceau de code, et chercher si une valeur relue plusieurs fois ne pourrait pas rester dans un registre.

Synthèse

  • La mémoire est un tableau d'octets numérotés, et une adresse est ce numéro. Le processeur ne calcule que sur les registres : la mémoire ne se lit et ne s'écrit que par des instructions de transport, lw et sw en tête.
  • Le geste enseigné tient en deux temps : la pour charger l'adresse, puis 0(registre) pour lire ou écrire à travers elle. La forme lw t1, mesure est refusée, parce qu'elle masque ce qui coûte.
  • Une adresse s'écrit en hexadécimal, où un chiffre vaut quatre bits : un octet occupe toujours deux chiffres, une adresse de trente-deux bits toujours huit. La base est une notation, jamais une propriété de la valeur.
  • Les largeurs d'accès se choisissent : lw pour quatre octets, lh pour deux, lb pour un. Les variantes lbu et lhu complètent par des zéros, lb et lh recopient le bit de poids fort ; sur 0x000000F0, l'écart va de -16 à 240.
  • RISC-V est petit-boutiste : l'octet de poids faible occupe l'adresse la plus basse. Un mot 0x01020304 se relit en quatre lb comme 4, 3, 2 et 1, et c'est ce que rencontre toute lecture de trame ou de format binaire.
  • Un mot se lit à une adresse multiple de quatre, faute de quoi l'exécution s'arrête avec un message qui nomme l'adresse fautive. La contrainte vient du câblage de la mémoire, pas d'un choix de langage.
  • Trente-deux registres imposent l'aller-retour : les valeurs descendent en mémoire dès qu'elles ne servent plus, et remontent quand elles resservent. Chaque aller-retour coûte, et c'est ce que cherche à éviter tout code rapide.

Reste une question laissée de côté : à quoi sert d'écrire dans une case mémoire dont personne ne relira jamais le contenu. Agir sur le monde montre que certaines cases sont surveillées par du matériel, et qu'un sw bien placé allume une lampe.