Pourquoi MIPS utilise R0 comme «zéro» alors que vous pouvez simplement XOR deux registres pour produire 0?

10

Je pense que je cherche une réponse à une question triviale. J'essaie de comprendre pourquoi l'architecture MIPS utilise une valeur "zéro" explicite dans un registre alors que vous pouvez réaliser la même chose en XOR simplement n'importe quel registre contre lui-même. On pourrait dire que l'opération est déjà faite pour vous; cependant, je ne peux pas vraiment imaginer une situation où vous utiliseriez beaucoup de valeurs "zéro". J'ai lu les articles originaux de Hennessey, et il attribue simplement un zéro en fait sans aucune justification réelle.

Existe-t-il une raison logique d'avoir une affectation binaire codée en dur de zéro?

mise à jour: Dans 8k d'un exécutable de xc32-gcc pour le noyau MIPS dans le PIC32MZ, j'ai une seule instance de "zéro".

add     t3,t1,zero

la réponse réelle: j'ai attribué la prime à la personne qui avait les informations sur MIPS et les codes de condition. La réponse réside en fait dans l'architecture MIPS pour les conditions. Bien que je ne veuille initialement pas y consacrer de temps, j'ai examiné l'architecture pour opensparc , MIPS-V et OpenPOWER (ce document était interne) et voici les résultats résumés. Le registre R0 nécessaire à la comparaison sur les branches en raison de l'architecture du pipeline.

  • comparaison entre un entier et zéro et une branche (bgez, bgtz, blez, bltz)
  • entier comparer deux registres et branche (beq, bne)
  • entier compare deux registres et piège (teq, tge, tlt, tne)
  • registre de comparaison entier et immédiat et piège (teqi, tgei, tlti, tnei)

Cela se résume simplement à l'apparence du matériel dans la mise en œuvre. Dans le manuel MIPS-V, il y a une citation non référencée à la page 68:

Les branches conditionnelles ont été conçues pour inclure des opérations de comparaison arithmétique entre deux registres (comme cela est également fait dans PA-RISC et Xtensa ISA), plutôt que d'utiliser des codes de condition (x86, ARM, SPARC, PowerPC), ou pour comparer uniquement un registre contre zéro ( Alpha, MIPS), ou deux registres uniquement pour l'égalité (MIPS). Cette conception a été motivée par l'observation qu'une instruction combinée de comparaison et de dérivation se transforme en un pipeline régulier, évite un état de code de condition supplémentaire ou l'utilisation d'un registre temporaire, et réduit la taille du code statique et la récupération dynamique des instructions trac. Un autre point est que les comparaisons avec zéro nécessitent un retard de circuit non trivial (en particulier après le passage à la logique statique dans les processus avancés) et sont donc presque aussi chères que les comparaisons d'amplitude arithmétique. Un autre avantage d'une instruction de comparaison et de dérivation fusionnée est que les dérivations sont observées plus tôt dans le flux d'instructions frontales et peuvent donc être prédites plus tôt. Il y a peut-être un avantage à une conception avec des codes de condition dans le cas où plusieurs branches peuvent être prises sur la base des mêmes codes de condition, mais nous pensons que ce cas est relativement rare.

Le document MIPS-V ne frappe pas l'auteur de la section citée. Je remercie chacun pour son temps et sa considération.

