- Apprendre
- Informatique et numérique
- Informatique
- Architecture informatique : processeur, mémoire et RISC-V
- Jeux d’instructions et langage d’assemblage
Jeux d’instructions et langage d’assemblage
IntermédiaireUnité 2 sur 6 de la formation Formation : Architecture informatique : processeur, mémoire et RISC-V
Ce que tu vas apprendre
- 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’instructionsDé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 expressionsNomme 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émoireUtilise 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édiatesUtilise 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.
- Explique ce qu’est une architecture du jeu d’instructions
- 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écalageEffectue 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 inconditionnellesUtilise 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’assemblageTransforme 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.
- Utilise les instructions arithmétiques, logiques et de décalage
- 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 rangementDispose 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 machineCode 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.
- Code les instructions sur registres, les instructions à valeur immédiate et les instructions de rangement
- 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’appelTransmets 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écursivesAlloue 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’assemblageTransforme 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.
- Appelle des procédures et effectue les retours en respectant la convention d’appel
- 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 objetsDé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écutableDé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.
- Explique comment les compilateurs et les assembleurs produisent des fichiers objets
- 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 CISCDé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’instructionsUtilise 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.
- Distingue les caractéristiques des architectures RISC et CISC
Jeux d’instructions et langage d’assemblage : cours
1Instructions, 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.
| Registres | Noms de l’ABI | Rôle conventionnel |
|---|---|---|
| x0 | zero | Donne toujours 0 à la lecture |
| x1, x2 | ra, sp | Adresse de retour et pointeur de pile |
| x3, x4 | gp, tp | Pointeur global et pointeur de thread |
| x5–x7, x28–x31 | t0–t6 | Valeurs temporaires |
| x8–x9, x18–x27 | s0–s11 | Valeurs préservées lors des appels |
| x10–x17 | a0–a7 | Arguments ; 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 :
| Texte | Instruction de base ou développement |
|---|---|
mv rd, rs | addi rd, rs, 0 |
nop | addi x0, x0, 0 |
not rd, rs | xori rd, rs, -1 |
neg rd, rs | sub rd, x0, rs |
li rd, c, pour un petit c | addi 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.
2Calculs 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
| Instruction | Condition de branchement |
|---|---|
beq, bne | Égal, différent |
blt, bge | Inférieur en signé, supérieur ou égal en signé |
bltu, bgeu | Infé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.
3Champs 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éplacement | Bits 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.
4Appels 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.
5Du 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.
6Compromis 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
1Suis les opérandes d’une expression jusqu’à la mémoire
15 étapes
- 1IntroductionSuis 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.
- 2ExplicationLis le contrat de l’instruction
L’architecture du jeu d’instructions définit ce que
add a0, a1, a2doit 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. - 3DéfiCompte les instructions après l’assemblage
Un assembleur développe une instruction
lien deux instructions machine, suivies d’une instructionadd. 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.
- 4ExempleConserve la somme intermédiaire
Calcule
r = (u + v) - wavecu = 18,v = 7,w = 6dansa1,a2,a3:add t0, a1, a2 sub a0, t0, a3Après la première instruction,
t0contient 25. Après la seconde,a0contient 19. Les trois entrées restent disponibles, car aucune des deux instructions n’écrit dans leurs registres.t0a un rôle : préserver le premier résultat jusqu’à ce que la soustraction le lise. - 5DéfiChange la destination, conserve les sources
Avec les mêmes entrées, on exécute maintenant
sub a3, t0, a3après l’addition. Qu’est-ce qui change par rapport à l’écriture du résultat dansa0?---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.a0est inchangé : un appelant qui attendrait le résultat dans ce registre ne recevrait donc pas ce nouveau résultat. - 6ExplicationTransforme un indice en adresse
Un tableau d’entiers commence à l’adresse contenue dans
s1. ChaqueintRV32 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 chargementlw t0, 8(s1)lit la valeur de l’élément 2 ; il ne place pas l’adresse elle-même danst0. Le registre de base reste inchangé. - 7ExempleCharge, calcule, range
Mets en œuvre
A[5] = A[2] + ravecr = 19dansa0: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 dansA[5]. La lecture et l’écriture en mémoire nécessitent des instructions distinctes autour de l’opération arithmétique. La valeur deA[2]reste 11. - 8Dé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ésigneA[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.
- 9ExempleLis un octet d’un mot stocké
Range
0x7193D2A6à une adresse alignée sur une machine petit-boutiste. Ses octets, par adresses croissantes, sont0xA6,0xD2,0x93,0x71. Un chargement d’octet au déplacement 1 sélectionne0xD2.lbudonne 210, tandis quelbdonne −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. - 10ExplicationTrouve la partie basse signée
Construis maintenant
0x00003D20dans un registre. Ses 12 bits de poids faible sont0xD20, 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 le0x3visible. Choisir0x4donne0x4000, dont la soustraction de0x2E0permet d’atteindre la valeur voulue. - 11ExempleConstruis et vérifie la constante
Utilise :
lui t1, 0x4 addi t1, t1, -736La 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, 0x3D20laisse l’assembleur choisir la construction, mais son développement occupe tout de même de vraies instructions. - 12DéfiCorrige une décomposition de constante
Une décomposition utilise
lui t1, 0x3etaddi t1, t1, -736. De combien son résultat diffère-t-il de0x3D20, et quel champ faut-il corriger ?---reveal---
Elle produit
0x2D20, exactement 4096 de moins que la valeur voulue. Augmente le champ haut de0x3à0x4et conserve le terme bas signé. Remplacer le terme par la valeur positive0xD20dépasserait la plage positive signée deaddi. - 13ConseilSuis 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.
- 14DéfiCombine les étapes sur l’adresse et sur la valeur
Avec la base du tableau dans
s1, l’élémentA[6]vaut 17.t1contient la constante correctement construite0x3D20. Que changelw 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 dansA[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 danss1restent inchangés. - 15Ré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.
2Suis les calculs dans les structures conditionnelles et les boucles
14 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionRends le chemin d’exécution visible
- 2ExemplePlace le champ à la bonne position
- 3ExempleNe conserve que la largeur du champ
- 4DéfiPrévois un décalage signé
- 5ExplicationCompare avec la bonne interprétation
- 6ExempleDistingue un saut d’un appel
- 7DéfiTeste une configuration de deux façons
- 8ExempleDessine les deux chemins d’un if
- 9ExplicationVérifie la borne avant le chargement
- 10ExempleSuis trois exécutions du corps de la boucle
- 11DéfiCompte toutes les instructions du chemin exécuté
- 12LienRemplace les multiplications répétées par une adresse qui avance
- 13DéfiProtège un chargement par une évaluation en court-circuit
- 14RécapitulatifVérifie ensemble les valeurs et les chemins
3Code une instruction et reconstitue un branchement
12 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionTraduis dans les deux sens
- 2ExplicationRepère le format et les opérandes
- 3ExempleÉcris les champs sur toute leur largeur
- 4ExempleConvertis et décode le mot
- 5DéfiRepère la source de la valeur d’un rangement
- 6ExplicationMesure la distance vers l’arrière
- 7ExempleDivise la valeur immédiate du branchement
- 8ExempleAssemble le branchement complet
- 9DéfiRetrouve le bit zéro manquant
- 10LienConserve la distance relative lorsque le code se déplace
- 11DéfiUtilise le code opération pour rejeter un décodage erroné
- 12RécapitulatifVérifie un mot machine en sens inverse
4Conserve les valeurs nécessaires lors des appels imbriqués
15 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionProtège ce dont l’appelant a encore besoin
- 2ExemplePrécise le résultat de la procédure enveloppe
- 3ExplicationApplique les engagements de préservation
- 4DéfiChoisis les valeurs à conserver pendant l’appel
- 5ExempleAlloue et remplis le cadre
- 6ExempleCombine les résultats avant de libérer le stockage
- 7ExplicationRépète le schéma par récursivité
- 8DéfiDépile les arguments sauvegardés
- 9ExplicationDistingue une adresse de son objet
- 10ExempleChoisis des opérations sur les octets pour une chaîne
- 11DéfiTermine aux bonnes adresses
- 12ConseilRestaure la valeur sauvegardée, pas le nouveau résultat
- 13LienLa durée de vie d’un objet suit celle du cadre
- 14DéfiConserve un pointeur de destination pendant un appel auxiliaire
- 15RécapitulatifVérifie une frontière d’appel complète
5Suis un appel du code source au démarrage
11 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionDétermine ce que sait chaque outil
- 2ExplicationCompile pour une architecture du jeu d’instructions cible
- 3ExempleLaisse l’appel externe inachevé
- 4DéfiDistingue la production de données de l’exécution
- 5ExemplePlace le module cible
- 6ExempleCorrige la référence relative
- 7ExplicationChoisis quand fournir le code de bibliothèque
- 8DéfiSitue l’étape de l’échec
- 9LienCrée un processus avant d’appeler main
- 10DéfiExplique pourquoi un objet n’est pas le programme terminé
- 11RécapitulatifRelie une nouvelle erreur à son étape
6Compare les conceptions avec la bonne mesure
10 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionDistingue trois mesures
- 2ExempleCompare l’opération mémoire visible
- 3ExplicationRelie le codage au choix de conception
- 4DéfiClasse une caractéristique avec soin
- 5ExempleCalcule le premier temps d’exécution
- 6ExempleCompare un nombre d’instructions plus élevé
- 7DéfiDistingue les octets économisés de l’accélération
- 8LienLa compatibilité dépasse le jeu d’instructions
- 9DéfiÉvalue une affirmation sur une optimisation en assemblage
- 10RécapitulatifIndique les éléments qui étayent la comparaison
Jeux d’instructions et langage d’assemblage : QCM
Série 1 : Registres, mémoire et constantes10 Questions Pratiques
Question 1 sur 10
Afficher la réponseMasquer la réponse
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
sub a2, a0, a1, a0 contient 28 et a1 contient 9. Quelle valeur est écrite dans a2 ?Afficher la réponseMasquer la réponse
a2. Inverser les sources donnerait −19, mais ce n’est pas leur ordre ici.Question 3 sur 10
A[7] commence-t-il ?Afficher la réponseMasquer la réponse
Question 4 sur 10
sw range 0x86C4217D à 0x3000. Quelle valeur lbu t0, 2(s0) produit-il lorsque s0 = 0x3000 ?Afficher la réponseMasquer la réponse
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
lui et valeur immédiate signée de addi permettent de construire 0x00002ABC en RV32 ?Afficher la réponseMasquer la réponse
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
t3, appelle une procédure auxiliaire conforme à la convention, puis a besoin de la valeur initiale. Que doit-il prévoir ?Afficher la réponseMasquer la réponse
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
auipc t0, 0x2 s’exécute à 0x4000, suivi de addi t0, t0, -24. Quelle est la valeur finale de t0 ?Afficher la réponseMasquer la réponse
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
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 ?Afficher la réponseMasquer la réponse
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
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 ?Afficher la réponseMasquer la réponse
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)
Afficher la réponseMasquer la réponse
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
add ajoute 1 à 0xFFFFFFFF ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
bgt a2, a3, Done ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
srli t0, a0, 8 et andi t0, t0, 0x1F, quelle valeur se trouve dans t0 si a0 = 0x0000D6B9 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
a0 = -11 en signé, quels résultats srai t0, a0, 2 et la division entière C -11 / 4 produisent-ils respectivement ?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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
while (A[i] == 6) i++; commence avec i = 1 et A = {6, 6, 6, 4, 6}. Quelle est la valeur de i à sa sortie ?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)
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
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 ?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
sw a2, 20(sp), quel registre fournit le champ rs1 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
addi t2, s1, -36 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
0x308C cible 0x3068. Quel déplacement signé en octets est codé ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
jal à 0x1200 a un déplacement signé de +84 et ra comme destination. Quelles sont la cible et l’adresse de retour obtenues ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
addi t2, s1, -36 ? Ici, t2 = x7, s1 = x9, le code opération ADDI est 0x13 et funct3 = 000.Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
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.Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
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 ?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
s4. Quelle valeur s4 doit-il contenir à son retour ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
a0 vers l’élément suivant lorsqu’il contient un int * ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
sp = 0x8000, alloue 32 octets, puis exécute sw ra, 28(sp). À quelle adresse le mot sauvegardé commence-t-il ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
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é ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
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 ?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
gcc -c module.c produit-il sans effectuer d’édition de liens ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
a7, 7 dans a0, puis exécute ecall. Quel service est demandé ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
main. Qu’est-ce qui explique cet échec, et qu’en serait-il du code de cette bibliothèque inclus statiquement ?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
add eax, [ebx+12], avec eax correspondant à a0 et ebx à s1 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
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 ?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
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
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
A
Adressage avec déplacement
lw t0, 20(s1), le mot chargé commence 20 octets après l’adresse stockée dans s1.Adressage indexé avec facteur d’échelle
Adressage par octet
Adresse relative au compteur ordinal
auipc peut atteindre des données locales même lorsque tout le groupe de code et de données se déplace.Alignement naturel
Allocation de registres
Appel système
ecall avec 93 dans a7 demande la sortie du processus, en utilisant a0 pour le code de sortie.Architecture à chargement et rangement
Architecture du jeu d’instructions
Assembleur
call et peut laisser des entrées de relogement pour une fonction définie dans un autre module.B
Bit implicite
Bloc de base
Borne de boucle
Branchement conditionnel
bne t0, t1, Loop ne revient à Loop que tant que les configurations des deux registres diffèrent.C
Cadre de pile
Caractère nul terminal
Champ de fonction
funct7.Champ de registre
a0 par le numéro de registre 10, écrit 01010 en binaire.Chargement d’adresse
la t0, Item, un chargement distinct est nécessaire pour lire la valeur contenue à Item.Chargeur
CISC
Code indépendant de la position
Code opération
Compatibilité binaire
Compilateur
Compilation à la volée
Compteur ordinal
Convention d’appel
a0 à a7.Cycles par instruction (CPI)
D
Débordement de registres
Décalage arithmétique à droite
Décalage logique à droite
Densité du code
Dépassement de capacité
Déplacement d’un champ de structure
Déplacement de branchement
Déréférencement
lw t0, 0(a0) lit un entier de 4 octets à l’adresse actuellement contenue dans a0.Désassemblage
Directive d’assemblage
.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
E
Extension de signe
0xF0 avec lb produit 0xFFFFFFF0, la représentation de −16 sur 32 bits.Extension par des zéros
0xF0 avec lbu produit la valeur positive 240 dans un registre de 32 bits.É
Éditeur de liens
Édition de liens dynamique
Édition de liens statique
Épilogue
sp garde leurs déplacements relatifs au cadre alloué.Évaluation en court-circuit
&& empêche l’évaluation du second opérande en C.F
Fichier objet relogeable
-c arrête le traitement avant la combinaison des bibliothèques et des modules nécessaire à un exécutable.Format d’instruction
I
Indicateurs de condition
Instruction compressée
Interface binaire d’application (ABI)
L
Langage d’assemblage
add a0, a1, a2 nomme une opération dont le codage en code machine RISC-V est défini.Liaison paresseuse
M
Masque de bits
0xFF conserve l’octet de poids faible tout en mettant à zéro toutes les positions de registre de poids supérieur.Micro-opération
Microarchitecture
Mise à un si inférieur
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
O
Ordre des octets petit-boutiste
0x12345678 place 0x78 dans le premier octet et 0x12 dans le quatrième.P
Pas entre éléments
Pointeur
Pointeur de pile
sp réserve un cadre aligné à des adresses plus basses dans la convention RISC-V.Poursuite en séquence
Principe du programme enregistré
Procédure feuille
Prologue
ra avant un appel imbriqué protège l’adresse nécessaire pour revenir à l’appelant extérieur.Pseudo-instruction
li dépend de la valeur de son opérande constant.Q
Quartet
R
Récursivité
Registre entier
Registre sauvegardé par l’appelant
t2 doit être sauvegardée avant l’appel d’une procédure auxiliaire libre d’utiliser ce registre.Registre sauvegardé par l’appelé
s1 en interne si elle restaure la valeur initiale de s1 de l’appelant avant de revenir.Registre zéro
zero lorsque le programme n’a pas besoin de sauvegarder une adresse de retour.Relogement relatif
RISC
S
Saut avec lien
jal ra, Helper enregistre l’endroit où l’appelant doit reprendre lorsque Helper revient.Saut indirect
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
Table des symboles
V
Valeur immédiate
addi t0, t0, 1 est codé dans l’instruction comme valeur immédiate.Valeur immédiate haute
lui place au-dessus de 12 zéros de poids faible lors de la construction d’une valeur de registre RV32.addi suivante peut fournir une partie basse signée pour compléter une constante entière sur 32 bits.- 1Performance des ordinateurs
- 2Jeux d’instructions et langage d’assemblage
- 3Arithmétique des ordinateurs
- 4Chemin de données du processeur
- 5Hiérarchie mémoire et caches
- 6Processeurs parallèles et multicœurs
À propos de cette unité
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.