Fonctionnement d'un ordinateur/Les registres du processeur

Un programmeur (ou un compilateur) qui souhaite programmer en langage machine peut manipuler les registres intégrés dans le processeur. À ce stade, il faut faire une petite remarque : tous les registres d'un processeur ne sont pas forcément manipulables par le programmeur. Il existe ainsi deux types de registres : les registres architecturaux, manipulables par des instructions, et les registres internes aux processeurs. Ces derniers servent à simplifier la conception du processeur ou mettre en œuvre des optimisations de performance. Dans ce qui suit, nous allons parler uniquement des registres architecturaux.

L'utilisation des registres modifier

Les registres architecturaux se distinguent par le type de données que peuvent contenir leurs registres. Des processeurs ont des registres banalisés qui peuvent contenir tout type de données, tandis que d'autres ont des registres spécialisés pour chaque type de données. Les deux solutions ont des avantages et inconvénients différents. Elles sont toutefois complémentaires et non exclusives. Certains processeurs ont des registres généraux complémentés avec des registres spécialisés, pour des raisons de facilité.

Les registres spécialisés modifier

Les registres spécialisés ont une utilité bien précise et leur fonction est fixée une bonne fois pour toutes. Un registre spécialisé est conçu pour stocker soit des nombres entiers, des flottants, des adresses, etc; mais pas autre chose. Pour ce qui est des registres spécialisés, le plus important est clairement le Program Counter. On peut aussi trouver les registres pour gérer la pile (le Stack Pointer et le Frame Pointer). Pour ce qui est des registres de données les plus courants, en voici la liste.

  • Les registres entiers sont spécialement conçus pour stocker des nombres entiers.
  • Les registres flottants sont spécialement conçus pour stocker des nombres flottants.
  • Les registres de constante 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).
  • Les registres d'Index servent à calculer des adresses, afin de manipuler rapidement des données complexes comme les tableaux.

Nous avons parlé de ces registres dans les chapitres précédents, ce qui fait que nous n'allons pas revenir dessus. Par contre, nous n’avons pas encore vu le registre spécialisé par excellence : le registre d'état.

Le registre d'état modifier

Le registre d'état contient au minimum 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. Il arrive que le registre d'état soit mis à jour non seulement par les instructions de test, mais aussi par les instructions arithmétiques. Par exemple, si le résultat d'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. 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 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.

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.

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.

Les registres généraux modifier

Fournir des registres très spécialisés n'est pas très flexible. Prenons un exemple : j'ai un processeur disposant d'un Program Counter, de 4 registres entiers, de 4 registres d'Index pour calculer des adresses, et de 4 registres flottants. Si jamais j’exécute un morceau de programme qui manipule beaucoup de nombres entiers, mais qui ne manipule pas d'adresses ou de nombre flottants, j'utiliserais juste les 4 registres entiers. Une partie des registres du processeur sera inutilisé : tous les registres flottants et d'Index. Le problème vient juste du fait que ces registres ont une fonction bien fixée.

En réfléchissant, un registre est un registre, et il ne fait que stocker une suite de bits. Il peut tout stocker : adresses, flottants, entiers, etc. Pour plus de flexibilité, certains processeurs ne fournissent pas de registres spécialisés comme des registres entiers ou flottants, mais fournissent à la place des registres généraux utilisables pour tout et n'importe quoi. Ce sont des registres qui n'ont pas d'utilité particulière et qui peuvent stocker toute sorte d’information codée en binaire. Pour reprendre l'exemple du dessus, un processeur avec des registres généraux fournira un Program Counter et 12 registres généraux, qu'on peut utiliser sans vraiment de restrictions. On pourra s'en servir pour stocker 12 entiers, 10 entiers et 2 flottants, 7 adresses et 5 entiers, etc. Ce qui sera plus flexible et permettra de mieux utiliser les registres.

Les méthodes hybrides modifier

Le choix entre registres spécialisés et registres généraux est une question de pragmatisme. Il existe bien des processeurs qui ne le sont pas et où tous les registres sont des registres généraux, même le Program Counter. 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.