b degnan
la source
6
Vous souhaitez souvent utiliser un registre de valeur 0 dans certaines opérations comme valeur source. La mise à zéro d'un registre représenterait une surcharge pour ces opérations, donc les performances sont avantageuses si vous pouvez simplement utiliser un zéro fourni au lieu de le créer vous-même chaque fois que vous en avez besoin. Les exemples incluent l'ajout d'un indicateur de portage.
JimmyB
3
Sur l'architecture AVR, gcc prend soin d'initialiser r1 à zéro au démarrage et ne touche plus jamais cette valeur, en utilisant r1 comme source partout où un 0 immédiat ne peut pas être utilisé. Ici, le registre zéro dédié est «émulé» dans le logiciel par le compilateur pour des raisons de performances. (La plupart des AVR ont 32 registres, donc en mettre un (deux, en fait) de côté ne coûte pas cher par rapport aux avantages possibles en termes de performances et de taille de code.)
JimmyB
1
Je ne connais pas MIPS, mais il peut être plus rapide de déplacer r0 vers un autre registre par rapport à XORing ce registre pour le faire effacer.
JimmyB
Vous n'êtes donc pas d'accord sur le fait que le zéro est si fréquent qu'il mérite une place dans le fichier du registre? Alors vous avez probablement raison, car il est vrai que cela est controversé et que de nombreuses ISA choisissent de ne pas réserver un registre zéro. Comme d'autres fonctionnalités controversées à l'époque comme les fenêtres d'enregistrement, les emplacements de branche, la prédication d'instructions de "l'ancien temps" ... si vous devez concevoir un ISA, vous n'avez pas à les utiliser si vous décidez de ne pas le faire.
user3528438
2
Il peut être intéressant de lire l'un des anciens documents RISC de Berkeley, RISC I: Un ordinateur VLSI à jeu d'instructions réduit . Il montre comment l'utilisation d'un registre zéro câblé, R0, permet à un certain nombre d'instructions VAX et de modes d'adressage d'être implémentés dans une seule instruction RISC.
Mark Plotnick

Réponses:

14

Le registre zéro sur les processeurs RISC est utile pour deux raisons:

C'est une constante utile

Selon les restrictions de l'ISA, vous ne pouvez pas utiliser un littéral dans le codage de certaines instructions, mais vous pouvez être sûr de pouvoir l'utiliser r0pour obtenir 0.

Il peut être utilisé pour synthétiser d'autres instructions

C'est peut-être le point le plus important. En tant que concepteur ISA, vous pouvez échanger un registre à usage général contre un registre zéro pour pouvoir synthétiser d'autres instructions utiles. La synthèse d'instructions est bonne car en ayant moins d'instructions réelles, vous avez besoin de moins de bits pour encoder une opération dans un opcode, ce qui libère de l'espace dans l'espace d'encodage des instructions. Vous pouvez utiliser cet espace pour avoir, par exemple, des décalages d'adresse et / ou des littéraux plus importants.

La sémantique du registre zéro est comme /dev/zerosur les systèmes * nix: tout ce qui y est écrit est supprimé et vous relisez toujours 0.

Voyons quelques exemples de la façon dont nous pouvons faire des pseudo-instructions à l'aide du r0registre zéro:

; ### Hypothetical CPU ###

; Assembler with syntax:
; op rd, rm, rn 
; => rd: destination, rm: 1st operand, rn: 2nd operand
; literal as #lit

; On an CPU architecture with a status register (which contains arithmetic status
; flags), `sub` can be used, with r0 as destination to discard result.
cmp rn, rm     ; => sub r0, rn, rm

; `add` instruction can be used as a `mov` instruction:
mov rd, rm     ; => add rd, rm, r0
mov rd, #lit   ; => add rd, r0, #lit

; Negate:
neg rd, rm     ; => sub rd, r0, rm

; On CPU without status flags,
nop            ; => add r0, r0, r0

; RISC-V's `jal` instruction -- Jump and Link: Jump to PC-relative instruction,
; save return address into rd; we can synthesize a `jmp` instruction out of it.
jmp dest       ; => jal r0, dest

