Compare commits

...

12 Commits

17 changed files with 230 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,230 @@
---
title: HE32, Rust & "the NAND2TETRIS experience"
author: kaqu
status: Aktiv
difficulty: no
time: ~1h
date: 2022-08-28
image: HE32_Overview.png
keywords: NAND2TETRIS, jack, hack
---
{{< image alt="Showtime" src="HE32_Overview.png">}}
## Motivation ##
Vor einem Vierteljahr habe ich das Buch "The Elements of Computing Systems" (Nisan/Schocken,
wichtig: 2.Auflage!) geschenkt bekommen und die ca. 300 Seiten gleich in einem Rutsch durchgelesen.
Faszinierend, insbesondere wenn ich mich an mein eher rudimentäres Verständnis meines alten
Dragon-Books erinnere (dort werden im Wesentlichen die Grundlagen für die Nutzung von
lex & yacc gelegt - heute: flex & bison). Ich habe damals nach ca. 3/4 des Buches aufgehört …
<div class="pure-g">
{{< image alt="Cover1" src="BookCover.jpg" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Cover2" src="BookCoverOld.jpg" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Das neuere Buch ist ursprünglich aus dem - in der angelsächsischen Welt - recht bekannten
"Nand-to-Tetris"-Kurs entstanden.<br />
Weitere Infos & Unterlagen können auf der zugehörigen [Website](https://www.nand2tetris.org/) abgerufen werden.
## 1. Der Kurs ##
Inhaltlich geht es in 12 Kapiteln vom NAND-Gatter bis zur einfachen "Betriebssystem"-Erstellung
(die auf der [Website](https://www.nand2tetris.org/course) unter "Projects" abgerufen werden).
- Die ersten fünf Kapitel befassen sich mit dem Aufbau einer "eigenen" CPU, Speicherelementen
etc. aus einfachsten NAND-Gattern und deren Zusammenschaltung zum "Hack"-Computer!
- Kapitel 6 behandelt die Erstellung eines passenden Assemblers,
- Kapitel 7 & 8 befassen sich mit der Erstellung eines Übersetzers einer eigenen VM bzw. deren "intermediate
Language" (VM/IL -> Assembler)
- Kapitel 9 - 11 behandeln dann die Erstellung eines Compilers für eine prozedurale aber
schon objektorientierte Sprache (genannt "Jack", übersetzt wird dann in VM-Code/IL).
- Kapitel 12 erklärt, wie man ein passendes "Betriebssystem" (OS, "Operating System") erstellt - wobei
es hier eigentlich um eine Kombination von BIOS & Standard-Library geht.
Das Geniale: Der Schwierigkeitsgrad bleibt immer akzeptabel, gerade für das Selbststudium!
Man merkt an vielen Stellen, wie die Rückfragen der Studenten im Laufe der Jahre in den Text
zurückgeflossen sind. Praktisch alle Problemstellen (für das Verständnis) werden sehr
detailliert erläutert.<br />
Technisch werden im Software-Teil ab Kapitel 6 immer recht enge API-Vorgaben gemacht,
jedoch ist man frei in der Wahl der Programmiersprache zur Realisierung (*ich habe meine Lösung zeitsparend
in Python erstellt - natürlich hier nicht veröffentlicht, schließlich soll sich jeder seine
Lösung selbst erarbeiten!*).<br />
Mitgeliefert werden alle notwendigen Emulatoren und Hilfsmittel um die Ergebnisse mit den korrekten
Lösungen automatisiert zu vergleichen (auf der [Website](https://www.nand2tetris.org/software) unter "Software").
Ich habe zwei Monate für den ganzen Kurs gebraucht. Für die ersten 10 Kapitel einen Monat, für die
restlichen zwei noch einmal einen ganzen Monat (der Schwierigkeitsgrad steigt zwar kaum an, jedoch
baut sich immer mehr Wissen auf, d.h. die Komplexität nimmt zu).<br />
Meine Empfehlung: Pädagogisch wertvoll!
## 2. Ausführung ##
### 2.1 Hardware & Assembler ##
Zunächst wird im Hardware-Teil eine [ISA](https://de.wikipedia.org/wiki/Befehlssatzarchitektur) definiert,
d.h. man "baut" analog zu einer echten CPU eine Theoretische, die "Hack"-CPU mit dem "Hack"-Befehlssatz
(in einer Kurs-spezifischen [HDL](https://de.wikipedia.org/wiki/Hardwarebeschreibungssprache)). Diese wird
im beigestellten Simulator mittels Testscript verifiziert.
Das sieht dann ungefähr so aus:
<div class="pure-g">
{{< image alt="HDL Code Sample" src="hdl_code.png" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="HDL Simulator" src="hdl_simulator.png" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Der Befehlssatz en détail:
{{< image alt="IS Overview" src="hack_is.png" >}}
Hierfür ist ein Assembler zu erstellen, der als Output 16-Bit Code (als "Binär-Text") generiert, z.Bsp. wie folgt:
<div class="pure-g">
{{< image alt="Assembly Sample" src="asm_code.png" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="Machine Code Sample" src="machine_code.png" class="pure-u-1 pure-u-md-1-2" >}}
</div>
### 2.2 VM / IL ###
Zunächst macht man sich mit einer vordefinierten IL (Intermediate language) für eine theoretische VM (Virtual Machine)
vertraut. Hierzu kann der beigestellte VM-Emulator genutzt werden:
<div class="pure-g">
{{< image alt="VM Code Sample" src="vm_code.png" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="VM Emulator" src="vm_emulator.png" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Als nächstes wird ein Übersetzer erstellt, von VM-Code zu Assembly Language. Wenn man diesen mit dem unter
2.1 erstellten Assembler kombiniert, kann man also schon von VM-Code direkt in "Native Code" übersetzen …
### 2.3 Hochsprache ###
Als Krönung soll jetzt noch ein "echter" Hochsprachen-Compiler erstellt werden. Hierzu wird eine relativ
einfache Sprache - genannt "Jack" - vorgegeben (es gibt schon ein VSC-Plugin!).
Sogar Objekte können bereits angelegt werden!
Hier mal die formale Definition:
{{< image alt="Jack Grammar" src="jack_grammar.png" >}}
Praktisch sieht der Code z.Bsp. so aus:
{{< image alt="Jack Code Sample" src="jack_code.png" >}}
Auch hierfür ist - unter Anleitung - ein Compiler zu erstellen. Dieser erzeugt aus Jack dann VM-Code:
<div class="pure-g">
{{< image alt="Jack Code" src="jack_code_main.png" class="pure-u-1 pure-u-md-1-2" >}}
{{< image alt="VM Code" src="vm_code_main.png" class="pure-u-1 pure-u-md-1-2" >}}
</div>
Sieht doch gar nicht so kompliziert aus!
Wenn man jetzt diesen Compiler vor VM-Übersetzer und Assembler schaltet, hat man eine komplette Toolchain
um aus Jack den Hack-Maschinencode zu erzeugen.
### 2.4 OS ###
Final erstellt man im Kurs eine Mischung aus BIOS und "Standard"-Lib (nach vorgegebenem API) - etwas großspurig
als "Betriebssystem" bezeichnet.
Interessant ist zum Beispiel das Modul "Memory". Hier schraubt man eine Speicherverwaltung selber zusammen, inkl.
Garbage Collection Strategie - wirklich lehrreich!
Bei Screen- und Output-Modul befasst man sich mit Bildschirmspeicher und Character-Generierung, wie zu C64-Zeiten …
## 3. Ein kleines Defizit und dessen Beseitigung ##
### 3.1 Von 16-Bit zu 32-Bit Worten ###
Leider ist der Addressbereich des "Instruction Set" auf 15-Bits limitiert, es lassen sich also nur 32768
Speicherstellen adressieren. Für das RAM ist das zunächst egal, was wirklich kneift ist der eingeschränkte
ROM-Bereich. Wenn man also gemäß 2.4 (s.o.) ein "OS" erstellt hat, kann man es nicht komplett laden, da es ca.
60K Wortcode erzeugt. Auch der mitgelieferte Emulator lehnt entsprechende Code-Mengen ab.
Könnte man da vielleicht Abhilfe schaffen? (Spoiler-Alert: Man kann!)
Wenn man den Addressbereich nur um 3 Bit erweitert könnte man 256K Worte addressieren - das müsste doch für OS
und die zusätzliche eigentliche Applikation reichen?
Ich habe also alles der Einfachheit halber auf 32-Bit umgestellt, drei Bits einfach oben auf die unteren 15 Bits
"aufgepfropft" und das alte Bit 15 in das Bit 31 verschoben.
Den bereits existierenden (selbstgeschriebenen) Assembler habe ich um eine Option für die Generierung von diesem
32-Bit Code erweitert.
Fehlt nur noch eine Testumgebung …
### 3.2 Becoming a Rustacean ###
Mit **Kresse** hab' ich eine Wette laufen: Jedes Jahr mindestens eine neue Sprache zu lernen. Nachdem Linus Torvalds
verkündet hat, das auch [Rust](https://en.wikipedia.org/wiki/Rust_(programming_language)) im Linux Kernel genutzt
werden darf, fand ich, das es Zeit ist hier mal nachzurüsten.
**Xperimental**: *Mach lieber [Go](https://en.wikipedia.org/wiki/Go_(programming_language)),
Rust ist wie C++ (reichlich überladen …). Hätte ich mal auf ihn gehört!*
Was mir auch nicht gleich klar war: Rust ist eine funktionale Sprache (wie Scheme, Lisp, Haskell u.ä.), es sieht
nur optisch wie eine Prozedurale aus.
Bis Kapitel 17 (inkl., von insgesamt 21) habe ich mich durch das [Rust-Manual](https://doc.rust-lang.org/stable/book/)
vorgearbeitet, dann habe ich die Arbeit am HE32-Projekt in Rust aufgenommen ("Hack"-Emulator 32-Bit) - und bin
gleich am indirekten Vector-Zugriff gescheitert (ein solideres Verständnis von "RefCell" hätte geholfen -
Danke **Vespasian**)!<br />
Auch hatte ich zunächst für parallele Threads geplant und vorsichtshalber eine Menge mit Arc & Mutex hantiert -
überflüssigerweise & eine echte Performance-Bremse.
Weil eine neue Sprache lernen ja immer eine Unterforderung ist (💀!), habe ich mich zwecks Komplexitätssteigerung
auch gleich für eine GUI-Lösung entschieden 🏋 (hier sieht man noch, das die GUI-Landschaft für Rust im Fluß
ist - nach einiger Web-Recherche habe ich mich für [Qt5 mit ritual](https://rust-qt.github.io/) entschieden).
Eigentlich soll Rust ja "sicher" im Hinblick auf Speicherzugriffe sein, schade auch, das für Qt5 dann große Teile
wieder auf "unsafe" gedreht werden (müssen) 🖕 …<br />
Wenigstens läßt sich der Timer-Event von Qt gut nutzen!<br />
Nicht so gut funktioniert das Einklinken in die Event-Schleife zwecks Abfangen der Tastatur und Weiterreichung
an die Emulation (das Symbol wird nicht richtig aufgelöst - ist zumindest nicht auffindbar?!).
Daher habe ich das Modul ("crate" in Rust-Terminologie) device_query dafür genutzt.
Da es so etwas wie globale Variablen nicht gibt 🤔(?) habe ich das Crate "lazy_static" ebenfalls hinzugezogen.
Nach einigen Performance-Optimierungen läuft der HE32 jetzt ganz passabel. Nur an zwei Stellen waren die
System-Bibliotheken anzupassen: In Math.multiply musste die Bitzahl erhöht werden und Sys.wait musste an
die neue Abarbeitungsgeschwindigkeit angepasst werden.
{{< image alt="HE32 running Pong" src="he32_run_pong.png" >}}
Die [Quellen zu HE32](https://git.hacknology.de/projekte/HE32) finden sich wie gehabt auf dem hacKNology git-server.
## 4. Was kann man - ganz praktisch - an Erkenntnissen gewinnen? ##
- Der Kurs empfiehlt sich unbedingt für Ingenieure oder Naturwissenschaftler. Wer im Informatik-Studium nur die
"Fertigprodukte" Flex und Bison kennengelernt hat, jedoch interessiert ist am Eigenbau, ist hier ebenfalls richtig.
Ist schon ein tolles Gefühl, wenn man eine "eigene" Compiler-Toolchain gebaut hat!<br />
Ich würde immer das Buch empfehlen als Grundlage (und die PDFs nur bei Bedarf hinzuziehen). Hier wird quasi die
Vorlesung nachgeliefert.
- Der Compilerbau hat seit den 80ern Fortschritte gemacht: Die Erfindung einer zwischengeschalteten VM/IL erlaubt
eine deutliche Komplexitätsreduzierung, man muß eben nicht mehr direkt aus der Hochsprache Assembly-Code für die
Zielplattform generieren.<br />
Ok, schneller wird's nicht - Performance ist allerdings heutzutage nicht mehr so das Problem.<br />
Außerdem erlaubt das Konzept die Aufteilung in Frontend/VM/Backend - und als Frontend
können dann bei Bedarf verschiedene Hochsprachen Verwendung finden, als Backend sind unterschiedliche Targets
mit vertretbarem Aufwand machbar (siehe hierzu z.Bsp.: [LLVM](https://llvm.org/)).
- 💡Rust: Der Verzicht auf "saubere" Mehrtaskfähigkeit beschleunigt die Abarbeitung ca. vierfach.
- 💡Eine CPU mit nur zwei Registern (ohne PC) muss extrem viel über den Stack abwickeln. Sowohl in der Emulation wie
auch in der Realität (externer Speicherzugriff!) ist das SEHR langsam. Hier gilt also: Viel(e Register) hilft viel!
## 5. Ausblick ##
Man könnte z.Bsp. mit einer CPU mit mehr Registern (oder auch einem CPU-lokalen Teil-Stack?) experimentieren … 🤔<br />
Ebenfalls denkbar: Höherwertige CPU-Befehle (womit wir bei der alten RISC/CISC Diskussion angekommen wären!).<br />
Man sieht: Der Kurs macht Lust auf mehr …
VM: Ein VM-Emulator in Rust wird schneller als die derzeitige Java-Variante sein. Wenigstens ist der mitgelieferte
VM-Emulator langsamer als HE32, er müsste aber eigentlich schneller sein (wg. der enorm reduzierten Anzahl
höherwertiger Befehle die sich aber immer noch gut/schnell emulieren lassen).
🗣 Wenn ich ihn richtig verstanden habe, will **Vespasian** eine verbesserte HE32-Version gestützt auf das Tokio-Framework
entwickeln (oder eine qemu-Version implementieren, die den Assembler-Code wieder auf VM-Code wandelt - oder so ähnlich?).
Beides um die Performance nochmal richtig zu steigern. Ich bin gespannt …

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB