# Fonctionnement d'un ordinateur/Les registres du processeur

Le processeur incorpore un ou plusieurs registres, des mémoires de petite taille, capables de mémoriser un nombre entier/flottant. Naïvement, les registres sont utilisés pour stocker les opérandes des instructions et leur résultat. Un programmeur (ou un compilateur) qui programme en langage machine manipule ces registres intégrés dans le processeur. Cependant, tous les registres d'un processeur ne sont pas forcément manipulables par le programmeur. Il faut distinguer les registres architecturaux, manipulables par des instructions, des registres internes aux processeurs.

## Les différents types de registres architecturaux

Dans ce qui suit, nous allons parler uniquement des registres architecturaux. Les registres internes seront vu dans les chapitre sur la microarchitecture d'un processeur. Ils servent à simplifier la conception du processeur, à mettre en œuvre des optimisations de performance. Les registres architecturaux, eux, font partie de l'interface que le processeur fournit aux programmeurs. Ils font partie du jeu d'instruction, qui liste les registres, les instructions supportées, comment instructions et registres interagissent, etc. Il existe plusieurs types de registres architecturaux, qui sont assez difficiles à classer, que nous allons décrire ci-dessous.

### Les registres généraux : des registres pour les entiers et les adresses

Les registres de données mémorisent des informations comme des entiers, des adresses, des flottants, manipulés par un programme. Ils sont classés en deux grand types, appelés registres entiers et flottants, dont les noms sont assez transparents. Les registres entiers sont spécialement conçus pour stocker des nombres entiers. Les registres entiers sont aussi appelés des registres généraux, car ils servent non seulement pour les entiers, mais aussi les adresses et d'autres informations codées en binaire.
Les registres entiers ne font pas que mémoriser les opérandes/résultats, et peuvent contenir n'importe quelle information codée par des nombres entiers. Notamment, ils peuvent mémoriser des adresses mémoire. L'avantage est que cela permet de faire des calculs sur des adresses mémoire, chose très importante pour supporter des structures de données comme les tableaux. Nous en reparlerons plus en détail dans le chapitre sur les modes d'adressage.
Pour le moment, vous avez juste à savoir que les registres entiers sont en réalité des registres généraux utilisables pour tout et n'importe quoi, qui peuvent stocker toute sorte d’information codée en binaire. Par exemple, un processeur avec 8 registres généraux pourra les utiliser sans vraiment de restrictions. On pourra s'en servir pour stocker 8 entiers, 6 entiers et 2 adresses, 1 adresse et 5 entiers, etc. Ce qui sera plus flexible et utilisera les registres disponibles au maximum.
De nombreux processeurs incorporent des registres entiers ou flottants en lecture seule, qui contiennent des constantes assez souvent utilisées. Par exemple, certains processeurs possèdent des registres initialisés à zéro pour accélérer la comparaison avec zéro ou l'initialisation d'une variable à zéro. On peut aussi citer certains registres flottants qui stockent des nombres comme pi, ou e pour faciliter l'implémentation des calculs trigonométriques. Ils sont appelés des registres de constante, leur nom étant assez clair.

### Le registre d'état (entier)

