Aller au contenu principal

Le processeur et l'assembleur

Ce que ce chapitre apporte 8 points
  • Expliquer ce qu'est une instruction, et pourquoi un processeur ne sait rien faire d'autre.
  • Dire ce qu'est un jeu d'instructions, et pourquoi sa liste est fixe et publiée.
  • Nommer les trois pièces qui composent l'état d'un processeur : les registres, la mémoire, le compteur ordinal.
  • Lire une instruction RISC-V et dire quel registre elle modifie, à partir de quelles sources.
  • Suivre l'exécution pas à pas et prévoir la valeur d'un registre avant de la vérifier.
  • Expliquer pourquoi l'ordre des instructions change le résultat.
  • Distinguer une instruction réellement exécutée d'un raccourci d'écriture proposé par l'assembleur.
  • Situer l'assembleur dans un métier d'ingénieur : ce qu'il sert à lire, et ce qu'il ne sert plus à écrire.

Un langage de haut niveau est fait de promesses. total = total + 1 promet une addition, afficher(x) promet un affichage, t[3] promet un accès au quatrième élément. Ces promesses tiennent presque toujours. Presque toujours : un jour, un programme se comporte d'une façon que son texte n'explique pas, et il faut regarder ce qui se passe réellement. L'assembleur est le langage de ce moment-là. Il ne promet rien : il dit qu'il ajoute le contenu d'un registre à celui d'un autre, et range le résultat dans un troisième. Ce chapitre dit de quoi ce langage est fait, installe les trois pièces dont une machine a besoin, et les montre bouger.

Le processeur ne comprend que des instructions

Cette phrase, que tous les cours du domaine répètent, passe pour une formule. Elle est littérale.

Un processeur est un circuit. Il reçoit un nombre de trente-deux bits et, selon la disposition de ces bits, laisse passer les valeurs vers son additionneur, vers son comparateur, ou vers la sortie qui parle à la mémoire. Ce nombre s'appelle une instruction. Rien à l'intérieur ne ressemble à un mot ou à un nom de variable : rien qui puisse être interprété, deviné, ou compris au sens courant du terme.

add t0, a0, a1 n'existe donc pas dans la machine : c'est la transcription lisible du nombre qui, lui, existe. Le nom add s'appelle un mnémonique, et l'assembleur traduit ces mnémoniques en nombres, presque toujours un pour un.

Un mot mérite d'être écarté : ce ne sont pas des commandes. Une commande se tape dans un terminal et peut échouer en expliquant pourquoi ; une instruction s'adresse à un circuit et ne discute pas.

La conséquence est vertigineuse : tout ce qu'une machine exécute, un interpréteur Python, un navigateur, un système d'exploitation, est une suite d'instructions choisies dans une liste très courte.

Trois pièces, et rien d'autre

Un processeur ne contient ni variables, ni fonctions, ni types. Son état complet tient dans trois choses.

Les registres sont des cases de calcul internes, en nombre fixe et très petit : trente-deux en RISC-V, chacune contenant un nombre de trente-deux bits. Ce sont les seules cases sur lesquelles le processeur sait calculer directement. Elles portent des noms courts, t0, a0, sp, qui sont des étiquettes sur des cases, pas des noms de variables.

La mémoire est un très grand tableau d'octets numérotés, dont le numéro s'appelle une adresse. Le processeur ne sait pas calculer dessus : il sait seulement en recopier un morceau dans un registre, ou l'inverse. Registres et mémoire s'occupe de ce va-et-vient.

Le compteur ordinal est un registre à part, qui contient l'adresse de la prochaine instruction à exécuter. C'est lui, et lui seul, qui décide de ce qui se passe ensuite. Il porte souvent son nom anglais, program counter, abrégé pc.

Exécuter un programme consiste à répéter le même geste : lire l'instruction que le compteur ordinal désigne, la faire, avancer le compteur ordinal.

La première instruction

Voici l'instruction la plus simple du jeu : elle prend le contenu de deux registres, les additionne, et range le résultat dans un troisième.

RISC-V RV32Ipas 0 sur 1pc = 0x0

programme

  1. 0x0add t0, a0, a1

registres

RegistreValeurValeur précédente
t00
a03
a14

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

Cliquer une fois sur « Exécuter une instruction » et regarder la ligne t0 du tableau des registres : elle passe de 0 à 7, et la figure signale la valeur qu'elle avait avant.

