SectorC: Ein in 512 Byte gebauter C-Compiler
(xorvoid.com)- SectorC, geschrieben in x86-16-Assembly, ist ein extrem kleiner C-Compiler, der in den Bootsektor (512 Byte) einer x86-Maschine passt und eine Teilmenge der C-Sprache unterstützt, die groß genug ist, um tatsächlich lauffähige Programme zu schreiben
- Unterstützt werden unter anderem globale Variablen, Funktionen, if/while-Anweisungen, Operatoren, Pointer-Dereferenzierung, Kommentare und Inline-Assembly, sodass selbst mit minimaler Struktur vollständige Programme ausgeführt werden können
- Um den Tokenizer zu vereinfachen, wurde mit leerzeichenbasierter Tokenisierung und einem
atoi()-Hash die Sprache Barely C entworfen, die die bestehende C-Syntax beibehält und zugleich eine extreme Größenreduktion erreicht - Bei der Code-Optimierung wurden verschiedene Komprimierungstechniken auf Assembly-Ebene eingesetzt, darunter Entfernung von Sprüngen, Zusammenlegung von Aufrufen, Nutzung von 8-Bit-Offsets sowie
stosw/lodsw, wodurch der Code von 468 Byte auf 303 Byte schrumpfte - Im Ergebnis wurde innerhalb von 512 Byte ein vollständiger C-Compiler inklusive Tokenizer, Parser, Codegenerator und Runtime implementiert und damit ein extremes Beispiel für Software-Minimierung gezeigt
Überblick über SectorC
- SectorC ist ein in x86-16-Assembly geschriebener C-Compiler, der vollständig in einen 512-Byte-Bootsektor passt
- Das GitHub-Repository ist xorvoid/sectorc
- Die unterstützte Sprache ist eine C-Teilmenge, mit der sich reale Programme schreiben lassen
- Zu den unterstützten Funktionen gehören globale Variablen, Funktionen, Kontrollstrukturen (if/while), verschiedene Operatoren, Pointer-Dereferenzierung, Inline-Assembly und Kommentare
- Als Beispielprogramm wird Code zum Zeichnen einer Sinuswellen-Animation im VGA-Modus gezeigt
Designhintergrund und Ansatz
- Da ein herkömmlicher C-Tokenizer so groß ist, dass er in 512 Byte praktisch unmöglich unterzubringen wäre, musste die Sprachstruktur selbst vereinfacht werden
- Big Insight #1: Wie bei der Sprache Forth wurde eine durch Leerzeichen getrennte Token-Struktur eingeführt und eine abgewandelte Sprache namens „Barely C“ entworfen
- Beispiel: Syntax wie
int(main)(){while(!done){wird als einzelnes „Mega-Token“ behandelt - Sie wird auch von bestehenden C-Compilern weiterhin als gültiger C-Code erkannt
- Beispiel: Syntax wie
- Big Insight #2: Die Funktion
atoi()wird wie eine Hash-Funktion verwendet, um Tokens in Zahlen umzuwandeln- Integer-Literale, Schlüsselwörter und Bezeichner werden alle über den Rückgabewert von
atoi()verarbeitet - Auf Bezeichner wird über ihren Index in einem 64K-Array zugegriffen
- Integer-Literale, Schlüsselwörter und Bezeichner werden alle über den Rückgabewert von
Umsetzung von Barely C
- Die erste Implementierung war 468 Byte groß und nutzte einen rekursiven Abstiegparser mit
atoi-basierten Tokens- Ohne Symboltabelle wird direkt über den Hashwert auf ein 64K-Segment zugegriffen
- Die Codegenerierung folgt dem OTCC-Stil und verwendet das Register
axals Speicherort für Ergebnisse
- Später wurde mit byte-threaded code experimentiert, um eine Forth-artige Struktur zu versuchen, doch innerhalb von 512 Byte erwies sich das als eher ineffizient und wurde verworfen
Techniken zur Code-Minimierung
- Durch die Rückkehr zu einer geradlinigen Struktur schrumpfte der Code von 468 Byte auf 303 Byte
- Verwendet wurden Techniken wie Fall-through zur Sprungvermeidung, Tail-Call, Call Fusion, Nutzung von
stosw/lodswund das Beibehalten von 8-Bit-Sprung-Offsets
- Verwendet wurden Techniken wie Fall-through zur Sprungvermeidung, Tail-Call, Call Fusion, Nutzung von
- Dadurch wurden 207 Byte freier Platz für zusätzliche Funktionen gewonnen
Ausbau zu vollständigerer C-Funktionalität
- Mit zusätzlichen 200 Byte wurde Unterstützung für vollständige C-Syntax erreicht
- Verschachtelte
if-/while-Blöcke sowie verschiedene *binäre Operatoren (+, -, , &, |, ^, <<, >>, ==, !=, <, >, <=, >=) - Unterstützung für Funktionsdefinitionen und rekursive Aufrufe, Inline-Assembly (
asm) sowie einzeilige und mehrzeilige Kommentare - Über eine Operatortabelle (
binary_oper_tbl) wird jeder Operator mit 4 Byte definiert, sodass 14 Operatoren in 56 Byte verarbeitet werden
- Verschachtelte
Grammatikstruktur
- Die gesamte Grammatik besteht aus
program,var_decl,func_decl,statement,exprusw. - Sowohl
//- als auch/* */-Kommentare werden unterstützt - Der Text der Grammatikdefinition selbst ist mit 704 Byte größer als die eigentliche Implementierung
Inline-Assembly und Runtime
- Über die
asm-Syntax kann x86-16-Maschinencode direkt eingefügt werden- Eine unverzichtbare Funktion für I/O-Verarbeitung
- Die Runtime (
rt/) besteht aus zwei in C geschriebenen Dateienrt/lib.c: Bibliotheksroutinen auf Basis von Inline-Assemblyrt/_start.c: Programmeinstiegspunkt_start()
Beispielprogramme
examples/hello.c: Gibt Text direkt in den Speicher0xB8000ausexamples/sinwave.c: Zeigt eine Sinuswellen-Animation im VGA-Modus 0x13 anexamples/twinkle.c: Spielt „Twinkle Twinkle Little Star“ über den PC-Lautsprecher ab (mit Lautstärkewarnung)
Fazit
- SectorC ist ein ultrakleiner C-Compiler, der ein scheinbar unmögliches Ziel erreicht, und zeigt ein extremes Beispiel für Software-Minimierung und kreatives Sprachdesign
- Der Artikel endet mit humorvollen Auswahlmöglichkeiten zu „Was haben wir gelernt?“ und betont satirisch die Haltung, das Unmögliche herauszufordern, sowie den Wert der Vereinfachung von Software
1 Kommentare
Hacker-News-Kommentare
Optimierende Compiler der 2000er hätten solche Tokens vielleicht einfach zu No-ops wegoptimiert 😉
-wTokenHashCollisionnicht aktiviert! Das UB ist wegen deiner Unwissenheit entstanden. Die Spezifikation ist vollkommen eindeutig!“ lautete eine scherzhafte ReaktionBootsektor-Spiele zu bauen ist wirklich eine magische, nostalgische Erfahrung. Es erinnert mich an eine Zeit, in der Programmieren noch wirklich Spaß gemacht hat
Schade, dass solche Projekte im heutigen KI-Zeitalter so unterschätzt werden
Link zu meinem Projekt
Dein Projekt scheint eine Option zu haben, Code für Bootsektoren zu erzeugen.
Beide sind interessant, aber die Gemeinsamkeiten beschränken sich auf „Bootsektor“, „C“ und „Compiler“
Deshalb habe ich das Gefühl, nicht mehr besonders zu sein
Wir stapeln immer mehr Abstraktionen aufeinander, sodass man für ein einziges „Hello World“ schon 200 MB
node_modulesbrauchtUnd dann packt jemand einen C-Compiler in 512 Byte
Niemand sagt, dass alle Bootsektor-Code schreiben sollten, aber solche Projekte zu lesen ist wirklich eine demütigende Erfahrung. Der pädagogische Wert ist ebenfalls groß
Dieses Projekt zeigt gut, wie einfach die Kernstruktur von C ist.
Interessant ist, dass sich C aus B entwickelt hat und B wiederum aus einer abgespeckten Form von Fortran
In Assembler gibt es am Ende ja ohnehin nur jmp.
Und es sollte nicht „choose your own adventure“, sondern „chose your own adventure“ heißen :)
Ich liebe Minimalismus
Man beginnt mit einer sehr kleinen plattformspezifischen Binärdatei und arbeitet sich dann zu immer komplexeren Tools und Compilern vor
Als Beispiele kann man sich die Projekte mishmashvm und tcc_bootstrap_alt ansehen
Die damalige Diskussion fand hier statt
SectorC: A C Compiler in 512 bytes - Link - Mai 2023 (80 Kommentare)
Dieses Projekt hier kann dagegen nur einen Teil von C verarbeiten
Es ist also eher ein Compiler für eine Teilmenge von C als ein vollständiger C-Compiler.
Code, den dieser Compiler kompilieren kann, lässt sich auch mit einem echten C-Compiler kompilieren, aber nicht umgekehrt
Hoffentlich habe ich damals nicht genug Symbole benutzt, um 32-Bit-Kollisionen zu erzeugen
Übrigens sind im Bereich 0x01e0~0x01fd noch 21 Byte freier Platz übrig. Vielleicht passt da noch etwas hinein ;)
atoi()auf normalem Text wie eine schlechte Hash-Funktion arbeitet, ist interessantAber soweit ich mich erinnere, war
atoi()so definiert, dass es bei ungültiger Eingabe 0 zurückgibtBeim Ausführen unter Linux muss man im qemu-Aufruf statt coreaudio einfach alsa verwenden
Ich habe dazu einen Pull Request auf GitHub eingereicht. Wenn dem Autor mein ausschweifender Shellskript-Stil gefällt, wird er vielleicht gemergt
Jetzt scheint es Zeit zu sein, dort einen C-Compiler hinzuzufügen
Link zu meinem OS-Projekt