Jeux d’instructions et langage d’assemblage

Découvre comment les logiciels communiquent avec le matériel par l’intermédiaire d’un jeu d’instructions. Avec RISC-V, lis et écris du langage d’assemblage en utilisant les registres, les opérandes en mémoire, les constantes, les opérations arithmétiques et logiques et les branchements. Traduis en instructions les expressions, les structures conditionnelles, les boucles, les pointeurs et les chaînes du C. Code les instructions en code machine sur 32 bits et décode-les, construis des appels de procédure avec la pile et suis le parcours d’un programme à travers la compilation, l’assemblage, l’édition de liens et le chargement. Enfin, compare RISC-V aux jeux d’instructions complexes comme x86 et évalue les conséquences de la conception d’un jeu d’instructions sur la performance.

IntermédiaireUnité 2 sur 6 de la formation Formation : Architecture informatique : processeur, mémoire et RISC-V

Ce que tu vas apprendre

  1. Explique ce qu’est un jeu d’instructions, lis du langage d’assemblage RISC-V et utilise les registres, la mémoire et les constantes comme opérandes des instructions.
    • Explique ce qu’est une architecture du jeu d’instructions
      Décris le jeu d’instructions comme un contrat entre le logiciel et le matériel, le principe du programme enregistré et les liens entre langage d’assemblage, code machine et jeu de base RISC-V.
    • Utilise les registres pour évaluer des expressions
      Nomme les 32 registres entiers RISC-V et leurs rôles conventionnels, et traduis des expressions arithmétiques en séquences d’instructions sur registres.
    • Charge et range des données en mémoire
      Utilise les adresses d’octets, les tailles de données, l’alignement et l’ordre des octets pour transférer des mots, des demi-mots et des octets entre la mémoire et les registres, y compris des éléments de tableau.
    • Manipule des constantes et des valeurs immédiates
      Utilise des opérandes immédiats pour les petites constantes et construis des constantes et des adresses complètes sur 32 bits avec lui, auipc, addi et les pseudo-instructions li et la.
  2. Utilise les instructions arithmétiques, logiques, de décalage et de comparaison, et traduis les structures conditionnelles et les boucles du C en branchements et en sauts.
    • Utilise les instructions arithmétiques, logiques et de décalage
      Effectue des calculs avec l’addition, la soustraction, les opérations logiques bit à bit, les décalages et la mise à un si inférieur, en tenant compte des dépassements de capacité, des interprétations signée et non signée, des masques et de la multiplication ou de la division par des puissances de 2.
    • Effectue des branchements et des sauts avec des instructions conditionnelles et inconditionnelles
      Utilise les instructions de comparaison et de branchement, les conditions signées et non signées, les sauts et leurs pseudo-instructions pour changer l’instruction exécutée ensuite.
    • Traduis les structures conditionnelles et les boucles du C en langage d’assemblage
      Transforme les structures if-else, les conditions composées, les boucles while et for et les instructions switch en branchements, puis suis le code obtenu avec des valeurs réelles.
  3. Passe des instructions d’assemblage au code machine sur 32 bits et inversement à l’aide des six formats d’instruction RISC-V, et explique les choix de conception de ces formats.
    • Code les instructions sur registres, les instructions à valeur immédiate et les instructions de rangement
      Dispose les champs des instructions de type R, I et S et convertis des instructions comme add, sub, addi, les décalages, lw et sw en binaire et en hexadécimal.
    • Code les branchements, les sauts et les valeurs immédiates hautes, et décode le code machine
      Code les instructions de type B, J et U avec des déplacements relatifs au compteur ordinal et des valeurs immédiates hautes, puis décode tout mot RV32I de 32 bits en langage d’assemblage.
  4. Suis la convention d’appel RISC-V pour transmettre des arguments, renvoyer des résultats et préserver les registres, utilise des cadres de pile pour les procédures imbriquées et récursives, et traduis les pointeurs, les tableaux et les chaînes du C.
    • Appelle des procédures et effectue les retours en respectant la convention d’appel
      Transmets les arguments et les résultats dans les registres, appelle avec jal et effectue un retour avec ret, puis détermine quels registres l’appelant ou l’appelé doit préserver.
    • Construis les cadres de pile pour les procédures imbriquées et récursives
      Alloue et libère des cadres de pile, sauvegarde l’adresse de retour et les registres lors des appels imbriqués et récursifs, et situe la pile dans l’organisation d’un programme en mémoire.
    • Traduis les pointeurs, les tableaux et les chaînes du C en langage d’assemblage
      Transforme les déréférencements et les calculs sur les pointeurs, les champs de structure, les tableaux passés en paramètre et les chaînes terminées par un caractère nul en chargements, rangements et calculs d’adresses.
  5. Explique comment un compilateur, un assembleur, un éditeur de liens et un chargeur transforment le code source C en programme exécuté en mémoire, en abordant les fichiers objets, le relogement et l’édition de liens dynamique.
    • Explique comment les compilateurs et les assembleurs produisent des fichiers objets
      Décris la hiérarchie de traduction, le rôle des compilateurs et des assembleurs, les directives d’assemblage et les symboles, ainsi que les parties d’un fichier objet.
    • Explique comment les éditeurs de liens et les chargeurs créent et démarrent un exécutable
      Décris comment un éditeur de liens place les modules et corrige les adresses, compare l’édition de liens statique et dynamique, et explique comment un chargeur et des appels système démarrent et terminent un programme.
  6. Distingue RISC-V des jeux d’instructions complexes comme x86 et évalue les compromis entre performance, taille du code et compatibilité dans la conception d’un jeu d’instructions.
    • Distingue les caractéristiques des architectures RISC et CISC
      Décris les caractéristiques qui définissent les jeux d’instructions RISC et CISC et compare RISC-V à x86 du point de vue des opérandes, du codage, des modes d’adressage, des registres et des codes de condition.
    • Évalue les compromis entre performance, taille du code et compatibilité dans la conception d’un jeu d’instructions
      Utilise l’équation de performance pour comparer les architectures, explique comment les processeurs modernes brouillent la distinction entre RISC et CISC, et évalue les idées fausses courantes sur les jeux d’instructions.

Jeux d’instructions et langage d’assemblage : cours

6 chapitres

1

Instructions, registres et mémoire

Un jeu d’instructions relie un programme à un processeur. Commence par lire la destination et les sources d’une instruction RISC-V, puis suis les valeurs dans les registres et la mémoire. Les adresses d’octets expliquent comment accéder aux tableaux et aux structures ; les champs immédiats expliquent pourquoi certaines constantes nécessitent plus d’une instruction. Distinguer les valeurs, les adresses et le contenu de la mémoire facilite le suivi de chaque ligne d’assemblage.

1.1 Le jeu d’instructions comme contrat logiciel

Un processeur exécute des instructions représentées par des bits. Une architecture du jeu d’instructions donne à ces bits un sens commun, afin que les logiciels puissent demander les mêmes opérations sur différentes implémentations.

Ce que définit l’architecture du jeu d’instructions

Elle spécifie les instructions, les registres visibles, les tailles des données, le comportement de la mémoire et les codages des instructions. Une instruction comme add a0, a1, a2 doit produire le résultat défini sur chaque processeur qui la prend en charge. Les circuits internes et l’ordonnancement relèvent de la microarchitecture. Deux puces peuvent différer fortement en vitesse et en consommation d’énergie tout en implémentant la même architecture du jeu d’instructions.

La compatibilité binaire exige aussi que l’environnement logiciel corresponde. Un programme qui appelle un service du système d’exploitation ou une bibliothèque dépend de plus que du sens des instructions. Les conventions relatives aux registres et l’organisation des données font partie de l’interface binaire d’application, ou ABI.

Du code lisible aux bits

Pour C, un compilateur sélectionne des instructions d’assemblage ; un assembleur les code en code machine. Le langage d’assemblage est un texte contenant des noms d’instructions, des registres, des commentaires et des étiquettes. Le code machine est la représentation binaire que le processeur va chercher. Une étiquette comme Start: désigne l’adresse d’une instruction ; ce n’est pas une instruction exécutée pendant le programme. Un commentaire commence par #.

Start:
    add a0, a1, a2   # a0 receives a1 + a2

Le premier registre est la destination. Si a1 contient 14 et a2 contient 5, l’instruction écrit 19 dans a0 ; elle laisse les sources inchangées. Les expressions plus complexes nécessitent plusieurs opérations. Additionner quatre valeurs de registre prend trois additions, les résultats intermédiaires étant conservés dans des registres.

La plupart des noms d’instructions correspondent à une instruction machine. Une pseudo-instruction est un raccourci de l’assembleur : mv demande une copie de registre et li demande une constante. Une pseudo-instruction peut se développer en une ou plusieurs instructions réelles : compter les lignes d’assemblage ne donne donc pas toujours la taille du code.

Instructions stockées en mémoire

Selon le principe du programme enregistré, les instructions et les données sont toutes deux représentées par des nombres en mémoire. Le compteur ordinal, pc, désigne l’instruction à aller chercher. Pour une séquence d’instructions de 32 bits, l’exécution ordinaire le fait avancer de 4 octets. Les branchements et les sauts choisissent une autre adresse pour l’instruction suivante. Le processeur exécute le codage, et non l’écriture en langage d’assemblage qui l’a produit à l’origine.

Bases et extensions RISC-V

RISC-V est une norme ouverte et libre de redevance d’architecture du jeu d’instructions, maintenue par RISC-V International. RV32I fournit 40 instructions de base pour les entiers et des registres entiers de 32 bits ; RV64I utilise des registres entiers de 64 bits. Les deux fournissent 32 registres entiers. Des extensions facultatives ajoutent des capacités : M fournit la multiplication et la division entières, A les opérations atomiques, F et D la virgule flottante, C les instructions compressées et V les vecteurs. Les exemples utilisent RV32I et font appel à M lorsque mul apparaît.

Le langage d’assemblage RISC-V est propre à RISC-V. Un programme C peut être recompilé pour x86 ou ARM, mais son code machine RISC-V ne peut pas s’exécuter directement selon ces architectures du jeu d’instructions. Un simulateur peut exécuter des instructions RISC-V sur un hôte doté d’une autre architecture du jeu d’instructions et montrer les changements des registres et de la mémoire pas à pas.

Points clés

  • L’architecture du jeu d’instructions définit le comportement visible ; la microarchitecture l’implémente.
  • Un assembleur code le texte d’assemblage ; les pseudo-instructions peuvent se développer en plusieurs instructions.
  • Avec des instructions de 32 bits, l’exécution en séquence fait avancer le compteur ordinal de 4 octets.
  • RISC-V combine une architecture du jeu d’instructions de base et des extensions facultatives.

1.2 Registres et résultats d’expressions

Les registres contiennent les valeurs qu’une instruction peut utiliser immédiatement. Dans RV32I, chaque registre entier contient 32 bits, et un nom de registre désigne un emplacement de stockage, non une variable C ou un type de données.

Noms et rôles des registres

Il y a 32 registres entiers, de x0 à x31. Le compteur ordinal est distinct. Cinq bits identifient un registre entier, car il existe 32 configurations possibles sur 5 bits. Un petit banc de registres limite aussi le coût d’accès et l’espace occupé par les champs de registre dans les instructions.

RegistresNoms de l’ABIRôle conventionnel
x0zeroDonne toujours 0 à la lecture
x1, x2ra, spAdresse de retour et pointeur de pile
x3, x4gp, tpPointeur global et pointeur de thread
x5–x7, x28–x31t0–t6Valeurs temporaires
x8–x9, x18–x27s0–s11Valeurs préservées lors des appels
x10–x17a0–a7Arguments ; a0–a1 renvoient aussi les résultats

Le registre s0 peut aussi servir de fp, le pointeur de cadre. Les écritures dans zero sont ignorées : il peut donc fournir un opérande nul sans charger d’abord une constante. Par exemple, add a0, a1, zero copie a1.

Ces noms expriment un accord logiciel. En particulier, le matériel ne préserve pas automatiquement un registre s. Une procédure qui le modifie doit prévoir de restaurer sa valeur initiale. L’interface binaire d’application standard réserve aussi gp et tp à leurs rôles dans l’environnement ; ce ne sont pas des registres temporaires ordinaires.

Décomposer une expression en opérations

Traduis f = (g + h) - (i + j); avec g, h, i, j dans s1 à s4 et le résultat dans s0 :

add t0, s1, s2
add t1, s3, s4
sub s0, t0, t1

Avec les valeurs 9, 4, 6 et 1, la première instruction laisse 13 dans t0. La deuxième laisse 7 dans t1. La soustraction écrit 6 dans s0. Chaque registre temporaire conserve un résultat jusqu’à ce que l’instruction suivante en ait besoin.

L’ordre des deux opérandes sources compte pour la soustraction : sub s0, t1, t0 donnerait −6. La destination vient toujours en premier : sub t0, s0, t1 écraserait donc un registre temporaire et lirait l’ancienne valeur de s0. Lors du suivi, note la destination après chaque ligne ; ne mets pas à jour les deux registres sources.

Les bits prennent leur sens à l’usage

Un registre ne porte aucune indication de valeur signée ou non signée. La configuration 0xFFFFFFFF peut représenter −1 en signé ou 4 294 967 295 en non signé. Une comparaison signée et une comparaison non signée peuvent donc donner des résultats différents pour les mêmes bits stockés. Les adresses sont aussi des configurations de bits dans les registres ; un chargement interprète sa base comme une adresse.

Conserver les valeurs aussi longtemps que nécessaire

Un compilateur attribue les valeurs fréquemment utilisées aux registres. S’il a besoin de plus de valeurs actives que les registres ne peuvent en contenir, il en stocke certaines en mémoire : c’est le débordement de registres. Il les recharge plus tard. Ce transfert entraîne un coût réel de rangement et de chargement.

Les appels créent une autre frontière. Une procédure appelée peut écraser t0 à t6 et a0 à a7. Conserve une valeur nécessaire après un appel dans un registre s correctement préservé, ou sauvegarde-la sur la pile avant l’appel. Choisir t0 simplement parce qu’il était libre ne garantit pas que sa valeur sera conservée.

Points clés

  • RV32I possède 32 registres entiers de 32 bits ; le compteur ordinal est distinct.
  • Les écritures dans zero sont ignorées et les lectures donnent toujours 0.
  • Les opérations arithmétiques sur registres écrivent dans le premier opérande et lisent les suivants.
  • Les résultats temporaires doivent rester disponibles jusqu’à leur dernière utilisation.
  • Les registres sauvegardés par l’appelant peuvent changer lors d’un appel de procédure.

1.3 Transférer des valeurs entre la mémoire et les registres

Les adresses mémoire désignent des octets. Les chargements transfèrent des données dans les registres et les rangements recopient des bits de registre en mémoire. Dans une architecture à chargement et rangement, les opérations arithmétiques ne lisent pas directement leurs opérandes en mémoire.

Tailles et déplacements en octets

Un octet occupe une adresse, un demi-mot deux et un mot de 32 bits quatre. RV32 possède un espace d’adressage sur 32 bits pouvant atteindre 4 GiB. Dans l’interface binaire d’application C ILP32, char occupe 1 octet, short 2, et int, long et les pointeurs 4.

Les instructions lw et sw transfèrent des mots. Les chargements de demi-mots sont lh et lhu, et les chargements d’octets sont lb et lbu. Les rangements utilisent sh et sb pour les tailles plus petites. Un rangement copie les bits de poids faible de sa source ; sb n’écrit que les 8 bits de poids faible.

Une adresse est la somme d’un registre de base et d’un déplacement signé en octets sur 12 bits :

lw t0, 32(s3)
sw t0, 48(s3)

Le chargement lit le mot à l’adresse contenue dans s3 augmentée de 32. Il n’ajoute pas 32 à la valeur chargée et ne modifie pas s3. Le rangement écrit cette valeur à l’adresse de base augmentée de 48. Le déplacement va de −2048 à 2047.

Traduire un accès à un tableau

Pour un tableau de int dont la base est dans s3, l’élément A[i] commence à l’adresse de base augmentée de 4 fois l’indice. Pour mettre en œuvre A[12] = h + A[8];, avec h dans s1 :

lw t0, 32(s3)      # A[8]
add t0, s1, t0
sw t0, 48(s3)      # A[12]

Si A[8] vaut 80 et h vaut 3, A[12] devient 83. Un déplacement de 8 sélectionnerait A[2], puisque l’adresse compte des octets. Pour un indice variable dans s4, forme d’abord l’adresse :

slli t1, s4, 2
add t1, s3, t1
lw t0, 0(t1)

Le facteur dépend de la taille de l’élément, pas du nom du processeur. Un pointeur ou un long RV64 LP64 occupe 8 octets et utilise ld/sd ; un int RV64 occupe toujours 4 octets.

Étendre une valeur chargée de petite taille

Un registre doit contenir 32 bits même lorsqu’un chargement n’en lit que 8. lb copie le bit de signe de l’octet dans chaque bit de poids supérieur ; lbu remplit les bits de poids supérieur avec des zéros. Ainsi, l’octet 0xF0 devient 0xFFFFFFF0 (−16) avec lb, mais 240 avec lbu. La même distinction s’applique à lh et lhu. Les octets non signés, y compris ceux des chaînes, utilisent lbu.