Le registre d'état est un registre aux fonctions assez variées, qui varient selon le processeur. Au minimum, il contient des bits qui indiquent le résultat d'une instruction de test. Il contient aussi d'autres bits, mais dont l'interprétation dépend du jeu d'instruction. En général, le registre d'état contient les bits suivants :
* le bit d'overflow, qui est mis à 1 lors d'un débordement d'entiers ;
* le bit de retenue, qui indique si une addition/soustraction a donné une retenue ;
* le bit null précise que le résultat d'une instruction est nul (vaut zéro) ;
* le bit de signe, qui permet de dire si le résultat d'une instruction est un nombre négatif ou positif.
Le registre d'état est mis à jour par les instructions de test, mais aussi par les instructions arithmétiques entières (sur des opérandes entiers). Par exemple, si une opération arithmétique entraine un débordement d'entier, le registre d'état mémorisera ce débordement. Dans le chapitre précédent, nous avions vu que les débordements sont mémorisés par le processeur dans un bit dédié, appelé le bit de débordement. Et bien ce dernier est un bit du registre d'état. Il en est de même pour le bit de retenue vu dans le chapitre précédent, qui mémorise la retenue effectuée par une opération arithmétique comme une addition, une soustraction ou un décalage.
Le bit de débordement est parfois présent en double : un bit pour les débordements pour les nombres non-signés, et un autre pour les nombres signés (en complément à deux). En effet, la manière de détecter les débordements n'est pas la même pour des nombres strictement positifs et pour des nombres en complément à deux. Certains processeurs s'en sortent avec un seul bit de débordement, en utilisant deux instructions d'addition : une pour les nombres signés, une autre pour les nombres non-signés. Mais d'autres processeurs utilisent une seule instruction d'addition pour les deux, qui met à jour deux bits de débordements : l'un qui détecte les débordements au cas où les deux opérandes sont signés, l'autre si les opérandes sont non-signées. Sur les processeurs ARM, c'est la seconde solution qui a été choisie.
N'oublions pas les bits de débordement pour les entiers BCD, à savoir le bit de retenue et le bit half-carry, dont nous avions parlé au chapitre précédent.
Sur certains processeurs, comme l'ARM1, chaque instruction arithmétique existe en deux versions : une qui met à jour le registre d'état, une autre qui ne le fait pas. L'utilité de cet arrangement n'est pas évident, mais il permet à certaines instructions arithmétiques de ne pas altérer le registre d'état, ce qui permet de conserver son contenu pendant un certain temps.
Le fait que le registre d'état est mis à jour par les instructions arithmétiques permet d'éviter de faire certains tests gratuitement. Par exemple, imaginons un morceau de code qui doit vérifier si deux entiers A et B sont égaux, avant de faire plusieurs opérations sur la différence entre les deux (A-B). Le code le plus basique pour cela fait la comparaison entre les deux entiers avec une instruction de test, effectue un branchement, puis fait la soustraction pour obtenir la différence, puis les calculs adéquats. Mais si la soustraction met à jour le registre d'état, on peut simplement faire la soustraction, faire un branchement qui teste le bit null du registre d'état, puis faire les calculs. Une petite économie toujours bonne à prendre.
Il faut noter que certaines instructions sont spécifiquement conçues pour altérer uniquement le registre d'état. Par exemple, sur les processeurs x86, certaines instructions ont pour but de mettre le bit de retenue à 0 ou à 1. Il existe en tout trois instructions capables de manipuler le bit de retenue : l'instruction CLC (CLear Carry) le met à 0, l'instruction STC (SeT Carry) le met à 1, l'instruction CMC (CompleMent Carry) l'inverse (passe de 0 à 1 ou de 1 à 0). Ces instructions sont utilisées de concert avec les instructions d'addition ADDC (ADD with Carry) et SUBC (SUB with Carry), qui effectuent le calcul A + B + Retenue et A - B - Retenue, et qui sont utilisées pour additionner/soustraire des opérandes plus grandes que les registres. Nous avions vu ces instructions dans le chapitre sur les instructions machines, aussi je ne reviens pas dessus.
Le registre d'état n'est pas présent sur toutes les architectures, notamment sur les jeux d'instruction modernes, mais beaucoup d'architectures anciennes en ont un.

### Les registres de contrôle

Les registres de contrôle permettent de configurer le processeur pour qu'il fonctionne comme souhaité. Ils sont très variables et dépendent fortement du jeu d'instruction, mais aussi du modèle de processeur considéré.
Il y a quelques chapitres, nous avons vu que certains processeurs gèrent deux modes de fonctionnement : le mode noyau et le mode protégé. Il se trouve que le registre de contrôle contient un bit qui précise si le processeur est en mode noyau ou protégé. Modifier ce bit permet de passer du mode noyau au mode protégé, ce qui est fait par le système d'exploitation quand il veut faire un appel système.
Le registre de contrôle incorpore souvent un bit qui permet d'activer/désactiver les interruptions de manière globale. Seules les interruptions non-masquables peuvent être désactivées. En modifiant ce bit, on peut activer ou désactiver les interruptions. Le bit en question n'est souvent modifiable qu'en mode noyau. Et ce n'est pas le seul, d'autres bits de configuration ne sont modifiables qu'en mode noyau, pas en mode utilisateur.
D'autres bits de contrôle sont dédiés à la gestion du cache. Il est ainsi possible de configurer le cache, voire de le désactiver. Nous ne pouvons pas en parler en détail ici, car nous ne savons pas comment fonctionne une mémoire cache pour le moment. Mais nous détaillerons les bits de contrôle du cache dans le chapitre sur la mémoire cache. Pour le moment, nous ne pouvons parler que d'un seul bit de contrôle du cache :; celui qui l'active ou le désactive.

### Les registres flottants : registres de données, d'état et de contrôle flottant

