Willst du einen Compiler bauen? Dann musst du nur diese zwei Aufsätze lesen (2008)
(prog21.dadgum.com)- Die meisten Compiler-Lehrbücher sind theoriezentriert und sehr umfangreich, was es Einsteigern erschwert, einen tatsächlich funktionierenden Compiler zu implementieren
- Als praktisches Material, das dieses Problem überwindet, wird die Reihe Jack Crenshaws „Let’s Build a Compiler!“ vorgestellt, die einen kompakten Pascal-Compiler mit Single-Pass-Struktur behandelt
- Das Tutorial unterstützt experimentelles Lernen durch die Kombination von Parsing und Codegenerierung, minimale Optimierung sowie Versionen in C und Forth
- Der zweite Text, der Aufsatz „A Nanopass Framework for Compiler Education“, stellt eine modulare Compiler-Architektur vor, die aus zahlreichen einfachen Transformationen (Passes) besteht
- Erst nachdem man mit diesen beiden Materialien praktische Implementierungserfahrung gesammelt hat, kann man bei Bedarf zur Vertiefung auf klassische Lehrbücher (Dragon Book) zurückgreifen
Die Realität des Compiler-Lernens und zwei zentrale Aufsätze
- Es wird kritisiert, dass bestehende Compiler-Fachbücher zu umfangreich und schwer zugänglich sind, sodass Anfänger nur schwer einen tatsächlich lauffähigen Compiler schreiben können
- Die meisten Bücher behandeln riesige Themenfelder wie reguläre Ausdrücke, Grammatiken und Theorie, ohne einen praktischen Einstiegspunkt zu bieten
- Dadurch entstehen Missverständnisse und Mythen wie „Compiler sind schwierig“
- Als repräsentatives Material, das diese Vorstellung aufbricht, wird die Reihe Jack Crenshaws „Let’s Build a Compiler!“ vorgestellt
- Dieses 1988 begonnene Tutorial behandelt einen Single-Pass-Compiler auf dem Niveau von Turbo Pascal
- Die Struktur kombiniert Parsing und Codegenerierung und führt nur minimale Optimierungen durch
- Ursprünglich wurde es in Pascal geschrieben; später entstanden auch eine C-Version und eine Forth-Übersetzung
- Die Forth-Version erleichtert aufgrund der interaktiven Spracheigenschaften Experimente und das Verständnis
- Eine Grenze der Crenshaw-Reihe besteht darin, dass es keine interne Programmdarstellung (Abstract Syntax Tree, AST) gibt
- In Pascal wurde dies ausgelassen, weil die Arbeit mit Bäumen dort kompliziert ist; in Python, Ruby, Erlang, Haskell, Lisp und anderen Hochsprachen lässt es sich jedoch leicht umsetzen
- Diese Sprachen wurden von Grund auf für die Manipulation von Baumdatenstrukturen entworfen
- Als zweites empfohlenes Material wird der Aufsatz von Sarkar, Waddell und Dybvig genannt:
„A Nanopass Framework for Compiler Education“
- Die Kernidee lautet, dass ein Compiler eine Folge von Prozessen ist, die interne Programmdarstellungen schrittweise transformieren
- Vorgeschlagen wird eine Struktur aus Dutzenden bis Hunderten einfacher Transformationen (Passes)
- Jede Transformation soll so einfach wie möglich bleiben und Kopplung zwischen den Transformationen vermeiden
- Das Framework definiert Ein- und Ausgaben jedes Passes explizit
- Die Implementierungssprache ist Scheme; die Prüfung erfolgt zur Laufzeit auf Basis dynamischer Typisierung
- Nachdem man mit diesen beiden Materialien tatsächlich einen Compiler geschrieben hat, kann man bei Bedarf mit klassischen Lehrbüchern wie dem Dragon Book weiter vertiefen
- Doch schon diese beiden Materialien reichen aus, um ausreichend praktische Erfahrung beim Bau von Compilern zu sammeln
1 Kommentare
Hacker-News-Kommentare
Donald Knuths The Art of Computer Programming wird noch immer geschrieben, und inzwischen ist es eher unwahrscheinlich, dass es überhaupt noch das Thema Compiler behandeln wird
Ich stimme der Behauptung des Autors nicht zu. Kapitel 2 des Dragon Book (von Aho et al.) ist auch für sich allein gelesen ein in sich abgeschlossenes Einführungsbuch in Compiler
Ein weiteres hervorragendes Einführungsbuch ist Niklaus Wirths Compilers, das auf nicht einmal 100 Seiten den Quellcode eines vollständigen Compilers und klare Erklärungen enthält
Mit diesen beiden Büchern habe ich in der Oberstufe genug gelernt, um selbst einen Compiler zu bauen
Ich finde, ein praxisorientiertes Buch, das den tatsächlichen Bau eines Compilers Schritt für Schritt begleitet, ist viel besser
Referenzmaterial ist unter diesem Link zusammengestellt
In der 2. Auflage wurde Datenflussanalyse ergänzt, aber die SSA-Form (Static Single Assignment), ein Kern moderner Compiler wie GCC oder LLVM, wird auf genau einer Seite behandelt
Wer ein modernes Backend bauen will, muss SSA-Theorie separat lernen
Siehe die offizielle TAOCP-Seite
Abdulaziz Ghuloums Arbeit An Incremental Approach to Compiler Construction
räumt mit der Vorstellung auf, „Compiler seien so etwas wie Magie“, und zeigt, dass man Compiler genauso leicht wie Interpreter bauen kann
Sie beschreibt detailliert, wie man schrittweise einen Compiler aufbaut, der große Teile der Sprache Scheme unterstützt und Assembler für Intel x86 erzeugt
In jüngerer Zeit hat sich die Compiler-Technik mit Meta-Compilern, Adaptive Compilation und JIT-Compilern stark weiterentwickelt
Alan Kays Forschungsgruppe VPRI befasst sich mit dem Komplexitätsproblem
Weiterführendes Material: Ometa-Paper, Video zu Adaptive Compilation, Vortrag von Alan Kay
Ich habe einmal einen guten Rat zum Lesen von Büchern gehört — Bücher sind wie RAM per Random Access zugänglich
Wenn man nur die benötigten Teile liest, wirken selbst dicke Bücher weniger abschreckend
Das funktioniert allerdings nicht, wenn man gar nicht weiß, was man nicht weiß. Deshalb sind leichte Einführungen so wichtig
Heutzutage wird oft Crafting Interpreters empfohlen
Der Nanopass-Paper-Link ist kaputt
Deshalb habe ich einen Spickzettel erstellt, der die Kernaussagen von Crafting Interpreters zusammenfasst
Das Buch ist nicht bloß ein Handbuch, sondern ein erfahrungsorientiertes Lehrbuch, in dem auch der Spaß des Autors an Dingen wie dem Visitor-Pattern steckt
In letzter Zeit baue ich zum Spaß einen Toy-Compiler
Statt komplexer Parsing-Theorie oder DSLs verwende ich den Megaparsec Parser-Combiner
Die Parsing-Logik ist klar, leicht wiederverwendbar, und der Aufwand der traditionellen Trennung von Lexer und Parser entfällt
Sie haben weniger Bugs, liefern bessere Fehlermeldungen, und die meisten Sprachen wie C# oder Rust nutzen diesen Ansatz ebenfalls
Tree-sitter ist auch großartig, bringt aber viele Abhängigkeiten mit. Rekursiver Abstieg erlaubt dagegen eine kompakte Implementierung ohne Abhängigkeiten
Der Vorteil des Parser-Combiner-Ansatzes ist, dass sowohl Lexer als auch Parser als derselbe Parser-Typ behandelt werden können
Damit lassen sich Whitespace-Behandlung und Tokenisierungsprobleme sauber lösen
Das früher tote Nanopass-Paper ist hier zu finden
Mir hat ein Artikel über den Tiny-Pascal-Compiler im BYTE Magazine von 1978 Compiler beigebracht
Optimierung habe ich in einem Sommerkurs von Ullman und Hennessy in Stanford gelernt
Der Codegenerator war ein eigener, selbst entwickelter Ansatz
Das Dragon Book besitze ich, habe es aber nie wirklich gelesen
Der Kern des Nanopass-Ansatzes ist nicht die Anzahl der Passes, sondern dass für jeden Pass explizite Ein- und Ausgabesprachen definiert werden
Dieses strukturierte Denken hilft dabei, viele Bugs schon vor der Ausführung zu erkennen
Crenshaws Tutorial ist ebenfalls hervorragend, aber dieses Management sprachlicher Invarianten macht den Unterschied aus, wenn man wirklich skalierbare Compiler bauen will
Aus meiner Zeit an der UC Irvine erinnere ich mich daran, im Kurs CS241 von Professor Michael Franz, einem Schüler von Niklaus Wirth, einen optimierenden Compiler implementiert zu haben
Die Aufgabe bestand darin, Bytecode für eine virtuelle Maschine namens DLX zu erzeugen
Verwandtes Material findet sich in dieser Beschreibung der DLX-Architektur sowie in dieser Referenzgrafik zum Registerallokationsalgorithmus