Lire l’ordre des octets

Dans une mémoire petit-boutiste, l’octet de poids faible occupe l’adresse la plus basse. Ranger 0x12345678 à 0x1000 place 0x78, 0x56, 0x34, 0x12 aux adresses de 0x1000 à 0x1003. Un chargement d’octet à la première adresse lit 0x78. L’ordre gros-boutiste place 0x12 en premier ; les systèmes RISC-V peuvent définir l’un ou l’autre ordre, tandis que les systèmes d’enseignement habituels sont petit-boutistes.

Une adresse naturellement alignée est un multiple de la taille de l’accès : 4 pour un mot et 2 pour un demi-mot. Les accès naturellement alignés sont pris en charge par l’architecture. Un accès non aligné peut fonctionner, prendre plus de temps ou provoquer une exception, selon l’environnement d’exécution. Place les mots à des adresses multiples de 4 et avance de 4 pour atteindre le mot suivant.

Points clés

  • Les déplacements des chargements et rangements comptent des octets, pas des éléments de tableau.
  • Calcule l’adresse d’un indice variable avant d’effectuer le chargement ou le rangement.
  • Les chargements signés effectuent une extension de signe ; les chargements non signés effectuent une extension par des zéros.
  • La mémoire petit-boutiste place l’octet de poids faible en premier.
  • L’alignement naturel dépend de la taille de l’accès.
  • RV64 LP64 augmente la taille des pointeurs et des valeurs long, tandis que int reste à 4 octets.

1.4 Faire tenir les constantes dans les champs d’instruction

Une valeur immédiate est une constante codée dans une instruction. Elle permet à une opération courante, comme l’incrémentation d’un compteur, d’éviter un chargement séparé.

Petites constantes

addi t0, t0, 1 ajoute 1 à t0. Sa valeur immédiate est signée, sur 12 bits en complément à deux, de −2048 à 2047. Le processeur effectue une extension de signe à 32 bits avant l’addition. Soustrais une petite constante avec une valeur immédiate négative, comme dans addi t0, t0, -5 ; RV32I ne possède pas d’instruction de soustraction à valeur immédiate.

Les chargements, jalr et les instructions arithmétiques et logiques ordinaires à valeur immédiate utilisent aussi des valeurs immédiates signées sur 12 bits. En particulier, andi, ori et xori n’étendent pas leurs constantes avec des zéros. Les instructions de décalage à valeur immédiate codent quant à elles un nombre de positions décalées allant de 0 à 31 en RV32.

Parmi les raccourcis utiles de l’assembleur :

TexteInstruction de base ou développement
mv rd, rsaddi rd, rs, 0
nopaddi x0, x0, 0
not rd, rsxori rd, rs, -1
neg rd, rssub rd, x0, rs
li rd, c, pour un petit caddi rd, x0, c

Après extension de signe, la constante −1 ne contient que des uns : c’est pourquoi xori avec −1 inverse chaque bit.

Construire les 32 bits

lui place un champ de 20 bits dans les bits 31 à 12 et met les 12 bits de poids faible à zéro. Pour 0x12345678 :

lui t0, 0x12345
addi t0, t0, 0x678

La valeur intermédiaire est 0x12345000 ; l’addition de 1 656 fournit la partie basse. Une partie basse dont le bit 11 est à un demande plus d’attention. Sa configuration sur 12 bits représente un terme négatif : augmente donc la partie haute avant de l’ajouter.

Pour 0x00001800, la configuration basse 0x800 signifie −2048. Utilise :

lui t0, 0x2
addi t0, t0, -2048

Le premier résultat est 0x2000. Soustraire 0x800 produit 0x1800. Utiliser le champ haut 0x1 produirait 0x800, soit un bloc de 4096 en dessous de la valeur voulue.

Pour toute constante sur 32 bits, interprète ses 12 bits de poids faible comme une valeur signée. Soustrais cette valeur signée de la constante voulue, divise par 4096 et conserve les 20 bits de poids faible pour lui. Pour 0xDEADBEEF, la partie basse signée est −273 : utilise donc le champ haut 0xDEADC, puis addi avec −273. Les assembleurs effectuent cette décomposition pour li.

Constantes, adresses et contenu

li t0, 100 place le nombre 100 dans un registre. lw t0, 100(zero) lit le contenu de la mémoire à l’adresse 100. Une adresse peut elle-même être chargée comme valeur : la t0, Item demande à l’assembleur l’adresse désignée par Item.

auipc ajoute une valeur immédiate haute décalée de 12 bits vers la gauche à l’adresse de cette instruction. Combinée à une opération à valeur immédiate ou à un chargement, elle permet un accès relatif au compteur ordinal sur environ ±2 GiB. Une instruction la locale utilise couramment auipc suivi de addi ; d’autres développements permettent l’accès aux bibliothèques partagées.

Une constante comme 5 000 ne peut pas être un opérande de addi. Charge-la dans un second registre avec li, puis utilise add. Une valeur plus grande ne bénéficie pas d’un champ immédiat plus large simplement parce qu’elle est écrite en hexadécimal.

Points clés

  • Une valeur immédiate signée sur 12 bits va de −2048 à 2047.
  • lui fournit les 20 bits de poids fort et met les 12 bits de poids faible à zéro.
  • Si la partie basse de 12 bits est négative, compense dans le champ haut.
  • li charge une valeur, la forme une adresse et lw lit la mémoire.
  • auipc construit une adresse relative à son propre compteur ordinal.
2

Calculs arithmétiques et contrôle du déroulement

Une fois les valeurs dans les registres, les instructions calculent des résultats et déterminent ce qui s’exécute ensuite. Les opérations logiques bit à bit et les décalages agissent sur les représentations ; les comparaisons signées et non signées donnent des sens différents à ces configurations. Les branchements et les sauts transforment les structures conditionnelles et les boucles du C en chemins explicites. Suivre un chemin consiste à suivre à la fois les valeurs courantes et l’adresse de l’instruction suivante, y compris le dernier test de boucle qui échoue.

2.1 Calcule avec des bits et des valeurs signées

Une instruction arithmétique lit les bits des registres et écrit une nouvelle configuration de bits. Pour comprendre cette configuration, il faut connaître l’opération et savoir si elle doit être interprétée comme signée ou non signée.

Addition, soustraction et dépassement de capacité

Dans RV32I, add, sub et addi conservent les 32 bits de poids faible du résultat. Ajouter 1 à 0x7FFFFFFF produit 0x80000000, qui représente −2 147 483 648 comme entier signé. L’instruction ne provoque aucune exception de dépassement de capacité.

Pour une addition non signée, le logiciel peut détecter un retour à zéro :

add t0, a0, a1
bltu t0, a0, Overflow

Une somme non signée qui a bouclé est inférieure à un opérande d’entrée. Ce test non signé n’est pas la règle de détection d’un dépassement en arithmétique signée, où les signes des entrées et du résultat comptent.

L’extension M fournit mul, mulh, mulhu, div, divu, rem et remu. mul conserve les bits de poids faible du produit ; les instructions donnant les bits de poids fort du produit distinguent les entrées signées et non signées. Une division par zéro renvoie un quotient composé uniquement de uns et le dividende initial comme reste, sans exception de division.

Sélectionner, mettre à un et inverser des bits

and, or et xor agissent indépendamment sur chaque bit. Un ET avec 1 conserve un bit ; un ET avec 0 le met à zéro. Un OU avec 1 met un bit à un, et un OU exclusif avec 1 l’inverse.

srli t0, a0, 4
andi t0, t0, 0xF

Avec a0 = 0xABCD, le décalage vers la droite donne 0xABC. L’application du masque 0xF laisse 0xC, les bits 7 à 4 d’origine. Le décalage place le champ en bas ; le masque supprime tout ce qui se trouve au-dessus. Pour mettre le bit 4 à un sans changer les autres, utilise ori a0, a0, 0x10. xori avec −1 inverse les 32 bits.

Choisir un décalage selon sa règle de remplissage

Un décalage vers la gauche, sll ou slli, remplit les positions de poids faible avec des zéros. Un décalage logique vers la droite, srl ou srli, remplit les positions de poids fort avec des zéros. Un décalage arithmétique vers la droite, sra ou srai, copie le bit de signe dans les positions de poids fort. En RV32, les décalages utilisant un registre ne prennent que les 5 bits de poids faible du registre indiquant le nombre de positions.

Décaler 13 de 3 positions vers la gauche produit 104. Cela multiplie par 8 parce qu’aucun bit nécessaire n’est perdu. Les décalages vers la gauche qui éliminent des bits significatifs ne conservent que la partie du résultat qui tient dans le registre.

Pour les valeurs non signées, un décalage vers la droite divise par une puissance de 2 et arrondit à l’entier inférieur. Pour les valeurs signées, un décalage arithmétique vers la droite arrondit vers l’infini négatif : décaler −7 de 1 position vers la droite donne −4. La division signée en C tronque vers zéro et donne −3 pour -7 / 2. Un compilateur doit apporter une correction avant de remplacer cette division signée par un décalage. Un décalage logique de la même configuration négative remplit avec des zéros et donne un grand résultat positif.

Comparer pour produire une valeur

slt écrit 1 lorsque sa première source signée est inférieure à la seconde, et 0 sinon. sltu utilise une interprétation non signée. Avec 0xFFFFFFFF et 1 en entrée, slt écrit 1, tandis que sltu écrit 0. Les versions à valeur immédiate sont slti et sltiu ; cette dernière effectue tout de même une extension de signe de sa valeur immédiate avant la comparaison non signée.

Le ET bit à bit diffère de && en C. Un résultat bit à bit peut contenir de nombreux bits à un ; le ET logique produit 0 ou 1 et peut ne pas évaluer la seconde expression.

Points clés

  • Les opérations arithmétiques RV32 conservent les 32 bits de poids faible et ne provoquent pas de trappe en cas de dépassement de capacité.
  • ET sélectionne des bits, OU les met à un et OU exclusif les inverse.
  • Le décalage arithmétique à droite préserve le signe ; le décalage logique à droite remplit avec des zéros.
  • srai et la division signée en C arrondissent différemment les valeurs négatives qui ne sont pas des multiples du diviseur.
  • slt et sltu interprètent différemment les mêmes configurations de bits dans les registres.
  • La multiplication et la division de l’extension M sont distinctes de RV32I.

2.2 Choisis l’instruction suivante

Un branchement conditionnel compare deux registres et modifie le compteur ordinal lorsque son test réussit. Lorsqu’il échoue, l’exécution se poursuit avec l’instruction suivante.

Choisir la comparaison
InstructionCondition de branchement
beq, bneÉgal, différent
blt, bgeInférieur en signé, supérieur ou égal en signé
bltu, bgeuInférieur en non signé, supérieur ou égal en non signé

RISC-V combine la comparaison et le branchement en une instruction. Il n’a pas d’indicateurs de condition entiers à définir au préalable. Pour tester une valeur par rapport à zéro, utilise zero comme l’une des sources. beqz a0, L se développe en beq a0, zero, L.

Les formes « supérieur à » et « inférieur ou égal à » sont des raccourcis de l’assembleur dont les sources sont inversées. bgt a0, a1, L se développe en blt a1, a0, L. Inverser l’ordre des sources compte autant que choisir le bon mnémonique.

Contrôle du déroulement en signé et en non signé

Si a0 contient 0xFFFFFFFF et a1 contient 1, blt a0, a1, L effectue le branchement car −1 en signé est inférieur à 1. bltu ne l’effectue pas, car la première configuration représente une grande valeur non signée. Utilise des comparaisons adaptées au sens des données, notamment une comparaison non signée pour les adresses.

Une vérification non signée des bornes peut rejeter aussi bien les indices signés négatifs que les indices supérieurs ou égaux à une petite longueur non négative :

bgeu a0, a1, OutOfBounds

Pour une longueur de 20, un indice de −1 apparaît comme 4 294 967 295 en non signé et provoque le branchement. Un indice de 19 ne le provoque pas ; un indice de 20 le provoque. La longueur doit représenter la longueur réelle non négative du tableau.

Sauts et adresses de retour

jal ra, f écrit l’adresse de l’instruction suivante, pc + 4, dans ra, puis saute à f. Un simple j L se développe en jal zero, L ; sa valeur de lien est ignorée. Cela convient à un saut de retour de boucle, mais ne crée pas d’adresse de retour pour un appel.

jalr rd, offset(rs1) ajoute un déplacement signé de 12 bits à une valeur de registre, met à zéro le bit 0 de la cible et écrit son propre pc + 4 dans rd. ret se développe en jalr zero, 0(ra) et revient à l’adresse sauvegardée sans conserver de nouveau lien.

Les branchements conditionnels et jal stockent des déplacements relatifs aux adresses de leurs propres instructions. Leurs portées sont respectivement d’environ ±4 KiB et ±1 MiB. jalr utilise une cible fondée sur un registre et peut atteindre tout l’espace d’adressage. Pour un transfert conditionnel éloigné, on peut inverser le test afin de sauter par-dessus un saut de plus grande portée :

bne a0, a1, Skip
jal zero, Far
Skip:

Ici, Far est à portée de jal. Si l’égalité est vérifiée, le branchement court laisse l’exécution se poursuivre vers le saut ; sinon, il le contourne.

Reconnaître un bloc de base

Un bloc de base possède une entrée à son début et aucun transfert de contrôle avant sa fin. À l’intérieur, les instructions s’exécutent en séquence. Les cibles de branchement commencent de nouveaux blocs ; les branchements et les sauts les terminent. Cela permet de déterminer quelles affectations s’exécutent toujours avant un test ou une sortie donnée.

Points clés

  • Choisis des branchements signés ou non signés selon le sens des opérandes.
  • Les branchements RISC-V comparent directement les registres sans indicateurs de condition.
  • jal sauvegarde un lien ; j l’ignore.
  • jalr utilise un registre et une valeur immédiate, puis met à zéro le bit 0 de la cible.
  • Un déplacement conditionnel est relatif à l’instruction de branchement elle-même.

2.3 Transforme le contrôle du déroulement en C en branchements

Le langage d’assemblage rend explicite chaque chemin d’une structure conditionnelle ou d’une boucle. Les étiquettes marquent les destinations, les branchements conditionnels choisissent les chemins et les sauts simples empêchent une poursuite en séquence non souhaitée.

Une structure if-else a besoin d’un chemin de sortie

Traduis if (i == j) f = g + h; else f = g - h;, avec f, g, h, i, j dans s0 à s4 :

bne s3, s4, Else
add s0, s1, s2
j Exit
Else:
sub s0, s1, s2
Exit:

Le premier branchement teste l’opposé de la condition C, de sorte qu’en cas d’égalité l’exécution se poursuit vers l’addition. Le saut après l’addition empêche l’exécution de la soustraction. Avec g = 10 et h = 3, des indices égaux donnent 13 ; des indices différents donnent 7. Si l’on supprime j Exit, la soustraction s’exécute aussi après l’addition.

Pour a < b && b < c, effectue un branchement vers la partie « sinon » dès qu’une comparaison échoue. Pour a < b || b < c, effectue un branchement vers la partie « alors » dès qu’une comparaison réussit. Cela met en œuvre l’évaluation en court-circuit, notamment en évitant d’évaluer une seconde expression dont la valeur n’est pas nécessaire.

Une boucle while teste avant son corps

Pour while (save[i] == k) i++;, conserve i dans s3, k dans s5 et la base du tableau dans s6 :

Loop:
slli t1, s3, 2
add t1, t1, s6
lw t0, 0(t1)
bne t0, s5, Exit
addi s3, s3, 1
j Loop
Exit:

À chaque passage, la boucle forme l’adresse de l’élément courant, le charge et le vérifie. Avec le tableau {7, 7, 7, 2, 7}, l’indice initial 0 et k = 7, la boucle se termine avec l’indice 3. Le test qui échoue n’incrémente pas l’indice. Le tableau doit contenir un élément d’arrêt dans sa plage valide.

Additionner un nombre donné d’éléments d’un tableau

Avec un nombre non négatif d’éléments dans a1 et la base du tableau dans a0 :

li t1, 0
li t0, 0
Loop:
bge t0, a1, Done
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
j Loop
Done:
mv a0, t1
ret

Le test placé au début traite une longueur nulle sans chargement. Avec les quatre éléments {5, -2, 10, 7}, la somme cumulée vaut 5, 3, 13, puis 20. La séquence exécute 33 instructions : 2 instructions d’initialisation, 7 pour chacune des quatre itérations réussies, un test qui échoue et 2 instructions de fin.

Une version utilisant un pointeur forme une seule fois une adresse de fin et avance l’adresse courante de 4. Pour une plage de tableau valide dont l’adresse de fin ne résulte pas d’un bouclage, elle peut utiliser des comparaisons d’adresses non signées :

li t1, 0
slli t0, a1, 2
add t0, a0, t0
bgeu a0, t0, Done
Loop:
lw t3, 0(a0)
add t1, t1, t3
addi a0, a0, 4
bltu a0, t0, Loop
Done:
mv a0, t1
ret

Il y a 4 instructions d’initialisation, 4 par itération et 2 instructions de fin, soit 22 au total pour quatre éléments. Le branchement initial saute le corps pour une plage vide ; le pointeur avance d’un élément après chaque chargement.