Les registres flottants sont spécialement conçus pour stocker des nombres flottants. Ils ne sont présents que sur les processeurs qui supportent les nombres flottants. Tous les processeurs modernes séparent les registres flottants et entiers, pour de bonnes raisons. Une des raisons est que les flottants et entiers n'ont pas le même encodage et n'ont pas forcément la même taille. Les flottants font 32 et 64 bits, ce qui posait problème sur les architectures 32 bits. Mais surtout, les flottants et entiers sont vraiment traités séparément dans le processeur : ils ont des circuits de calcul distincts, ils sont traités par des instructions séparées. Les mettre dans des registres séparés aide beaucoup pour la conception du processeur, comme on le verra dans quelques chapitres. Et cela n'entraine pas de problèmes de performances.
Les processeurs qui gèrent les nombres flottants incorporent aussi un registre d'état flottant, qui s'occupe des nombres flottants. Sur les CPU x86, qui utilisaient l'extension x87, il était appelé le Status Word. Celui-ci fait lui aussi 16 bits et contient tout ce qu'il faut pour qu'un programme puisse comprendre la cause d'une exception. Voici son contenu, à peu de chose près.
| Bit | Utilité |
| U | Mis à 1 lorsqu'un débordement a lieu. |
| O | Pareil que U, mais pour les overflow |
| Z | Bit mis à 1 lors d'une division par zéro |
| D | Bit mis à 1 lorsqu'un résultat de calcul est un dénormal ou lorsqu'une instruction doit être exécutée sur un dénormal |
| I | Bit mis à 1 lors de certaines erreurs telles que l'exécution d'une instruction de racine carrée sur un négatif ou une division du type 0/0 |
Les registres de contrôle flottant configurent les opérations flottantes. Ils configurent quel mode d'arrondi utiliser, comment traiter les infinis, si les flottants utilisés sont simple (32 bits) ou double précision (64 bits). Pour donner un exemple, voici le registre control word utilisé sur les anciens CPU x86, pour l'extension x87. L'extension x87 ajoutait le support des nombres flottants aux CPU x86, mais ceux-ci n'étaient pas tout à fait compatibles avec la norme IEEE 754. Une différence notable est que les flottants étaient codés sur 80 bits maximum.
| Bit | Utilité |
| Infinity Control | Mode de gestion des infinis, codé sur 2 bits : 0 : Les infinis sont tous traités comme s'ils valaient $+\infty$. 1 : Les infinis sont traités normalement. |
| Rouding Control | Mode d'arrondi codé sur 2 bits : 00 : vers le nombre flottant le plus proche : c'est la valeur par défaut ; 01 : vers - l'infini ; 10 : vers + l'infini ; 11 : vers zéro |
| Precision Control | Taille de la mantisse, configurée via deux bits. Les valeurs 00 et 10 demandent au processeur d'utiliser des flottants non pris en compte par la norme IEEE 754. 00 : mantisse codée sur 24 bits ; 01 : valeur inutilisée ; 10 : mantisse codée sur 53 bits ; 11 : mantisse codée sur 64 bits |

### Les registres spécialisés pour les adresses et les indices

Quelques processeurs incorporent des registres spécialisés dans les adresses et leur calcul. Les registres d'adresse contiennent des adresses. Ils étaient surtout présents sur les architectures 16 bits, plus rarement sur les architectures 32 bits. L'usage de registres d'adresse s'explique par le fait que sur les anciennes architectures, les adresses n'ont pas la même taille que les données.
Un exemple est celui des processeurs Motorola 68000, sur lequel les entiers faisaient 32 bits et les adresses faisaient 24 bits. Le packaging du processeur ne permettait pas de mettre trop de broches, ce qui fait que les broches d'adresse étaient limitée à 24 bits, ce qui était suffisant pour l'époque. L'usage de registres d'adresse séparés des registres entiers permettait de gérer au mieux cette différence de taille. Ce problème a été corrigé à l'arrivée du 68020, qui avait des adresses sur 32 bits et 32 broches d'adresse, mais a conservé la séparation entre registres d'adresse et entiers pour des raisons de compatibilité.
Un autre exemple est celui du processeur du CDC 6600, qui avait 8 registres d'adresse couplés à 8 registres d'entrée. Les registres d'adresse fonctionnaient d'une manière totalement inédite, qu'on ne retrouve pas sur d'autres processeurs avec registre d'adresse. Tout registre d'adresse était associé à un registre entier. Concrètement, les 8 registres d'adresse étaient numérotés de 0 à 7, idem pour les registres entier. Le CDC 6600 n'avait pas d'instruction LOAD ou STORE, tout passait par des écritures dans ces registres. Le comportement dépendait du registre concerné.
* Une écriture dans le registre A0 ne faisait rien, il sert d'exception. Le registre D0 n'est pas altéré lors d'une écriture dans le registre A0.
* Les registres A1 à A5 servaient pour les lectures. L'écriture d'une adresse dans un de ces registres entrainait une lecture de cette adresse. La donnée lue était copiée automatiquement dans le registre entier associé, le registre entier de même numéro.
* Les registres A5 à A7 servaient pour les écriture. L'écriture d'une adresse dans un de ces registres entrainait une écriture à cette adresse. La donnée à écrire était prise dans dans le registre entier associé, le registre entier de même numéro.
L'usage de registres d'adresse dédiés est très rare, les processeurs préfèrent utiliser des registres généraux qui servent à la fois de registres entier et de registres d'adresse. La raison est que les adresses sont encodés avec des entiers en binaire. Les opérations effectuées sur les adresses sont des opérations entières basiques : additions/soustractions, parfois multiplications entières, opérations de masquage, bit à bit, etc. Aussi, séparer adresses et entiers dans des registres séparés n'est pas très pertinent.
Prenons un exemple : j'ai un processeur disposant d'un Program Counter, de 4 registres entiers et de 4 registres d'adresse. Si j’exécute un morceau de programme qui ne manipule presque pas d'adresses, mais fait beaucoup de calcul, les 4 registres d'adresse seront sous-utilisés alors que je manquerais de registres entiers. Utiliser 8 registres généraux permet de contourner le problème. On peut se servir de ces 8 registres généraux pour stocker 8 entiers, 6 entiers et 2 adresses, 1 adresse et 5 entiers, etc. Ce qui sera plus flexible et utilisera les registres disponibles au maximum.
Les registres d'indice servent à calculer des adresses, afin de manipuler rapidement des données complexes comme les tableaux. Ils étaient présents sur les premiers ordinateurs et ont perduré jusqu’aux architectures 16 bits inclues. Dans les faits, ils étaient présent sur une classe particulière de processeurs, appelés les architectures à accumulateur, qui aura droit à son chapitre dédié. Nous parlerons en détail des registres d'indice dans ce chapitre dédié aux architectures à accumulateur.
Le registre pointeur de pile est un registre spécialisé dans la gestion de la pile d'appel. Il mémorise le pointeur de pile, à savoir l'adresse le sommet de la pile d'appel. Il a déjà été abordé il y a quelques chapitres. Il existait sur les anciens processeurs mais a totalement disparu avec l'arrivée des processeurs 32 bits. Il a subit le même traitement que les registres d'adresse sur les processeurs modernes, pour les mêmes raisons. Le pointeur de pile est concrètement une adresse encodée avec un nombre entier, qui est incrémentée/décrémentée avec des opérations entières classiques. Au lieu d'utiliser des instructions spéciales agissant sur un registre à part, autant utiliser un registre entier pour le pointeur de pile et effectuer des instructions arithmétiques normales. Toutes les architectures modernes font ainsi.

