2016-05-17 9 views
5

Diese Frage steht im Zusammenhang mit dem Schreiben eines C-Compilers für eine 16-Bit-Homebrew-CPU.Was ist auf Assembly-Ebene sinnvoller, 64 Register oder drei Operandenbefehle?

Ich habe 12 Bits des Operanden für ALU-Anweisungen (wie ADD, SUB, UND usw.).

Ich könnte Anweisungen geben drei Operanden aus 16 Registern oder zwei Operanden aus 64 Registern.

z.B.

SUB A <- B - C (registers r0-r15) 

vs

SUB A <- A - B (registers r0-r63) 

Sind sechzehn Register, mit Drei-Operanden-Befehle, nützlicher als 64 Register mit zwei Operanden-Befehle, um C-Compiler und ihre Autoren?

+0

Auf nur eine erste zwar (in x86 sorry nur Assembly Ich weiß). Die meisten Programme, die ich durch Dinge wie IDA durchgebe, benutzen normalerweise Register Eax bis Edx, also ist das 4. Dann hast du Ebp und Esp so 6. Eip sollte die ALU nicht brauchen. Eflags (wieder keine Notwendigkeit für ALU) ESI und EDI macht 8. So nur von einem ersten Gedanken glaube ich nicht, dass die meisten Programme mehr als 16 Register verwenden. Ich vermisse vielleicht ein paar, aber ich denke, ein guter erster Vernunfttest, um dies zu bestimmen, würde aussehen, was gcc kompiliert und herauszufinden, ob es sogar mehr als 16 Register auf der ALU verwendet. – arduic

+0

Wo werden Sie den Adressierungsmodus programmieren? –

+1

@ WeatherVane Es ist RISC - Laden und Speichern sind explizite Operationen mit ihrem eigenen Opcode. Es ist eine Homebrew-CPU - die einzigen Adressierungsmodi für Lasten und Speicher sind 8 Bit sofortige Offsets von Null, vom PC oder von einem anderen Register. – fadedbee

Antwort

4

16 Register mit nicht-destruktiven 3-Operanden-Anweisungen ist wahrscheinlich besser.

Sie sollten jedoch auch in Betracht ziehen, etwas anderes interessantes mit diesen Befehlsbits zu tun. Für Homebrew, ist es wahrscheinlich nicht wichtig, für zukünftige Erweiterungen zu reservieren, und wollen nicht eine Tonne von zusätzlichen Opcodes (like PPC does) hinzufügen.

ARM nimmt den interessanten Ansatz, einen Operanden für jede Anweisung the barrel shifter durchlaufen zu lassen, so dass jede Anweisung eine "shift-and-whatever" -Anweisung kostenlos ist. Dies wird sogar im "Daumen" -Modus unterstützt, wo die gebräuchlichsten Befehle nur 16 Bits sind.(ARM Modus hat die traditionelle RISC 32-Bit-Festbefehlsgröße. Es widmet 4 diese Bits zu sagte Ausführung für jede Anweisung.)


Ich erinnere mich, eine Studie über die perf Gewinne aus zu sehen, die Anzahl der Register in einer Verdoppelung theoretische Architektur, für SPECint oder so. 8-> 16 war vielleicht 5 oder 10%, 16-> 32 war nur ein paar%, und 32-> 64 war noch kleiner.

Also 16 Integer-Register ist "genug" die meiste Zeit, es sei denn, Sie arbeiten mit int32_t viel, da jeder solche Wert zwei 16-Bit-Register nehmen wird. x86-64 hat nur 16 GP-Register, und die meisten Funktionen können einen Großteil ihres Zustands in Registern recht komfortabel speichern. Selbst in Schleifen, die Funktionsaufrufe ausführen, gibt es genug Register, in denen die Aufrufe erhalten bleiben, in dem ABI, dass das Überlaufen/Neuladen häufig nicht in der Schleife stattfinden muss.

Die Erhöhung der Codegröße und der Befehlsanzahl aus Anweisungen mit 3 Operanden ist größer als die Speicherung gelegentlicher Überläufe/Neuladungen. gcc output muss die ganze Zeit mov haben und lea als nicht-destruktive add/shift verwenden.


Wenn Sie Ihre CPU für Software-Pipelining optimieren möchten Speicherauslastung Latenz (which is simpler than full out-of-order execution) zu verstecken, sind mehrere Register groß, esp. wenn Sie keine Registerumbenennung haben. Ich bin mir jedoch nicht sicher, wie gut Compiler bei static instruction scheduling sind. Es ist kein heißes Thema mehr, da alle Hochleistungs-CPUs außer Betrieb sind. (OTOH, eine Menge Software, die tatsächlich benutzt wird, läuft auf ARM-CPUs in Smartphones.) Ich habe keine Erfahrung damit, Compiler zu finden, um CPUs in der richtigen Reihenfolge zu optimieren. IDK also, wie lebensfähig es ist Das.

