Warum verwendet MIPS R0 als "Null", wenn Sie nur zwei Register XOR können, um 0 zu erzeugen?

10

Ich denke, ich suche nach einer Antwort auf eine wichtige Frage. Ich versuche zu verstehen, warum die MIPS-Architektur einen expliziten "Null" -Wert in einem Register verwendet, wenn Sie dasselbe erreichen können, indem Sie einfach ein Register gegen sich selbst XOR'en. Man könnte sagen, dass die Operation bereits für Sie durchgeführt wurde; Ich kann mir jedoch keine Situation vorstellen, in der Sie viele "Null" -Werte verwenden würden. Ich habe Hennesseys Originalarbeiten gelesen, und es wird tatsächlich nur eine Null zugewiesen, ohne dass dies wirklich gerechtfertigt ist.

Gibt es einen logischen Grund für eine fest codierte binäre Zuweisung von Null?

Update: In 8k einer ausführbaren Datei von xc32-gcc für den MIPS-Kern im PIC32MZ habe ich eine einzelne Instanz von "Null".

add     t3,t1,zero

Die eigentliche Antwort: Ich habe das Kopfgeld an die Person vergeben, die die Informationen über MIPS und Zustandscodes hatte. Die Antwort liegt tatsächlich in der MIPS-Architektur für Bedingungen. Obwohl ich diesem ursprünglich keine Zeit zuweisen wollte, überprüfte ich die Architektur für opensparc , MIPS-V und OpenPOWER (dieses Dokument war intern). Hier sind die zusammenfassenden Ergebnisse. Das R0-Register, das aufgrund der Architektur der Pipeline für den Vergleich in Zweigen erforderlich ist.

  • Ganzzahlvergleich gegen Null und Verzweigung (bgez, bgtz, blez, bltz)
  • Ganzzahl vergleiche zwei Register und einen Zweig (beq, bne)
  • Ganzzahl vergleiche zwei Register und Trap (teq, tge, tlt, tne)
  • Ganzzahl-Vergleichsregister und Sofort- und Falle (teqi, tgei, tlti, tnei)

Es kommt einfach darauf an, wie die Hardware in der Implementierung aussieht. Im MIPS-V-Handbuch finden Sie ein nicht referenziertes Zitat auf Seite 68:

Die bedingten Verzweigungen wurden so konzipiert, dass sie arithmetische Vergleichsoperationen zwischen zwei Registern (wie auch in PA-RISC und Xtensa ISA) enthalten, anstatt Bedingungscodes (x86, ARM, SPARC, PowerPC) zu verwenden oder nur ein Register mit Null zu vergleichen ( Alpha, MIPS) oder zwei Register nur für Gleichheit (MIPS). Dieses Design wurde durch die Beobachtung motiviert, dass ein kombinierter Vergleichs- und Verzweigungsbefehl ts in eine reguläre Pipeline ts einen zusätzlichen Zustand des Bedingungscodes oder die Verwendung eines temporären Registers vermeidet und die statische Codegröße und den dynamischen Befehlsabruf-Trac reduziert. Ein weiterer Punkt ist, dass Vergleiche gegen Null eine nicht triviale Schaltungsverzögerung erfordern (insbesondere nach der Umstellung auf statische Logik in fortgeschrittenen Prozessen) und daher fast so teuer sind wie arithmetische Größenvergleiche. Ein weiterer Vorteil eines fusionierten Vergleichs- und Verzweigungsbefehls besteht darin, dass Verzweigungen früher im Front-End-Befehlsstrom beobachtet werden und daher früher vorhergesagt werden können. Ein Entwurf mit Bedingungscodes hat vielleicht einen Vorteil, wenn mehrere Zweige basierend auf denselben Bedingungscodes verwendet werden können, aber wir glauben, dass dieser Fall relativ selten ist.

Das MIPS-V-Dokument trifft den Autor des zitierten Abschnitts nicht. Ich danke allen für ihre Zeit und Rücksichtnahme.