### Le program counter

Le Program Counter mémorise l'adresse de l’instruction en cours ou de la prochaine instruction (le choix entre les deux dépend du processeur). C'est bel et bien un registre architectural, car ils sont manipulés par les instructions de branchement, bien qu'implicitement. Ce n'est pas un registre utilisé à des fins d'optimisation ou de simplicité d'implémentation.
Il existe des processeurs où le Program Counter subit le même traitement que les registres d'adresse ou pointeur de pile et devient adressable. Sur ces processeurs, on peut parfaitement lire ou écrire dans le Program Counter sans trop de problèmes. Ainsi, au lieu d'effectuer des branchements sur le Program Counter, on peut simplement utiliser une instruction qui ira écrire l'adresse à laquelle brancher dans le registre. On peut même faire des calculs sur le contenu du Program Counter : cela n'a pas toujours de sens, mais cela permet parfois d'implémenter facilement certains types de branchements avec des instructions arithmétiques usuelles.
Le program counter et le registre d'état sont parfois fusionnés en un seul registre appelé le Program status word, abrévié en PSW. L'avantage est que le Program status word regroupe tout ce qui est utile pour les branchements et test. Les branchements écrivent dans le program counter pour brancher à l'adresse finale, lire l'adresse dans le program counter pour certains branchements dits relatifs, les tests/branchements peuvent lire le registre d'état. Avec un PSW, tout cela est regroupé dans le PSW, les tests et branchements altérent tous deux le PSW. L'avantage est mineur et pose des problèmes niveau implémentation matérielle.
Il peut y avoir un avantage en terme de taille des registres. Par exemple, l'ARM1 fusionne le registre d'état et le program counter en un seul registre de 32 bits. La raison à cela est que ses registres font 32 bits, que le program counter n'a besoin que de 24 bits pour fonctionner ce qui laisse 8 bits pour le registre d'état. Précisément, le program counter est censé gérer des adresses de 26 bits, mais les instructions de ce processeur font exactement 32 bits et elles sont alignées en mémoire, ce qui fait que les 2 bits de poids faibles du program counter sont inutilisés. Au total, cela fait 8 bits inutilisés. Et ils ont été réutilisés pour mémoriser les bits du registre d'état.

## L'adressage des registres architecturaux

Outre leur taille, les registres du processeur se distinguent aussi par la manière dont on peut les adresser, les sélectionner. Les registres du processeur peuvent être adressés par trois méthodes différentes. À chaque méthode correspond un mode d'adressage différent. Les modes d'adressage des registres sont les modes d'adressages absolu (par adresse), inhérent (à nom de registre) et/ou implicite.

### Les registres nommés

Dans le premier cas, chaque registre se voit attribuer une référence, une sorte d'identifiant qui permettra de le sélectionner parmi tous les autres. C'est un peu la même chose que pour la mémoire RAM : chaque byte de la mémoire RAM se voit attribuer une adresse. Pour les registres, c'est un peu la même chose : ils se voient attribuer quelque chose d'équivalent à une adresse, une sorte d'identifiant qui permettra de sélectionner un registre pour y accéder.
L'identifiant en question est ce qu'on appelle un nom de registre ou encore un numéro de registre. Ce nom n'est rien d'autre qu'une suite de bits attribuée à chaque registre, chaque registre se voyant attribuer une suite de bits différente. Celle-ci sera intégrée à toutes les instructions devant manipuler ce registre, afin de sélectionner celui-ci. Le numéro/nom de registre permet d'identifier le registre que l'on veut, mais ne sort jamais du processeur, il ne se retrouve jamais sur le bus d'adresse. Les registres ne sont donc pas identifiés par une adresse mémoire.

### Les registres adressés

Mais il existe une autre solution, utilisée sur de très vieux ordinateurs des années 50 à 70, ou quelques microcontrôleurs. C'est le cas du PDP-10.. L'idée est d'adresser les registres via une adresse mémoire. Les registres se voient attribuer les adresses mémoires les plus basses, à partir de l'adresse 0. Par exemple, un processeur avec 16 registres utilisait les 16 adresses basses, une par registre.