L'ordre des trois noms se lit toujours de la même façon en RISC-V : la destination d'abord, les sources ensuite. add t0, a0, a1 se lit donc « ranger dans t0 la somme de a0 et de a1 ». Cette régularité vaut pour presque tout le jeu, et c'est une des raisons pour lesquelles ce cours l'a choisi.

La ligne registres: en tête n'est pas une instruction : elle décrit la machine avant le départ. Sans elle, tous les registres valent zéro.

Instruction

Une instruction est un ordre élémentaire que le matériel sait exécuter en un temps fixe. Elle nomme au plus un registre destination et au plus deux sources, et ne fait qu'une chose. Aucune ne calcule une moyenne, ne trie un tableau ni n'affiche un texte : chacune de ces opérations est une suite d'instructions.

Une liste fixe, et publiée

Un langage de programmation s'étend sans cesse : une fonction définie, une bibliothèque importée, et le vocabulaire disponible a grandi. Un jeu d'instructions ne fonctionne pas ainsi. La liste est close. RISC-V de base en compte une quarantaine, décidées une fois et gravées dans le circuit : en ajouter une ne s'écrit pas, cela se fabrique.

Fixe au sens le plus matériel : le décodeur est câblé pour reconnaître ces motifs de bits et aucun autre. Publiée, ensuite : la spécification de RISC-V est un document public, ce qui permet d'écrire un compilateur ou un débogueur pour cette machine, puis d'exécuter le résultat sur la puce d'un autre fabricant.

Plusieurs de ces listes coexistent : x86-64 sur les serveurs, ARM sur les téléphones, RISC-V dans l'embarqué. Un programme compilé pour l'une ne s'exécute pas sur l'autre, et c'est ce que veut dire « un exécutable pour telle machine ».

Jeu d'instructions

Le jeu d'instructions d'un processeur est la liste complète des ordres qu'il sait exécuter, avec pour chacun la disposition exacte de ses bits et l'effet exact qu'il produit. C'est un contrat : le fabricant de la puce s'engage à exécuter cette liste, celui qui écrit les programmes à n'écrire que dedans.

La conséquence pratique est immédiate : quand un calcul ne figure pas dans la liste, il faut le construire avec ce qui s'y trouve. RISC-V de base n'a ni multiplication ni division : doubler une valeur se fait en l'ajoutant à elle-même, et multiplier deux valeurs quelconques demande une boucle. Le chapitre suivant donne la liste entière, famille par famille.

Le compteur ordinal avance de quatre en quatre

Chaque instruction occupe quatre octets en mémoire. La première du programme est donc à l'adresse 0x0, la deuxième à 0x4, la troisième à 0x8. L'écriture 0x annonce un nombre en hexadécimal, une notation que le chapitre sur le binaire et l'hexadécimal explique ; il suffit ici de savoir que 0x8 vaut huit.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0li t0, 10addi t0, zero, 10
  2. 0x4addi t0, t0, 5
  3. 0x8slli t0, t0, 1

registres

RegistreValeurValeur précédente
t00

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

Exécuter les trois instructions une par une en regardant l'en-tête de la figure : le compteur ordinal passe de 0x0 à 0x4, puis à 0x8. Reculer ensuite d'un pas pour retrouver l'état précédent.

Le registre t0 prend successivement les valeurs 10, 15, puis 30. Trois instructions, trois états : addi t0, t0, 5 ajoute cinq à ce que t0 contient déjà, et slli t0, t0, 1 décale son contenu d'un bit vers la gauche, ce qui revient à le doubler.

Le bouton qui recule d'un pas mérite d'être essayé tout de suite : il rend accessible l'instant précis où une valeur a changé, sans relancer le programme depuis le début.

Vérification rapideon peut se reprendre

Que contient le compteur ordinal ?

L'ordre est le programme

Les trois mêmes instructions, dans un autre ordre, ne donnent pas le même résultat.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0li t0, 10addi t0, zero, 10
  2. 0x4slli t0, t0, 1
  3. 0x8addi t0, t0, 5

registres

RegistreValeurValeur précédente
t00

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

Comparer avec la figure précédente, qui contient exactement les mêmes trois instructions. Aller à la fin des deux et relever les deux valeurs de t0 : 30 dans un cas, 25 dans l'autre.