b degnan
quelle
6
Sie wollen oft verwenden ein 0 Wert Register in irgendeiner Operation als Quellenwert. Es wäre ein gewisser Aufwand, ein Register vor diesen Vorgängen auf Null zu setzen. Dies hat also Leistungsvorteile, wenn Sie nur eine bereitgestellte Null verwenden können, anstatt sie bei Bedarf selbst zu erstellen. Beispiele umfassen das Hinzufügen eines Übertragsflags.
JimmyB
3
In der AVR-Architektur sorgt gcc dafür, dass r1 beim Start auf Null initialisiert wird, und berührt diesen Wert nie wieder. Dabei wird r1 als Quelle verwendet, wenn eine sofortige 0 nicht verwendet werden kann. Hier wird das dedizierte Nullregister vom Compiler aus Leistungsgründen in der Software "emuliert". (Die meisten AVRs haben 32 Register, so dass das Weglegen eines (eigentlich zwei) Registers im Verhältnis zu den möglichen Vorteilen für Leistung und Codegröße nicht viel kostet.)
JimmyB
1
Ich weiß nichts über MIPS, aber es kann schneller sein, r0 in ein anderes Register zu verschieben, als dieses Register zu XORen, um es zu löschen.
JimmyB
Sie sind sich also nicht einig darüber, dass Null so häufig ist, dass es eine Position in der Registerdatei wert ist? Dann haben Sie wahrscheinlich Recht, denn es ist wahr, dass dies umstritten ist und es viele ISAs gibt, die sich dafür entscheiden, kein Nullregister zu reservieren. Wie andere kontroverse Funktionen zu dieser Zeit wie Registerfenster, Verzweigungsslots, Anweisungsprädikation aus "alten Zeiten" ... wenn Sie eine ISA entwerfen möchten, müssen Sie sie nicht verwenden, wenn Sie sich dagegen entscheiden.
user3528438
2
Es kann interessant sein, eines der alten Berkeley RISC-Papiere zu lesen, RISC I: Ein VLSI-Computer mit reduziertem Befehlssatz . Es zeigt, wie die Verwendung eines fest verdrahteten Nullregisters R0 die Implementierung einer Reihe von VAX-Befehlen und Adressierungsmodi in einem einzigen RISC-Befehl ermöglicht.
Mark Plotnick

Antworten:

14

Das Nullregister auf RISC-CPUs ist aus zwei Gründen nützlich:

Es ist eine nützliche Konstante

Abhängig von den Einschränkungen der ISA können Sie in einigen Anweisungscodierungen kein Literal verwenden, aber Sie können sicher sein, dass Sie r0damit 0 erhalten können.

Es kann verwendet werden, um andere Anweisungen zu synthetisieren

Dies ist vielleicht der wichtigste Punkt. Als ISA-Designer können Sie ein Allzweckregister gegen ein Nullregister austauschen, um andere nützliche Anweisungen zu synthetisieren. Das Synthetisieren von Anweisungen ist gut, da Sie durch weniger tatsächliche Anweisungen weniger Bits benötigen, um eine Operation in einem Opcode zu codieren, wodurch Speicherplatz im Anweisungscodierungsraum frei wird. Sie können diesen Bereich verwenden, um z. B. größere Adressversätze und / oder Literale zu erhalten.

Die Semantik des Nullregisters ist wie /dev/zeroauf * nix-Systemen: Alles, was darauf geschrieben wird, wird verworfen, und Sie lesen immer 0 zurück.

Sehen wir uns einige Beispiele an, wie wir mit Hilfe des r0Nullregisters Pseudoanweisungen erstellen können:

; ### 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]

Der Fall von MIPS

Ich habe mir den MIPS-Befehlssatz genauer angesehen. Es gibt eine Handvoll Pseudo-Anweisungen, die verwendet werden $zero; Sie werden hauptsächlich für Zweige verwendet. Hier sind einige Beispiele von dem, was ich gefunden habe:

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

Warum Sie $zeroin Ihrer Demontage nur eine Instanz des Registers gefunden haben , ist vielleicht Ihr Disassembler, der klug genug ist, bekannte Befehlssequenzen in ihre äquivalente Pseudobefehl umzuwandeln.

Ist das Nullregister wirklich nützlich?