### Les registres adressés implicitement

Les registres de contrôle n'ont pas forcément besoin d'avoir un nom. Par exemple, la gestion de la pile se fait alors via des instructions Push et Pop qui sont les seules à pouvoir manipuler ces registres. Toute manipulation des registres de pile se faisant grâce à ces instructions, on n'a pas besoin de leur fournir un numéro/nom de registre pour les sélectionner. C'est aussi le cas du Program Counter : à part sur certains processeurs vraiment très rares, on ne peut modifier son contenu qu'en utilisant des instructions de branchements. Idem pour le registre d'état, manipulé obligatoirement par les instructions de comparaisons et de test, et certaines opérations arithmétiques.
Dans ces cas bien précis, on n'a pas besoin de préciser le ou les registres à manipuler : le processeur sait déjà quels registres manipuler et comment, de façon implicite. Le seul moyen de manipuler ces registres est de passer par une instruction appropriée, qui fera ce qu'il faut. Les registres adressés implicitement sont presque toujours des registres de contrôle, beaucoup plus rarement des registres de données. Mais précisons encore une fois que sur certains processeurs, le registre d'état et/ou le Program Counter sont adressables, pareil pour les registres de pile. Inversement, il arrive que certains registres de données puissent être adressés implicitement, notamment certains registres impliqués dans la gestion des adresses mémoire.

## La taille des registres architecturaux

