Un processeur ne comprend que des nombres binaires : chaque instruction est un mot de 0 et de 1. Un langage assembleur donne à chacun de ces mots un nom lisible, add a0, a1, a2 plutôt que 00c58533, sans rien cacher de la machine : registres, adresses mémoire, pile, sauts. Cinq planches pour voir comment une instruction est codée, comment fonctionnent la pile et les appels de sous-programmes, ce que produit un compilateur, comment le processeur enchaîne les instructions, puis pour exécuter de vrais programmes en assembleur RISC-V.
Les premiers ordinateurs se programmaient directement en binaire ou en octal, en consultant la table des codes de chaque opération. Dès 1947, Kathleen Booth décrit pour l'ordinateur ARC de Londres une notation abrégée des instructions ; en 1949, à Cambridge, l'EDSAC de Maurice Wilkes charge ses programmes grâce aux « ordres initiaux » de David Wheeler, qui traduisent une lettre (A pour ajouter, S pour soustraire…) en code machine. Le programme traducteur prend le nom d'assembleur. Chaque famille de processeurs a son propre jeu d'instructions, donc son propre assembleur : celui des ordinateurs centraux IBM, du 6502 des premiers micro-ordinateurs, du x86 des PC, de l'ARM des téléphones, ou du RISC-V, un jeu d'instructions libre né en 2010 à l'université de Berkeley, que cette page utilise parce qu'il est simple et régulier.
En RISC-V, chaque instruction occupe exactement 32 bits. Les 7 bits de droite, l'opcode, indiquent la famille d'instruction ; les autres champs désignent les registres utilisés (32 registres de 32 bits, numérotés de 0 à 31 et surnommés zero, ra, sp, a0…) et une éventuelle valeur immédiate. Il n'y a que six formats (R, I, S, B, U, J), et les numéros de registres sont toujours au même endroit : le décodage par le processeur en est simplifié. Écrivez une instruction, l'assembleur la traduit.
add, lw, beq) remplacent les codes. Les étiquettes (boucle:) remplacent les adresses, que l'assembleur calcule lui-même. Les pseudo-instructions sont des raccourcis : li a0, 100000 devient deux instructions réelles (lui puis addi), car une instruction de 32 bits ne peut pas contenir un nombre de 32 bits ; mv, ret ou j sont des cas particuliers de addi, jalr et jal. Essayez li a0, 100000, puis la a1, message.Un sous-programme est un morceau de code que l'on appelle depuis plusieurs endroits : call fact range l'adresse de retour dans le registre ra et saute à fact ; ret revient à l'adresse rangée dans ra. Mais si fact s'appelle elle-même, chaque appel écraserait ra et la valeur de n. La solution, imaginée dès les années 1950, est la pile : une zone de mémoire où chaque appel ouvre un cadre en diminuant le pointeur de pile sp, y sauve ce qu'il doit conserver, puis le referme avant de revenir. Suivez le calcul de 4! = 4 × 3 × 2 × 1.
fact ajoute un cadre de 16 octets contenant n et l'adresse de retour.addi sp, sp, -16 réserve 16 octets, addi sp, sp, 16 les libère. Pendant le calcul de 4!, quatre cadres s'empilent, puis se dépilent dans l'ordre inverse : c'est la récursivité vue de l'intérieur. Les registres obéissent à une convention d'appel commune à tous les programmes RISC-V : arguments et résultat dans a0 à a7, registres t modifiables librement, registres s à restaurer avant de revenir. Grâce à cette convention, un sous-programme écrit en assembleur peut être appelé depuis du C, et inversement. Une pile trop profonde déborde : c'est le célèbre stack overflow.Depuis Fortran (1957), on écrit surtout dans des langages de haut niveau, que le compilateur traduit en assembleur, puis en code machine. Voici deux petites fonctions en langage C, et le code RISC-V réellement produit par le compilateur gcc 13, sans optimisation (-O0) ou en optimisant (-O2). Sans optimisation, chaque variable vit en mémoire, dans le cadre de pile : elle est relue et réécrite à chaque utilisation. En optimisant, gcc garde tout dans les registres et réorganise la boucle.
-O2 dans somme : au lieu de compter les tours de boucle, il compare directement l'adresse courante à l'adresse de fin du tableau, ce qui économise une instruction par tour.Un processeur exécute chaque instruction en plusieurs étapes : lire l'instruction (IF), la décoder et lire les registres (ID), calculer (EX), accéder à la mémoire (MEM), écrire le résultat (WB). Comme sur une chaîne de montage, il commence l'instruction suivante sans attendre la fin de la précédente : c'est le pipeline, idéalement une instruction terminée par cycle d'horloge. Mais si une instruction a besoin du résultat de la précédente, elle doit attendre : une bulle apparaît. Et quand un saut est pris, les instructions déjà lues après lui sont annulées.
lw), dont la valeur n'arrive qu'à l'étape MEM. Le programmeur en assembleur, ou le compilateur, peut alors réordonner les instructions pour glisser un travail utile dans la bulle. C'est un exemple de ce que l'on gagne en connaissant la machine. Les processeurs actuels vont beaucoup plus loin : des pipelines de 15 à 20 étapes, plusieurs instructions par cycle, l'exécution dans le désordre et la prédiction des sauts.Voici un assembleur et un simulateur RISC-V, écrits pour cette page, qui fonctionnent dans votre navigateur. Ils acceptent les instructions de base de RV32I, la multiplication et la division (extension M), les pseudo-instructions courantes (li, la, mv, call, ret, j, beqz, bgt…), les étiquettes et les directives de données (.word, .byte, .ascii, .asciz, .space, .align). Les programmes parlent au système d'exploitation comme sous Linux : ecall avec a7 = 64 pour écrire, a7 = 93 pour s'arrêter. Chaque exemple a aussi été assemblé par GNU as et exécuté par l'émulateur QEMU : les sorties et le nombre d'instructions exécutées sont comparés.
affiche_entier) qui extrait les chiffres un à un par divisions par 10, les convertit en codes de caractères et les range dans la pile. Le processeur ne vérifie rien non plus : dans l'exemple 6, la suite de Fibonacci dépasse 2 147 483 647, le plus grand entier sur 32 bits, et devient négative sans aucun message. Le mode « pas à pas » montre l'effet de chaque instruction sur les registres. Essayez de modifier les exemples : changez le texte de l'exemple 1 (sans oublier sa longueur dans a2), ou les valeurs du tableau de l'exemple 4.Les premières instructions exécutées à la mise sous tension, le passage d'un programme à l'autre dans un système d'exploitation, la gestion des interruptions : ces quelques centaines de lignes de Linux, Windows ou macOS restent écrites en assembleur, car le langage C ne peut pas manipuler directement certains registres du processeur.
Dans les microcontrôleurs des appareils ménagers, des voitures ou des capteurs, chaque octet de mémoire et chaque microseconde comptent. L'assembleur y sert pour les routines critiques ; l'ordinateur de bord d'Apollo, en 1969, était programmé en assembleur.
Les décodeurs vidéo (comme dav1d pour le format AV1), les bibliothèques de cryptographie ou de calcul utilisent des routines écrites à la main en assembleur pour exploiter les instructions vectorielles (SIMD), qui traitent 8, 16 ou 32 nombres à la fois.
Quand on ne dispose que du programme exécutable, on le lit en assembleur grâce à un désassembleur : pour corriger un bogue, vérifier ce que fait un logiciel inconnu, analyser un logiciel malveillant ou comprendre une faille.
Tout compilateur produit, au bout de la chaîne, de l'assembleur ou du code machine. WebAssembly (2017), malgré son nom, est un code machine pour une machine virtuelle, exécuté par les navigateurs presque aussi vite que du code natif.
Les jeux des consoles 8 bits, comme la NES et son processeur 6502, étaient écrits en assembleur, tout comme le jeu RollerCoaster Tycoon (1999) de Chris Sawyer. Des passionnés écrivent encore des démos et des jeux pour ces machines.
Les programmes de la page ont été assemblés par GNU as 2.42 et exécutés par l'émulateur QEMU 8.2 (processeur RISC-V 32 bits) au moment de la fabrication de la page ; le code de la planche 3 a été produit par gcc 13 ; les cycles de la planche 4 ont été recalculés en Python à partir de la suite d'instructions relevée par QEMU. Votre navigateur refait tous ces calculs et les compare. Le pipeline de la planche 4 est le modèle classique à cinq étapes des manuels, pas celui d'un processeur commercial précis.