Die Codegenerierung ist die letzte Phase eines Compilers. Sie übersetzt die optimierte Zwischendarstellung in Zielcode — in der Regel Assembler- oder Maschinensprache für eine konkrete Prozessorarchitektur (x86-64, ARM, RISC-V und andere). Damit schließt sie die Lücke zwischen dem plattformunabhängigen Frontend und der Hardware.
Aufgaben der Codegenerierung
- Instruktionsauswahl: Jede Operation der IR wird auf eine oder mehrere passende Maschineninstruktionen abgebildet (Instruction Selection). Verbreitet ist das Überdecken des IR-Baums mit Zielcode-Schablonen (Tree Pattern Matching).
- Registerallokation: Die unbegrenzte Zahl von IR-Variablen wird auf die wenigen echten CPU-Register verteilt; zu viele gleichzeitig lebende Werte landen im Speicher (Spilling). Details im Artikel Registerallokation.
- Instruktionsplanung: Die Reihenfolge der Instruktionen wird so gewählt, dass die Pipeline des Prozessors gut ausgelastet ist (Instruction Scheduling, Ausnutzung von Instruction-Level Parallelism).
- Peephole-Optimierung: Nach der Erzeugung wird ein kurzes Fenster („Guckloch") über den Code geschoben und lokal verbessert: überflüssige Kopien wie
mov r1, r1entfernen, Sprungketten verkürzen, konstante Ausdrücke vereinfachen.
Typische Backend-Architekturen
GCC: IR → RTL (Register Transfer Language) → Assembler
LLVM: LLVM IR → SelectionDAG → MachineIR → MC-Schicht → Assembler
JVM/.NET: Bytecode/CIL → JIT-Compiler → nativer Maschinencode (zur Laufzeit)
GCC arbeitet intern mit RTL, einer niedrigen Zwischenform. LLVM durchläuft mehrere Stufen, bis der Maschinencode über die MC-Schicht (Machine Code) erzeugt und assembliert wird. Bei interpretierten und verwalteten Laufzeiten übernimmt ein Just-in-Time-Compiler (JIT) die Codegenerierung erst während der Ausführung — er übersetzt häufig genutzte Bytecode-Abschnitte in nativen Code.
Von Assembly zum lauffähigen Programm
Die Codegenerierung endet meist beim Assemblertext. Ein Assembler übersetzt ihn in ein Objektfile, ein Linker fügt Bibliotheken und andere Module hinzu und erzeugt das ausführbare Programm. Beim Cross-Compiling erzeugt der Compiler Code für eine andere Plattform als die, auf der er selbst läuft — etwa Firmware für eingebettete Systeme vom Entwicklungsrechner aus.
Weitere Aspekte
- Calling Conventions: Wie Parameter übergeben und Rückgabewerte zurückgegeben werden (Register, Stack), ist Teil der Zielcode-Erzeugung.
- Positionsunabhängiger Code (PIC): Für Bibliotheken wird Code erzeugt, der an beliebiger Speicheradresse lauffähig ist.
- Debug-Informationen: Der Generator verknüpft Maschinencode mit Quellcode-Zeilen, damit Debugger und Profiler ihren Dienst tun können.
- Optimierungsstufen:
-O0erzeugt schnell übersetzbaren, klar strukturierten Code,-O2und-O3maximieren die Laufzeitgeschwindigkeit — die Stufe steuert, wie viel Aufwand die Codegenerierung treiben darf.
Zusammenfassung
Die Codegenerierung übersetzt die Zwischendarstellung in echten Maschinencode: Instruktionsauswahl, Registerallokation, Instruktionsplanung und Peephole-Optimierung sind ihre Kernaufgaben. Der Syntaxbaum des Frontends ist da längst verarbeitet — hier zählt allein, was auf der Zielhardware schnell und korrekt läuft.