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.
quelle
Antworten:
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
r0
damit 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/zero
auf * 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
r0
Nullregisters Pseudoanweisungen erstellen können: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:Warum Sie
$zero
in 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. )
quelle
slt
,slti
,sltu
).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 ) ...
Innerhalb des Prozessors wird dies zu einem General dekodiert
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:
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.
quelle
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, RY
Befehl häufig als implementiertadd RX, RY, R0
. Ohne ein 0-Wert-Register müssten Siexor RZ, RZ
jedes Mal, wenn Sie verwenden möchtenmov
.Ein weiteres Beispiel ist der
cmp
Befehl und seine Varianten (wie "Vergleichen und Springen", "Vergleichen und Bewegen" usw.), mit denencmp RX, R0
auf negative Zahlen getestet wird.quelle
MOV Rx,Ry
alsAND Rx,Ry,Ry
?mov RX, Imm
odermov RX, mem[RY]
wenn Ihr Befehlssatz nur einen einzigen Sofortwert und einen einzigen Speicherzugriff pro Befehl unterstützt.mov
ist ein schlechtes Beispiel; Sie können es mit einer sofortigen 0 anstelle eines Nullregisters implementieren. zBori dst, src, 0
. Aber ja, Sie würden einen Opcode für mov-instant benötigen, um sich zu registrieren, wenn Sie keinen hättenaddiu $dst, $zero, 1234
, wielui
aber für die unteren 16 Bits anstelle der oberen 16. Und Sie könnten nicht verwendennor
odersub
einen Operanden nicht / neg erstellen .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 eax
Anweisung durch Umbenennen von Registern verwenden können.quelle
R0
liegen 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.std::memory_order_consume
) erfordern, dass XOR die Abhängigkeit weitergibt.lui
aber 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 mitaddiu $dst, $zero, 1234
oder ungleich Null Werteori
, 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.)