Dix, doublé, plus cinq : vingt-cinq. Dix, plus cinq, doublé : trente. L'écart n'a rien de surprenant, mais il dit l'essentiel sur la machine : un processeur n'a aucune notion de priorité des opérations. Pas de parenthèses, pas d'expression. Une suite de gestes, et le résultat est ce que cette suite produit.

Traduire une formule vers l'assembleur consiste donc à choisir un ordre, et à trouver où ranger les résultats intermédiaires.

Un programme est une trajectoire, pas une description. Un langage de haut niveau décrit un résultat et laisse le compilateur choisir le chemin. L'assembleur est le chemin : deux programmes qui finissent sur le même registre peuvent passer par des états entièrement différents.

Vérification rapideon peut se reprendre

Dans sub t2, t0, t1, quel registre reçoit le résultat ?

Le registre qui ne change jamais

Parmi les trente-deux registres, le premier est particulier. Il s'appelle zero, et il contient zéro pour toujours : le matériel l'a câblé ainsi, et les instructions qui prétendent l'écrire sont exécutées normalement, leur résultat jeté.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0li t0, 7addi t0, zero, 7
  2. 0x4addi zero, t0, 100
  3. 0x8add t1, zero, zero

registres

RegistreValeurValeur précédente
t00
t10

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

Exécuter les trois instructions. La deuxième range 107 dans zero, du moins en apparence : la figure ne signale aucun changement, et la troisième instruction en tire bien 0.

Une case qui ne retient rien peut sembler un gâchis. C'est en réalité l'astuce la plus rentable du jeu, parce qu'elle évite d'inventer des instructions : copier un registre n'a pas d'instruction propre, c'est une addition avec zero, et poser une constante non plus. Le chapitre Les branchements conditionnels en donnera d'autres usages, autour des comparaisons.

Ce que l'assembleur écrit à la place de ce qui est écrit

Dans les figures précédentes, li t0, 10 apparaît, mais la colonne de droite affiche addi t0, zero, 10. Les deux lignes ne se contredisent pas : li n'existe pas dans le matériel. C'est un raccourci d'écriture, une pseudo-instruction, que l'assembleur remplace par une instruction réelle au moment de produire le programme. Le remplacement n'est pas toujours aussi direct.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0li t0, 5addi t0, zero, 5
  2. 0x4li t1, 100000lui t1, 24
  3. 0x8addi t1, t1, 1696

registres

RegistreValeurValeur précédente
t00
t10

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

Les deux lignes se ressemblent, et la colonne de droite montre qu'elles ne produisent pas la même chose : une instruction pour la première, deux pour la seconde. Avancer jusqu'à la fin et regarder t1 passer par une valeur intermédiaire.

La raison tient à une contrainte de format. Une instruction occupe trente-deux bits, qui doivent loger l'opération, les registres et la constante : il ne reste que douze bits pour cette dernière, soit -2048 à 2047. Cent mille n'y tient pas, et l'assembleur fabrique donc deux instructions, la figure montrant t1 passer par 98 304 avant d'atteindre 100 000.

Une ligne écrite n'est pas toujours une instruction exécutée

Compter les lignes ne donne pas le nombre d'instructions, et une étiquette placée après une pseudo-instruction ne désigne pas forcément l'adresse attendue. Les figures affichent les deux colonnes, la ligne écrite et l'instruction réelle, et c'est le même écart qui explique qu'un débogueur s'arrête parfois « au milieu » d'une ligne de code.

Les pseudo-instructions sont une vingtaine, et le chapitre suivant en donne la table complète : il n'y a aucune raison de les retenir.

Vérification rapideon peut se reprendre

1.Que se passe-t-il quand une instruction range une valeur dans zero ?

2.Pourquoi li t1, 100000 produit-il deux instructions alors que li t0, 5 n'en produit qu'une ?

Traduire un petit calcul

Soit à calculer, à partir d'une mesure rangée dans a0 et d'une correction rangée dans a1, la valeur (a0 - a1) × 2 - 2. Faute de multiplication, doubler un nombre revient à l'ajouter à lui-même.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0sub t0, a0, a1
  2. 0x4add t1, t0, t0
  3. 0x8addi t2, t1, -2

registres

RegistreValeurValeur précédente
t00
t10
t20
a025
a14

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