D'autres processeurs font des choix moins extrême mais tout aussi discutables. 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 ce processeur manipule des manipule entiers et adresses de 32 bits, ce qui fait que ses registres font 32 bits, le le program counter ne fait pas exception. Mais le program counter n'a besoin que de 26 bits pour fonctionner. Il reste donc 32-26=6 bits à utiliser pour autre chose. De plus, les instructions de ce processeur font exactement 32 bits, pas un de plus ni de moins, et elles sont alignées en mémoire. Donc, 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.

Mais le cas précédent est rare, très rare. Dans la réalité, les processeurs utilisent souvent une espèce de mélange entre les deux solutions. Généralement, une bonne partie des registres du processeur sont des registres généraux, à part quelques registres spécialisés, accessibles seulement à travers quelques instructions bien choisies. Sur les processeurs modernes, l'usage de registres spécialisés est tombé en désuétude, sauf évidemment pour le program counter. Les registres d'index ont disparus, les registres pour gérer la pile aussi, le registre d'état est en voie de disparition car il se marie mal avec les optimisations modernes que nous verrons dans quelques chapitres (pipeline, exécution dans le désordre, renommage de registres).

L'adressage des registres architecturaux modifier

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 modifier

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 bien précise. 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. Cet identifiant est ce qu'on appelle un nom 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. Ce numéro, ou nom de registre, permet d'identifier le registre que l'on veut, mais ne sort jamais du processeur : ce nom de registre, ce numéro, ne se retrouve jamais sur le bus d'adresse. Les registres ne sont donc pas identifiés par une adresse mémoire.

 
Adressage des registres via des noms de registre.

Les registres adressés modifier

Mais il existe une autre solution, assez peu utilisée. Sur certains processeurs assez rares, on peut adresser les registres via une adresse mémoire. Il est vrai que c'est assez rare, et qu'à part quelques vielles architectures ou quelques microcontrôleurs, je n'ai pas d'exemples à donner. Mais c'est tout à fait possible ! C'est le cas du PDP-10.

 
Adressage des registres via des adresses mémoires.

Les registres adressés implicitement modifier

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 identifiant pour pouvoir les sélectionner. C'est aussi le cas du registre d'adresse d'instruction : sur certains processeurs, il est manipulé automatiquement par le processeur et par les instructions de branchement. C'est aussi le cas pour le 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 modifier

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).

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 32bits alors que le bus de données peut contenir 64 bits en même temps. Cela a une petite incidence sur la façon dont une donnée est transférée entre la mémoire et un registre. On peut donc se retrouver dans deux situations différentes : soit le bus de données a une largeur égale à la taille d'un registre, soit la largeur du bus de données est plus petite que la taille d'un registre. Dans le premier cas, le bus de données peut charger en une seule fois le nombre de bits que peut contenir un registre. Dans le second cas, on ne peut pas charger le contenu d'un registre en une fois, et on doit charger ce contenu morceau par morceau.

La taille d'un registre est souvent une puissance de deux modifier

Aujourd'hui, les processeurs utilisent presque tous des registres de la même taille que le byte, qui est une puissance de 2 (8, 16, 32, 64, 128, 256, voire 512 bits). Mais cette règle souffre évidemment d'exceptions. Aux tout débuts de l'informatique, 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.

L'usage de bytes qui ne sont pas des puissances de 2 posent quelques problèmes techniques en termes d’adressage. Les problèmes en question surviennent sur les processeurs qui adressent la mémoire par mot (pour rappel, un mot est un groupe de plusieurs bytes). À partir de l'adresse d'un byte, le processeur doit en déduire l'adresse du mot à lire. Et une fois le mot récupéré, le processeur doit décider quel est le byte à prendre en compte. Un mot contenant N bytes, le calcul de l'adresse du mot est une simple division : on divise l'adresse du byte par N. Le reste de la division correspond à la position du byte dans le mot. Avec  , la division est un simple décalage et le modulo est un simple ET logique, deux opérations très rapides. Si ce n'est pas le cas, on doit faire une vraie division et un vrai modulo, deux opérations excessivement lentes.

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