Nun, anscheinend findet ARM ein Nullregister nützlich genug, dass es in seinem (etwas) neuen ARMv8-A-Kern, der AArch64 implementiert, jetzt ein Nullregister im 64-Bit-Modus gibt; Es gab vorher kein Nullregister. (Das Register ist etwas speziell, in einigen Codierungskontexten ist es ein Nullregister, in anderen bezeichnet es stattdessen den Stapelzeiger. )

Jarhmander
quelle
Ich glaube nicht, dass MIPS Flags verwendet, oder? Das Nullregister bietet die Möglichkeit, bestimmte Adressen bedingungslos zu lesen / schreiben, ohne Rücksicht auf den Inhalt von CPU-Registern, und erleichtert eine Operation im "Mov-Sofort" -Stil. Andere Bewegungen können jedoch durch logisches Oder-Verknüpfen der Quelle mit sich selbst erfolgen .
Supercat
1
Tatsächlich gibt kein Register, die arithmetischen Fahnen halten, stattdessen gibt es drei Befehle , die helfen , emulieren gemeinsame bedingte Verzweigungen ( slt, slti, sltu).
Jarhmander
Wenn ich mir den MIPS-Befehlssatz ansehe und feststelle, dass nach meinem Verständnis jeder Befehl zum Zeitpunkt der Ausführung des vorherigen Befehls abgerufen wird, frage ich mich, ob es schwierig gewesen wäre, einen Opcode zu haben, der nichts direkt zu tun hat, sondern dies stattdessen sagt Wenn ein Sofortmodusbefehl ausgeführt wird und der nächste abgerufene Befehl dieses Bitmuster aufweist, werden die oberen 16 Bits des Operanden aus dem vorabgerufenen Befehl entnommen. Das würde 32-Bit-
Sofortmodusoperationen
... einen Operanden laden und dann einen dritten Zyklus, um ihn tatsächlich zu verwenden.
Supercat
7

Die meisten ARM / POWER / SPARC-Implementierungen haben ein verstecktes RAZ-Register

Sie könnten denken, dass ARM32, SPARC usw. kein 0-Register haben, aber tatsächlich! Auf der Ebene der Mikroarchitektur fügen die meisten CPU-Konstrukteure ein 0-Register hinzu, das für die Software möglicherweise unsichtbar ist (das Nullregister von ARM ist unsichtbar), und verwenden dieses Nullregister, um die Befehlsdecodierung zu optimieren.

Stellen Sie sich ein typisches modernes ARM32-Design mit einem unsichtbaren Software-Register vor, z. B. R16, das mit 0 verdrahtet ist. Betrachten Sie die ARM32-Last. Viele Fälle von ARM32-Ladeanweisungen fallen in eine dieser Formen (Ignorieren Sie die Indizierung vor dem Post für eine Weile, um die Diskussion einfach zu halten ) ...

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

Innerhalb des Prozessors wird dies zu einem General dekodiert

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

vor dem Eintritt in die Ausgabephase, in der Register gelesen werden. Beachten Sie, dass rx das Register zum Zurückschreiben der aktualisierten Adresse darstellt. Hier sind einige Dekodierungsbeispiele:

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.

Auf Schaltungsebene sind alle drei Lasten tatsächlich der gleiche interne Befehl, und ein einfacher Weg, um diese Art von Orthogonalität zu erhalten, besteht darin, ein Erdungsregister R16 zu erstellen. Da R16 immer geerdet ist, werden diese Anweisungen natürlich ohne zusätzliche Logik korrekt decodiert. Das Zuordnen einer Befehlsklasse zu einem einzelnen internen Format hilft bei superskalaren Implementierungen erheblich, da die logische Komplexität verringert wird.

Ein weiterer Grund ist eine optimierte Methode zum Wegwerfen von Schreibvorgängen. Anweisungen können deaktiviert werden, indem einfach das Zielregister und die Flags auf R16 gesetzt werden. Es ist nicht erforderlich, ein anderes Steuersignal zu erstellen, um das Zurückschreiben usw. zu deaktivieren.