Vous avez certainement déjà entendu parler de processeurs 32 ou 64 bits. Derrière cette appellation qu'on retrouve souvent dans la presse ou comme argument commercial se cache un concept simple. Il s'agit de la quantité de bits qui peuvent être stockés dans les registres principaux. Les registres principaux en question dépendent de l'architecture. Sur les architectures avec des registres généraux, la taille des registres est celle des registres généraux. Sur les autres architectures, la taille mentionnée est généralement celle des nombres entiers mais les autres registres peuvent avoir une taille totalement différente. Sur les processeurs x86, un registre pour les nombres entiers contient environ 64 bits tandis qu'un registre pour nombres flottants contient entre 80 et 256 bits (suivant les registres utilisés).
Aujourd'hui, les processeurs utilisent presque tous des registres dont la taille est une puissance de 2 : 8, 16, 32, 64, 128, 256, voire 512 bits. L'usage de registres qui ne sont pas des puissances de 2 posent quelques problèmes techniques en termes d’adressage, comme on le verra dans le chapitre sur l'alignement et le boutisme. Mais ca n'a pas toujours été le cas.
Aux tout début de l'informatique, les processeurs utilisaient tous l'encodage BCD, ce qui fait qu'ils codaient leurs chiffres sur 5/6/7 bits. La taille des registres était donc un multiple de 4/5/6. Les registres de 36 bits et de 48 bits étaient la norme sure les gros ordinateurs de type mainframe, qu'ils soient commerciaux ou destinés au calcul scientifique. Certaines machines utilisaient des registres de 3, 7, 13, 17, 23, 36 et 48 bits ; mais elles sont aujourd'hui tombées en désuétude.
On peut aussi citer les processeurs dédiés au traitement de signal audio, que l'on trouve dans les chaînes HIFI, les décodeurs TNT, les lecteurs DVD, etc. Ceux-ci utilisent des registres de 24 bits, car l'information audio est souvent codée par des nombres de 24 bits.
Le nombre de bits que peut contenir un registre est parfois différent de la largeur du bus de données (c'est à dire du nombre de bits qui peuvent transiter en même temps sur le bus de données). Exemple : sur les processeurs x86-32 bits, un registre stockant un entier fait 32 bits alors que le bus de données peut contenir 64 bits en même temps. La raison à cela est la présence d'un cache entre la mémoire et le CPU.

### Le système d'aliasing de registres sur les processeurs x86

Sur les processeurs x86, on trouve des registres de 8 bits, d'autres de 16, d'autres de 32, et on a même des registres de 64 bits depuis plus d'une décennie. Limitons-nous pour le moment aux registres 8 et 16 bits, sur lesquels il y a beaucoup de choses à dire.
Intel a commencé à produire un processeur 8 bits nommé le 8008, avant de sortir deux processeurs 16 bits appelés 8086 et 8088. Si c'étaient des processeurs 16 bits, ils étaient des versions améliorées et grandement remaniées du 8008 8 bit. En théorie, la rétrocompatibilité n'était pas garantie, car les jeux d'instruction étaient différents entre le 8086 et le 8008. Mais Intel avait prévu quelques améliorations pour rendre la transition plus facile. Et l'une d'entre elle concerne directement le passage des registres de 8 à 16 bits.
Les registres 16 bits étaient découpés en deux portions de 8 bits, chacune adressable séparément. On pouvait adresser un registre de 16, ou alors adresser seulement les 8 bits de poids fort ou les 8 bits de poids faible. Ces octets étaient considérés comme des registres à part entière. Du moins, c'est le cas pour 4 registres de données, nommés AX, BX, CX et DX. Le registre AX fait 16 bits, l'octet de poids fort est adressable comme un registre à part entière nommé AH, l'octet de poids faible est aussi un registre nommé AL (H pour High et L pour Low). Idem avec les registres BX, BH et BL, les registres CX, CH et CL, ou encore les registres DX, DH, DL. Les autres registres ne sont pas concernés par ce découpage, que ce soit les registres de données, d'adresse ou autres.
Tout cela décrit un système d'alias de registres, qui permet d'adresser certaines portions d'un registre comme un registre à part entière. Les registres AH, AL, BH, BL, ..., ont tous un nom de registre et peuvent être utilisés dans des opérations arithmétiques, logiques ou autres. Dans ce cas, les opérations ne modifient que l'octet sélectionné, pas le reste du registre. Une même opération peut donc agir sur 16 ou 8 bits suivant le registre sélectionné.
Par la suite, le jeu d'instruction x86 a étendu ses registres à 32 et enfin 64 bits. Les registres 32 bits ont le même système d'alias, mais légèrement modifié. Sur un registre 32 bits, les 16 bits de poids faible sont adressables séparément, mais pas les 16 bits de poids fort. Les registres de 16 bits originaux sont toujours adressables comme avant, avec le même nom de registre que sur les anciens modèles. Par contre, le registre étendu a un nouveau nom de registre.
Pour rendre tout cela plus clair, voyons l'exemple du registre EAX des processeurs 64 bits. C'est un registre 32 bits, dont les 16 bits de poids faible sont tout simplement le registre AX vu plus haut, ce dernier pouvant être subdivisé en AH et AL. La même chose a lieu pour les registres EBX, ECX et EDX. Et cette fois-ci, presque tous les registres ont étés étendus ainsi, même le program counter, les registres liés à la pile et quelques autres, notamment pour adresser plus de mémoire.
Lors du passage au 64 bits, les registres 32 bits ont étés étendus de la même manière, et les registres étendus à 64 bits ont reçu un nom de registre supplémentaire, RAX, RBX, RCX ou RDX. Le passage à 64 bits s'est accompagné de l'ajout de 4 nouveaux registres.
Un point intéressant est qu'Intel a beaucoup utilisé ce système d'alias pour éviter d'avoir à réellement ajouter certains registres. Pour le moment, bornons-nous à citer les exemples les plus frappants et parlons du MMX, du SSE et de l'AVX.
Le MMX est une extension du x86, qui ajoute des instructions au jeu d'instruction x86 de base. Elle ajoutait 8 registres entiers appelés MM0, MM1, MM2, MM3, MM4, MM5, MM6 et MM7, d'une taille de 64 bits. En théorie, ces registres devraient être des registres séparés des autres, ajoutés aux anciens. Mais Intel utilisa le système d'alias pour éviter d'avoir à rajouter des registres. Il étendit les 8 registres flottants de 80 bits déjà existants. Chaque registre MMX correspondait aux 64 bits de poids faible d'un des 8 registres flottants de la x87 ! Cela posa pas mal de problèmes pour les programmeurs qui voulaient utiliser l'extension MMX. Il était impossible d'utiliser à la fois le MMX et les flottants x87...
Par la suite, l'extension SSE ajouta plusieurs registres de 128 bits, les XMM registers illustrés ci-contre. Le SSE fût décliné en plusieurs versions, appelées SSE1, SSE2, SSE3, SS4 et ainsi de suite, chacune rajoutant de nouvelles instructions. Les registres SSE sont bien séparés des autres, Intel n'utilisa pas le système d'alias.
Puis, l'arrivée de l'extension AVX changea la donne. L'AVX complète le SSE et ses extensions, en rajoutant quelques instructions et surtout en permettant de traiter des données de 256 bits. Et cette dernière ajoute 16 registres d'une taille de 256 bits, nommés de YMM0 à YMM15 et dédiés aux instructions AVX. Et c'est là que le système dalias a encore frappé. Les registres AVX sont partagés avec les registres SSE : les 128 bits de poids faible des registres YMM ne sont autres que les registres XMM.
Puis, arriva l'AVX-512 qui ajouta 32 registres de 512 bits, et des instructions capables de les manipuler, d'où son nom. Là encore, les 256 bits de poids faible de ces registres correspondent aux registres de l'AVX précédent. Du moins, pour les premiers 16 registres, vu qu'il n'y a que 16 registres de l'AVX normal.
Pour résumer, ce système permet d'ajouter des registres de plus grande taille, en étendant des registres existants pour en augmenter la taille. La longévité des architectures x86 a fait que cette technique a beaucoup été utilisée. Mais les autres architectures n'implémentent pas vraiment ce système. De plus, ce système marche assez mal avec les processeurs modernes, dont la conception interne se marie mal avec laliasing de registres, pour des raisons que nous verrons plus tard dans ce cours (cela rend plus difficile le renommage de registres et la détection des dépendances entre instructions).

### Le pseudo-aliasing des registres sur les CPU Intel et le Z80

Les premiers processeurs Intel avaient un système de pseudo-aliasing de registres. Formellement, ce n'est pas un système d'alias, mais un système où les registres sont regroupés lors de certaines opérations.
Les premiers CPU Intel étaient des processeurs 8 bits, qui contiennent plusieurs registres généraux de 8 bits nommés A, B, C, D, E, H, L. Le registre A n'est pas concerné par ce qui va suivre. La quasi-totalité des opérations arithmétiques ne manipule que ces registres de 8 bits, mais l'opération d'incrémentation est un peu à part.
Le Z80 supporte 3 paires de registres aliasés. Chaque paire contient deux registres de 8 bits, mais une paire est considérée comme un registre unique de 16 bits pour certaines opérations. Les registres 16 bits sont la paire BC, la paire DE et la paire HL. Par exemple, le registre BC de 16 bits est composé des deux registres B et C de 8 bits, idem pour les paires DE et HL. Le système de paires de registres permet d'effectuer une opération d'incrémentation sur une paire de 16 bit complète, avec une instruction spécialisée.
Cela peut paraître étrange, mais c'est en réalité un petit plus qui se marie bien avec le reste de l'architecture. Le Z80 gère des adresses de 16 bits, ce qui signifie que les registres dédiés aux adresses sont de 16 bits. Et cela concerne son pointeur de pile et son program counter, qui sont de 16 bits tous les deux. Aussi, pour mettre à jour le pointeur de pile et le program counter, le processeur incorpore un incrémenteur de 16 bits. Les concepteurs du processeur ont sans doute cherché à rentabiliser cet incrémenteur et lui ont permis d'incrémenter des registres de données. Pour cela, il fallait regrouper les registres de 8 bits par paire de deux, ce qui rendait l’implémentation matérielle la plus simple possible.

### La taille des registres flottants et les doubles arrondis

Les nombres flottants sont standardisés par l'IEEE, avec le standard IEEE754. Cependant, de nombreux processeurs ne suivent pas ce standard à la lettre. Par exemple, les coprocesseurs x87, ainsi que les processeurs x86 32 bits utilisaient des flottants codés sur 80 bits. Et leurs registres flottants faisaient eux aussi 80 bits, ce qui posait quelques problèmes.
Lors des accès mémoire, il y avait parfois des conversions entre flottants 80 bits et flottants 32/64 bits. L'instruction LOAD flottantes pouvait lire soit un flottant 32 bits, soit un flottant 64 bits, soit un flottant 80 bits. Les flottants 32 et 64 bits étaient convertis en flottants 80 bits lors du chargement. Même chose pour l'enregistrement en mémoire via l'instruction STORE flottante. Les flottants 80 bit était soit convertit en flottant 32 ou 64 bits, soit enregistrés directement avec 80 bits.
Le problème est que faire des calculs intermédiaires sur 80 bits avant de les arrondir ne donne pas le même résultat que si on avait fait les calculs sur 32 ou 64 bits nativement. Les résultats intermédiaires ont une précision supérieure, donc le résultat peut être différent. De plus, la conversion lors des écritures mémoire effectue un arrondi pour faire rentrer le résultat sur 32/64 bits, arrondi qui modifie encore les résultats. Pour citer un exemple, sachez que des failles de sécurité de PHP et de Java aujourd'hui corrigées étaient causées par ces arrondis supplémentaires.
Une autre conséquence est que les résultats sont impactés par l'ordre des accès mémoire, par la manière dont sont gérés les registres flottants. En effet, les problèmes d'arrondis ont lieu lors de l'écriture. Plus longtemps les résultats intermédiaires sont enregistrés dans les registres, plus on retarde les problèmes. Mais il arrive fatalement un moment où des flottants doivent quitter les registres flottants pour arriver en RAM.
Et ce moment dépend du nombre de registres et du nombre d'opérandes traitées. Si vous vous débrouillez pour faire tous vos calculs flottants avec les 8 registres disponibles, vous ne ferez d'arrondi qu'à la toute fin de vos calculs, pour enregistrer les résultats. Si vous utilisez plus, vous aller devoir faire un vas et vient entre RAM et registres. Dans ce cas, suivant l'ordre des accès mémoire, les arrondis se feront à des instants différents.
Pour limiter la casse, il existe une solution : sauvegarder tout résultat d'un calcul sur un flottant directement dans la mémoire RAM. Comme cela, on se retrouve avec des calculs effectués uniquement sur des flottants 32/64 bits ce qui supprime pas mal d'erreurs de calcul.

## Les optimisations liées aux registres architecturaux

Afin d'améliorer les performances, les concepteurs des processeurs ont optimisé les registres architecturaux au mieux. Leur nombre, leur taille, leur adressage : tout est optimisé sur les jeux d'instruction dignes de ce nom. Et sur certains processeurs, on trouve des optimisations assez spéciales, qui visent à dupliquer les registres architecturaux sans que cela se voie dans le jeu d'instruction. Pour le dire autrement, un registre architectural correspond à plusieurs registres physiques dans le processeur. Par exemple, les processeurs modernes utilisent des techniques de renommage de registre, que l'on ne peut pas aborder pour le moment. Mais d'autres techniques similaires sur le principe sont possibles, comme le fenêtrage de registres.

### Les registres dédiés aux interruptions

Sur certains processeurs, les registres généraux sont dupliqués en deux ensembles identiques. Le premier ensemble est utilisé pour exécuter les programmes normaux, alors que le second ensemble est dédié aux interruptions. Mais les noms de registres sont identiques dans les deux ensembles.
Prenons l'exemple du processeur Z80 pour simplifier les explications. Comme dit plus haut, ce processeur a beaucoup de registres et tous ne sont pas dupliqués. Les registres pour la pile ne sont pas dupliqués, le program counter non plus. Par contre, les autres registres, qui contiennent des données, sont dupliqués. Il s'agit des registres nommés A (accumulateur), le registre d'état F et les registres généraux B, C, D, E, H, L, ainsi que les registres temporaires W et Z. L'ensemble de registres pour interruption dispose lui aussi de registres nommés A, F et les registres généraux B, C, D, E, H, L, mais il s'agit de registres différents. Pour éviter les confusions, ils sont notés A', F', B', C', D', E', H', L', mais les noms de registres sont en réalité identiques. Le processeur ne confond pas les deux, car il sait s'il est dans une interruption ou non.
Les deux ensembles de registres sont censés être isolés, il n'est pas censé y avoir d'échanges de données entre les deux. Mais le Z80 permettait d'échanger des données entre les deux ensembles de registres. Dans le détail, une instruction permettait d'échanger le contenu d'une paire de registres avec la même paire dans l'autre ensemble. En clair, on peut échanger les registres BC avec BC', DE avec DE′, et HL avec HL′. Par contre, il est impossible d'échanger le registre A avec A', et F avec F'. Le résultat est que les programmeurs utilisaient l'autre ensemble de registres comme registres pour les programmes, même s'ils n'étaient pas prévus pour.
Ce système permet de simplifier grandement la gestion des interruptions matérielles. Lors d'une interruption sur un processeur sans ce système, l'interruption doit sauvegarder les registres qu'elle manipule, qui sont potentiellement utilisés par le programme qu'elle interrompt. Avec ce système, il n'y a pas besoin de sauvegarder les registres lors d'une interruption, car les registres utilisés par le programme et l'interruption ne sont en réalité pas les mêmes. Les interruptions sont alors plus rapides.
Notons qu'avec ce système, seuls les registres adressables par le programmeur sont dupliqués. Les registres comme le pointeur de pile ou le program counter ne sont pas dupliqués, car ils n'ont pas à l'être. Et attention : certains registres doivent être sauvegardés par l'interruption. Notamment, l'adresse de retour, qui permet de reprendre l'exécution du programme interrompu au bon endroit. Elle est réalisée automatiquement par le processeur.

### Le fenêtrage de registres

Le fenêtrage de registres est une amélioration de la technique précédente, qui est adaptée non pas aux interruptions, mais aux appels de fonction/sous-programmes. Là encore, lors de l'appel d'une fonction, on doit sauvegarder les registres du processeur sur la pile, avant qu'ils soient utilisés par la fonction. Plus un processeur a de registres architecturaux, plus leur sauvegarde prend du temps. Et là encore, on peut dupliquer les registres pour éviter cette sauvegarde. Pour limiter le temps de sauvegarde des registres, certains processeurs utilisent le fenêtrage de registres, une technique qui permet d'intégrer cette pile de registre directement dans les registres du processeur.
La technique de fenêtrage de registres la plus simple duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux.
Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres. Ainsi, pas besoin de sauvegarder les registres de cette fenêtre, vu qu'ils étaient vides de toute donnée. S'il ne reste pas de fenêtre inutilisée, on est obligé de sauvegarder les registres d'une fenêtre dans la pile. Les interruptions peuvent aussi utiliser le fenêtrage de registres. Lorsqu'une interruption se déclenche, elle se voit allouer sa propre fenêtre de registres.
L'implémentation précédente a des fenêtres de taille fixe, qui sont toutes isolées les unes des autres. C'était le fenêtrage de registre implémenté sur le processeur Berkeley RISC, et quelques autres processeurs. Des techniques similaires permettent cependant d'avoir des fenêtres de taille variable !
Avec des fenêtres de registre de taille variable, chaque fonction peut réserver un nombre de registres différent des autres fonctions. Une fonction peut réserver 5 registres, une autre 8, une autre 6, etc. Par contre, il y a une taille maximale. Les registres du processeur se comportent alors comme une pile de registres. Un exemple est celui du processeur AMD 29000, qui implémente des fenêtres de taille variable, chaque fenêtre pouvant aller de 1 à 8 registres. C'était la même chose sur les processeurs Itanium.
Il faut noter que les processeurs Itanium et AMD 29000 n'utilisaient le fenêtrage de registres que sur une partie des registres généraux. Par exemple, l'AMD 29000 disposait de 196 registres, soit 64 + 128. Les registres sont séparés en deux groupes : un de 64, un autre de 128. Les 128 registres sont ceux avec le fenêtrage de registres, à savoir qu'ils forment une pile de registres utilisée pour les appels de fonction. Les 64 registres restants sont des registres généraux normaux, où le fenêtrage de registre ne s'applique pas, et qui est accessible par toute fonction. Cette distinction entre pile de registres (avec fenêtrage) et registres globaux (sans fenêtrage) existe aussi sur l'Itanium, qui avait 32 registres globaux et 96 registres avec fenêtrage.