13 votes

Décodage des instructions lorsque les instructions sont variables en longueur

Voici quelques instructions et leurs encodages correspondants :

55                      push   %ebp
89 e5                   mov    %esp,%ebp
83 ec 18                sub    $0x18,%esp
a1 0c 9f 04 08          mov    0x8049f0c,%eax
85 c0                   test   %eax,%eax
74 12                   je     80484b1 <frame_dummy+0x21>
b8 00 00 00 00          mov    $0x0,%eax
85 c0                   test   %eax,%eax
74 09                   je     80484b1 <frame_dummy+0x21>
c7 04 24 0c 9f 04 08    movl   $0x8049f0c,(%esp)

Les microprocesseurs d'aujourd'hui sont souvent de 32 ou 64 bits et je suppose qu'ils lisent généralement les données de la mémoire par tranches de 4 ou 8 octets. Cependant, les instructions peuvent avoir une longueur variable. Comment un microprocesseur décode-t-il ces instructions et pourquoi ne sont-elles pas de longueur constante pour faciliter leur mise en œuvre ?

11voto

Mackie Messer Points 3129

Il y a de très bonnes raisons d'avoir une longueur d'instruction fixe, la simplicité de mise en œuvre étant la plus importante. C'est la raison pour laquelle de nombreux processeurs ont effectivement une longueur d'instruction fixe, par exemple Processeurs RISC et de nombreux premiers ordinateurs.

Jeux d'instructions CISC comme le x86 sont conçus pour être décodés de manière séquentielle (étape par étape) par microcode . (vous pouvez considérer le microcode comme une sorte d'interprète pour les instructions CISC). C'était l'état de l'art au début des années 80 lorsque le x86 a été conçu.

Aujourd'hui, c'est un problème, car le microcode est mort. Les instructions x86 sont maintenant décomposées en de plus petites unités. µ-ops qui n'est pas sans rappeler les instructions RISC. Mais pour ce faire, les instructions x86 doivent d'abord être décodées. Et les CPU actuels décodent jusqu'à 4 instructions par cycle. Comme il n'y a pas le temps de décoder séquentiellement une instruction après l'autre, cela fonctionne simplement par force brute. Lorsqu'une ligne est amenée de le cache d'instruction de nombreux décodeurs décodent la ligne en parallèle. Un décodeur d'instruction à chaque décalage d'octet possible. Après le désocodage, la longueur de chaque instruction est connue, et le processeur décide quels décodeurs fournissent réellement des instructions valides. C'est un gaspillage, mais très rapide.

Des tailles d'instruction variables introduisent davantage de problèmes, par exemple une instruction peut couvrir deux lignes de cache ou même deux pages de mémoire. Votre observation est donc pertinente. Aujourd'hui, personne ne concevrait un jeu d'instructions CISC comme le x86. Cependant, certains RISC ont récemment introduit une deuxième taille d'instruction pour obtenir un code plus compact : MIPS16, ARM-Pouce etc.

7voto

dwelch Points 27195

EDIT : en espérant le rendre plus lisible.

Le matériel ne considère pas la mémoire comme une longue liste d'octets non organisés. Tous les processeurs, à longueur de mot fixe ou variable, ont une méthode de démarrage spécifique. Il s'agit généralement d'une adresse connue dans l'espace mémoire/adresse du processeur, avec soit une adresse de la première instruction du code d'amorçage, soit la première instruction elle-même. A partir de là et pour chaque instruction, l'adresse de l'instruction courante est l'endroit où commencer le décodage.

Pour un x86 par exemple, il doit regarder le premier octet. Selon le décodage de cet octet, il peut avoir besoin de lire d'autres octets d'opcode. Si l'instruction nécessite une adresse, un offset ou une autre forme de valeur immédiate, ces octets sont également présents. Très rapidement, le processeur sait exactement combien d'octets contient cette instruction. Si le décodage montre que l'instruction contient 5 octets et qu'elle a commencé à l'adresse 0x10, l'instruction suivante se trouve à 0x10+5 ou 0x15. Ce processus se poursuit indéfiniment. Les branchements inconditionnels, qui, selon le processeur, peuvent prendre différentes formes, ne supposent pas que les octets qui suivent l'instruction sont une autre instruction. Les branchements, conditionnels ou inconditionnels, vous donnent un indice de l'endroit où une autre instruction ou une série d'instructions commence dans la mémoire.

