- Dozer ist ein auf reinem C basierender Rust-Compiler, der Rust in einer früheren Bootstrap-Phase nutzbar machen soll, und wird ohne C++,
flex, yacc oder Makefile geschrieben
- Der offizielle Compiler rustc ist in Rust geschrieben, daher wird eine neue Version mit einer früheren rustc-Version gebaut; diese Kette reicht zurück bis zum ersten Rust-Compiler, der in OCaml geschrieben war, und weiter bis zu Schichten aus Guile und C
- Der Linux-Bootstrap von Bootstrappable Builds startet mit einem 512-Byte-Binary-Seed und erweitert sich über einfache Compiler, Shell, ein C-Subset, TinyCC, yacc, coreutils, Bash, autotools, GCC bis hin zu Linux
- Derzeit taucht Rust erst in einer späten Phase über mrustc auf, das in C++ geschrieben ist und rustc 1.56 kompiliert; vor der Einführung von C++ ist Rust daher schwer nutzbar
- Dozer zielt auf einen mit TinyCC bootstrappbaren Rust-Compiler, der später zu libcore, dem Cranelift-Backend von rustc, einem cargo-Ersatzwerkzeug und schließlich zum Neuaufbau des kanonischen rustc/cargo führen soll
Ziele und Einschränkungen von Dozer
- Dozer ist ein Rust-Compiler, der in reinem C geschrieben wird
- Er verwendet kein C++ und auch nicht
flex, yacc oder Makefile
- Das Hauptziel ist, einen Compiler zu bauen, mit dem sich Rust aus C heraus bootstrappen lässt
- Insbesondere soll er mit TinyCC bootstrappbar sein; dabei wird angenommen, dass das System außer einem C-Compiler und einer sehr einfachen Shell kaum nützliche Werkzeuge besitzt
Das Problem, dass der Rust-Compiler sich selbst baut
- Um Rust-Code auszuführen, muss er kompiliert werden, und
cargo build ruft intern normalerweise rustc auf
- rustc selbst ist ebenfalls ein in Rust geschriebener Rust-Compiler, daher wird ein neues rustc mit einer älteren rustc-Version kompiliert
- rustc 1.80.0 wird mit rustc 1.79.0 kompiliert
- Diese Kette setzt sich mit noch älteren Versionen wie rustc 1.78.0 fort
- Die frühen Stufen reichen bis zu Rust 0.7 zurück; der Compiler zu diesem Zeitpunkt war in OCaml geschrieben
- Da auch ein OCaml-Compiler benötigt wird, führt die Bootstrap-Kette weiter zu Implementierungen in anderen Sprachen
- camlboot kann mithilfe von Guile einen OCaml-Compiler kompilieren
- Der Guile-Interpreter ist in C geschrieben
Die unteren Stufen der Bootstrappable-Builds-Kette
- Bootstrappable Builds beschreibt einen Ablauf, bei dem ein vollständiges System aus einem kleinen Binary-Seed heraus gebootstrapped wird
- Der Linux-Bootstrap-Prozess beginnt mit einem 512-Byte-Binary-Seed
- Dieser Seed enthält einen extrem einfachen Compiler, der Hexadezimalzahlen entgegennimmt und die entsprechenden Rohbytes ausgibt
- Eine Folge von Hex-Bytes gilt technisch ebenfalls als analysierbarer Quellcode, solange Kommentare und Leerraum außen vor bleiben
- Die folgenden Stufen bauen schrittweise Werkzeuge auf höherem Niveau
- ein sehr einfaches Betriebssystem
- eine grundlegende Shell
- ein etwas weiterentwickelter Compiler
- eine Stufe, die wie Assemblercode aussieht
- ein sehr grundlegendes C-Subset
- ein fortgeschrittenerer C-Compiler, der in diesem C-Subset geschrieben ist
- Nach einigen Schritten lässt sich TinyCC kompilieren; danach folgen
yacc, grundlegende coreutils, Bash, autotools, GCC und Linux
- Alle Stufen sind in live-bootstrap parts.rst aufgeführt
Rust erscheint in der Bootstrap-Kette viel zu spät
- Aktuell erscheint Rust in diesem Prozess erst sehr spät
- Verwendet wird mrustc, eine alternative Rust-Implementierung in C++
- mrustc kann rustc 1.56 kompilieren und von dort aus weiter bis zu modernem Rust-Code
- Wenn C++ in die Bootstrap-Kette eingeführt wird, ist der Bootstrap allerdings praktisch schon fast abgeschlossen
- Wenn Rust schon vor der Einführung von C++ nutzbar sein soll, wird ein aus C heraus bootstrappbarer Rust-Compiler benötigt
Aktueller Implementierungsstand von Dozer
- An Dozer wird seit etwa zwei Monaten gearbeitet, ohne Erweiterungen zu verwenden
- Der Compiler lässt sich derzeit sowohl mit TinyCC als auch mit cproc problemlos kompilieren
- Als Backend wird QBE verwendet
- Die Implementierung befindet sich noch in einer frühen Phase
- Der Lexer ist fertig
- Der Parser ist zu großen Teilen implementiert
- Makro-/Modulerweiterung wird so weit wie möglich nach hinten verschoben
- Die Typprüfung unterstützt derzeit nur
i32
- Die Codegenerierung ist noch grob
- Der folgende Rust-Code lässt sich derzeit erfolgreich kompilieren
fn rust_main() -> i32 {
(2 - 1) * 6 + 3
}
Der Plan bis hin zu rustc
- Ziel ist, Dozer schrittweise so weiterzuentwickeln, dass zunächst grundlegende Beispiele mit
libc und danach libcore sowie schließlich rustc selbst kompiliert werden können
- Für die Kompilierung von rustc soll das Cranelift-Backend verwendet werden
- Das Cranelift-Backend ist vollständig in Rust geschrieben
- Da kein C++ vorausgesetzt wird, kann LLVM nicht kompiliert werden
- Geplant ist außerdem ein cargo-Ersatzwerkzeug, mit dem sich Rust-Pakete über Dozer kompilieren lassen
- In den rustc-Quellen müssen automatisch generierte Dateien gefunden und entfernt werden
- Nach den Regeln des Bootstrappable-Projekts ist automatisch generierter Code nicht zulässig
- Das Endziel ist, rustc und
cargo zu kompilieren und anschließend mit dem selbst kompilierten rustc/cargo das kanonische rustc/cargo erneut zu kompilieren
- Das Projekt sei die bisher schwierigste Aufgabe; auch wenn Zweifel an der Fertigstellbarkeit bestehen, wolle man es weiter versuchen
1 Kommentare
Meinungen auf Hacker News
Wenn man Rust bootstrappen wollte, würde man vermutlich ein Proto-Rust in C bauen, das weniger Funktionen hat als vollständiges Rust, und mit diesem Proto-Rust dann einen vollständigen Rust-Compiler schreiben
Zum Beispiel hätte Proto-Rust keinen Borrow Checker, nur eingeschränkte oder gar keine Makro-Unterstützung, würde Speicher vielleicht nicht freigeben und müsste auch keinen guten Code erzeugen
Im Grunde wäre es eher C mit Rust-Syntax, aber aus Sicht von Rust-Fans wirkt das besser, als einen Rust-Compiler in „C mit C-Syntax“ zu schreiben, worauf dieses Projekt abzielt
Ich frage mich, warum man nicht diesen Weg gewählt hat
Wenn man den Borrow Checker entfernt, gehen korrekte Programme nicht kaputt; es lassen sich nur massenhaft falsche Programme kompilieren
Der Haupteinsatzzweck von mrustc ist, rustc zu kompilieren, und da man bereits weiß, dass rustc sich selbst ohne Borrow-Checker-Fehler kompilieren kann, ist das in Ordnung
Da der Scala-Compiler ineffizienten Code erzeugt, kompiliert man den echten Compiler anschließend noch einmal mit sich selbst
So erhält man am Ende einen effizienten echten Compiler, der guten Code erzeugt, und dieser Prozess ist Teil des Standard-Builds der Sprache
https://github.com/mozart/mozart2
Ich baue als Hobby einen C-Compiler in Rust und nenne ihn scherzhaft Small C Compiler, weil Rust natürlich schwergewichtiger ist als C. Das ist eine Parodie auf „Tiny C Compiler“
Als Backend verwende ich Cranelift, aber die gesamte Compiler-Architektur soll über viele Traits austauschbar und leicht zu hacken sein
Ich habe nicht vor, ihn als Open Source zu veröffentlichen, bevor er zumindest so weit funktioniert, dass er
printf("%s", "Hello World!")verarbeitetIch wollte den Präprozessor und den Parser implementieren und habe mich wegen des berüchtigten typedef-Problems auch bei rust-peg und HimeCC eingebracht
Ich weiß, dass man in der Industrie Symboltabellen verwendet, um den typedef-Kontext beizubehalten, aber es gab die Einschränkung, dass man weiter unten liegende Typen nicht lesen kann. Ich frage mich, wie die akademische Lösung aussieht; mir fällt nur transaktionaler Speicher ein
Wenn etwas Hilfreiches dabei herauskommt, werde ich es wohl irgendwann veröffentlichen
Später baute James Hendrix es zu einem Buch mit einer vollständigeren Implementierung aus. Als Kind fand ich dieses Buch auf dem Wühltisch bei CompUSA, wodurch ich C lernte, und ich habe es immer noch
https://archive.org/details/dr_dobbs_journal_vol_05_201803/p...
https://www.amazon.com/Small-Compiler-Language-Theory-Design...
Wirklich cool; interessant ist, dass es dieselbe Art von Bootstrapping-Problem auch bei Hardware gibt
Wodurch werden Computer gebaut? Durch zuvor gebaute Computer und die Software, die darauf läuft. Je mehr man darüber nachdenkt, desto spannender wird es
Vor ein paar Monaten habe ich jemanden getroffen, der bei einem Startup arbeitet, das Materiallieferung/Fulfillment für Bauprojekte macht
Dafür braucht man andere Expertise als bei normalem Versand à la Amazon, nicht nur weil Materialien oft ungewöhnliche oder gefährliche physikalische Eigenschaften haben, sondern auch weil der Lieferort häufig noch gar keine Adresse hat
Das ist lösbar, scheint aber Expertise zu erfordern, die über die üblichen Fähigkeiten moderner Zustelldienste hinausgeht
Der Grund war die Zusammenarbeit mit europäischen Unternehmen und der Nachweis gegenüber Regulierungsbehörden, dass es keine Backdoors gibt
Das war ein sehr interessantes, aber extrem schwieriges Problem; unser Team war nur indirekt beteiligt, arbeitete aber daran, Daten über einen Proxy zu leiten, der sicherstellt, dass alle Daten auditierbar sind und nichts gesendet wird, was nicht gesendet werden darf
Ich verließ das Unternehmen, bevor es fertig war, und hörte später, dass es verworfen wurde, weil es zu schwierig war
Später erschien x86 ohne riesiges Budget oder große Abnehmer, und man entwarf das Assembly möglichst effizient und dicht
Dadurch gingen Eigenschaften verloren, die man auf anderen Maschinen bequem haben konnte
Theoretisch kann man aus Einzelbauteilen selbst einen sehr einfachen Computer bauen
Er wäre groß, ineffizient und unglaublich langsam, aber man könnte ihn dazu bringen, einer bestimmten Instruction Set Architecture zu folgen, und darauf ein Bootstrap-Programm bauen
Dann könnte man argumentieren, dass das Ergebnis eines vollständig verständlichen schlechten Computers mit dem Ergebnis moderner Hardware übereinstimmt, der man nicht vollständig vertraut
Das ist eine Art Bootstrapping-Problem. Zum Beispiel sind die heutigen Ölvorkommen schwerer zu erschließen als vor 100 Jahren, und ich frage mich, ob man sich bis dorthin wieder hochbootstrappen könnte
Es war etwas nervig, dass ich viermal Links folgen musste, um eine übergeordnete Begründung zu finden, die die Vorteile von Bootstrapping erklärt.
Ich hatte erwartet, dass der „Why“-Teil des Titels genau das behandelt.
https://bootstrappable.org/benefits.html
Sicherheit ist ein wichtiger Grund und der Punkt, den das bootstrappable-Team hauptsächlich betont.
Um das trusting-trust-Problem und Angriffe wie die jüngste xz-Backdoor zu vermeiden, muss man alles aus reinem Quellcode bootstrappen können.
Sie entfernen sogar alle vorab generierten Dateien, sodass nur noch von Hand Geschriebenes und Überprüfbares als Abhängigkeit bleibt. Beim Python-Bootstrapping wird es zum Beispiel ziemlich kompliziert, weil der Quellcode Code enthält, der von Python-Skripten generiert wurde.
Mich interessiert dagegen eher der Aspekt der kulturellen Bewahrung. Ich möchte moderne Medien an Orten wie dem Arctic World Archive für künftige Archäologen bewahren, aber das ist bedeutungslos, wenn es keine Möglichkeit gibt, sie zu dekodieren.
Man kann Spezifikationen bewahren, aber man kann nicht erwarten, dass sie x265 und alles Nötige von Grund auf implementieren. Wenn man Binärdateien bewahrt, müsste man tausend Jahre alte Hardware betreiben oder eine tausend Jahre alte CPU virtualisieren.
Man könnte ihnen eine einfache Lisp-Definition und darauf laufenden Code geben, aber wer implementiert schon x265 in einfachem Lisp? Das ist nicht realistisch.
Deshalb habe ich in meinem Projekt eine einfache virtuelle Maschine gebaut und darauf C gebootstrappt.
Sie lässt sich nicht nur auf heutige Architekturen, sondern auch auf künftige oder außerirdische Architekturen sehr einfach portieren. Künftige Archäologen oder außerirdische Zivilisationen könnten an einem Tag die VM implementieren, darauf den C-Bootstrap laufen lassen und anschließend ffmpeg und Ähnliches kompilieren, um unsere Medien zu dekodieren.
Es gibt keine Blackboxes; alles ist debugbar, überprüfbar und offener, von Hand geschriebener Quellcode.
https://github.com/ludocode/onramp?tab=readme-ov-file#why-bo...
https://en.wikipedia.org/wiki/Arctic_World_Archive
Ich bin etwas verwirrt. Erst in der Mitte des Artikels wird klar, warum die im Titel beschriebene Reise begonnen wurde: Der Kern ist, dass das Bootstrapping praktisch schon abgeschlossen ist, sobald C++ in die Bootstrap-Chain kommt, sodass es keine Möglichkeit gibt, vorher Rust zu verwenden, selbst wenn man das möchte.
Daher scheint die Absicht zu sein, einen Rust-Compiler zu haben, der in C, genauer gesagt von TinyCC aus, auf einem System gebootstrappt werden kann, von dem man annimmt, dass es noch keine nützlichen Werkzeuge hat.
Das widerspricht aber den Annahmen am Anfang. rustc wird von 1.80.0 mit 1.79.0 kompiliert, 1.79.0 mit 1.78.0 und so weiter zurück bis 0.7; der Compiler zu diesem Zeitpunkt war in OCaml geschrieben.
Außerdem wurde erwähnt, dass es ein Projekt gibt, das erfolgreich einen OCaml-Compiler mit Guile kompiliert, und dass der Guile-Interpreter in C geschrieben ist.
Dann gibt es den vom Autor gewünschten C++-freien Pfad im Grunde bereits; er ist nur nicht der Pfad, den das rustc-Team im Alltag nutzt.
Am Ende ist die Motivation nicht klar. Geht es darum, einen besseren C-basierten Bootstrap-Prozess zu schaffen, ihn zum alltäglichen Bootstrap-Verfahren von rustc zu machen, warum die C++-Stufe entfernt werden soll, oder warum eine C-Stufe bevorzugt wird?
Wenn es einfach darum geht, es machen zu wollen, ist das in Ordnung, aber selbst nach einem ziemlich langen Artikel ist mir kein anderer Zweck wirklich klar.
Jeder Schritt dauert mehrere Stunden, und da 1.80 1.79 benötigt, 1.79 1.78 und so weiter, kann man bis 0.7 keinen Schritt überspringen.
Selbst vollständig automatisiert könnte dieses Bootstrapping Monate dauern.
Außerdem haben die frühen rustc-Versionen meines Wissens nur LLVM ausgegeben, sodass man ohnehin einen C++-Compiler bootstrappen müsste, um LLVM zu kompilieren.
Wenn man einen C++-Compiler hat, kann man einfach mrustc kompilieren. Derzeit unterstützt mrustc nur bis rustc 1.54, man müsste also trotzdem durch etwa 35 Versionen hindurch kompilieren.
Dieser gesamte Prozess ist unpraktisch. Das Ziel von Dozer ist, einen kleinen C-Compiler zu bootstrappen, Dozer zu kompilieren und dann direkt ein aktuelles rustc zu kompilieren.
So bekommt man Rust unmittelbar, ohne C++ oder Zwischenschritte zu bootstrappen.
Wenn man GCC 4 und binutils von den ursprünglichen Build-Skripten trennen könnte, ließe sich vermutlich etwa die Hälfte der Liste streichen.
Ein beträchtlicher Teil der Einträge dort besteht nur darin, autoconf-artige Werkzeuge und deren Abhängigkeiten immer wieder neu zu bauen.
https://github.com/fosslinux/live-bootstrap/blob/master/part...
Ich verstehe den Punkt nicht ganz. Um neue Binärdateien zu erzeugen, die auf der Zielmaschine laufen, muss rustc die Zielarchitektur unterstützen.
Wenn man diese Unterstützung zu rustc hinzugefügt hat, kann man rustc einfach sich selbst bauen lassen.
Manchmal stelle ich mir vor, einen C++-Interpreter oder -Compiler in Scheme zu schreiben.
Direkt von Scheme zum aktuellen GCC zu kommen, könnte eine enorme Abkürzung sein.
Aber der gängigen Auffassung nach gilt das Schreiben eines C++-Compilers als nahezu unmöglich. Zum Lernen wäre es trotzdem wohl hilfreich.
Wenn man den gesamten Stack ab dem Sub-Assembler betrachtet: Könnte das ein Weg sein, das trusting trust-Problem zu umgehen?
https://www.cs.cmu.edu/~rdriley/487/papers/Thompson_1984_Ref...
Trotzdem gibt es Dinge wie https://en.m.wikipedia.org/wiki/Underhanded_C_Contest, und bei einigen der Einreichungen hätte ich sie wohl übersehen, selbst wenn ich sie auditiert hätte.
Als ich ein wenig C gelernt habe, habe ich nachgeschaut, wie Leute in C Dinge im Stil von C++ machen, und Implementierungen von Objekten, Exceptions und Concurrency gesehen.
Wenn mrustc in C++ geschrieben ist, wäre es dann nicht einfacher, den funktionierenden C++-Code nach C zu portieren, indem man solche C++-artigen C-Primitiven verwendet?
Es scheint auch möglich, die starke Interoperabilität zwischen C++ und C zu nutzen und den Code schrittweise zu migrieren.
Natürlich weiß ich, dass das eine schwierige Portierung mit vielen Fallstricken ist. Man muss aber bedenken, dass der Vergleichsmaßstab darin besteht, einen Rust-Compiler in C neu zu schreiben.
Mir fallen auch frühere C++-to-C-Compiler ein. Ich weiß nicht, ob es die noch gibt.
Auch heute wären Rust-to-C/C++ und ein C++-to-menschlich-lesbares-C-Compiler wohl nützlich, weil man damit die Sicherheitsvorteile der einen Seite mit dem Tool-Ökosystem der anderen kombinieren könnte.