Choix entre plusieurs cas

Une instruction switch peut utiliser des comparaisons répétées ou une table de sauts lorsque les valeurs des cas sont rapprochées. Pour choisir un cas à l’aide d’une table, le programme vérifie la plage des cas, multiplie un indice par la taille de 4 octets d’une entrée, charge l’adresse du cas et y saute avec jalr zero, 0(t0). La vérification de la plage doit précéder le chargement dans la table afin qu’un cas invalide ne puisse pas sélectionner une adresse sans rapport.

Points clés

  • Pour contourner la partie « alors », le branchement utilise couramment la condition opposée.
  • Saute la partie « sinon » après avoir terminé la partie « alors ».
  • Les conditions composées doivent préserver l’évaluation en court-circuit.
  • Vérifie la condition d’une boucle à compteur avant de lire son premier élément.
  • Les boucles utilisant des pointeurs peuvent éviter de multiplier l’indice à chaque passage.
  • Une table de sauts exige une vérification de la plage avant le choix de la cible.
3

Champs d’instruction et code machine

Les noms du langage d’assemblage deviennent des champs fixes dans les instructions machine. Pars de l’opération pour trouver son format, puis attribue les registres et les valeurs immédiates à leurs positions de bits. Les chargements, les rangements, les branchements et les sauts utilisent des dispositions différentes parce qu’ils transportent des informations différentes. Coder et décoder dans les deux sens fait apparaître les erreurs de déplacement, de signe et d’ordre des champs que le langage d’assemblage lisible peut masquer.

3.1 Code les opérations sur registres, les chargements et les rangements

Une instruction machine est un mot de 32 bits divisé en champs. Le codage consiste à choisir la bonne disposition, à attribuer chaque opérande à un champ et à écrire chaque champ sur toute sa largeur.

Des positions fixes simplifient le décodage

Les 7 bits de poids faible, de 6 à 0, contiennent le code opération. Lorsqu’ils sont présents, rd occupe les bits 11 à 7, funct3 les bits 14 à 12, rs1 les bits 19 à 15 et rs2 les bits 24 à 20. Les champs de registre sur cinq bits désignent x0 à x31. Les champs de fonction distinguent les opérations partageant un code opération.

RV32I utilise les formats R, I, S, B, U et J. Ils font tenir différents opérandes dans les mêmes 32 bits tout en gardant les positions des registres fixes. Pour les valeurs immédiates signées, le bit 31 porte le signe.

Type R : deux sources et une destination

Du bit 31 au bit 0, la disposition R est :

funct7 | rs2 | rs1 | funct3 | rd | opcode
   7      5     5       3     5       7 bits

Pour add s0, a0, a1, rd = 8, rs1 = 10, rs2 = 11. ADD utilise le code opération 0110011, funct3 = 000, funct7 = 0000000 :

0000000 | 01011 | 01010 | 000 | 01000 | 0110011
0000 0000 1011 0101 0000 0100 0011 0011

Le mot hexadécimal est 0x00B50433. SUB ne remplace que funct7 par 0100000, ce qui donne 0x40B50433 pour sub s0, a0, a1. Les champs sources apparaissent dans un ordre différent de celui des opérandes d’assemblage : ne te contente donc pas de concaténer les noms de registres affichés.

Type I : une source et une valeur immédiate

La disposition I est imm[11:0] | rs1 | funct3 | rd | opcode. addi utilise le code opération 0010011 et funct3 = 000. Dans addi sp, sp, -16, les deux champs de registre contiennent 00010. La valeur immédiate de 12 bits est 111111110000 (0xFF0), ce qui produit 0xFF010113.

Un chargement utilise aussi le type I, avec la base de l’adresse comme rs1 et le registre chargé comme rd. lw t0, 32(s3) a pour valeur immédiate 32, pour base x19, pour destination x5, pour code opération 0000011 et funct3 = 010, ce qui donne 0x0209A283.

Les opérations de décalage à valeur immédiate utilisent les 5 bits de poids faible de la zone immédiate comme nombre de positions décalées. Les bits de commande de poids fort distinguent les décalages : srli t0, t1, 3 donne 0x00335293, tandis que srai t0, t1, 3 donne 0x40335293. Le bit 30 sélectionne la règle de remplissage arithmétique.

Type S : ranger une valeur source

Un rangement n’a pas de registre de destination. La disposition S utilise cet espace pour une partie de sa valeur immédiate :

imm[11:5] | rs2 | rs1 | funct3 | imm[4:0] | opcode

Pour sw t0, 48(s3), rs2 = 5 fournit la valeur rangée et rs1 = 19 fournit la base. La valeur immédiate 48 sur 12 bits se décompose en 0000001 et 10000. Avec funct3 = 010 et le code opération 0100011, le résultat est 0x0259A823. Il n’y a pas de rd à remplir avec t0.

Après avoir réuni les champs, regroupe la chaîne complète de 32 bits en huit quartets de 4 bits et convertis chacun en un chiffre hexadécimal. Décode ensuite le résultat : vérifie le code opération, les numéros de registre et la valeur immédiate signée. Il est essentiel de compléter un champ de registre à 5 bits ; omettre un zéro initial change les positions des champs suivants.

Points clés

  • Trouve le format à partir de l’opération avant d’attribuer les champs d’opérandes.
  • Les champs de registre occupent 5 bits et se trouvent à des positions fixes.
  • Le type R utilise funct7 pour distinguer add de sub.
  • Le type I garde ensemble les 12 bits de la valeur immédiate.
  • Le type S divise le déplacement et possède rs2 mais pas rd.
  • Vérifie un mot codé en le décodant pour retrouver ses opérandes.

3.2 Reconstitue les déplacements et décode les mots machine

Les codages des branchements et des sauts économisent de la place en omettant un bit de déplacement toujours nul. Leurs champs représentent toujours des distances en octets, et le signe doit être reconstitué avant de calculer la cible.

Déplacements de branchement de type B

Calcule le déplacement en soustrayant l’adresse du branchement lui-même de l’adresse de la cible. Une instruction bne à 0x1010 qui cible 0x1000 utilise −16 octets, et non −20 ni −4 instructions.

Le type B utilise le code opération 1100011. Son déplacement signé comporte 13 bits, dont le bit 0, nul et non stocké. La plage va de −4096 à +4094 octets, par pas de 2. Les positions sont :

Bits du déplacementBits de l’instruction
imm[12]31
imm[10:5]30–25
imm[4:1]11–8
imm[11]7

Les champs sources restent à leurs positions habituelles. Les valeurs de funct3 sont 000 pour beq, 001 pour bne, 100 pour blt, 101 pour bge, 110 pour bltu et 111 pour bgeu.

Pour bne t0, t1, -16, le déplacement sur 13 bits est 1 1111 1111 0000. Ses parties dispersées sont 1, 111111, 1000 et 1 dans l’ordre du tableau. Avec rs1 = 5 et rs2 = 6, l’instruction est 0xFE6298E3. Remplacer le déplacement par +12 donne 0x00629663.

Les déplacements permettent des pas de 2 octets parce que les instructions compressées peuvent mesurer 2 octets. Une cible effectivement atteinte doit tout de même respecter l’alignement des instructions en vigueur : RV32I sans instructions compressées exige un alignement sur 4 octets.

Types J et U

jal utilise le code opération 1101111. Son déplacement signé en octets sur 21 bits omet le bit 0 et place les bits dans l’ordre imm[20|10:1|11|19:12] sur les bits 31 à 12 de l’instruction. La plage va de −1 048 576 à +1 048 574 octets. jal ra, 192 devient 0x0C0000EF.

Le type U place directement sa valeur haute de 20 bits dans les bits 31 à 12 de l’instruction, suivis de rd et du code opération. lui utilise 0110111 ; auipc utilise 0010111. Ainsi, lui t0, 0x12345 donne 0x123452B7.

Décoder d’abord à partir du code opération

Pour 0x00A12423, les 7 bits de poids faible sont 0100011 : il s’agit d’un rangement. funct3 = 010 sélectionne sw. Les champs sources donnent la base x2 (sp) et la valeur x10 (a0). Les deux fragments de la valeur immédiate réunis donnent 8 : l’instruction d’assemblage est donc sw a0, 8(sp).

Pour un branchement, réunis les fragments dispersés dans l’ordre des bits du déplacement, ajoute le zéro implicite, puis effectue une extension de signe du résultat complet sur 13 bits. Traiter les 12 bits stockés comme une valeur immédiate ordinaire de type I fait perdre à la fois la bonne disposition et le facteur d’échelle. La position fixe du bit de signe et la réutilisation des positions d’autres champs réduisent le travail du décodeur, même si la disposition affichée est moins pratique à lire à la main.

Le relogement préserve les relations

Si un branchement et sa cible sont déplacés de la même distance, la soustraction de leurs nouvelles adresses donne l’ancien déplacement. Une référence vers une autre section peut changer lorsque les sections sont disposées séparément. Un éditeur de liens peut donc préserver un branchement relatif interne tout en corrigeant une référence à un symbole externe.

Points clés

  • Les déplacements des branchements et de jal comptent les octets depuis l’adresse de l’instruction.
  • Les types B et J omettent le bit 0, qui vaut zéro.
  • Reconstitue un déplacement avant d’interpréter son signe.
  • Le code opération détermine la façon dont les bits restants sont répartis.
  • Déplacer une instruction et sa cible ensemble préserve leur distance relative.
4

Appels de procédure et stockage sur la pile

Un appel doit transmettre les arguments, préserver les informations utiles et revenir au bon endroit. Les conventions relatives aux registres permettent à des procédures distinctes de coopérer ; les cadres de pile séparent les informations des appels imbriqués et récursifs. Les mêmes calculs d’adresses que pour les opérandes en mémoire servent aussi à mettre en œuvre les pointeurs, les champs de structure et les chaînes. Suis une valeur lors d’un appel aussi attentivement que lors d’une instruction arithmétique.

4.1 Transmets les arguments et préserve les valeurs des registres

Un appel de procédure transfère le contrôle et les données entre des parties de code écrites indépendamment. La convention d’appel indique aux deux parties où placer les arguments et les résultats, et quelles valeurs de registre doivent être conservées.

La séquence d’appel et de retour

L’appelant place les huit premiers arguments entiers dans a0 à a7 ; les arguments supplémentaires vont sur la pile. La procédure renvoie un résultat entier dans a0, en utilisant aussi a1 pour un résultat de 64 bits en RV32.

jal ra, f stocke sa propre adresse augmentée de 4 dans ra, puis saute à f. jal f est sa forme abrégée. La pseudo-instruction call f se développe normalement en auipc suivi de jalr pour une plus grande portée ; l’éditeur de liens peut la raccourcir en une seule instruction jal. Dans les deux cas, l’adresse de retour suit la séquence d’appel effectivement utilisée.

ret correspond à jalr zero, 0(ra). L’instruction saute à l’adresse sauvegardée et ignore le nouveau lien. Un simple j f ignore aussi son lien : il ne met donc pas en place un appel et un retour ordinaires.

Déterminer qui sauvegarde une valeur

Les registres sauvegardés par l’appelant comprennent t0 à t6, a0 à a7 et ra. L’appelé peut les modifier. Si l’appelant a besoin plus tard de l’une de leurs valeurs courantes, il la sauvegarde avant l’appel.

Les registres sauvegardés par l’appelé comprennent s0 à s11 et sp. Un appelé qui modifie un registre s doit sauvegarder sa valeur initiale et la restaurer avant de revenir. Restaurer sp ramène l’appelant à son propre cadre. Le processeur n’impose pas le respect de ces engagements ; les modules compilés coopèrent parce que leurs logiciels suivent l’interface binaire d’application.

Supposons qu’un appelant ait un compteur de boucle dans t0 et appelle une procédure auxiliaire. Celle-ci peut utiliser t0 pour son propre calcul sans enfreindre la convention. Déplacer le compteur dans s1 peut permettre de le conserver pendant les appels aux procédures auxiliaires, mais la procédure qui l’entoure doit alors préserver la valeur de s1 reçue de son propre appelant.

Une procédure feuille peut ne pas avoir besoin de cadre

Une procédure feuille n’appelle aucune autre procédure. Cette fonction calcule (g + h) - (i + j) à partir des arguments dans a0 à a3 :

leaf:
add t0, a0, a1
add t1, a2, a3
sub a0, t0, t1
ret

Elle n’utilise que des registres de travail sauvegardés par l’appelant et laisse ra inchangé. Pour les entrées 9, 4, 6 et 1, elle renvoie 6. Elle n’a pas besoin de cadre de pile.

Si le calcul utilise s0 à la place, la procédure doit préserver ce registre :

addi sp, sp, -16
sw s0, 12(sp)
add t0, a0, a1
add t1, a2, a3
sub s0, t0, t1
mv a0, s0
lw s0, 12(sp)
addi sp, sp, 16
ret

Le résultat est déplacé dans a0 avant la restauration de l’ancienne valeur de s0. Si la valeur initiale de s0 était 1234, elle est toujours 1234 après l’appel. Le cadre alloué mesure 16 octets pour préserver l’alignement standard de la pile, même si le mot sauvegardé n’en utilise que 4.

Un appel peut se lire comme six actions : placer les arguments, transférer le contrôle, obtenir du stockage, effectuer la tâche, placer le résultat et revenir. À chaque étape, distingue la valeur renvoyée des anciennes valeurs que la convention impose à la procédure de préserver.

Points clés

  • Les arguments entiers commencent dans a0 à a7, et le premier résultat est dans a0.
  • ra pointe après l’instruction d’appel effectivement utilisée ou la séquence d’appel développée.
  • Les appelants protègent les valeurs encore utiles des registres sauvegardés par l’appelant ; les appelés restaurent les registres sauvegardés qu’ils modifient.
  • Une procédure feuille qui n’utilise que des registres sauvegardés par l’appelant peut ne pas avoir besoin de cadre.
  • Déplace le résultat avant de restaurer un registre sauvegardé qui le contenait.

4.2 Donne à chaque appel son propre cadre de pile

Les appels imbriqués ont besoin d’un endroit où conserver les données de l’appel extérieur. La pile fournit un stockage selon le principe « dernier entré, premier sorti » : l’appel le plus récent se termine en premier et libère son propre cadre.

Allouer sous l’ancien pointeur de pile

La pile grandit vers les adresses les plus basses. addi sp, sp, -16 réserve 16 octets ; addi sp, sp, 16 les libère. L’interface binaire d’application standard maintient sp à un multiple de 16 pendant toute l’exécution. Un cadre peut contenir des registres sauvegardés, l’adresse de retour, des valeurs transférées en mémoire faute de registres, des objets locaux et de l’espace pour les arguments transmis sur la pile aux procédures appelées.

Un schéma courant pour une procédure non feuille est :

addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
# body, including calls
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret

Les déplacements se rapportent au nouveau sp. S’il vaut 0x2000, le ra sauvegardé occupe les adresses de 0x200C à 0x200F. L’épilogue correspondant restaure les valeurs pendant que le cadre existe encore, puis le libère. Chaque chemin de retour doit compenser l’allocation.

Un pointeur de cadre, conventionnellement s0, peut donner aux objets locaux des déplacements fixes lorsque sp change dans une procédure. Sa valeur initiale doit être préservée comme pour toute autre utilisation de s0.

La récursivité conserve des copies distinctes

Le calcul de la factorielle a besoin de la valeur initiale de n après avoir calculé fact(n - 1). Il a aussi besoin de sa propre adresse de retour après que le jal récursif a écrasé ra :

fact:
addi sp, sp, -16
sw ra, 12(sp)
sw a0, 8(sp)
li t0, 2
blt a0, t0, Base
addi a0, a0, -1
jal ra, fact
lw t1, 8(sp)
mul a0, t1, a0
j Done
Base:
li a0, 1
Done:
lw ra, 12(sp)
addi sp, sp, 16
ret

L’instruction mul de l’extension M multiplie le résultat renvoyé par l’appel interne par la valeur initiale de n. Pour fact(3), les cadres pour 3, 2 et 1 coexistent. L’appel de base renvoie 1 ; le suivant renvoie 2 ; l’appel extérieur renvoie 6. Avec sp = 0x7FFFFF00 au départ, trois cadres le font descendre de 48 octets à 0x7FFFFED0. Les retours les libèrent dans l’ordre inverse.

Le test de base traite les valeurs non négatives 0 et 1. Pour ce résultat sur 32 bits, la factorielle est exacte jusqu’à 12! : 479 001 600. Les bits de poids faible du produit ne représentent plus le résultat complet à 13!, qui vaut 6 227 020 800.

Situer la pile dans la mémoire du programme

Une organisation typique place le code dans le segment de code, les variables globales dans les données statiques, les objets alloués dynamiquement dans le tas et les cadres d’appel dans la pile. Le tas grandit souvent vers le haut et la pile vers le bas. L’emplacement réel dépend de l’environnement d’exécution.

Un pointeur vers un objet local sur la pile devient invalide lorsque la durée de vie de cet objet prend fin au retour. Restaurer sp n’efface pas les anciens octets, mais des appels ultérieurs peuvent les réutiliser. De même, modifier sp et continuer à utiliser les anciens déplacements de sauvegarde peut conduire à lire les octets du mauvais cadre.