Notez que le X86 d'aujourd'hui ne récupère pas un octet à la fois lorsqu'il décode une instruction, des lectures de taille raisonnable sont effectuées, probablement 64 bits à la fois, et le processeur en extrait les octets selon les besoins. Lors de la lecture d'un seul octet à partir d'un processeur moderne, le bus mémoire effectue toujours une lecture complète et présente tous ces bits sur le bus où le contrôleur de mémoire ne tire que les bits qu'il recherchait, ou bien il peut aller jusqu'à conserver ces données. Dans certains processeurs, il peut y avoir deux instructions de lecture de 32 bits à des adresses consécutives, mais une seule lecture de 64 bits a lieu sur l'interface mémoire.

Je vous recommande vivement d'écrire un désassembleur et/ou un émulateur. Pour les instructions de longueur fixe, c'est assez facile, il suffit de commencer par le début et de décoder au fur et à mesure que l'on avance dans la mémoire. Un désassembleur de longueur de mot fixe peut aider à apprendre le décodage des instructions, qui fait partie de ce processus, mais il ne vous aidera pas à comprendre comment suivre des instructions de longueur de mot variable et comment les séparer sans se désaligner.

Le MSP430 est un bon choix comme premier désassembleur. Il existe des outils gnu asm et C, etc (et llvm d'ailleurs). Commencez par l'assembleur puis le C ou prenez des binaires pré-fabriqués. La clé est que vous devez parcourir le code comme le processeur, commencer par le vecteur de réinitialisation et parcourir tout le code. Lorsque vous décodez une instruction, vous connaissez sa longueur et savez où se trouve l'instruction suivante jusqu'à ce que vous trouviez un branchement inconditionnel. À moins que le programmeur n'ait intentionnellement laissé un piège pour tromper le désassembleur, supposez que toutes les branches conditionnelles ou inconditionnelles pointent vers des instructions valides. Une après-midi ou une soirée est tout ce qu'il faut pour faire le tour de la question ou au moins comprendre le concept. Il n'est pas nécessaire de décoder complètement l'instruction, ni d'en faire un désassembleur complet, il suffit d'en décoder suffisamment pour déterminer la longueur de l'instruction et déterminer s'il s'agit d'une branche et si oui, où. Comme il s'agit d'une instruction de 16 bits, vous pouvez, si vous le souhaitez, construire une table de toutes les combinaisons possibles de bits d'instruction et de leurs longueurs, ce qui peut vous faire gagner du temps. Vous devez toujours décoder votre chemin à travers les branches.

Certaines personnes pourraient utiliser la récursion, à la place j'utilise une carte mémoire qui me montre quels octets sont le début d'une instruction, quels octets/mots font partie d'une instruction mais pas le premier octet/mot et quels octets je n'ai pas encore décodés. Je commence par prendre les vecteurs d'interruption et de d'interruption et de réinitialisation et je les utilise pour marquer le point de départ des instructions. une boucle qui décode les instructions en cherchant d'autres points de départ. Si un passage se produit sans aucun autre point de départ, alors j'ai terminé cette phase. Si à un moment donné, je trouve un point de départ d'instruction qui se trouve au milieu d'une instruction, il y a un problème qui nécessitera une intervention humaine pour le résoudre. En désassemblant de vieilles roms de jeux vidéo par exemple, vous êtes susceptible de voir ceci, un assembleur écrit à la main. Les instructions générées par le compilateur ont tendance à être très propres et prévisibles. Si je passe par là avec une carte mémoire propre des instructions et de ce qui reste (supposons des données), je peux faire un passage en sachant où sont les instructions, les décoder et les imprimer. Ce qu'un désassembleur pour des jeux d'instructions à longueur de mot variable ne peut jamais faire, c'est trouver chaque instruction. Si le jeu d'instructions possède par exemple une table de saut ou une sorte d'adresse d'exécution calculée, vous ne les trouverez pas toutes sans exécuter réellement le code.

Il existe un certain nombre d'émulateurs et de désassembleurs, si vous voulez essayer de suivre le mouvement au lieu d'écrire le vôtre, j'en ai quelques-uns moi-même. http://github.com/dwelch67 .

Il y a des avantages et des inconvénients pour et contre la longueur variable et fixe des mots. La longueur fixe a des avantages, c'est sûr, facile à lire, facile à décoder, tout est beau et correct, mais pensez à la RAM, au cache en particulier, vous pouvez faire entrer beaucoup plus d'instructions x86 dans le même cache qu'un ARM. D'un autre côté, un ARM peut décoder beaucoup plus facilement, avec beaucoup moins de logique, d'énergie, etc. Historiquement, la mémoire était coûteuse, la logique était coûteuse et le système fonctionnait par octets. Un code d'opération d'un seul octet vous limitait à 256 instructions, ce qui faisait que certains codes d'opération nécessitaient plus d'octets, sans parler des immédiats et des adresses qui rendaient la longueur du mot variable. Gardez la compatibilité inverse pendant des décennies et vous vous retrouvez là où vous êtes maintenant.

Pour ajouter à toute cette confusion, ARM, par exemple, a maintenant un jeu d'instructions à longueur de mot variable. Thumb avait une seule instruction à mot variable, le branchement, mais vous pouvez facilement la décoder comme étant de longueur fixe. Mais ils ont créé thumb2 qui ressemble vraiment à un jeu d'instructions à longueur de mot variable. En outre, la plupart des processeurs qui prennent en charge les instructions ARM 32 bits prennent également en charge les instructions thumb 16 bits, de sorte que même avec un processeur ARM, vous ne pouvez pas simplement aligner les données par mots et décoder au fur et à mesure, vous devez utiliser une longueur de mot variable. Ce qui est pire, c'est que les transitions ARM vers/depuis la poucette sont décodées par exécution, vous ne pouvez normalement pas simplement désassembler et comprendre le bras de la poucette. Un branchement généré par le compilateur en mode mixte implique souvent le chargement d'un registre avec l'adresse vers laquelle le branchement doit être effectué, puis l'utilisation d'une instruction bx pour y accéder. Le désassembleur doit donc examiner la bx, remonter dans l'exécution pour trouver le registre utilisé dans le branchement, espérer y trouver un chargement et espérer qu'il s'agisse du segment .text à partir duquel il est chargé.

4voto

Mysticial Points 180300

Je ne pourrai pas répondre à la question de savoir comment ils sont exactement décodés, mais je peux répondre à la question de savoir pourquoi ils sont de longueur variable.

La raison de la longueur variable est due à la fois au désir de maintenir une taille de code réduite et à des extensions imprévues du jeu d'instructions.


Réduire la taille des instructions

Certaines instructions (par nature), nécessitent beaucoup plus d'espace pour être encodées. Si toutes les instructions étaient fixées à une longueur fixe suffisamment grande pour les accueillir, il y aurait beaucoup d'espace perdu dans le code d'instruction. Les instructions de longueur variable permettent de "comprimer" les instructions à une taille plus petite.


Extensions (imprévues) du jeu d'instructions

L'autre raison est l'extension des jeux d'instructions. À l'origine, le x86 n'avait que 256 opcodes. (1 octet) Puis, comme il fallait ajouter des instructions, on a supprimé une instruction et utilisé son opcode comme caractère d'échappement pour les nouveaux opcodes. Le résultat est que les nouvelles instructions étaient plus longues. Mais c'était la seule façon d'étendre le jeu d'instructions et de maintenir la rétrocompatibilité.

Quant à la façon dont le processeur les décode, c'est un processus compliqué. Pour chaque instruction, le processeur doit trouver la longueur et décoder à partir de là. Cela conduit à un processus de décodage intrinsèquement séquentiel qui est un goulot d'étranglement commun en termes de performances.

Les processeurs x86 modernes possèdent ce que l'on appelle un cache uop (micro-op) qui met en cache les instructions décodées dans un format plus facile à gérer (et de type RISC) par le processeur.

3voto

DigitalRoss Points 80400

Vous avez réinventé RISC

Hmm, l'objection que vous faites à la classique x86 (voir CISC) C'est exactement ce qui a motivé les concepteurs des architectures de CPU RISC à créer des architectures de jeu d'instructions simples, alignées et de taille fixe.

Il s'avère que les x86 d'aujourd'hui traduisent en fait l'ISA visible par l'utilisateur en un flux de micro-opérations plus proche de celui des RISC, qui vit dans un cache interne.

Bonne observation.


Notes.
1. Les micro-ops ne sont qu'une des techniques disponibles. Dans le cas général, tant que le décodage et l'alignement des instructions ont lieu dans un ou plusieurs étages du pipeline, le temps réel pris ne sera pas ajouté au temps moyen d'exécution des instructions. Si la prédiction de branchement fonctionne et que le pipeline est maintenu plein, le temps supplémentaire nécessaire au décodage et à l'alignement des instructions est pris en charge par la logique qui s'exécute en parallèle avec les opérations d'instruction réelles. Avec les millions de portes dont disposent les concepteurs d'aujourd'hui, ils peuvent consacrer beaucoup de logique au décodage de la complexe ISA x86.
2. Vous avez mentionné la largeur du bus mémoire ; il s'avère que le chemin de la mémoire est généralement supérieur à 32 ou 64 bits, également. La taille du mot architectural fait simplement référence à la taille de l'ALU et du pointeur. La largeur réelle des interfaces de mémoire et de cache est souvent égale à 2 ou 4 fois la taille du mot architectural.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X