5.7 KiB
Projektplan: Hybride Monomorphisierung
- Datum: 30.10.2025
- Version: 1.0
1. Motivation
Das aktuelle Typsystem (Myc.Ast.Types) ist mächtig, aber die Inferenz für Lambda-Parameter endet bei stUnknown. Dies erzwingt Laufzeit-Typüberprüfungen im TEvaluatorVisitor und verkompliziert AOT/JIT-Kompilierung.
2. Ziel
Implementierung einer hybriden Monomorphisierungsstrategie zur Compile-Zeit (innerhalb des TAstBinder).
- Statische Spezialisierung: Alle Funktionsaufrufe, deren Callee und Argumenttypen zur Compile-Zeit statisch bekannt sind, werden spezialisiert.
stUnknownwird für diese Pfade eliminiert. - Dynamischer Fallback: Alle dynamischen Aufrufe (z.B. Higher-Order-Funktionen, deren Callee aus einer Variable stammt) nutzen weiterhin den bestehenden dynamischen Pfad (
vkMethodmitTDataValue-Wrapper). - Optimierungs-Vorbereitung: Der statisch spezialisierte AST (der "Entrypoint") dient als saubere Basis für optionale Optimierungen (z.B. LLVM-Codegen).
3. Ergebnis: Die Strategie
Die Implementierung erfordert eine Erweiterung des TAstBinder, um einen Spezialisierungs-Cache zu verwalten und bei Bedarf rekursive Binding-Pässe durchzuführen.
Phase 1: Modifikation der Kern-Typen
TBoundLambdaExpressionNode(Generic):- Muss seinen originalen, ungebundenen Body (
IAstNode) behalten, um als Vorlage für das Re-Binding zu dienen. - Führt einen Spezialisierungs-Cache (z.B.
TDictionary<TSignatureHash, ISpecializedBody>).
- Muss seinen originalen, ungebundenen Body (
ISpecializedBody(Interface):- Repräsentiert einen erfolgreich monomorphisierten Entrypoint.
function GetBodyAst: IAstNode;function GetScopeDescriptor: IScopeDescriptor;function GetReturnType: IStaticType;
TBoundFunctionCallNode:- Benötigt ein neues Feld, um entweder den dynamischen Callee (wie bisher) oder den statischen
ISpecializedBody(den Entrypoint) zu halten.
- Benötigt ein neues Feld, um entweder den dynamischen Callee (wie bisher) oder den statischen
Phase 2: Anpassung des TAstBinder (Trigger)
TAstBinder.VisitFunctionCall wird zur zentralen Weichenstellung:
- Binde alle Argumente und ermittle ihre statischen Typen (die
Aufrufsignatur). - Binde den
Callee. - Fallunterscheidung:
- Fall A (Dynamischer Aufruf): Der
Calleeist nicht alsTBoundLambdaExpressionNodestatisch bekannt (z.B.(map (if flag f1 f2) ...)).- Aktion: Der Aufruf wird als dynamisch belassen (Fallback). Es wird lediglich geprüft, ob der
Calleeden TypstMethodhat. Es findet keine Spezialisierung statt.
- Aktion: Der Aufruf wird als dynamisch belassen (Fallback). Es wird lediglich geprüft, ob der
- Fall B (Statischer Aufruf): Der
Calleeist einTBoundLambdaExpressionNode.- Aktion: Trigger die Monomorphisierung.
- Rufe
Callee.GetSpecialization(Aufrufsignatur)auf. - Der
TBoundFunctionCallNodewird modifiziert und verweist direkt auf das zurückgegebeneISpecializedBody. - Der
StaticTypedesTBoundFunctionCallNodewird aufISpecializedBody.GetReturnTypegesetzt.
- Fall A (Dynamischer Aufruf): Der
Phase 3: Die Monomorphisierung (Der Re-Bind Pass)
Dies ist die Kernlogik (z.B. in TBoundLambdaExpressionNode.GetSpecialization):
- Cache-Lookup: Prüfe, ob für die
Aufrufsignaturbereits einISpecializedBodyim Cache existiert. - Cache-Miss (Re-Binding):
- Erzeuge einen neuen, temporären
IScopeDescriptorfür den Sub-Bind-Prozess. - Populiere den Scope: Definiere die Parameter-Namen (z.B.
[a b]) in diesem Scope, aber verwende die konkreten Typen derAufrufsignatur(z.B.stFloat,stOrdinal) anstelle vonstUnknown. - Rekursiver Binder: Erzeuge einen neuen
TAstBinder(oder rufe den aktuellen re-entrant auf), der den originalen, ungebundenen Body des Lambdas mit diesem spezialisierten Scope bindet. - Fehlerbehandlung: Schlägt dieser Sub-Bind-Prozess fehl (z.B.
ETypeException, weila(jetztstFloat) mit einemTextaddiert wird), ist der Aufruf ungültig -> Compile-Fehler. - Erfolg: Das Ergebnis ist der spezialisierte
IAstNode(Body) und derIScopeDescriptor. Erzeuge einISpecializedBody-Objekt, speichere es im Cache und gib es zurück.
- Erzeuge einen neuen, temporären
Phase 4: Anpassung des TEvaluatorVisitor (Ausführung)
TEvaluatorVisitor.VisitFunctionCall muss nun zwei Arten von Calls behandeln:
- Dynamischer Call:
CalleeistvkMethod. (Wie bisher:(calleeValue.AsMethod)(argValues)). - Statischer/Monomorphisierter Call:
Calleeist einISpecializedBody.- Erzeuge den
IExecutionScope(viaISpecializedBody.GetScopeDescriptor). - Populiere den Scope mit den (bereits evaluierten)
argValues. - Führe den
ISpecializedBody.GetBodyAstdirekt mitAccept(Self)aus. (TCO muss hier ebenfalls beachtet werden).
- Erzeuge den
Phase 5: LLVM-Optimierung (Optional)
Der in Phase 3 erzeugte ISpecializedBody ist der "corner case" für die Optimierung:
- Wenn ein
ISpecializedBodyerfolgreich erstellt wurde und dieser AST-Body die "harten" Kriterien erfüllt (keine dynamischen Internals), wird er als Kandidat für LLVM-AOT/JIT markiert. - Ein LLVM-Backend kann diese Kandidaten aufnehmen, LLVM-IR generieren und den
ISpecializedBodyim Cache durch einen nativen Funktionspointer (den FFI-Entrypoint) ersetzen.
4. TODOs
TBoundLambdaExpressionNodeerweitern, um den originalen Body und den Spezialisierungs-Cache zu halten.ISpecializedBody(oder äquivalente Struktur) definieren.TAstBinder.VisitFunctionCallum die Weichenstellung (Fall A/B) erweitern.- Den re-entranten Monomorphisierungs-Pass (Phase 3) implementieren.
TEvaluatorVisitor.VisitFunctionCallfür die Ausführung vonISpecializedBodyanpassen.- Typsystem (
TTypeRules) auf Robustheit für die neuen, strikten Prüfungen im Sub-Bind-Pass testen.