Points clés

  • Soustrais de sp pour allouer ; ajoute la même quantité pour libérer.
  • L’interface binaire d’application standard exige un alignement de la pile sur 16 octets.
  • Un appel imbriqué écrase ra : préserve donc l’adresse de retour de l’appel extérieur.
  • Chaque activation récursive possède ses propres arguments sauvegardés et informations de retour.
  • Tous les chemins de sortie doivent restaurer le cadre et les registres requis.
  • Un objet local ne survit pas à son cadre de pile.

4.3 Utilise les adresses pour les tableaux, les structures et les chaînes

Un pointeur est une valeur d’adresse. Le langage d’assemblage met en œuvre un déréférencement en utilisant cette adresse comme base d’un chargement ou d’un rangement, et les calculs sur les pointeurs en ajoutant une distance en octets.

Suivre le pointeur, puis accéder à la valeur

Avec int *p dans a0, lw t0, 0(a0) lit *p, tandis que sw t0, 0(a0) l’écrit. Le registre contient l’adresse ; la mémoire à cette adresse contient l’entier. Si une variable pointeur est elle-même stockée en mémoire, charge d’abord la valeur de ce pointeur, puis utilise-la comme base d’un second chargement.

Obtenir l’adresse d’un objet C local exige un stockage adressable, normalement un emplacement sur la pile. Un registre matériel n’a pas d’adresse mémoire pouvant être transmise comme &x.

En C, les calculs sur les pointeurs tiennent automatiquement compte de la taille des éléments. Pour RV32 ILP32, incrémenter int *p ajoute 4 octets, tandis qu’incrémenter char *p ajoute 1. Le langage d’assemblage n’a pas de type pointeur pour fournir ce facteur :

addi a0, a0, 4    # next int
addi a1, a1, 1    # next char

Un tableau passé en argument est son adresse de base, pas une copie de ses éléments. Transmets séparément le nombre d’éléments lorsqu’une procédure en a besoin ; le pointeur seul ne code pas la longueur du tableau.

Les champs de structure ont des déplacements en octets

Pour struct item { int id; int score; };, id occupe les octets 0 à 3 et score les octets 4 à 7. Avec l’adresse de l’objet dans a0, lw t0, 4(a0) lit le score. En général, les déplacements des champs suivent les règles de disposition et de remplissage de l’interface binaire d’application. Ne déduis pas chaque déplacement de champ en te contentant de compter les déclarations du code source.

Pour un tableau de ces structures de 8 octets, l’élément suivant commence 8 octets plus loin. Dans chaque élément, le score reste à 4 octets du début de cet élément. Le pas entre éléments et le déplacement du champ sont deux parties distinctes du calcul de l’adresse.

Copier une chaîne terminée par un caractère nul

Une chaîne d’octets C se termine par un octet nul. Pour la copier, lis et range un octet avant de déterminer si la copie est terminée :

Loop:
lbu t0, 0(a1)
sb t0, 0(a0)
addi a0, a0, 1
addi a1, a1, 1
bnez t0, Loop
ret

Ici, a1 est la source et a0 la destination. La destination doit avoir assez d’espace. lbu lit un octet non signé ; sb ne range que ses 8 bits de poids faible. Les deux pointeurs avancent de 1, y compris à la dernière itération. Le branchement teste l’octet copié : le zéro terminal est donc lui aussi rangé.

Copier « RISC » prend cinq itérations pour les quatre lettres et l’octet nul. À cinq instructions par itération plus ret, la procédure exécute 26 instructions. Une chaîne vide nécessite tout de même une itération pour copier son caractère terminal. Un chargement de mot lirait quatre octets à la fois et ne mettrait pas en œuvre cet algorithme octet par octet.

UTF-8 peut coder un caractère sur plusieurs octets. Cette boucle copie ces octets sans les modifier ; elle ne compte pas les caractères affichés. De même, les boucles sur des tableaux utilisant des pointeurs peuvent avancer selon le pas entre éléments sans recalculer à chaque fois un indice multiplié. Les compilateurs optimisants peuvent effectuer cette transformation même lorsque le programme C utilise des indices de tableau ordinaires.

Points clés

  • Le déréférencement utilise l’adresse contenue dans le pointeur.
  • Les calculs sur les pointeurs en langage d’assemblage doivent tenir compte de la taille des éléments.
  • Les tableaux passés en paramètre nécessitent une longueur distincte lorsque la procédure utilise un nombre d’éléments.
  • Le pas entre structures et le déplacement d’un champ correspondent à des parties différentes d’une adresse.
  • Une boucle de copie de chaîne doit ranger l’octet nul terminal.
5

Du code source à l’exécution

Un programme passe par des outils qui connaissent différents aspects de sa forme finale. Un compilateur choisit les instructions, un assembleur les code, un éditeur de liens réunit les modules et un chargeur prépare l’exécution. Les symboles et les relogements transmettent les références non résolues entre les étapes. Suivre un appel depuis le code source jusqu’à son adresse finale rend concrets les rôles des fichiers objets, des bibliothèques partagées et du code de démarrage.

5.1 Traduis le code source en objet relogeable

Un fichier source devient des instructions exécutables à travers plusieurs étapes de traduction. Chaque étape a une responsabilité différente et les fichiers intermédiaires conservent les informations nécessaires aux étapes suivantes.

Le compilateur choisit les instructions

Pour C, le parcours habituel est le code source, le langage d’assemblage, le fichier objet, l’exécutable, puis un processus en cours d’exécution. Un compilateur analyse le code source, l’optimise, alloue les registres et sélectionne les instructions pour une architecture du jeu d’instructions cible. Un pilote de compilation peut exécuter plusieurs étapes en une commande.

gcc -S program.c s’arrête après avoir produit le langage d’assemblage. gcc -c program.c produit un fichier objet sans édition de liens. Le compilateur cible détermine l’architecture du jeu d’instructions utilisée par ce langage d’assemblage ; un programme RISC-V nécessite une chaîne d’outils ciblant RISC-V.

Avec -O0, les valeurs passent souvent par des emplacements sur la pile et le langage d’assemblage suit de près les différentes instructions du code source. Avec -O2, l’allocation de registres, le travail supprimé et les boucles restructurées peuvent masquer cette correspondance. Une instruction C n’a pas une seule traduction fixe en langage d’assemblage.

L’assembleur code et enregistre

L’assembleur transforme les mnémoniques et les noms de registres en bits, développe les pseudo-instructions et résout les étiquettes qu’il peut situer. Un branchement en avant fait référence à une étiquette rencontrée plus loin. Un assembleur peut d’abord recueillir les positions puis coder les références, ou enregistrer les endroits à remplir une fois la cible connue.

Les directives indiquent à l’assembleur comment organiser sa sortie ; le processeur ne les exécute pas :

.data
.align 2
count:
.word 5
message:
.string "hi"
.text
.globl main
main:
# instructions follow

.word 5 produit une valeur de données de 4 octets. .string "hi" produit les octets des caractères et un zéro terminal. Avec l’assembleur GNU RISC-V, .align 2 aligne l’élément suivant sur une adresse multiple de 4 octets. .globl rend un symbole visible aux autres fichiers objets. Les étiquettes désignent des positions ; elles ne réservent pas d’octets à elles seules.

Ce que conserve un fichier objet

Un objet relogeable contient généralement un en-tête, du code dans des sections de code, des données statiques, une table des symboles et des informations de relogement. La table des symboles distingue les noms définis ici des références définies ailleurs. Des informations de débogage facultatives relient les instructions aux lignes du code source ; -g les demande.

Les formats d’objet courants comprennent ELF, Mach-O et PE/COFF. Le format organise le code machine et les métadonnées ; il est distinct de l’architecture du jeu d’instructions qui définit les bits de chaque instruction.

Pour call printf, l’assembleur peut produire une séquence auipc/jalr sans connaître l’adresse finale de la fonction. Il enregistre un symbole non défini et des entrées de relogement afin que l’éditeur de liens puisse terminer la référence. La distance d’un branchement local peut déjà être connue lorsque sa cible se déplace avec la même section, même si des changements ultérieurs de disposition, comme la relaxation, peuvent nécessiter un ajustement.

Autres parcours d’exécution

Un interpréteur effectue les opérations par l’intermédiaire d’un autre programme en cours d’exécution. Un compilateur à la volée génère du code machine pendant l’exécution, souvent après avoir repéré le code fréquemment utilisé. Java utilise couramment du bytecode, suivi d’une interprétation et d’une compilation à la volée ; Python utilise couramment un interpréteur. La compilation à la volée échange un travail de compilation pendant l’exécution contre une exécution répétée plus rapide.

Un fichier objet est donc plus qu’une simple liste brute d’instructions, mais ce n’est pas encore un processus complet en cours d’exécution. Les symboles non résolus et la disposition finale doivent encore être traités par l’éditeur de liens et le chargeur.

Points clés

  • Le compilateur sélectionne et optimise les instructions pour une architecture du jeu d’instructions cible.
  • L’assembleur code les instructions et développe les pseudo-instructions.
  • Les directives organisent la sortie ; les étiquettes désignent des positions.
  • Les symboles et les relogements conservent les références que les étapes suivantes doivent résoudre.
  • -S s’arrête au langage d’assemblage, tandis que -c s’arrête à un fichier objet.
  • Les interpréteurs et les compilateurs à la volée offrent des voies d’exécution différentes.

5.2 Relie les modules et démarre un processus

Un éditeur de liens construit un fichier exécutable à partir de modules objets et de bibliothèques. Un chargeur utilise ce fichier pour créer un processus en cours d’exécution. Savoir quelle étape traite une adresse aide à expliquer les erreurs de relogement comme celles de démarrage.

Disposer les modules et corriger les références

L’éditeur de liens place les sections d’entrée dans l’espace d’adressage de l’exécutable, résout les définitions des symboles et utilise les entrées de relogement pour corriger les références. Un nom qu’aucun module lié ne définit produit une erreur « undefined reference ».

Supposons que le code commence à 0x00010000 et les données à 0x10000000. L’objet A apporte 0x100 octets de code et 0x20 octets de données ; B suit immédiatement. Le code de B commence à 0x00010100 et ses données à 0x10000020.

Une instruction jal dans A au déplacement 0x40 du code a pour adresse 0x00010040. Si procB commence au début du code de B, le déplacement de l’appel est 0xC0, soit 192 octets, codé avec ra sous la forme 0x0C0000EF. Une variable au déplacement 0x10 dans les données de B se retrouve à 0x10000030. Avec gp = 0x10000800, son déplacement est −2000, ce qui tient dans un déplacement de chargement signé sur 12 bits. Ce sont deux calculs distincts pour le code et les données.

Choisir comment le code des bibliothèques est fourni

L’édition de liens statique copie le code objet nécessaire des bibliothèques dans l’exécutable. Le programme porte ce code, ce qui augmente la taille du fichier ; appliquer une correction de bibliothèque exige une nouvelle édition de liens. Les bibliothèques statiques ne suppriment pas la dépendance du programme envers son interface avec le système d’exploitation.

L’édition de liens dynamique conserve le code des bibliothèques dans des fichiers partagés, comme .so, .dll ou .dylib. Les pages de code d’une bibliothèque compatible peuvent être partagées par des processus. Une bibliothèque compatible mise à jour peut servir les programmes sans nouvelle édition de liens de chaque exécutable, mais la version nécessaire doit être disponible au moment voulu.

Avec la liaison paresseuse ELF, le premier appel passe par un code intermédiaire de la table de liaison des procédures (PLT). L’éditeur de liens dynamique résout la cible et l’enregistre dans la table globale des déplacements (GOT) ; les appels suivants utilisent cette adresse enregistrée. D’autres configurations résolvent les symboles au chargement.

Le code indépendant de la position atteint les cibles locales par adressage relatif, notamment avec des séquences auipc, et utilise une indirection pour les cibles externes. Les distances relatives internes d’une bibliothèque restent valides lorsqu’elle est déplacée en bloc. La liaison des références externes peut tout de même nécessiter un travail de relogement.

Le chargeur crée l’état d’exécution

Le chargeur lit les métadonnées de l’exécutable, crée un espace d’adressage, projette ou copie le code et les données, prépare les arguments et la pile initiale, initialise l’état nécessaire des registres et transfère le contrôle au code de démarrage. Le code de démarrage appelle main ; après son retour, le code d’exécution demande la fin du processus. Les systèmes modernes peuvent projeter les pages du fichier et les charger en mémoire selon les besoins.

ecall demande un service de l’environnement. Sous Linux RISC-V, a7 contient le numéro d’appel système et a0 à a5 contiennent les arguments. li a7, 93, li a0, 0, ecall demande la fin du programme avec le code de sortie 0 ; le service 64 est write. Les numéros de service d’un simulateur proviennent de sa propre interface.

Une bibliothèque manquante pendant l’édition de liens provoque son échec. Une bibliothèque partagée nécessaire, supprimée après l’édition de liens, peut empêcher le chargement ou provoquer un échec pendant la liaison à l’exécution. L’exécutable peut avoir été construit avec succès même s’il ne peut plus démarrer.

Points clés

  • L’éditeur de liens organise un exécutable, résout les références aux symboles et les corrige.
  • Le chargeur projette cet exécutable et crée l’état initial du processus.
  • L’édition de liens statique copie le code des bibliothèques ; l’édition de liens dynamique nécessite des bibliothèques partagées compatibles.
  • La liaison paresseuse enregistre une cible résolue pour les appels ultérieurs.
  • L’indépendance de la position préserve les références relatives locales lors d’un déplacement.
  • Les numéros de service ecall dépendent de l’environnement d’exécution.
6

Compromis dans la conception des jeux d’instructions

Les jeux d’instructions font des choix différents quant à la quantité de travail exprimée par une instruction. Compare RISC-V et x86 à travers leurs opérandes, leurs codages et leurs calculs d’adresses, puis relie ces choix à des contraintes réelles. Pour évaluer la performance, il faut considérer ensemble le nombre d’instructions exécutées, le CPI et la durée du cycle d’horloge ; la densité du code et la compatibilité logicielle répondent à d’autres questions. Pour évaluer utilement une conception, précise la charge de travail et l’implémentation comparées.

6.1 Compare les instructions RISC-V et x86

RISC et CISC décrivent de grandes approches de conception des jeux d’instructions. Pour les comparer utilement, examine comment chacune représente le travail : emplacement des opérandes, longueur des instructions, calcul des adresses et contrôle du déroulement.

Opérations régulières ou instructions plus riches

Les architectures RISC privilégient les opérations simples, de nombreux registres et les accès par chargement et rangement. Cette approche s’est développée avec les travaux d’IBM sur le 801 au milieu des années 1970, puis avec RISC à Berkeley et MIPS à Stanford au début des années 1980. RISC-V, ARM AArch64, MIPS et Power s’inscrivent globalement dans cette tradition.

Les architectures CISC proposent des instructions de longueur variable, un adressage plus riche et des opérations qui peuvent combiner plusieurs étapes. x86 est issu du 8086 présenté en 1978 ; les familles CISC antérieures comprennent VAX et Motorola 68000.

Dans la syntaxe x86 d’Intel, add eax, [ebx+8] lit une valeur de 32 bits en mémoire et l’ajoute à eax. Une opération RISC-V équivalente nécessite un chargement suivi d’une opération arithmétique :

lw t0, 8(s1)
add a0, a0, t0

L’instruction x86 combine un accès mémoire et une addition. RISC-V les présente comme des instructions distinctes. x86 appelle une valeur de 32 bits un double mot ; son mot mesure 16 bits. Dans les exemples RV32, un mot mesure 32 bits.

Codage et adressage

Une instruction x86 mesure de 1 à 15 octets. Les instructions de base RISC-V occupent 4 octets, avec des formes compressées de 2 octets disponibles grâce à C. Des positions de champs régulières simplifient le décodage. Les longueurs variables peuvent économiser de l’espace en utilisant des codages courts pour les opérations courantes.

RISC-V utilise des opérandes dans des registres, des valeurs immédiates, des adresses formées d’une base et d’un déplacement, et des adresses relatives au compteur ordinal. jalr relève de l’adressage avec un registre de base, tandis que les branchements, jal et auipc utilisent le compteur ordinal.

x86 combine aussi une base, un index multiplié par un facteur d’échelle et un déplacement. mov eax, [ebx + esi*4 + 8] lit en une instruction un élément indexé de 4 octets avec un déplacement supplémentaire. RISC-V forme l’index multiplié et l’adresse effective avec des instructions distinctes avant le chargement. Dans les deux processeurs, ce travail doit tout de même être effectué en interne.

Registres et comparaisons

RISC-V possède 32 registres entiers, dont son registre zéro fixe. x86-64 conventionnel possède 16 registres à usage général ; x86 sur 32 bits en possède 8. De nombreuses instructions arithmétiques x86 utilisent une destination qui est aussi une source : add eax, ebx modifie eax. L’instruction add à trois opérandes de RISC-V peut conserver les deux valeurs sources tout en écrivant dans un troisième registre.

Les instructions arithmétiques x86 et cmp définissent des indicateurs de condition ; un branchement ultérieur comme jl teste ces indicateurs. RISC-V utilise blt pour comparer et effectuer directement un branchement. ARM AArch64 utilise aussi des indicateurs : leur seule présence ne permet donc pas de classer une architecture du jeu d’instructions comme CISC.