Sur les processeurs x86, on trouve des registres de taille différentes. Certains registres sont 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. Les premiers processeurs x86 étaient des processeurs 16 bits, mais ils s’inspiraient grandement des processeurs 8008 qui étaient des processeurs 8 bits. Le 8086 et le 8088 étaient en effet des versions améliorées et grandement remaniées des premiers 8008 d'Intel. En théorie, la rétrocompatibilité n'était pas garantie, dans le sens où 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, alors que 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. Pour ces registres, on a 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é. Les autres registres ne sont pas concernés par ce découpage, que ce soit les registres de données, d'adresse ou autres.

 
Registres du 8086, processeur x86 16 bits. Certains registres sont liés à la segmentation ou à d'autres fonctions que nous n'avons pas encore expliqué à ce point du cours, aussi je vais vous demander de les ignorer.

Au départ, l'architecture x86 avait des registres de 16 bits. Puis, par la suite, l'architecture a étendu ses registres à 32 et enfin 64 bits. Mais cela s'est ressentit au niveau des registres, où le système d'alias a été étendu. 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. La raison est que les registres 16 bits originaux, présents sur les processeurs x86 16 bits, ont été étendus à 32 bits. Les 16 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. Chose étonnante, 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.

 
Registres des processeurs x86 32 bits. Certains registres sont liés à la segmentation ou à d'autres fonctions que nous n'avons pas encore expliqué à ce poitn du cours, aussi je vais vous demander de les ignorer.

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. Dans le chapitre sur les architectures à parallélisme de données, nous verrons plusieurs cas assez impressionnants de cela. Pour le moment, bornons-nous à citer les exemples les plus frappants, sans rentrer dans les détails, et parlons du MMX, du SSE et de l'AVX.

Le MMX est une extension du x86, à savoir l'ajout d'instructions particulières au jeu d’instruction x86 de base. Cette extension ajoutait 8 registres appelés MM0, MM1, MM2, MM3, MM4, MM5, MM6 et MM7, d'une taille de 64 bits, qui ne pouvaient contenir que des nombres entiers. En théorie, on s'attendrait à ce que ces registres soient des registres séparés. Mais Intel utilisa le système d'alias pour éviter d'avoir à rajouter des registres. À la place, il étendit les registres flottants déjà existants, eux-même ajoutés par l'extension x87, qui définit 8 registres flottants de 80 bits. 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...

 
Registres AVX.

Par la suite, Intel ajouta une nouvelle extension appelée le SSE, qui ajoutait 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. Puis, Intel ajouta une nouvelle extension, l'AVX. 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-515 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'éviter d'ajouter des registres de plus grande taille, en étendant des registres existants pour en augmenter la taille. Ce n'était peut-être pas sa fonction première, du moins sur les processeurs Intel, mais c’est ainsi qu'il a été utilisé à l'excès par Intel. En soi, la technique est intéressante et permet certainement des économies de circuits dignes de ce nom. La longévité des architectures x86 a fait que cette technique a beaucoup été utilisée, l'architecture ayant été étendue deux fois : lors du passage de 16 à 32 bits, puis de 32 à 64 bits. Le système d'extension a aussi été la source de plusieurs usage de l'aliasing de registres. 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 le processeur Z80 modifier

Le processeur Z80 est très inspiré des premiers processeurs Intel, mais avec un jeu d'instruction légèrement différent. Il a 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.

Le Z80 est un processeur 8 bits, qui contient plusieurs registres généraux de 8 bits nommés A, B, C, D, E, F, H, L. Les registres A et F correspondent à l'accumulateur et aux registres d'état, ils ne sont pas concernés 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. Le registre BC de 16 bits est composé du registre B de 8 bits et du registre C de 8 bits, et ainsi de suite 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 complète. Les paires sont concrètement utilisées seulement pour l'incrémentation, avec une instruction spécialisée, qui incrémente le registre de 16 bit d'une paire.

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.

Les optimisations liées aux registres architecturaux modifier

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.

Les registres dédiés aux interruptions modifier

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 modifier

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.

Le fenêtrage de registres de taille fixe modifier

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.

 
Fenêtre de registres.

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.

Le fenêtrage de registres de taille variable modifier

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.

Le renommage de registre modifier

Les processeurs récents utilisent des techniques de renommage de registre, que l'on ne peut pas aborder pour le moment (par manque de concepts importants). Mais d'autres techniques similaires sur le principe sont possibles, comme le fenêtrage de registres.