Introduces a new optimizer pass that can "crack" closures, allowing for more aggressive specialization. It also enables inlining of upvalues that point to immutable global variables. This removes overhead for higher-order functions and currying when arguments are statically resolvable.
3.8 KiB
Optimierung: Closure-Cracking und Upvalue-Inlining
- Datum: 21.02.2026
- Status: Entwurf / Motivation
1. Motivation
Die aktuelle Monomorphisierung (Specializer) ist auf zustandslose Funktionen (Funktionen ohne Upvalues) beschränkt. Sobald eine Funktion Variablen aus ihrem äußeren Scope einfängt (Closures), wird sie für den Specializer zur "Blackbox". Dies verhindert die Optimierung von Higher-Order Functions (HOFs) und Currying, was in einer funktional orientierten DSL essenziell für die Performance ist.
2. Die Garantie
Zwei Faktoren in MyC erlauben eine aggressive Optimierung von Closures, die in anderen Sprachen (wie JavaScript oder Python) aufgrund von Mutabilität schwierig ist:
- Immutability von Globals: Nach der Bootstrap-Phase sind
Address::Global-Werte unveränderlich. - Partial Evaluation: Der Specializer nutzt die VM, um konstante Teilausdrücke bereits zur Compile-Zeit zu evaluieren. Das Ergebnis sind oft
Constant(Value::Object(Closure))-Knoten.
3. Das Konzept
3.1 Closure-Cracking
Wenn der Specializer einen Funktionsaufruf sieht, dessen Callee eine Constant(Closure) ist (z. B. das Ergebnis einer Partial Application), kann er die Closure "knacken":
- Extraktion: Der AST-Body der Closure und das dazugehörige
upvalues-Array (die konkreten Daten) werden extrahiert. - Substitution: Jeder Zugriff auf einen Upvalue (
Get(UpValue(i))) im AST-Body wird durch einenConstant(Value)-Knoten mit dem echten Wert aus demupvalues-Array ersetzt. - Rekursive Spezialisierung: Der nun "flache" (upvalue-freie) AST wird erneut durch den Specializer geschickt.
3.2 Upvalue-Inlining (Global-Propagation)
Wenn eine Closure Upvalues besitzt, die auf Address::Global verweisen, können diese ebenfalls inlined werden:
- Da Globals unveränderlich sind, kann der
Specializerden Wert des Globals zum Zeitpunkt der Spezialisierung aus demEnvironmentlesen und direkt alsConstantin den AST einbauen. - Dies eliminiert den Indirektions-Overhead über das Upvalue-Array und den Global-Lookup zur Laufzeit.
4. Beispiel-Analyse
Betrachte den Ausdruck: (((fn [x] (fn [] x)) 5))
-
Innerer Call:
((fn [x] (fn [] x)) 5)- Der Specializer erkennt das Lambda und das Argument
5. - Die VM evaluiert dies zu einer
Closure-Instanz mitupvalues: [Value::Int(5)]. - Der Knoten wird zu
Constant(Value::Object(Closure)).
- Der Specializer erkennt das Lambda und das Argument
-
Äußerer Call:
( <Constant(Closure)> )- Ohne Optimierung: Dynamischer Dispatch der Closure zur Laufzeit.
- Mit Closure-Cracking:
- Der Specializer sieht die
Constant(Closure). - Er extrahiert den Body
(Get x (UpValue 0)). - Er ersetzt
Get xdurchConstant(5). - Der gesamte Call wird zu
Constant(5)reduziert.
- Der Specializer sieht die
5. Vorteile
- Zero-Cost Abstractions: HOFs und Currying haben keinen Laufzeit-Overhead mehr, wenn die Argumente statisch auflösbar sind.
- Flattening: Komplexe, verschachtelte Funktionshierarchien kollabieren zu flachen, effizienten Operationen.
- JIT-Vorbereitung: Ein upvalue-freier, spezialisierter AST ist die ideale Eingabe für einen späteren LLVM- oder Cranelift-Backend.
6. Implementation-Plan
specializer.rs: Erweiterung derspecialize_call_logic, um nebenBoundKind::GetauchBoundKind::Constant(mit Typ-Check auf Closure) als Callee zu akzeptieren.environment.rs: Dercompiler-Closure innerhalb vonspecialize_nodemuss Zugriff auf dasupvalues-Array der aktuellen Closure-Instanz erhalten, um die Substitution durchzuführen.- Substitution-Visitor: Implementierung eines einfachen AST-Transformers, der
Get(UpValue(i))gegenConstant(values[i])austauscht.