Le sens de « réduit »

RISC ne signifie pas qu’une architecture moderne du jeu d’instructions doit avoir un nombre total minuscule d’instructions. RV32I possède 40 instructions de base, mais les extensions facultatives en ajoutent beaucoup. L’idée centrale est d’utiliser des opérations et des codages réguliers et relativement simples. Les codages compressés montrent aussi qu’une longueur d’instruction fixe est une tendance de conception, et non une définition que chaque instruction RISC doit respecter.

Points clés

  • RISC-V sépare les accès mémoire des opérations arithmétiques.
  • x86 peut combiner des opérandes en mémoire et une opération arithmétique en une instruction.
  • La longueur des instructions et les modes d’adressage influent sur le décodage et la densité du code.
  • RISC-V compare dans un branchement ; x86 utilise couramment des indicateurs de condition.
  • Le nombre d’instructions à lui seul ne définit pas une architecture RISC.

6.2 Évalue la vitesse, la taille du code et la compatibilité

Un jeu d’instructions détermine la façon dont un programme exprime le travail à effectuer, mais la vitesse d’exécution dépend aussi du processeur qui l’implémente. Compare le comportement mesuré ou modélisé d’un programme, et non le nombre d’opérations promises par une instruction.

Combiner les trois facteurs de performance

Le temps processeur est égal au nombre d’instructions exécutées multiplié par le nombre moyen de cycles par instruction (CPI), puis par la durée du cycle d’horloge. On peut aussi diviser le produit du nombre d’instructions exécutées par le CPI par la fréquence d’horloge. Un nombre inférieur d’instructions peut être contrebalancé par un CPI plus élevé ou une fréquence d’horloge plus basse.

À 2 GHz, chaque cycle dure 0,5 ns. Une version avec 1,0 milliard d’instructions et un CPI de 2,0 utilise 2,0 milliards de cycles et prend 1,0 s. Une autre avec 1,3 milliard d’instructions et un CPI de 1,2 utilise 1,56 milliard de cycles et prend 0,78 s. La seconde exécute 30 % d’instructions en plus, mais elle est environ 1,28 fois plus rapide. Le CPI est une moyenne pour ce programme sur cette implémentation, et non une propriété universelle d’une architecture du jeu d’instructions.

Les instructions complexes exigent toujours du travail interne

De nombreux processeurs x86 modernes traduisent la plupart des instructions en micro-opérations internes plus simples. Celles-ci peuvent ensuite utiliser des techniques d’exécution semblables à celles des cœurs RISC modernes. L’architecture du jeu d’instructions visible reste importante pour le codage, le décodage et la compatibilité, même lorsque les opérations internes se ressemblent.

Une instruction complexe ne garantit pas un nombre fixe de cycles. Une instruction de copie de bloc et une boucle de copie simple peuvent être tour à tour plus performantes selon les processeurs et les charges de travail. La version du processeur, la taille des données et le comportement de la mémoire comptent ; un code source qui tient en une instruction ne permet pas de déterminer le résultat.

La densité du code est une mesure distincte

Des séquences de code machine plus courtes peuvent réduire le stockage nécessaire et la pression sur le cache d’instructions. Les longueurs variables de CISC favorisent souvent la densité. L’extension C de RISC-V fournit des codages sur 16 bits pour les opérations courantes, à côté des instructions sur 32 bits. La spécification indique une utilisation typique pour 50 à 60 % des instructions, ce qui rend le code environ 25 à 30 % plus petit.

Pour 100 instructions dont la moitié sont compressées, 50 utilisent 2 octets et 50 en utilisent 4 : 300 octets au lieu de 400. Le nombre d’instructions exécutées reste de 100. Une réduction de taille ne détermine pas à elle seule une accélération, car le temps d’exécution dépend de plus que des octets récupérés.

La compatibilité et les outils influent sur le choix

Les fichiers binaires et les bibliothèques x86 existants donnent une valeur considérable à la compatibilité. RISC-V fige ses jeux de base et ajoute des extensions. Le code RV32I peut s’exécuter sur une implémentation qui prend en charge RV32I lorsque l’interface binaire d’application et l’environnement d’exécution correspondent ; utiliser M ou V ajoute ces exigences. RV32E est une autre base avec un nombre réduit de registres et ne garantit pas la compatibilité avec RV32I.

ARM est largement utilisé dans les appareils mobiles ainsi que dans les ordinateurs portables et les serveurs ; x86 est largement utilisé dans les PC et les serveurs ; RISC-V sert aux systèmes embarqués, aux accélérateurs, à la recherche et à l’enseignement. La disponibilité des logiciels, les licences et la qualité des puces choisies comptent toutes dans le choix d’une plateforme.

Le langage d’assemblage écrit à la main peut aider pour une petite routine critique ou donner accès à une instruction spéciale, mais il lie le code à une architecture du jeu d’instructions et augmente le travail de maintenance. Les compilateurs optimisants gèrent bien l’allocation de registres et la restructuration du code. Appuie-toi sur des mesures pour justifier le remplacement d’un code source clair par du langage d’assemblage.

Points clés

  • Compare ensemble le nombre d’instructions exécutées, le CPI et la durée du cycle.
  • Les micro-opérations ne changent pas l’architecture du jeu d’instructions visible par les logiciels.
  • Les codages compressés réduisent le nombre d’octets sans nécessairement réduire celui des instructions exécutées.
  • La compatibilité binaire exige aussi que l’interface binaire d’application et l’environnement correspondent.
  • Mesure un programme et une implémentation précis avant d’affirmer qu’une architecture du jeu d’instructions est plus rapide.

Jeux d’instructions et langage d’assemblage : exemples

6 Leçons Interactives

1

Suis les opérandes d’une expression jusqu’à la mémoire

15 étapes
Lis une instruction, suis les valeurs temporaires, mets à jour un tableau et construis une constante plus grande.
  1. 1
    IntroductionSuis une valeur à la fois

    Une instruction d’assemblage nomme à la fois son opération et ses opérandes. Commence par un calcul lisible sur registres, puis charge une valeur depuis la mémoire et range un résultat mis à jour. Enfin, construis une constante trop grande pour une seule valeur immédiate. À chaque étape, suis précisément ce qui change : un registre de destination, un groupe d’octets en mémoire ou l’adresse de l’instruction suivante.

  2. 2
    ExplicationLis le contrat de l’instruction

    L’architecture du jeu d’instructions définit ce que add a0, a1, a2 doit faire : lire les deux sources et écrire leur somme dans la destination. La microarchitecture détermine comment la puce exécute cette demande. Le langage d’assemblage donne à la demande des noms lisibles ; l’assembleur produit les bits qui la codent. Différentes puces qui implémentent le même contrat d’instruction doivent donner le même résultat visible.

  3. 3
    DéfiCompte les instructions après l’assemblage

    Un assembleur développe une instruction li en deux instructions machine, suivies d’une instruction add. Avec uniquement des codages de 32 bits, combien d’octets d’instructions ces deux lignes d’assemblage produisent-elles ?

    ---reveal---

    Il y a trois instructions réelles de 4 octets chacune, soit 12 octets. Le texte compte deux lignes, mais le développement détermine la taille du code machine. Les étiquettes et les commentaires n’ajoutent aucune instruction exécutée.

  4. 4
    ExempleConserve la somme intermédiaire

    Calcule r = (u + v) - w avec u = 18, v = 7, w = 6 dans a1, a2, a3 :

    add t0, a1, a2
    sub a0, t0, a3
    

    Après la première instruction, t0 contient 25. Après la seconde, a0 contient 19. Les trois entrées restent disponibles, car aucune des deux instructions n’écrit dans leurs registres. t0 a un rôle : préserver le premier résultat jusqu’à ce que la soustraction le lise.

  5. 5
    DéfiChange la destination, conserve les sources

    Avec les mêmes entrées, on exécute maintenant sub a3, t0, a3 après l’addition. Qu’est-ce qui change par rapport à l’écriture du résultat dans a0 ?

    ---reveal---

    Le résultat numérique vaut toujours 19, mais il remplace l’ancienne valeur 6 dans a3. L’instruction lit les valeurs sources avant d’écrire dans sa destination. a0 est inchangé : un appelant qui attendrait le résultat dans ce registre ne recevrait donc pas ce nouveau résultat.

  6. 6
    ExplicationTransforme un indice en adresse

    Un tableau d’entiers commence à l’adresse contenue dans s1. Chaque int RV32 ILP32 occupe 4 octets : l’élément 2 commence donc 8 octets après la base et l’élément 5 commence 20 octets après celle-ci. Le chargement lw t0, 8(s1) lit la valeur de l’élément 2 ; il ne place pas l’adresse elle-même dans t0. Le registre de base reste inchangé.

  7. 7
    ExempleCharge, calcule, range

    Mets en œuvre A[5] = A[2] + r avec r = 19 dans a0 :

    lw t0, 8(s1)
    add t0, t0, a0
    sw t0, 20(s1)
    

    Si A[2] vaut 11, le chargement donne 11, l’addition donne 30 et le rangement écrit 30 dans A[5]. La lecture et l’écriture en mémoire nécessitent des instructions distinctes autour de l’opération arithmétique. La valeur de A[2] reste 11.

  8. 8
    DéfiRepère la mauvaise unité dans un déplacement

    Quelqu’un remplace la première ligne par lw t0, 2(s1) parce que l’expression désigne A[2]. Quelle erreur a été commise ?

    ---reveal---

    Le déplacement est de 2 octets, et non de deux éléments. L’élément 2 nécessite 8 octets à partir de la base. L’adresse modifiée n’est pas non plus naturellement alignée pour un mot lorsque la base est alignée : l’accès peut donc échouer ou nécessiter un traitement particulier avant même de pouvoir fournir des données utiles.

  9. 9
    ExempleLis un octet d’un mot stocké

    Range 0x7193D2A6 à une adresse alignée sur une machine petit-boutiste. Ses octets, par adresses croissantes, sont 0xA6, 0xD2, 0x93, 0x71. Un chargement d’octet au déplacement 1 sélectionne 0xD2. lbu donne 210, tandis que lb donne −46 en étendant le bit 7 aux positions de poids fort du registre. L’octet en mémoire est identique ; l’instruction de chargement fournit l’interprétation.

  10. 10
    ExplicationTrouve la partie basse signée

    Construis maintenant 0x00003D20 dans un registre. Ses 12 bits de poids faible sont 0xD20, qui représentent −736 comme valeur immédiate signée sur 12 bits. L’étape de construction de la partie haute doit donc commencer un bloc de 4096 octets plus haut que ne le suggère le 0x3 visible. Choisir 0x4 donne 0x4000, dont la soustraction de 0x2E0 permet d’atteindre la valeur voulue.

  11. 11
    ExempleConstruis et vérifie la constante

    Utilise :

    lui t1, 0x4
    addi t1, t1, -736
    

    La première instruction écrit 16 384 ; la seconde soustrait 736, ce qui laisse 15 648, soit 0x3D20. La partie basse est un terme muni d’un signe, et non de simples chiffres à coller en place. li t1, 0x3D20 laisse l’assembleur choisir la construction, mais son développement occupe tout de même de vraies instructions.

  12. 12
    DéfiCorrige une décomposition de constante

    Une décomposition utilise lui t1, 0x3 et addi t1, t1, -736. De combien son résultat diffère-t-il de 0x3D20, et quel champ faut-il corriger ?

    ---reveal---

    Elle produit 0x2D20, exactement 4096 de moins que la valeur voulue. Augmente le champ haut de 0x3 à 0x4 et conserve le terme bas signé. Remplacer le terme par la valeur positive 0xD20 dépasserait la plage positive signée de addi.

  13. 13
    ConseilSuis un registre jusqu’à sa dernière utilisation

    Lorsque tu suis une séquence, écris une valeur à côté du registre qui la contient actuellement. Ne la barre que lorsque ce registre est écrasé. Un appel de procédure ultérieur risque d’écraser les registres temporaires et les registres d’arguments, même si aucune affectation explicite n’apparaît à côté de l’appel. Sauvegarde une valeur encore nécessaire avant de franchir cette étape.

  14. 14
    DéfiCombine les étapes sur l’adresse et sur la valeur

    Avec la base du tableau dans s1, l’élément A[6] vaut 17. t1 contient la constante correctement construite 0x3D20. Que change lw t0, 24(s1); add t0, t0, t1; sw t0, 4(s1) ?

    ---reveal---

    Cette séquence lit A[6], ajoute 17 à 15 648 et range 15 665 dans A[1]. Les deux déplacements comptent des octets, tandis que la constante est une valeur arithmétique. L’élément d’origine à l’indice 6 et l’adresse de base dans s1 restent inchangés.

  15. 15
    RécapitulatifSuis toi-même la prochaine séquence

    Pour une nouvelle séquence d’assemblage, repère les registres lus et écrits, développe les pseudo-instructions dont la taille compte et traduis chaque déplacement mémoire en octets. Vérifie l’extension des chargements de petite taille séparément de l’ordre des octets. Lorsque tu construis une constante, vérifie la valeur haute intermédiaire ajoutée à son terme bas signé avant d’utiliser le résultat comme donnée ou comme adresse.

2

Suis les calculs dans les structures conditionnelles et les boucles

14 étapes
Extrais un champ, choisis des comparaisons signées et suis chaque chemin dans une boucle à compteur.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionRends le chemin d’exécution visible
  2. 2
    ExemplePlace le champ à la bonne position
  3. 3
    ExempleNe conserve que la largeur du champ
  4. 4
    DéfiPrévois un décalage signé
  5. 5
    ExplicationCompare avec la bonne interprétation
  6. 6
    ExempleDistingue un saut d’un appel
  7. 7
    DéfiTeste une configuration de deux façons
  8. 8
    ExempleDessine les deux chemins d’un if
  9. 9
    ExplicationVérifie la borne avant le chargement
  10. 10
    ExempleSuis trois exécutions du corps de la boucle
  11. 11
    DéfiCompte toutes les instructions du chemin exécuté
  12. 12
    LienRemplace les multiplications répétées par une adresse qui avance
  13. 13
    DéfiProtège un chargement par une évaluation en court-circuit
  14. 14
    RécapitulatifVérifie ensemble les valeurs et les chemins
3

Code une instruction et reconstitue un branchement

12 étapes
Place les opérandes dans les champs, vérifie un mot codé et reconstitue un déplacement de branchement dispersé.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionTraduis dans les deux sens
  2. 2
    ExplicationRepère le format et les opérandes
  3. 3
    ExempleÉcris les champs sur toute leur largeur
  4. 4
    ExempleConvertis et décode le mot
  5. 5
    DéfiRepère la source de la valeur d’un rangement
  6. 6
    ExplicationMesure la distance vers l’arrière
  7. 7
    ExempleDivise la valeur immédiate du branchement
  8. 8
    ExempleAssemble le branchement complet
  9. 9
    DéfiRetrouve le bit zéro manquant
  10. 10
    LienConserve la distance relative lorsque le code se déplace
  11. 11
    DéfiUtilise le code opération pour rejeter un décodage erroné
  12. 12
    RécapitulatifVérifie un mot machine en sens inverse
4

Conserve les valeurs nécessaires lors des appels imbriqués

15 étapes
Construis une procédure enveloppe correcte, suis des cadres récursifs et parcours une chaîne d’octets en mémoire.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionProtège ce dont l’appelant a encore besoin
  2. 2
    ExemplePrécise le résultat de la procédure enveloppe
  3. 3
    ExplicationApplique les engagements de préservation
  4. 4
    DéfiChoisis les valeurs à conserver pendant l’appel
  5. 5
    ExempleAlloue et remplis le cadre
  6. 6
    ExempleCombine les résultats avant de libérer le stockage
  7. 7
    ExplicationRépète le schéma par récursivité
  8. 8
    DéfiDépile les arguments sauvegardés
  9. 9
    ExplicationDistingue une adresse de son objet
  10. 10
    ExempleChoisis des opérations sur les octets pour une chaîne
  11. 11
    DéfiTermine aux bonnes adresses
  12. 12
    ConseilRestaure la valeur sauvegardée, pas le nouveau résultat
  13. 13
    LienLa durée de vie d’un objet suit celle du cadre
  14. 14
    DéfiConserve un pointeur de destination pendant un appel auxiliaire
  15. 15
    RécapitulatifVérifie une frontière d’appel complète
5

Suis un appel du code source au démarrage

11 étapes
Suis un symbole pendant la compilation, l’assemblage, l’édition de liens, la résolution dynamique et le chargement du processus.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionDétermine ce que sait chaque outil
  2. 2
    ExplicationCompile pour une architecture du jeu d’instructions cible
  3. 3
    ExempleLaisse l’appel externe inachevé
  4. 4
    DéfiDistingue la production de données de l’exécution
  5. 5
    ExemplePlace le module cible
  6. 6
    ExempleCorrige la référence relative
  7. 7
    ExplicationChoisis quand fournir le code de bibliothèque
  8. 8
    DéfiSitue l’étape de l’échec
  9. 9
    LienCrée un processus avant d’appeler main
  10. 10
    DéfiExplique pourquoi un objet n’est pas le programme terminé
  11. 11
    RécapitulatifRelie une nouvelle erreur à son étape
6