Suivre les trois résultats intermédiaires : t0 reçoit l'écart, t1 son double, t2 le résultat final. Modifier ensuite mentalement l'ordre des deux dernières instructions et prévoir ce qui se passerait.

La première ligne emploie sub, la soustraction, qui se lit comme add : la destination d'abord, puis les deux sources dans l'ordre. sub t0, a0, a1 range donc dans t0 la valeur de a0 moins celle de a1, et cet ordre, indifférent pour une addition, décide ici du signe : sub t0, a1, a0 aurait rangé -21.

Vingt-cinq moins quatre donne vingt et un, doublé quarante-deux, moins deux quarante. Trois registres pour trois étapes : c'est la forme la plus lisible, et pas la plus économe, puisque chaque étape n'a besoin que de la précédente et qu'un seul registre suffirait.

Noter aussi ce qui n'apparaît nulle part : le fait que a0 soit une mesure et a1 une correction. La machine voit deux nombres, et tout le sens est dans la tête de qui écrit, ou dans les commentaires.

La machine ne connaît aucun type

Un registre ne contient pas « un entier », « un caractère » ou « une adresse » : il contient trente-deux bits, et c'est l'instruction choisie qui décide de les lire comme un nombre signé, un motif de bits ou une adresse. Cette absence de type est la source d'une grande partie des bugs que les langages de haut niveau rendent impossibles, et le chapitre Les branchements conditionnels la reprend en détail.

Vérification rapideon peut se reprendre

Deux programmes contiennent exactement les mêmes trois instructions, dans un ordre différent. Que peut-on dire de leurs résultats ?

Ce que l'assembleur sert à faire, et ce qu'il ne sert plus à faire

Autant le dire : un ingénieur qui lit ce module n'écrira probablement jamais un programme en assembleur. Ce travail a été confié aux compilateurs il y a plusieurs décennies, et ils le font mieux : choisir quel résultat garder dans quel registre, ordonner les instructions pour que le processeur ne s'arrête pas à attendre, sont des tâches de comptabilité sur des milliers de lignes, où une machine bat un humain sans discussion.

Ce qui reste ne disparaît pas.

Lire. Un rapport de plantage, un débogueur arrêté dans une bibliothèque sans code source, un profileur qui désigne une ligne coûteuse : tous affichent des instructions. Savoir les lire transforme un écran incompréhensible en une phrase simple : « un chargement à une adresse qui vaut zéro ».

Vérifier. Deux écritures équivalentes dans un langage de haut niveau produisent parfois trois instructions dans un cas et trente dans l'autre, et seul le compilateur le dit.

Écrire quelques lignes, là où rien d'autre ne marche : le démarrage d'une machine, un gestionnaire d'interruption, un pilote qui parle à un composant à l'octet près. Quelques dizaines de lignes dans des systèmes qui en comptent des millions, mais quelqu'un les écrit.

Lire plutôt qu'écrire. Le bénéfice de ce module n'est pas une compétence de rédaction, c'est un modèle : un rez-de-chaussée où rien n'est caché, auquel il devient possible de descendre quand l'étage au-dessus cesse d'être compréhensible. Les bugs qui semblent relever de la magie ont presque toujours, ici, une explication ennuyeuse.

Vérification rapideon peut se reprendre

1.Qu'est-ce qu'un jeu d'instructions ?

2.Un registre contient la valeur 65. S'agit-il du nombre soixante-cinq ou du caractère A ?

Mettre en pratique

Le programme suivant contient une instruction sans effet. La trouver avant de lancer la figure.

RISC-V RV32Ipas 0 sur 3pc = 0x0

programme

  1. 0x0add t0, a0, a0
  2. 0x4addi zero, t0, 1
  3. 0x8addi t1, t0, 4

registres

RegistreValeurValeur précédente
t00
t10
a06

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

Exécuter pas à pas et repérer la seule instruction que la figure exécute sans rien signaler comme modifié. Elle est parfaitement valide, et parfaitement inutile.
La réponse

addi zero, t0, 1 calcule bien treize et jette le résultat, puisque sa destination est zero. Les deux autres donnent t0 à douze et t1 à seize. Une instruction sans effet n'est pas toujours une erreur : nop est exactement cela, addi zero, zero, 0.

Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?

Dans la bibliothèque d'exercices