; You can even load from an absolute (direct) address, for a usually small range
; of addresses by using a literal offset as an address.
ld rd, addr    ; => ld rd, [r0, #addr]

Le cas de MIPS

J'ai regardé de plus près le jeu d'instructions MIPS. Il existe une poignée de pseudo-instructions qui utilisent $zero; ils sont principalement utilisés pour les branches. Voici quelques exemples de ce que j'ai trouvé:

move $rt, $rs          => add $rt, $rs, $zero

not $rt, $rs           => nor $rt, $rs, $zero

b Label                => beq $zero, $zero, Label ; a small relative branch

bgt $rs, $rt, Label    => slt $at, $rt, $rs
                          bne $at, $zero, Label

blt $rs, $rt, Label    => slt $at, $rs, $rt
                          bne $at, $zero, Label

bge $rs, $rt, Label    => slt $at, $rs, $rt
                          beq $at, $zero, Label

ble $rs, $rt, Label    => slt $at, $rt, $rs
                          beq $at, $zero, Label

Quant à savoir pourquoi vous n'avez trouvé qu'une seule instance du $zeroregistre dans votre désassemblage, c'est peut-être votre désassembleur qui est assez intelligent pour transformer des séquences d'instructions connues en leur pseudo-instruction équivalente.

Le registre zéro est-il vraiment utile?

Eh bien, apparemment, ARM trouve un registre zéro suffisamment utile pour que dans leur (quelque peu) nouveau cœur ARMv8-A, qui implémente AArch64, il y ait maintenant un registre zéro en mode 64 bits; il n'y avait pas de registre zéro auparavant. (Le registre est un peu spécial cependant, dans certains contextes d'encodage, c'est un registre nul, dans d'autres, il désigne plutôt le pointeur de pile )

Jarhmander
la source
Je ne pense pas que MIPS utilise des drapeaux, n'est-ce pas? Le registre zéro ajoute la possibilité de lire / écrire sans condition certaines adresses sans tenir compte du contenu des registres du processeur et aide à faciliter une opération de style "mov immédiat", mais d'autres movs pourraient être effectués par une logique ou-source de la source avec elle-même .
supercat
1
En effet, il n'y a pas de registre qui détiennent des drapeaux arithmétiques, au lieu il y a trois instructions qui aident Emuler communes branches conditionnelles ( slt, slti, sltu).
Jarhmander
En regardant le jeu d'instructions MIPS, et étant donné que d'après ce que je comprends, chaque instruction sera récupérée au moment où l'instruction précédente s'exécute, je me demande s'il aurait été difficile d'avoir un opcode qui ne fait rien de direct mais au lieu de dire que si une instruction en mode immédiat s'exécute et que la prochaine instruction extraite a ce modèle de bits, les 16 bits supérieurs de l'opérande seront extraits de l'instruction pré-extraite? Ce serait des opérations en mode immédiat 32 bits à gérer avec une instruction à deux cycles de deux mots plutôt que d'avoir à passer deux mots et deux cycles ...
supercat
... charger un opérande puis un troisième cycle pour l'utiliser réellement.
supercat
7

La plupart des implémentations ARM / POWER / SPARC ont un registre RAZ caché

Vous pourriez penser que ARM32, SPARC etc. n'ont pas de registre 0 mais en fait ils en ont! Au niveau de la micro-architecture, la plupart des ingénieurs en conception de CPU ajoutent un registre 0 qui peut être invisible pour le logiciel (le registre zéro d'ARM est invisible) et utilisent ce registre zéro pour rationaliser le décodage des instructions.