Compare les conceptions avec la bonne mesure

10 étapes
Distingue les instructions visibles, les octets codés et le temps d’exécution lorsque tu évalues un choix d’architecture du jeu d’instructions.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionDistingue trois mesures
  2. 2
    ExempleCompare l’opération mémoire visible
  3. 3
    ExplicationRelie le codage au choix de conception
  4. 4
    DéfiClasse une caractéristique avec soin
  5. 5
    ExempleCalcule le premier temps d’exécution
  6. 6
    ExempleCompare un nombre d’instructions plus élevé
  7. 7
    DéfiDistingue les octets économisés de l’accélération
  8. 8
    LienLa compatibilité dépasse le jeu d’instructions
  9. 9
    DéfiÉvalue une affirmation sur une optimisation en assemblage
  10. 10
    RécapitulatifIndique les éléments qui étayent la comparaison

Jeux d’instructions et langage d’assemblage : QCM

60 Questions Pratiques en 6 séries

Série 1 : Registres, mémoire et constantes10 Questions Pratiques

Question 1 sur 10

Deux processeurs implémentent RV32I avec des pipelines internes différents. Quelle spécification commune détermine le résultat d’une instruction add RV32I ?
  1. L’organisation des processus du système d’exploitation
  2. L’architecture du jeu d’instructions
  3. L’allocation de registres du compilateur
  4. La fréquence d’horloge du processeur
Afficher la réponseMasquer la réponse
Réponse : B
L’architecture du jeu d’instructions définit l’effet visible de add : les deux implémentations doivent donc produire ce résultat. Leurs pipelines internes peuvent être différents. La fréquence d’horloge influe sur le temps, mais ne redéfinit pas le sens de l’addition pour un programme.

Question 2 sur 10

Avant sub a2, a0, a1, a0 contient 28 et a1 contient 9. Quelle valeur est écrite dans a2 ?
  1. 37
  2. −19
  3. 9
  4. 19
Afficher la réponseMasquer la réponse
Réponse : D
Le premier opérande est la destination et la soustraction prend le deuxième opérande moins le troisième. L’instruction écrit donc 28 moins 9, soit 19, dans a2. Inverser les sources donnerait −19, mais ce n’est pas leur ordre ici.

Question 3 sur 10

Un tableau RV32 ILP32 contient des entiers de 4 octets. Combien d’octets après la base l’élément A[7] commence-t-il ?
  1. 7 octets
  2. 11 octets
  3. 28 octets
  4. 24 octets
Afficher la réponseMasquer la réponse
Réponse : C
Chaque élément précédent occupe 4 octets : l’indice 7 commence donc à 28 octets de la base. Utiliser 7 comme déplacement en octets reviendrait à confondre un indice d’élément avec un déplacement d’adresse. L’indice zéro se trouve déjà à la base et n’ajoute pas un élément au compte.

Question 4 sur 10

Dans une mémoire petit-boutiste, sw range 0x86C4217D à 0x3000. Quelle valeur lbu t0, 2(s0) produit-il lorsque s0 = 0x3000 ?
  1. 0x000000C4
  2. 0x00000021
  3. 0xFFFFFFC4
  4. 0x00000086
Afficher la réponseMasquer la réponse
Réponse : A
Les octets rangés sont 0x7D, 0x21, 0xC4 et 0x86 par adresses croissantes. Le déplacement 2 sélectionne 0xC4, et lbu l’étend avec des zéros jusqu’à 0x000000C4. Le résultat avec extension de signe nécessiterait lb, et non le chargement d’octet non signé utilisé ici.

Question 5 sur 10

Quels champ haut de lui et valeur immédiate signée de addi permettent de construire 0x00002ABC en RV32 ?
  1. 0x2 et +2748
  2. 0x2 et −1348
  3. 0x3 et −1348
  4. 0x3 et +2748
Afficher la réponseMasquer la réponse
Réponse : C
La configuration basse de 12 bits 0xABC représente −1348. Partir de 0x3000 et soustraire 1348 permet d’atteindre 0x2ABC : le champ haut est donc 0x3. Traiter 2748 comme un opérande positif de addi échoue, car cette valeur dépasse la limite positive signée sur 12 bits.

Question 6 sur 10

Un appelant conserve une valeur dans t3, appelle une procédure auxiliaire conforme à la convention, puis a besoin de la valeur initiale. Que doit-il prévoir ?
  1. Restaurer t3 uniquement dans la procédure auxiliaire
  2. Traiter t3 comme un registre sauvegardé par l’appelé
  3. Sauvegarder t3 après le retour de la procédure auxiliaire
  4. Protéger la valeur avant l’appel
Afficher la réponseMasquer la réponse
Réponse : D
t3 est sauvegardé par l’appelant : une procédure auxiliaire conforme à la convention peut donc l’écraser. L’appelant doit sauvegarder la valeur encore nécessaire ou prévoir un emplacement préservé équivalent avant l’appel. La procédure auxiliaire n’a pas l’obligation de restaurer les registres temporaires simplement parce que son appelant en aura besoin.

Question 7 sur 10

En RV32, auipc t0, 0x2 s’exécute à 0x4000, suivi de addi t0, t0, -24. Quelle est la valeur finale de t0 ?
  1. 0x00005FEC
  2. 0x00005FE8
  3. 0x00006018
  4. 0x00002000
Afficher la réponseMasquer la réponse
Réponse : B
auipc ajoute 0x2000 à sa propre adresse, ce qui produit 0x6000. Soustraire 24 laisse 0x5FE8. Utiliser l’adresse de l’instruction suivante comme base du compteur ordinal augmenterait à tort le résultat de 4 octets.

Question 8 sur 10

Un octet contenant 0xB2 est chargé une fois avec lb et une fois avec lbu. Quels résultats décimaux les deux chargements produisent-ils, dans cet ordre ?
  1. −78 et 178
  2. 178 et 178
  3. −78 et −78
  4. −50 et 178
Afficher la réponseMasquer la réponse
Réponse : A
Comme valeur signée sur 8 bits, 0xB2 vaut 178 moins 256, soit −78. lb effectue une extension de signe de cette valeur, tandis que lbu produit la valeur non signée 178. Rendre les deux résultats positifs reviendrait à ignorer la règle d’extension de signe du chargement signé.

Question 9 sur 10

Un tableau de structures utilise struct rec { int key; int value; }; sous RV32 ILP32, sans remplissage supplémentaire. Sa base est 0x5000. Quelle adresse est lue pour A[3].value, et quel octet signé lb renvoie-t-il depuis cette adresse si le champ contient 0x000000E9 en mémoire petit-boutiste ?
  1. 0x5018 et −23
  2. 0x501C et 233
  3. 0x5010 et −23
  4. 0x501C et −23
Afficher la réponseMasquer la réponse
Réponse : D
Chaque structure occupe 8 octets : l’élément 3 commence donc à 24 octets de la base ; son second champ ajoute 4, ce qui donne 0x501C. Le premier octet à cette adresse est 0xE9, que lb étend par son signe pour obtenir −23. Renvoyer 233 appliquerait la règle d’extension non signée.

Question 10 sur 10

En RV32, la séquence ci-dessous s’exécute avec t1 = 0x7000 ; le mot à 0x7030 vaut 6. Quelle valeur est rangée à 0x7034 ?

lui t0, 0x4
addi t0, t0, -1984
lw t2, 48(t1)
add t2, t2, t0
sw t2, 52(t1)
  1. 0x00003840
  2. 0x00003846
  3. 0x00004846
  4. 0x00004006
Afficher la réponseMasquer la réponse
Réponse : B
La constante devient 0x4000 moins 0x7C0, soit 0x3840. Le chargement fournit 6 : l’addition produit donc 0x3846, que le rangement final écrit en mémoire. 0x3840 est seulement la constante construite et omet l’opérande chargé.
Série 2 : Calculs arithmétiques et chemins dans un programme10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Quel résultat hexadécimal sur 32 bits RV32I produit-il lorsque add ajoute 1 à 0xFFFFFFFF ?
  1. 0xFFFFFFFF
  2. 0x80000000
  3. 0x00000000
  4. 0x7FFFFFFF
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Quelle instruction réelle met en œuvre bgt a2, a3, Done ?
  1. blt a2, a3, Done
  2. blt a3, a2, Done
  3. bge a3, a2, Done
  4. bge a2, a3, Done
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Après srli t0, a0, 8 et andi t0, t0, 0x1F, quelle valeur se trouve dans t0 si a0 = 0x0000D6B9 ?
  1. 0x00000016
  2. 0x000000D6
  3. 0x00000019
  4. 0x00000006
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Pour a0 = -11 en signé, quels résultats srai t0, a0, 2 et la division entière C -11 / 4 produisent-ils respectivement ?
  1. −3 et −2
  2. −2 et −3
  3. −3 et −3
  4. −2 et −2
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

a0 = 0xFFFFFFFE et a1 = 0x00000003. Laquelle des instructions blt a0, a1, L et bltu a0, a1, L effectue son branchement ?
  1. Seulement bltu
  2. Les deux branchements
  3. Aucun des deux branchements
  4. Seulement blt
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une traduction de if (x != y) r = 8; else r = 3; effectue un branchement vers Else lorsque l’égalité est vérifiée. Que faut-il placer après l’affectation de 8 pour que r reste égal à 8 sur le chemin où les valeurs sont différentes ?
  1. Un branchement de retour vers la comparaison
  2. Un saut après la partie « sinon »
  3. Une seconde affectation de 3
  4. Un rechargement de x avant Else
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

La boucle while (A[i] == 6) i++; commence avec i = 1 et A = {6, 6, 6, 4, 6}. Quelle est la valeur de i à sa sortie ?
  1. 2
  2. 4
  3. 3
  4. 5
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une procédure feuille exécute le code ci-dessous sur un tableau d’entiers de 4 octets {2, -5, 9}, dont la base est dans a0 et le nombre d’éléments 3 dans a1. Chaque instruction affichée est une instruction machine. Quels résultat et nombre dynamique d’instructions produit-elle ?

addi t0, zero, 0
addi t1, zero, 0
L: bge t0, a1, D
slli t2, t0, 2
add t2, a0, t2
lw t3, 0(t2)
add t1, t1, t3
addi t0, t0, 1
jal zero, L
D: addi a0, t1, 0
jalr zero, 0(ra)
  1. 6 et 25
  2. 9 et 26
  3. 6 et 24
  4. 6 et 26
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

À 0x8000, jalr ra, 7(t0) s’exécute avec t0 = 0x903A. Quelle cible est utilisée, quelle valeur est placée dans ra et quelle instruction peut effectuer un retour par ce lien ?
  1. 0x9041 ; 0x8004 ; jalr zero, 0(ra)
  2. 0x9040 ; 0x8004 ; jalr zero, 0(ra)
  3. 0x9040 ; 0x8000 ; jalr zero, 0(ra)
  4. 0x9040 ; 0x8004 ; jal zero, 0(ra)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

La condition C est p != 0 && *p == 9, avec p dans a0. Pour le chemin « alors », quel ordre de contrôle du déroulement préserve la condition et évite de déréférencer un pointeur nul ?
  1. Rejeter p nul ; charger *p ; rejeter si différent de 9
  2. Charger *p ; rejeter p nul ; comparer à 9
  3. Rejeter p non nul ; charger *p ; rejeter si différent de 9
  4. Rejeter p nul ; comparer p à 9 ; charger *p
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 3 : Champs d’instruction et cibles relatives10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Dans sw a2, 20(sp), quel registre fournit le champ rs1 ?
  1. a2 (x12)
  2. zero (x0)
  3. ra (x1)
  4. sp (x2)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Quelle est la configuration immédiate sur 12 bits pour addi t2, s1, -36 ?
  1. 1111 1101 1100
  2. 0000 0010 0100
  3. 1111 1101 1011
  4. 1111 1110 1100
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Quelle modification unique transforme une instruction ADD de type R en SUB avec les mêmes registres ?
  1. Mettre opcode à 0100011
  2. Mettre funct3 à 100
  3. Mettre funct7 à 0100000
  4. Mettre rd à la première source
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Un branchement à 0x308C cible 0x3068. Quel déplacement signé en octets est codé ?
  1. −40
  2. −36
  3. −9
  4. +36
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une instruction jal à 0x1200 a un déplacement signé de +84 et ra comme destination. Quelles sont la cible et l’adresse de retour obtenues ?
  1. 0x1258 et 0x1204
  2. 0x1254 et 0x1204
  3. 0x1254 et 0x1258
  4. 0x1350 et 0x1204
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

En utilisant les champs de type I, quel mot code addi t2, s1, -36 ? Ici, t2 = x7, s1 = x9, le code opération ADDI est 0x13 et funct3 = 000.
  1. 0xFDC48393
  2. 0x02448393
  3. 0xFDC38493
  4. 0xFDC483B3
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Décode 0xFEC12623. Le code opération 0x23 sélectionne un rangement et funct3 = 010 sélectionne un mot. Quelle instruction d’assemblage représente-t-il ?
  1. sw a2, -12(sp)
  2. lw a2, -20(sp)
  3. sw sp, -20(a2)
  4. sw a2, -20(sp)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une instruction bne a2, zero, Loop de type B se trouve à 0x4018, avec Loop à 0x400C. Avec le code opération 0x63 et funct3 = 001, quel mot codé atteint Loop ?
  1. 0xFE0618E3
  2. 0x00061663
  3. 0xFE061AE3
  4. 0xFE061CE3
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

À l’adresse 0x6000, le mot 0x000022B7 est suivi de 0xFE028293 à 0x6004. Quelle valeur reste dans t0 ? Ces mots se décodent respectivement en LUI de type U et ADDI de type I.
  1. 0x00002020
  2. 0x00001FDC
  3. 0x00007FE0
  4. 0x00001FE0
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Un branchement et sa cible se trouvent initialement dans la même section aux déplacements 0x30 et 0x54. La section se déplace de 0x9000 à 0xB000 sans changer sa disposition interne. Quels sont le déplacement du branchement et l’adresse cible obtenus ?
  1. +0x24 et 0x9054
  2. +0x2024 et 0xB054
  3. +0x24 et 0xB054
  4. +0x20 et 0xB054
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 4 : Appels, récursivité et accès par pointeur10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une procédure RISC-V conforme à la convention modifie s4. Quelle valeur s4 doit-il contenir à son retour ?
  1. La valeur initiale de s4 de l’appelant
  2. Le premier résultat de la procédure
  3. L’adresse après l’appel
  4. L’adresse actuelle du sommet de la pile
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

