Sprint 283: In-Memory JIT-Kompilierung & Ausführbarer Maschinencode ⚡
Willkommen zu Sprint 283 und 291! In dieser Beitragsreihe beleuchten wir den Meilenstein der KnotenCore-JIT-Kompilierung. Um eine maximale Recheneffizienz für autonome KI-Agenten zu erreichen, übersetzen wir AST-Deklarationen nun direkt im Arbeitsspeicher in rohe x86_64-Assemblerbefehle und führen diese ohne Zwischenprozess direkt auf der Host-CPU aus.
🧠 Dynamische Allokation von RWX-Speicherseiten
Moderne Betriebssysteme verbieten standardmäßig die Ausführung von Code aus normalen Speichersegmenten (wie dem Heap oder Stack) aus Sicherheitsgründen (W^X: Write XOR Execute). Um JIT-kompilierten Maschinencode ausführen zu können, müssen wir dem Betriebssystem explizit mitteilen, dass ein bestimmtes Speichersegment ausführbar sein soll.
Dazu verwenden wir in Sprint 283 das memmap2-Crate:
- Wir allokieren eine anonyme Speicherseite über
MmapMut::map_anon(size). - Wir kopieren die generierten Byte-Sequenzen der x86_64-Maschinenbefehle in diese Seite.
- Wir konfigurieren die Speicherseite mithilfe von
make_exec()als ausführbar. - Wir transmutieren den Zeiger auf die Speicherseite in einen Rust-Funktionszeiger (
extern "C" fn() -> i64) und rufen diesen auf.
🛠️ Register-Korrekturen & Testabsicherung (Sprint 291)
Während des Audits von Sprint 290 stellten wir fest, dass die Addition bei JIT-Befehlen das falsche Register pushte. Bei OpCode::Add wurde zwar add rcx, rax ausgeführt (wodurch das Ergebnis im rcx-Register abgelegt wurde), der Emitter pushte danach jedoch den unveränderten Operanden rax zurück auf den Stack.
In Sprint 291 wurde dies durch die Implementierung von emit_push_rcx korrigiert, wodurch JIT-Additionen mathematisch exakt auf der CPU berechnet werden. Der neue Test test_jit_native_execution_add verifiziert diese Korrektheit direkt auf Hardwareebene (10 + 5 = 15).
🧪 Verifizierung & Performance-Impact
Durch das Ausführen von JIT-Kompilaten direkt im Arbeitsspeicher entfällt jeglicher FFI- und Interpreter-Overhead. Berechnungen und mathematische Schleifen laufen mit der vollen Bare-Metal-Geschwindigkeit der CPU. Der Integrationstest-Lauf im CI prüft diese Pipeline bei jedem Push ab und sichert die JIT-Integrität ab.