Wenn Ihre CPU so einfach ist, dass sie während einer laufenden Ladung nichts anderes tun kann, spielt dies wahrscheinlich keine Rolle. (Dies ist immer wellig Hand wirklich, weil ich weiß nicht genug darüber, was für ein einfaches Design praktisch ist. Auch „einfache“ in Ordnung moderne CPUs pipelined werden.)


64 Register ist in „zu bekommen viele Gebiete, in denen das Speichern/Wiederherstellen viel Code erfordert. Die Speichermenge ist wahrscheinlich immer noch vernachlässigbar, aber da Sie keine Register durchlaufen können, benötigen Sie 64 Anweisungen.


Wenn Sie eine ISA von Grund auf neu sind der Gestaltung haben einen Blick auf Agner Fog's CRISC proposal und die daraus resultierende Diskussion. Ihre Ziele sind sehr unterschiedlich (hohe Leistung/Leistungsbudget 64-Bit-CPU gegenüber einfachen 16-Bit), so dass Ihre ISAs natürlich sehr unterschiedlich sein werden. Die Diskussion kann jedoch dazu führen, dass Sie an Dinge denken, die Sie nicht berücksichtigt haben, oder an Ideen, die Sie ausprobieren möchten.

+0

Sehr interessant zu sehen Fog destilliert sein Wissen in ein architektonisches Konzept. Sei nett, wenn er es bis zu dem Punkt formalisieren könnte, an dem Simulatoren realisiert werden könnten, wie Knuths MMIX. Zusammen mit Cache/Debug/Fault-Register, etc. Es scheint immer noch zu fehlen ein definitives Dokument ... –

+0

@BrettHale: Ich habe nicht über die aktuelle Version des Vorschlags. Einer der letzten Beiträge im Diskussionsthread war, dass Agner an Assembler- und Simulator-Unterstützung für ihn und solche Sachen arbeitet, aber dass er nicht viel Zeit für diese Arbeit hat. x86 könnte nicht ewig dauern, und es wäre wirklich notwendig, wenn eine "Open-Source" -Architektur mit von Anfang an entworfenen Vektoren übernehmen würde. –

2

In Bezug auf die Anzahl der Register, im Allgemeinen denke ich, dass die meisten C zu einem guten effizienten Maschinencode kompilieren können, wenn nur 16 Allzweckregister verfügbar sind (wie AMD64). Es kann jedoch nützlich sein, ein paar Register für Funktionsargumente zu haben und einige als flüchtig markiert zu haben - was bedeutet, dass sie innerhalb jeder Funktion verwendet werden können, aber durch irgendeine aufgerufene Funktion verfälscht werden könnten. Das Erhöhen auf 32 Register könnte von Vorteil sein, aber ich bezweifle, dass sich vieles verbessern wird, wenn Sie 64 Allzweckregister für eine normale 16-Bit-CPU haben. Sie müssen den ursprünglichen Inhalt der meisten Register, die Sie in Ihrer C-Funktion verwenden werden, auf jeden Fall im Stapel speichern. Das Begrenzen einer Funktion, um nur 7 Register gleichzeitig (statt 37) zu verwenden, könnte für einen C-Compiler immer noch (Stapel-) effizient sein, selbst wenn viel mehr Register verfügbar sind.

Viel hängt von der C calling convention, die Sie verwenden werden. In welchen Registern werden Werte vom Aufrufer zum Aufrufer übergeben, welche Register sind als flüchtig zu betrachten, wie hoch sind die Kosten für das Hin- und Herbewegen vom Stapel usw. Sie könnten mehr gewinnen, wenn Sie Register Window für die Verwaltung Ihrer Register und Stapelverwendung über Funktionsaufrufe hinweg. Sun Sparc hat zum Beispiel ein Register-Fenster von 8 vollständig "lokalen" Registern, 8 Registern, die mit dem Anrufer geteilt werden und 8 Registern, die mit jeder angerufenen Funktion geteilt werden. (Darüber hinaus können 8 globale Register adressiert werden.) Damit Sie sich keine Gedanken über Pushs auf den Stack machen müssen, wird es immer einen einzigen Push von 16 Registern für jeden Funktionsaufruf gleichzeitig geben, um den Ausführungszeiger und eine 16 zu ändern Registrieren Sie Pop für jede Rückkehr. Intel ia64 hat etwas ähnliches, aber mit einer konfigurierbaren Registerfenstergröße.

SUB C,A,B hat jedoch nur einen leichten Vorteil gegenüber SUB A,B, wenn Zwischenergebnisse zu bewahren wirklich wichtig ist (A muss oft erhalten werden) und ein einfaches Register zur Registrierung Kopie ist sehr teuer. Dies scheint in den meisten Fällen unwahrscheinlich.

Und werden Sie separate Floating oder Fixpunktregister verwenden?