En RV32 ILP32, quelle instruction fait avancer a0 vers l’élément suivant lorsqu’il contient un int * ?
  1. addi a0, a0, 1
  2. addi a0, a0, 2
  3. addi a0, a0, 8
  4. addi a0, a0, 4
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Une procédure commence avec sp = 0x8000, alloue 32 octets, puis exécute sw ra, 28(sp). À quelle adresse le mot sauvegardé commence-t-il ?
  1. 0x7FE0
  2. 0x801C
  3. 0x7FFC
  4. 0x7FF0
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une procédure feuille reçoit un argument dans a0, effectue un calcul dans s1, puis restaure la valeur initiale de s1 avant de revenir. Que doit-elle faire avant cette restauration pour renvoyer l’entier calculé ?
  1. Sauvegarder a0 de l’appelant à chaque sortie
  2. Déplacer le résultat calculé de s1 dans a0
  3. Restaurer s1, puis copier s1 dans a0
  4. Revenir en laissant le résultat calculé dans s1
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une boucle sur une chaîne range un octet à la fois, incrémente les deux pointeurs et revient en arrière si cet octet est non nul. Combien d’itérations copient « logic », y compris son caractère terminal C ?
  1. 5
  2. 6
  3. 7
  4. 10
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une procédure non feuille respectant l’interface binaire d’application standard ne doit sauvegarder que ra et s2, chacun sur un mot. Quelle est la plus petite taille positive de cadre qui préserve l’alignement requis de la pile ?
  1. 4 octets
  2. 8 octets
  3. 16 octets
  4. 12 octets
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une fonction reçoit n dans a0 et doit renvoyer helper(n) + n. helper suit l’interface binaire d’application et peut écraser tous les registres sauvegardés par l’appelant. La valeur initiale de ra de la fonction doit aussi être conservée. Quel plan fonctionne avec un cadre de 16 octets ?
  1. Sauvegarder ra et n ; appeler ; recharger n dans t0 ; additionner ; restaurer ra et sp
  2. Sauvegarder seulement ra ; copier n dans t0 ; appeler ; ajouter t0 ; restaurer ra et sp
  3. Sauvegarder seulement n ; appeler ; recharger n ; additionner ; restaurer sp ; revenir par ra
  4. Sauvegarder ra et n ; appeler ; restaurer n dans a0 ; ajouter a0 à lui-même ; restaurer ra
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une fonction récursive alloue un cadre de 16 octets à chaque entrée, sauvegarde n et ra, renvoie 1 pour n < 2 et renvoie sinon n * f(n - 1). Avec n = 4 et sp = 0x9000 au départ, quels sont le résultat et la plus basse valeur de sp atteinte ?
  1. 24 et 0x8FD0
  2. 6 et 0x8FC0
  3. 24 et 0x8FB0
  4. 24 et 0x8FC0
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Sous RV32 ILP32, la mémoire à 0xA000 contient la valeur de pointeur 0xB000. Un tableau d’entiers de 4 octets commence à 0xB000, et son élément 4 vaut 27. Avec a0 = 0xA000, quelle séquence laisse cet élément dans a0 ?
  1. lw a0, 16(a0) ; lw a0, 0(a0)
  2. lw t0, 0(a0) ; lw a0, 16(t0)
  3. addi t0, a0, 16 ; lw a0, 0(t0)
  4. lw t0, 0(a0) ; lw a0, 4(t0)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une procédure alloue 32 octets, range ra à 28(sp) et s0 à 24(sp), puis appelle une procédure auxiliaire. À la sortie, elle ajoute d’abord 16 à sp, effectue des chargements depuis 24(sp) et 28(sp), ajoute encore 16, puis revient. Pourquoi cela peut-il échouer alors que sp retrouve finalement sa valeur initiale ?
  1. Les chargements utilisent des adresses trop hautes de 16 octets
  2. La procédure auxiliaire doit préserver tous les registres temporaires
  3. Un cadre ne peut pas contenir d’octets inutilisés
  4. Le pointeur de pile final est mal aligné
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 5 : Construire, lier et démarrer un programme10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Quel fichier gcc -c module.c produit-il sans effectuer d’édition de liens ?
  1. Une image de processus exécutable
  2. Un texte source en langage d’assemblage
  3. Un fichier objet relogeable
  4. Un cache de symboles de bibliothèque partagée
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Quel composant projette le code et les données d’un exécutable dans un nouveau processus et prépare sa pile initiale ?
  1. Le compilateur
  2. Le chargeur
  3. L’assembleur
  4. L’éditeur de liens statique
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Un fichier objet définit main mais appelle mix, qui est défini dans un second objet. Qu’est-ce qui permet à l’éditeur de liens de relier cet appel à sa définition ?
  1. La table des lignes de débogage et les positions dans les fichiers sources
  2. Les codes opération et une convention commune pour les registres
  3. L’organisation initiale de la pile et le point d’entrée de l’exécutable
  4. Les informations sur les symboles et les entrées de relogement de l’appel
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une étiquette locale est référencée avant sa définition, située plus loin dans la même section d’assemblage. Comment un assembleur peut-il terminer ce branchement ?
  1. Enregistrer la référence et résoudre sa position plus tard
  2. Demander au chargeur d’analyser l’étiquette source
  3. Traiter le nom de l’étiquette comme un entier immédiat
  4. Produire le branchement avec un déplacement toujours nul
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Un programme a été lié statiquement à l’implémentation de filter d’une bibliothèque. L’archive de la bibliothèque est ensuite corrigée, mais l’exécutable reste inchangé. Quel code l’exécutable contient-il pour filter ?
  1. Le code d’origine copié lors de l’édition de liens
  2. Le code corrigé de l’archive au prochain appel
  3. Une nouvelle version interprétée du code source
  4. Un code intermédiaire PLT qui charge un code partagé de remplacement
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un programme RISC-V destiné à Linux place 93 dans a7, 7 dans a0, puis exécute ecall. Quel service est demandé ?
  1. Écrire 7 octets dans un flux standard
  2. Renvoyer 93 à l’appelant courant
  3. Terminer le processus avec le code de sortie 7
  4. Allouer un cadre de pile de 7 octets
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Un éditeur de liens place le code à 0x20000 et les données à 0x30000000. Le module P apporte 0x180 octets de code et 0x30 octets de données ; Q suit immédiatement. Un appel dans P se trouve au déplacement 0x70 dans le code ; sa cible dans Q se trouve au déplacement 0x20 dans le code de Q. La variable de Q se trouve au déplacement 0x14 dans ses données. Quels sont le déplacement en octets de l’appel et l’adresse de la variable ?
  1. +0x1A0 et 0x30000044
  2. +0x130 et 0x30000194
  3. +0x12C et 0x30000044
  4. +0x130 et 0x30000044
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un texte RV32I à 0x1000 utilise les développements suivants : li t0, 5000 utilise deux instructions, mv a0, t0 une et call Helper une paire de deux instructions sans relaxation. Helper revient normalement. À quelle adresse l’exécution reprend-elle après l’appel, et combien d’octets de code précèdent cette reprise ?
  1. 0x100C et 12 octets
  2. 0x1014 et 20 octets
  3. 0x1010 et 16 octets
  4. 0x1018 et 24 octets
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Une fonction de bibliothèque commence au déplacement 0x2C0 dans une bibliothèque partagée. Un appelant local se trouve au déplacement 0x90 ; un pointeur vers une fonction extérieure à la bibliothèque est stocké dans une entrée de la GOT. La base de la bibliothèque passe de 0x40000000 à 0x50000000. Quelle description traite correctement les deux références ?
  1. Ajouter 0x10000000 au déplacement local et garder chaque entrée de la GOT
  2. Garder la cible locale à 0x400002C0 ; reloger seulement l’instruction d’appel
  3. Soustraire 0x10000000 de la distance locale et vider toute la GOT
  4. Garder la distance locale +0x230 ; lier la cible externe de la GOT selon les besoins
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Un exécutable a été lié avec succès à une bibliothèque partagée. Plus tard, cette bibliothèque est absente et le chargement de l’exécutable échoue avant main. Qu’est-ce qui explique cet échec, et qu’en serait-il du code de cette bibliothèque inclus statiquement ?
  1. Le chargement exige le fichier partagé ; le code inclus serait déjà dans l’exécutable
  2. L’édition de liens se répète au lancement ; le code inclus exigerait encore l’archive d’origine
  3. L’objet d’entrée manque ; le code inclus exigerait encore tous les objets d’entrée
  4. Le fichier ne contient pas de code machine ; le code inclus contournerait le chargeur
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 6 : Évalue les conceptions et analyse le langage d’assemblage10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Quelle paire d’instructions RISC-V effectue l’addition avec la mémoire exprimée par l’instruction x86 add eax, [ebx+12], avec eax correspondant à a0 et ebx à s1 ?
  1. addi a0, a0, 12 ; add a0, a0, s1
  2. lw a0, 12(s1) ; add a0, a0, a0
  3. sw a0, 12(s1) ; add a0, a0, s1
  4. lw t0, 12(s1) ; add a0, a0, t0
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Un processeur possède une architecture du jeu d’instructions à chargement et rangement et des indicateurs de condition. Que peut-on conclure de l’affirmation selon laquelle ces indicateurs en font une architecture CISC ?
  1. Les indicateurs impliquent des opérations à deux opérandes : l’affirmation est donc juste.
  2. Les indicateurs peuvent exister dans une architecture RISC : l’affirmation est donc fausse.
  3. Une architecture à chargement et rangement ne peut pas utiliser d’indicateurs : la description est contradictoire.
  4. Les indicateurs impliquent des opérations arithmétiques combinées à la mémoire : l’affirmation est donc juste.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

La version P exécute 0,8 milliard d’instructions avec un CPI de 1,5 et une fréquence de 2 GHz. La version Q exécute 1,0 milliard d’instructions avec un CPI de 1,0 et une fréquence de 2 GHz. Quels sont les temps d’exécution de P et de Q ?
  1. 0,60 s et 0,50 s
  2. 0,40 s et 0,50 s
  3. 0,60 s et 1,00 s
  4. 1,20 s et 1,00 s
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Un programme comporte 120 instructions machine. Si 72 utilisent des codages sur 16 bits et les autres des codages sur 32 bits, combien d’octets de code occupe-t-il ?
  1. 240 octets
  2. 384 octets
  3. 336 octets
  4. 408 octets
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

La conception P exécute 1,2 milliard d’instructions avec un CPI de 1,1 et une fréquence de 2,2 GHz. La conception Q exécute 0,9 milliard d’instructions avec un CPI de 1,8 et une fréquence de 2,7 GHz. Une optimisation proposée réduit le nombre d’instructions de Q de 10 %, mais augmente son CPI de 20 %, sans changer sa fréquence. Comment Q optimisé se compare-t-il à P ?
  1. Q prend 0,648 s et est plus lent que P
  2. Q prend 0,54 s et est plus rapide que P
  3. Q prend 0,60 s et est à égalité avec P
  4. Q prend 0,72 s et est plus lent que P
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une routine de 200 instructions utilise initialement uniquement des codages sur 32 bits. Après le passage de 110 codages à 16 bits, la séquence d’instructions et le temps d’exécution mesuré sont inchangés. Quels sont la nouvelle taille et le constat justifié sur la performance ?
  1. 580 octets ; exécution 27,5 % plus rapide
  2. 620 octets ; moins d’octets et vitesse mesurée inchangée
  3. 400 octets ; moitié moins d’instructions exécutées
  4. 580 octets ; moins d’octets et vitesse mesurée inchangée
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Un fichier binaire utilise RV32I et mul, attend la convention d’appel ILP32 et invoque des appels système Linux RISC-V. Une carte annonce RV32I, utilise ILP32 et exécute un simulateur doté de sa propre interface de services. Quelles exigences supplémentaires doivent être satisfaites pour que ce fichier binaire fonctionne comme prévu ?
  1. La prise en charge de mul et l’interface de services actuelle du simulateur
  2. La prise en charge de mul et l’interface de services Linux attendue
  3. RV32I seul et l’interface de services Linux attendue
  4. La prise en charge de mul et la seule convention ILP32 correspondante
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un tableau RV32 ILP32 contient struct sample { int id; int reading; }; sans remplissage supplémentaire. Un pointeur vers l’élément 2 se trouve dans a0. Le champ reading de l’élément suivant vaut 31. Quels calcul d’adresse et chargement récupèrent ce champ sans modifier a0 ?
  1. addi t0, a0, 4 ; lw t1, 4(t0)
  2. addi t0, a0, 8 ; lw t1, 0(t0)
  3. addi t0, a0, 8 ; lw t1, 4(t0)
  4. addi t0, a0, 12 ; lw t1, 4(t0)
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Cette procédure feuille doit renvoyer la somme de n entiers, avec n non négatif. Elle est appelée avec n = 0 et une adresse qui ne doit pas être lue. Quelle correction minimale du contrôle du déroulement évite l’accès invalide tout en préservant le comportement pour un nombre positif d’éléments ?

addi t1, zero, 0
L: lw t0, 0(a0)
add t1, t1, t0
addi a0, a0, 4
addi a1, a1, -1
bne a1, zero, L
D: addi a0, t1, 0
ret
  1. Utiliser un chargement d’octet non signé dans la boucle
  2. Insérer beq a1, zero, D avant L
  3. Remplacer le bne final par un blt signé
  4. Initialiser t1 au premier élément du tableau
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une routine écrite à la main réduit le nombre d’instructions exécutées de 50 millions à 40 millions sur le même processeur à 2 GHz. Le CPI moyen passe de 1,2 à 1,6. Qu’arrive-t-il au temps d’exécution et que montre ce résultat sur l’affirmation relative au nombre d’instructions ?
  1. Il diminue de 30 ms à 24 ms ; la réduction du nombre d’instructions détermine le temps
  2. Il passe de 60 ms à 64 ms ; le CPI compense la réduction du nombre d’instructions
  3. Il passe de 30 ms à 32 ms ; le CPI compense la réduction du nombre d’instructions
  4. Il passe de 24 ms à 32 ms ; le CPI compense la réduction du nombre d’instructions
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Tu bloques sur ton propre exercice ? Aide aux Devoirs

Mots-Clés

82 Mots-Clés, chacun avec sa définition

A

Adressage avec déplacement
L’adressage avec déplacement forme une adresse mémoire effective en ajoutant un déplacement constant signé à la valeur d’un registre de base.
Dans lw t0, 20(s1), le mot chargé commence 20 octets après l’adresse stockée dans s1.
Adressage indexé avec facteur d’échelle
L’adressage indexé avec facteur d’échelle combine une base et un index multiplié par un pas, en ajoutant souvent un déplacement pour former une adresse mémoire.
Un opérande en mémoire x86 peut exprimer en une instruction une base augmentée de quatre fois un index et de 8.
Adressage par octet
L’adressage par octet est une organisation de la mémoire dans laquelle chaque adresse désigne un octet, si bien que les valeurs plus grandes occupent des adresses consécutives.
Deux éléments adjacents de 32 bits d’un tableau commencent à des adresses qui diffèrent de 4, et non de 1.
Adresse relative au compteur ordinal
Une adresse relative au compteur ordinal est formée à partir de l’adresse propre d’une instruction et d’un déplacement codé, ce qui situe un emplacement par rapport au code.
Une séquence auipc peut atteindre des données locales même lorsque tout le groupe de code et de données se déplace.
Alignement naturel
L’alignement naturel place un accès mémoire à une adresse divisible par la taille de l’accès, par exemple un multiple de 4 pour un mot.
Un compilateur peut insérer du remplissage afin qu’un demi-mot ou un mot commence à une adresse correctement alignée.
Allocation de registres
L’allocation de registres est l’attribution par le compilateur des valeurs du programme aux registres disponibles en nombre limité à chaque point de l’exécution.
Une boucle optimisée peut conserver son indice et sa somme cumulée dans des registres pendant de nombreuses itérations.
Appel système
Un appel système est une demande de service adressée par un programme au système d’exploitation par l’interface définie par son environnement d’exécution.
Sous Linux RISC-V, ecall avec 93 dans a7 demande la sortie du processus, en utilisant a0 pour le code de sortie.
Architecture à chargement et rangement
Une architecture à chargement et rangement utilise des instructions dédiées aux transferts mémoire, tandis que les opérations arithmétiques et logiques agissent sur les valeurs des registres et les valeurs immédiates.
Pour additionner un élément de tableau, RISC-V le charge dans un registre avant d’émettre une instruction arithmétique.
Architecture du jeu d’instructions
Une architecture du jeu d’instructions est le contrat qui définit les instructions, les registres et le comportement de la machine visibles par les logiciels.
Deux processeurs peuvent implémenter la même architecture du jeu d’instructions avec des circuits internes et des vitesses d’exécution différents.
Assembleur
Un assembleur convertit le texte d’assemblage en instructions et données codées tout en enregistrant les symboles et les références à résoudre ultérieurement.
Un assembleur développe call et peut laisser des entrées de relogement pour une fonction définie dans un autre module.

B

Bit implicite
Un bit implicite est une valeur reconstituée à partir d’une règle de codage sans occuper sa propre position de bit stocké.
Le bit de poids faible d’un déplacement de branchement de type B est implicitement nul, car la distance en octets est paire.
Bloc de base
Un bloc de base est une séquence d’instructions en ligne droite dans laquelle le contrôle entre au début et ne sort qu’à la fin lors du déroulement normal.
Une étiquette ciblée par un branchement commence un nouveau bloc qu’un compilateur peut analyser séparément.
Borne de boucle
Une borne de boucle limite les itérations permises par la comparaison d’une boucle et détermine ainsi les indices ou les adresses auxquels son corps peut accéder.
Un test qui provoque la sortie lorsque l’indice atteint la longueur maintient l’indice du tableau inférieur à cette longueur.
Branchement conditionnel
Un branchement conditionnel ne change l’adresse de l’instruction suivante que si sa comparaison réussit ; sinon, l’exécution se poursuit en séquence.
bne t0, t1, Loop ne revient à Loop que tant que les configurations des deux registres diffèrent.

C