Die meisten Prozessorimplementierungen, unabhängig von der Architektur, haben zu Beginn der Pipeline ein RAZ-Registermodell. Die MIPS-Pipeline beginnt im Wesentlichen an einem Punkt, der in anderen Architekturen nur wenige Schritte entfernt sein würde.

MIPS hat die richtige Wahl getroffen

Daher ist ein Null-Lese-Register in jeder modernen Prozessorimplementierung fast obligatorisch, und MIPS, das es für Software sichtbar macht, ist definitiv ein Pluspunkt, wenn man bedenkt, wie es die interne Decodierungslogik rationalisiert. Entwickler von MIPS-Prozessoren müssen kein zusätzliches RAZ-Register hinzufügen, da $ 0 bereits am Boden liegt. Da RAZ für den Assembler verfügbar ist, stehen MIPS viele Pseudo-Anweisungen zur Verfügung, und man kann sich vorstellen, dass ein Teil der Decodierungslogik auf den Assembler selbst übertragen wird, anstatt für jeden Befehlstyp dedizierte Formate zu erstellen, um das RAZ-Register vor der Software zu verbergen wie bei anderen Architekturen. Das RAZ-Register ist eine gute Idee und wurde deshalb von ARMv8 kopiert.

Wenn ARM32 ein $ 0-Register hätte, wäre die Decodierungslogik einfacher geworden und die Architektur wäre in Bezug auf Geschwindigkeit, Fläche und Leistung viel besser gewesen. Beispielsweise wären von den drei oben vorgestellten Versionen von LDR nur zwei Formate erforderlich. Ebenso besteht keine Notwendigkeit, eine Decodierungslogik für die MOV- und MVN-Befehle zu reservieren. Außerdem würde CMP / CMN / TST / TEQ redundant werden. Es wäre auch nicht erforderlich, zwischen kurzer (MUL) und langer Multiplikation (UMULL / SMULL) zu unterscheiden, da kurze Multiplikation als lange Multiplikation betrachtet werden könnte, wenn das hohe Register auf $ 0 usw. eingestellt ist.

Da MIPS ursprünglich von einem kleinen Team entworfen wurde, war die Einfachheit des Designs wichtig und daher wurde 0 US-Dollar explizit im Sinne von RISC ausgewählt. ARM32 behält viele traditionelle CISC-Funktionen auf architektonischer Ebene bei.

Revanth Kamaraj
quelle
1
Nicht alle ARM32-CPUs funktionieren so, wie Sie es beschreiben. Einige haben eine geringere Leistung für komplexere Ladeanweisungen und / oder zum Zurückschreiben in das Register. Sie können also nicht alle genau gleich dekodieren.
Peter Cordes
6

Disclamer: Ich kenne den MIPS-Assembler nicht wirklich, aber das 0-Wert-Register ist für diese Architektur nicht eindeutig, und ich denke, es wird auf die gleiche Weise wie in anderen mir bekannten RISC-Architekturen verwendet.

Das XOR-Erstellen eines Registers zum Erhalten von 0 kostet Sie einen Befehl, das Verwenden eines vordefinierten 0-Wert-Registers jedoch nicht.

Beispielsweise wird der mov RX, RYBefehl häufig als implementiert add RX, RY, R0. Ohne ein 0-Wert-Register müssten Sie xor RZ, RZjedes Mal, wenn Sie verwenden möchten mov.

Ein weiteres Beispiel ist der cmpBefehl und seine Varianten (wie "Vergleichen und Springen", "Vergleichen und Bewegen" usw.), mit denen cmp RX, R0auf negative Zahlen getestet wird.