Considérez une conception ARM32 moderne typique qui a un registre logiciel invisible, par exemple R16 câblé à 0. Considérez la charge ARM32, de nombreux cas d'instructions de chargement ARM32 se présentent sous l'une de ces formes (Ignorez l'indexation pré-post pendant un certain temps pour garder la discussion simple ) ...

LDR ra, [rb] // NOTE:The ! is optional and represents address writeback.
LDR ra, [rb, rc](!)
LDR ra, [rb, #k](!)

À l'intérieur du processeur, cela décode en général

ldr.uop ra, rb, rx, rc, #c // Internal decoded instruction format.

avant d'entrer dans la phase d'émission où les registres sont lus. Notez que rx représente le registre pour réécrire l'adresse mise à jour. Voici quelques exemples de décodage:

LDR R0, [R1]      ==> ldr.uop R0, R1, R16, R16, #0 // Writeback to NULL. 
LDR R0, [R1, R2]! ==> ldr.uop R0, R1, R1, R2,   #0 // Writeback to R1.
LDR R0, [R1, #2]  ==> ldr.uop R0, R1, R16, R16, #2 // Writeback to NULL.

Au niveau du circuit, les trois charges sont en fait la même instruction interne et un moyen facile d'obtenir ce type d'orthogonalité est de créer un registre de masse R16. Puisque R16 est toujours mis à la terre, ces instructions décodent naturellement correctement sans aucune logique supplémentaire. Le mappage d'une classe d'instructions à un seul format interne aide grandement dans les implémentations superscalaires car il réduit la complexité logique.

Une autre raison est un moyen simplifié de jeter les écritures. Les instructions peuvent être désactivées en réglant simplement le registre et les indicateurs de destination sur R16. Il n'est pas nécessaire de créer un autre signal de contrôle pour désactiver l'écriture différée, etc.

La plupart des implémentations de processeur, quelle que soit l'architecture, aboutissent très tôt à un modèle de registre RAZ dans le pipeline. Le pipeline MIPS commence essentiellement à un point qui, dans d'autres architectures, en serait à quelques étapes.

MIPS a fait le bon choix

Ainsi, un registre lu comme zéro est presque obligatoire dans toute implémentation de processeur moderne et MIPS le rendant visible pour le logiciel est certainement un point positif étant donné la façon dont il rationalise la logique de décodage interne. Les concepteurs de processeurs MIPS n'ont pas besoin d'ajouter un registre RAZ supplémentaire car 0 $ est déjà au sol. Étant donné que RAZ est disponible pour l'assembleur, beaucoup d'instructions de support sont disponibles pour MIPS et on peut penser à cela comme pousser une partie de la logique de décodage à l'assembleur lui-même au lieu de créer des formats dédiés pour chaque type d'instruction pour masquer le registre RAZ du logiciel comme avec d'autres architectures. Le registre RAZ est une bonne idée et c'est pourquoi ARMv8 l'a copié.

Si ARM32 avait un registre à 0 $, la logique de décodage serait devenue plus simple et l'architecture aurait été bien meilleure en termes de vitesse, de surface et de puissance. Par exemple, sur les trois versions de LDR présentées ci-dessus, seuls 2 formats seraient nécessaires. De même, il n'est pas nécessaire de réserver la logique de décodage pour les instructions MOV et MVN. De plus, CMP / CMN / TST / TEQ deviendrait redondant. Il ne serait pas non plus nécessaire de faire la différence entre une multiplication courte (MUL) et une multiplication longue (UMULL / SMULL) car une multiplication courte pourrait être considérée comme une multiplication longue avec le registre haut réglé à 0 $, etc.

Étant donné que MIPS a été initialement conçu par une petite équipe, la simplicité de conception était importante et donc 0 $ a été explicitement choisi dans l'esprit de RISC. ARM32 conserve de nombreuses fonctionnalités traditionnelles du CISC au niveau architectural.

Revanth Kamaraj
la source
1
Tous les processeurs ARM32 ne fonctionnent pas comme vous le décrivez. Certains ont des performances inférieures pour les instructions de chargement plus complexes et / ou pour la réécriture dans le registre. Ils ne peuvent donc pas tous décoder exactement de la même manière.
Peter Cordes
6

Disclamer: Je ne connais pas vraiment l'assembleur MIPS, mais le registre de valeur 0 n'est pas unique à cette architecture, et je suppose qu'il est utilisé de la même manière que dans d'autres architectures RISC que je connais.

XOR un registre pour obtenir 0 vous coûtera une instruction, alors que l'utilisation d'un registre de valeur 0 prédéfini ne le sera pas.

Par exemple, l' mov RX, RYinstruction est souvent implémentée en tant que add RX, RY, R0. Sans registre à valeur 0, vous devriez le faire à xor RZ, RZchaque fois que vous souhaitez l'utiliser mov.

Un autre exemple est l' cmpinstruction et ses variantes (comme "comparer et sauter", "comparer et déplacer", etc.), où cmp RX, R0est utilisé pour tester les nombres négatifs.

Dmitry Grigoryev
la source
1
Y aurait-il des problèmes de mise MOV Rx,Ryen œuvre en tant que AND Rx,Ry,Ry?
supercat
3
@supercat Vous ne pourrez pas encoder mov RX, Immou mov RX, mem[RY]si votre jeu d'instructions ne prend en charge qu'une seule valeur immédiate et un seul accès à la mémoire par instruction.
Dmitry Grigoryev
Je ne connais pas les modes d'adressage du MIPS. Je sais que l'ARM a les modes [Rx + Ry << scale] et [Rx + disp], et bien que pouvoir utiliser ce dernier pour certaines adresses absolues pourrait être utile dans certains cas, ce n'est généralement pas essentiel. Un mode [Rx] droit pourrait être émulé via [Rx + disp] en utilisant un déplacement nul. À quoi sert le MIPS?
supercat
movest un mauvais exemple; vous pouvez l'implémenter avec un 0 immédiat au lieu d'un registre nul. par exemple ori dst, src, 0. Mais oui, vous auriez besoin d'un opcode pour mov-immediate pour vous inscrire si vous n'en aviez pas addiu $dst, $zero, 1234, comme luipour les 16 bits inférieurs au lieu des 16. Et vous ne pouviez pas utiliser norou subpour construire un opérande non / neg .
Peter Cordes
@supercat: au cas où vous vous poseriez toujours la question: MIPS classique n'a qu'un seul mode d'adressage: register + disp16. MIPS moderne a ajouté d'autres opcodes pour les modes d'adressage à 2 registres pour les chargements / magasins FP, accélérant ainsi l'indexation de la matrice. (Mais toujours pas pour le chargement / stockage d'entiers, peut-être parce que cela pourrait nécessiter plus de ports de lecture dans le fichier de registre d'entiers pour 2 registres d'adresses + un registre de données pour un magasin. Voir Utiliser un registre comme décalage )
Peter Cordes
3

Lier quelques pistes à la terre à la fin de votre banque de registres est bon marché (moins cher que d'en faire un registre complet).

Faire le xor réel prend un peu de puissance et de temps pour commuter les portes et ensuite le stocker dans le registre, pourquoi payer ce coût lorsqu'une valeur 0 existante peut facilement être disponible.

Les processeurs modernes ont également un registre de valeur 0 (caché) qu'ils peuvent utiliser à la suite d'une xor eax eaxinstruction via un changement de nom de registre.

monstre à cliquet
la source
6
Le vrai coût R0n'est pas de mettre à la terre quelques fils, mais de devoir lui réserver un code dans chaque instruction traitant des registres.
Dmitry Grigoryev
Le xor est un hareng rouge. xor-zeroing n'est bon que sur x86, où les CPU reconnaissent l'idiome et évitent une dépendance sur les entrées. Comme vous le faites remarquer, la famille Sandybridge ne lance même pas d'uop pour elle, elle la gère simplement à l'étape de changement de nom de registre. ( Quelle est la meilleure façon de mettre un registre à zéro dans un assemblage x86: xor, mov ou and? ). Mais sur MIPS, XOR un registre aurait une fausse dépendance; Les règles de classement des dépendances de mémoire (équivalent HW de C ++ std::memory_order_consume) nécessitent que XOR propage la dépendance.
Peter Cordes
Si vous n'aviez pas de registre zéro, vous incluriez un opcode pour déplacer un immédiat vers un registre. Comme luimais pas décalé à gauche de 16. Ainsi, vous pouvez toujours mettre un petit nombre dans un registre avec une instruction. Autoriser uniquement zéro avec une fausse dépendance serait insensé. (Le MIPS normal crée des valeurs non nulles avec addiu $dst, $zero, 1234ou ori, donc votre argument "coût d'énergie" se décompose. Si vous vouliez éviter de lancer une ALU, vous incluriez un opcode pour mov-immediate à enregistrer au lieu d'avoir le logiciel ADD ou OR un immédiat avec zéro.)
Peter Cordes