Cadre de pile
Un cadre de pile est la zone allouée aux informations sauvegardées, aux objets locaux, aux valeurs transférées en mémoire faute de registres et aux arguments sur la pile pour une activation de procédure.
Trois appels actifs peuvent contenir trois copies distinctes de l’argument sauvegardé d’une même fonction.
Caractère nul terminal
Un caractère nul terminal est l’octet nul qui marque la fin d’une chaîne d’octets C et occupe un emplacement après le dernier octet non nul.
Une boucle de copie de chaîne range le caractère terminal afin que la destination ait elle aussi une fin reconnaissable.
Champ de fonction
Un champ de fonction précise un code opération afin de sélectionner une opération particulière, comme l’addition ou la soustraction parmi les opérations arithmétiques sur registres.
Les mêmes registres et le même code opération peuvent représenter ADD ou SUB selon les bits de funct7.
Champ de registre
Un champ de registre est une partie de largeur fixe du codage d’une instruction qui identifie par son numéro un registre source ou de destination.
Un champ de 5 bits représente a0 par le numéro de registre 10, écrit 01010 en binaire.
Chargement d’adresse
Le chargement d’adresse est une opération de l’assembleur qui forme l’adresse d’une étiquette comme valeur de registre sans lire l’objet stocké à cette adresse.
Après la t0, Item, un chargement distinct est nécessaire pour lire la valeur contenue à Item.
Chargeur
Un chargeur prépare un exécutable comme processus en cours d’exécution en projetant son contenu et en établissant l’état initial de la mémoire et des registres.
Le démarrage ne commence qu’après la préparation du code, des données et de la pile initiale du programme.
CISC
CISC est une approche de conception des jeux d’instructions qui prend en charge des opérations et des formes d’adressage plus riches, généralement grâce à des codages de longueur variable.
Une instruction x86 peut combiner la lecture d’un opérande en mémoire et son addition à un registre.
Code indépendant de la position
Le code indépendant de la position utilise des références relatives et l’indirection afin de pouvoir s’exécuter correctement à différentes adresses de chargement.
Déplacer une bibliothèque et ses cibles locales ensemble préserve les distances utilisées par ses références locales.
Code opération
Un code opération est un champ d’instruction qui identifie un groupe d’opérations et aide à déterminer la disposition utilisée pour décoder les autres bits.
Les 7 bits de poids faible distinguent un rangement de mot RV32I d’une instruction arithmétique sur registres.
Compatibilité binaire
La compatibilité binaire permet à des logiciels existants en code machine de s’exécuter sans recompilation lorsque leurs exigences d’architecture du jeu d’instructions, d’interface binaire d’application et d’environnement sont satisfaites.
L’utilisation d’une extension facultative ajoute une exigence matérielle à la prise en charge de l’architecture du jeu d’instructions de base.
Compilateur
Un compilateur traduit un programme dans une autre représentation, en sélectionnant et en optimisant couramment des instructions machine pour une architecture du jeu d’instructions cible.
Un compilateur ciblant RISC-V peut remplacer des calculs d’indice répétés par une incrémentation de pointeur.
Compilation à la volée
La compilation à la volée génère du code machine pendant l’exécution, en concentrant souvent l’effort de compilation sur le code exécuté à plusieurs reprises.
Une machine virtuelle peut d’abord interpréter le bytecode, puis compiler une méthode fréquemment exécutée.
Compteur ordinal
Le compteur ordinal est le registre qui contient l’adresse de l’instruction en cours de recherche ou d’exécution dans la séquence d’instructions.
Un branchement pris remplace la progression en séquence du compteur ordinal par l’adresse calculée pour sa cible.
Convention d’appel
Une convention d’appel est l’accord qui précise les emplacements des arguments et des résultats ainsi que la préservation des registres lors des appels de procédure.
Les procédures RISC-V sur les entiers transmettent leurs premiers arguments dans a0 à a7.
Cycles par instruction (CPI)
Les cycles par instruction sont le nombre moyen de cycles d’horloge utilisés par instruction exécutée pour un programme et une implémentation donnés.
Un programme comportant davantage d’instructions peut finir plus tôt si son CPI est suffisamment inférieur.

D

Débordement de registres
Le débordement de registres consiste à stocker en mémoire une valeur de registre encore nécessaire pour pouvoir réutiliser le registre, puis à recharger la valeur au besoin.
Des valeurs supplémentaires encore nécessaires peuvent ajouter des rangements et des chargements sur la pile même lorsque le code C initial n’a pas d’accès mémoire explicite.
Décalage arithmétique à droite
Un décalage arithmétique à droite déplace les bits vers les positions de poids faible et remplit les positions de poids fort avec le bit de signe initial.
Décaler −7 en signé de 1 position vers la droite donne −4, car le résultat est arrondi vers l’infini négatif.
Décalage logique à droite
Un décalage logique à droite déplace les bits vers les positions de poids faible et remplit les positions de poids fort libérées avec des zéros, ce qui permet notamment de diviser des valeurs non signées par des puissances de deux.
Un décalage logique à droite d’une configuration signée négative peut produire un grand résultat positif.
Densité du code
La densité du code décrit la compacité avec laquelle les instructions machine représentent un programme ; elle se compare couramment par le nombre d’octets nécessaires au code.
Les instructions compressées peuvent réduire le nombre d’octets du code tout en préservant le nombre d’opérations exécutées.
Dépassement de capacité
Un dépassement de capacité se produit lorsqu’un résultat arithmétique se trouve hors de la plage représentable pour la largeur et l’interprétation signée choisies.
L’addition RV32 conserve les bits de poids faible du résultat même lorsque la somme signée ne tient pas sur 32 bits.
Déplacement d’un champ de structure
Un déplacement d’un champ de structure est la distance en octets entre le début d’une structure et l’un de ses membres, déterminée par sa disposition selon l’interface binaire d’application.
Un chargement ajoute le déplacement du champ à l’adresse de l’objet courant pour atteindre le membre de cet objet.
Déplacement de branchement
Un déplacement de branchement est la distance signée en octets entre l’adresse propre de l’instruction de branchement et l’adresse de sa cible.
Une cible située en arrière donne un déplacement négatif même lorsque les deux adresses sont positives.
Déréférencement
Un déréférencement accède à l’objet désigné par une valeur de pointeur et se met en œuvre par un chargement ou un rangement mémoire approprié.
lw t0, 0(a0) lit un entier de 4 octets à l’adresse actuellement contenue dans a0.
Désassemblage
Le désassemblage consiste à reconvertir les bits d’instructions codées en noms d’instructions et en opérandes à l’aide des règles de décodage de l’architecture du jeu d’instructions.
Un désassembleur repère le code opération avant de traiter les autres bits comme des registres ou des fragments de valeurs immédiates.
Directive d’assemblage
Une directive d’assemblage contrôle la production de données, la sélection des sections, l’alignement ou les métadonnées des symboles sans être exécutée par le processeur.
La directive .word 5 produit une valeur de 4 octets, tandis que .text sélectionne l’endroit où placer le code suivant.
Durée de vie d’un objet
La durée de vie d’un objet est la période pendant laquelle son stockage représente un objet valide auquel le programme est autorisé à accéder.
Renvoyer un pointeur vers un objet local ne maintient pas cet objet en vie après le retour de sa fonction.

E

Extension de signe
L’extension de signe élargit une configuration de bits signée en copiant son bit de poids fort dans les nouvelles positions de poids supérieur, ce qui préserve sa valeur signée.
Charger l’octet 0xF0 avec lb produit 0xFFFFFFF0, la représentation de −16 sur 32 bits.
Extension par des zéros
L’extension par des zéros élargit une configuration de bits en remplissant les nouvelles positions de poids fort avec des zéros, ce qui préserve la valeur interprétée comme non signée.
Charger l’octet 0xF0 avec lbu produit la valeur positive 240 dans un registre de 32 bits.

É

Éditeur de liens
Un éditeur de liens combine des modules objets et des bibliothèques, dispose le contenu exécutable, résout les symboles et corrige les références dépendant des adresses.
Une erreur de référence non définie signifie que l’édition de liens finale n’a pas pu trouver une définition de symbole nécessaire.
Édition de liens dynamique
L’édition de liens dynamique relie un programme à du code de bibliothèque partagée distinct pendant le chargement ou l’exécution, ce qui exige la disponibilité de bibliothèques compatibles.
Une bibliothèque partagée manquante peut empêcher le démarrage d’un exécutable dont l’édition de liens a déjà réussi.
Édition de liens statique
L’édition de liens statique inclut le code objet nécessaire des bibliothèques dans l’exécutable, ce qui fait de ce code inclus une partie du fichier propre au programme.
Remplacer un fichier de bibliothèque statique ne met pas à jour le code déjà copié dans un ancien exécutable.
Épilogue
Un épilogue est la séquence de sortie qui restaure les informations sauvegardées, libère le cadre de pile courant et rend le contrôle à l’appelant.
Restaurer les registres avant d’augmenter sp garde leurs déplacements relatifs au cadre alloué.
Évaluation en court-circuit
L’évaluation en court-circuit arrête les tests d’une condition composée dès que sa valeur de vérité globale est déterminée.
Un premier opérande faux de && empêche l’évaluation du second opérande en C.

F

Fichier objet relogeable
Un fichier objet relogeable contient du code machine, des données et des métadonnées dont le placement final et les références externes peuvent être complétés ultérieurement.
Produire un objet avec -c arrête le traitement avant la combinaison des bibliothèques et des modules nécessaire à un exécutable.
Format d’instruction
Un format d’instruction est la disposition des champs dans une instruction codée, qui attribue des positions de bits aux opérandes et aux commandes.
Le type S possède une valeur immédiate divisée là où le type R aurait un champ de fonction et un registre de destination.

I

Indicateurs de condition
Les indicateurs de condition sont des bits d’état qui enregistrent des propriétés d’un résultat ou d’une comparaison que des instructions ultérieures peuvent tester.
x86 peut comparer des registres puis effectuer un branchement selon les indicateurs, tandis que les branchements RISC-V comparent directement les valeurs.
Instruction compressée
Une instruction compressée est un codage plus court d’une opération, comme une forme RISC-V C sur 16 bits utilisée à côté d’instructions sur 32 bits.
Si la moitié de 100 instructions utilisent 2 octets, la taille totale du code est de 300 octets au lieu de 400.
Interface binaire d’application (ABI)
Une interface binaire d’application est l’accord logiciel sur les conventions d’appel, l’organisation des données et les autres règles permettant aux fichiers binaires de fonctionner ensemble.
Une architecture du jeu d’instructions commune ne peut pas corriger un désaccord sur le registre qui contient la valeur de retour d’une fonction.

L

Langage d’assemblage
Le langage d’assemblage est un texte lisible, propre à une architecture du jeu d’instructions, qui nomme les instructions et les opérandes avant qu’un assembleur ne les code en bits.
Le texte add a0, a1, a2 nomme une opération dont le codage en code machine RISC-V est défini.
Liaison paresseuse
La liaison paresseuse reporte la résolution d’une procédure de bibliothèque partagée jusqu’à son premier appel et enregistre le résultat pour les appels ultérieurs.
Le premier appel peut invoquer l’éditeur de liens dynamique, tandis que les appels suivants utilisent une adresse déjà stockée.

M

Masque de bits
Un masque de bits est une configuration utilisée avec une opération bit à bit pour sélectionner, mettre à zéro, mettre à un ou inverser certaines positions d’une autre configuration.
Un ET avec 0xFF conserve l’octet de poids faible tout en mettant à zéro toutes les positions de registre de poids supérieur.
Micro-opération
Une micro-opération est une opération interne en laquelle un processeur peut traduire une instruction de son architecture du jeu d’instructions pour l’exécuter dans son cœur.
Une instruction x86 visible de l’extérieur peut nécessiter plusieurs micro-opérations internes plus simples.
Microarchitecture
Une microarchitecture est la conception interne d’un processeur qui met en œuvre les opérations et le comportement requis par son jeu d’instructions.
Modifier le matériel d’exécution peut améliorer la vitesse d’une puce tout en préservant son interface de code machine existante.
Mise à un si inférieur
La mise à un si inférieur est une opération de comparaison qui écrit 1 lorsque la première source est inférieure à la seconde et écrit 0 sinon.
slt et sltu peuvent donner des résultats différents lorsque le bit de poids fort d’une source lui donne des valeurs signée et non signée différentes.

N

Nombre dynamique d’instructions
Le nombre dynamique d’instructions est le nombre d’instructions effectivement exécutées sur le chemin d’un programme, y compris les passages répétés dans les boucles.
Le dernier test d’une boucle qui échoue contribue au nombre d’instructions exécutées même si son corps ne s’exécute pas de nouveau.

O

Ordre des octets petit-boutiste
L’ordre des octets petit-boutiste stocke l’octet de poids faible d’une valeur composée de plusieurs octets à son adresse mémoire la plus basse.
Un rangement de mot de 0x12345678 place 0x78 dans le premier octet et 0x12 dans le quatrième.

P

Pas entre éléments
Un pas entre éléments est la distance en octets entre des positions correspondantes dans des éléments consécutifs d’un tableau ou d’une organisation répétée.
Un tableau de structures contenant deux entiers a un pas de 8 octets, tandis que son second champ a un déplacement de 4.
Pointeur
Un pointeur est une valeur représentant l’adresse d’un objet ou d’un code, utilisée pour localiser ce à quoi un chargement, un rangement ou un transfert indirect accédera.
Un registre contenant un pointeur fournit une adresse, tandis qu’un chargement récupère la valeur stockée à cette adresse.
Pointeur de pile
Le pointeur de pile est le registre qui marque la limite courante du stockage alloué sur la pile et se déplace lorsque des cadres sont alloués ou libérés.
Soustraire 16 de sp réserve un cadre aligné à des adresses plus basses dans la convention RISC-V.
Poursuite en séquence
La poursuite en séquence est la continuation de l’exécution dans l’instruction ou le bloc de code suivant parce qu’aucun transfert de contrôle ne le saute.
La traduction d’une structure if-else nécessite un saut après la partie « alors » pour éviter une poursuite en séquence dans la partie « sinon ».
Principe du programme enregistré
Le principe du programme enregistré consiste à représenter les instructions comme des valeurs binaires en mémoire, ce qui permet à un processeur de les chercher et de les exécuter.
Le compteur ordinal fournit l’adresse à laquelle la prochaine instruction codée sera cherchée.
Procédure feuille
Une procédure feuille est une procédure qui n’appelle aucune autre procédure, ce qui lui permet de laisser intacte son adresse de retour initiale.
Une procédure feuille qui n’utilise que les registres d’arguments et les registres temporaires peut revenir sans allouer de cadre de pile.
Prologue
Un prologue est la séquence d’entrée qui établit le cadre d’une procédure et sauvegarde les valeurs initiales des registres qu’elle doit préserver.
Sauvegarder ra avant un appel imbriqué protège l’adresse nécessaire pour revenir à l’appelant extérieur.
Pseudo-instruction
Une pseudo-instruction est un raccourci de l’assembleur qui se développe en une ou plusieurs instructions effectivement définies par l’architecture du jeu d’instructions du processeur.
Le nombre d’instructions machine produites par li dépend de la valeur de son opérande constant.

Q

Quartet
Un quartet est un groupe de 4 bits qui correspond à un chiffre hexadécimal et facilite la conversion des mots d’instruction binaires en hexadécimal.
Une instruction complète de 32 bits se divise en huit quartets une fois tous ses champs réunis.

R

Récursivité
La récursivité se produit lorsqu’une procédure s’appelle elle-même directement ou indirectement, créant des appels actifs distincts avec leurs propres informations.
Le calcul de la factorielle recharge l’argument sauvegardé d’un appel extérieur après que l’appel interne a renvoyé son résultat.
Registre entier
Un registre entier est un petit emplacement de stockage du processeur qui contient une configuration de bits utilisée par les opérations entières ou les calculs d’adresses.
Dans RV32I, chacun des 32 registres entiers contient 32 bits, qu’il serve à une valeur ou à une adresse.
Registre sauvegardé par l’appelant
Un registre sauvegardé par l’appelant peut être écrasé par une procédure appelée : son appelant doit donc protéger toute valeur courante dont il aura besoin ensuite.
Une valeur encore nécessaire dans t2 doit être sauvegardée avant l’appel d’une procédure auxiliaire libre d’utiliser ce registre.
Registre sauvegardé par l’appelé
Un registre sauvegardé par l’appelé doit conserver sa valeur initiale après un appel ; une procédure qui le modifie doit donc la restaurer.
Une fonction peut utiliser s1 en interne si elle restaure la valeur initiale de s1 de l’appelant avant de revenir.
Registre zéro
Le registre zéro est le registre RISC-V x0, qui donne toujours zéro à la lecture et ignore toute valeur qu’une instruction y écrit.
Un saut peut écrire son lien dans zero lorsque le programme n’a pas besoin de sauvegarder une adresse de retour.
Relogement relatif
Le relogement relatif est l’ajustement d’une référence selon la distance entre une instruction ou une donnée placée et sa cible.
Déplacer une seule section peut changer un déplacement d’appel même lorsque les octets de l’instruction étaient initialement inchangés.
RISC
RISC est une approche de conception des jeux d’instructions qui privilégie des codages réguliers, des opérations relativement simples et des instructions distinctes pour les transferts mémoire.
L’ajout d’extensions peut élargir une architecture du jeu d’instructions RISC sans supprimer son organisation à chargement et rangement.

S

Saut avec lien
Un saut avec lien est un transfert de contrôle qui écrit aussi l’adresse de l’instruction suivante dans un registre de destination pour une utilisation ultérieure.
jal ra, Helper enregistre l’endroit où l’appelant doit reprendre lorsque Helper revient.
Saut indirect
Un saut indirect obtient sa cible par un calcul fondé sur un registre, ce qui permet à une adresse stockée de choisir l’instruction suivante.
jalr peut mettre en œuvre un appel par pointeur de fonction, un retour ou un choix de cible dans une table de sauts.

T

Table de sauts
Une table de sauts est un ensemble indexé d’adresses de code utilisé pour transférer efficacement le contrôle entre plusieurs cas.
Le choix d’un cas dans une instruction switch doit vérifier la plage des cas avant de lire une adresse dans sa table de sauts.
Table des symboles
Une table des symboles enregistre les définitions nommées et les références non résolues utilisées pour relier les instructions et les données entre modules traduits.
Une fonction peut être non définie dans un objet et définie dans un autre objet fourni à l’éditeur de liens.

V

Valeur immédiate
Une valeur immédiate est une constante représentée par des bits dans une instruction, disponible sans qu’il soit nécessaire de la chercher d’abord en mémoire.
Le dernier opérande de addi t0, t0, 1 est codé dans l’instruction comme valeur immédiate.
Valeur immédiate haute
Une valeur immédiate haute fournit un champ de 20 bits que lui place au-dessus de 12 zéros de poids faible lors de la construction d’une valeur de registre RV32.
Une instruction addi suivante peut fournir une partie basse signée pour compléter une constante entière sur 32 bits.

À propos de cette unité

Par StudeliSignaler une erreur

Crée ton propre Matériel d'Étude

Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.

Commencer