Dmitry Grigoryev
quelle
1
Gibt es Probleme bei der Implementierung MOV Rx,Ryals AND Rx,Ry,Ry?
Supercat
3
@supercat Sie können nicht codieren mov RX, Immoder mov RX, mem[RY]wenn Ihr Befehlssatz nur einen einzigen Sofortwert und einen einzigen Speicherzugriff pro Befehl unterstützt.
Dmitry Grigoryev
Ich bin nicht mit den Adressierungsmodi des MIPS vertraut. Ich weiß, dass der ARM die Modi [Rx + Ry << scale] und [Rx + disp] hat, und obwohl es in einigen Fällen nützlich sein kann, letzteres für einige absolute Adressen zu verwenden, ist dies im Allgemeinen nicht unbedingt erforderlich. Ein gerader [Rx] -Modus könnte über [Rx + disp] unter Verwendung einer Nullpunktverschiebung emuliert werden. Was verwendet das MIPS?
Supercat
movist ein schlechtes Beispiel; Sie können es mit einer sofortigen 0 anstelle eines Nullregisters implementieren. zB ori dst, src, 0. Aber ja, Sie würden einen Opcode für mov-instant benötigen, um sich zu registrieren, wenn Sie keinen hätten addiu $dst, $zero, 1234, wie luiaber für die unteren 16 Bits anstelle der oberen 16. Und Sie könnten nicht verwenden noroder subeinen Operanden nicht / neg erstellen .
Peter Cordes
@supercat: falls Sie sich immer noch wundern: klassisches MIPS hat nur einen einzigen Adressierungsmodus: register + disp16. Moderne MIPS haben weitere Opcodes für 2-Register-Adressierungsmodi für FP-Ladevorgänge / -Speicher hinzugefügt, um die Indexierung von Arrays zu beschleunigen. (Aber immer noch nicht für das Laden / Speichern von Ganzzahlen, möglicherweise, weil dafür mehr Leseports in der Ganzzahlregisterdatei für 2 Adressregister + ein Datenregister für ein Geschäft erforderlich sein könnten. Siehe Verwenden eines Registers als Offset )
Peter Cordes
3

Ein paar Leads am Ende Ihrer Registerbank an den Boden zu binden ist billig (billiger als ein vollwertiges Register zu machen).

Das eigentliche xor auszuführen erfordert ein wenig Energie und Zeit, um die Gates zu wechseln und es dann im Register zu speichern. Warum sollten Sie diese Kosten bezahlen, wenn ein vorhandener 0-Wert leicht verfügbar sein kann?

Moderne CPUs haben auch ein (verstecktes) 0-Wert-Register, das sie als Ergebnis einer xor eax eaxAnweisung durch Umbenennen von Registern verwenden können.

Ratschenfreak
quelle
6
Die tatsächlichen Kosten R0liegen nicht in der Erdung einiger Drähte, sondern in der Tatsache, dass Sie in jeder Anweisung, die sich mit Registern befasst, einen Code dafür reservieren müssen.
Dmitry Grigoryev
Der Xor ist ein roter Hering. xor-zeroing ist nur auf x86 gut, wo CPUs das Idiom erkennen und eine Abhängigkeit von den Eingaben vermeiden. Wie Sie hervorheben, führt die Sandybridge-Familie nicht einmal ein UOP dafür durch, sondern behandelt es nur in der Phase der Umbenennung des Registers. ( Wie kann ein Register in der x86-Assembly am besten auf Null gesetzt werden: xor, mov oder und? ) Bei MIPS hätte das XORing eines Registers jedoch eine falsche Abhängigkeit. Regeln für die Reihenfolge der Speicherabhängigkeit (HW-Äquivalent zu C ++ std::memory_order_consume) erfordern, dass XOR die Abhängigkeit weitergibt.
Peter Cordes
Wenn Sie kein Nullregister hätten, würden Sie einen Opcode einfügen, um ein Sofortregister in ein Register zu verschieben. Wie, luiaber nicht um 16 nach links verschoben. Sie können also immer noch eine kleine Zahl mit einer Anweisung in ein Register eintragen. Es wäre verrückt, nur Null mit einer falschen Abhängigkeit zuzulassen. (Normales MIPS erstellt mit addiu $dst, $zero, 1234oder ungleich Null Werte ori, sodass Ihr Argument "Stromkosten" zusammenbricht. Wenn Sie vermeiden möchten, dass eine ALU gestartet wird, fügen Sie einen Opcode für die sofortige Registrierung hinzu, anstatt Software ADD oder OR zu verwenden eine sofortige mit Null.)
Peter Cordes