6.8 KiB
Monomorphisierung
09.11.2025 19:10
Motivation
Der aktuelle Compiler ist in seiner Optimierungsfähigkeit limitiert. Der TAstLowerer-Pass implementiert eine hartkodierte statische Spezialisierung ausschließlich für vordefinierte Operatoren (z.B. (+) -> TBinaryExpressionNode). Alle anderen Aufrufe, einschließlich der restlichen RTL (z.B. Abs) und sämtlicher benutzerdefinierter Funktionen, werden zur Laufzeit dynamisch über den vkMethod-Pfad aufgelöst. Dieser Pfad erfordert das Boxing von Werten in TDataValue-Wrapper und einen Scope-Lookup, was einen signifikanten Performance-Overhead darstellt, selbst wenn alle Typen zur Compile-Zeit bekannt wären.
Ziel
Implementierung einer hybriden Monomorphisierungsstrategie zur Compile-Zeit. Das Ziel ist die Eliminierung des vkMethod-Overheads für alle Aufrufe, deren Callee und Argumenttypen statisch auflösbar sind.
- Statische Spezialisierung: Aufrufe an bekannte Funktionen (RTL oder User-Code) mit bekannten Argumenttypen werden zur Compile-Zeit an eine hochoptimierte, statisch gebundene Implementierung gemappt.
- Dynamischer Fallback: Aufrufe, die nicht statisch aufgelöst werden können (z.B. Higher-Order-Functions, bei denen der Callee aus einer Variable stammt), nutzen weiterhin den bestehenden
vkMethod-Pfad.
Diese Strategie macht die spezialisierten TBinaryExpressionNode und TUnaryExpressionNode sowie den TAstLowerer-Pass obsolet und ersetzt sie durch einen generalisierten und wesentlich leistungsfähigeren Mechanismus.
Ergebnis & Implementierungsplan
1. Neuer AST-Knoten: IStaticCallNode
Als Ersatz für TBinaryExpressionNode und zur Darstellung aller monomorphisierten Aufrufe wird IStaticCallNode in Myc.Ast.Nodes eingeführt.
- Er ersetzt
IFunctionCallNode, wenn ein Aufruf statisch gebunden werden kann. - Er speichert einen direkten Verweis auf die auszuführende Implementierung (z.B. einen Funktionspointer, eine Implementierungs-ID oder einen delegierten
TDataValue.TFunc). - Er speichert die statisch bekannten Typen der Argumente und des Rückgabewerts.
2. Neue Compiler-Phase: TStaticSpecializer
Ein neuer TAstTransformer namens TStaticSpecializer wird implementiert. Er läuft nach dem TTypeChecker (Phase 3) und ersetzt den TAstLowerer (Phase 4).
VisitFunctionCall-Logik: Dies ist die Kernmethode.- Sie prüft, ob der
CalleedesIFunctionCallNodeeinIIdentifierNodeist (z.B.+,Abs,my-func). - Sie prüft, ob alle Argumenttypen (
newArgs[i].StaticType) statisch bekannt sind (d.h. nichtstUnknown). - Statischer Pfad (Ja): Der
TStaticSpecializerfragt den "Monomorphisierungs-Cache" nach einer spezialisierten Implementierung ab. Bei Erfolg ersetzt er denIFunctionCallNodedurch einenIStaticCallNode. - Dynamischer Pfad (Nein): Der
IFunctionCallNodewird als Fallback beibehalten.
- Sie prüft, ob der
3. Der Monomorphisierungs-Cache
Der TStaticSpecializer verwaltet einen globalen Cache (z.B. TDictionary), der bereits spezialisierte Funktionen vorhält.
- Schlüssel:
(Funktions-ID, TArray<IStaticType>).- Funktions-ID: Ein eindeutiger Bezeichner für den Callee (z.B. die
TResolvedAddressderdef-Deklaration oder ein Hash desILambdaExpressionNode).
- Funktions-ID: Ein eindeutiger Bezeichner für den Callee (z.B. die
- Wert: Die
IStaticCallNode-Implementierungsdetails (z.B. der Funktionspointer).
4. Cache-Miss-Strategie (Inlining & User-Code)
Wenn der TStaticSpecializer einen statisch auflösbaren Aufruf (z.B. (my-func 10)) findet, der noch nicht im Cache ist (Cache Miss):
- Er holt den AST-Body der Zielfunktion (z.B.
(+ x x)aus(def my-func (fn [x] (+ x x)))). - Er instanziiert diesen Body, indem er das Wissen über die Argumenttypen (z.B.
x = stOrdinal) anwendet. - Er lässt diesen neuen, instanziierten AST-Body (
(+ <x:Ordinal> <x:Ordinal>)) rekursiv durch die relevanten Compiler-Phasen laufen (mindestensTypeCheckundSpecialize). - Der
Specialize-Pass wandelt den Body (z.B.(+ <x:Ordinal> <x:Ordinal>)) rekursiv in einenIStaticCallNodeauf die RTL-Funktion@TRtlFunctions.Add_Ordinal_Ordinalum. - Das Ergebnis (der
IStaticCallNode, der den optimierten Body repräsentiert) wird im Cache gespeichert. - Der ursprüngliche
IFunctionCallNode(my-func 10)wird durch den neuenIStaticCallNodeersetzt.
5. RTL-Erweiterung
Myc.Ast.RTL.Corewird um statisch typisierte Implementierungen (z.B.class function Add_Ordinal_Ordinal(A, B: Int64): Int64; static;) erweitert.- Die
TRtlRegistrywird angepasst, um diese statischen Signaturen zu indizieren und als "Bootstrap" für den Monomorphisierungs-Cache bereitzustellen.
6. Evaluator-Anpassung (TEvaluatorVisitor)
VisitStaticCallNodewird implementiert. Diese Methode ist hochoptimiert:- Wertet die Argument-Nodes aus (die bereits statisch typgeprüft sind).
- Marshallt die
TDataValue-Ergebnisse direkt in die erwarteten nativen Typen (z.B.AsScalar.Value.AsInt64). - Ruft den im
IStaticCallNodegespeicherten Funktionspointer direkt auf. - Wrappt das native Ergebnis (z.B.
Int64) zurück in einTDataValue.
VisitFunctionCallbehandelt nur noch den dynamischenvkMethod-Fallback.VisitBinaryExpressionundVisitUnaryExpressionwerden entfernt.
7. Auswirkungen auf TCO (Tail Call Optimization)
- Die TCO bleibt für
recurund dynamischefn-Aufrufe (dieIFunctionCallNodebleiben) voll funktionsfähig und erzeugtTThunks. - Ein
IStaticCallNode(z.B.(Abs x)) in einer Tail-Position wird nicht per TCO optimiert. Er ist per Definition keine Rekursion. DerTEvaluatorVisitorführt ihn direkt aus und beendet damit korrekt die Trampolin-Schleife.
TODO
IStaticCallNodeinMyc.Ast.Nodesdefinieren undTAst-Factory-Methode erstellen.IAstVisitor(und alleTAstVisitor...-Basisklassen) umVisitStaticCallNodeerweitern.TBinaryExpressionNode,TUnaryExpressionNode(und ihreVisit...-Methoden) aus allen Units (Nodes,Visitor,Dumper,Evaluator,Lowerer,Json) entfernen.TAstLowereraus dem Kompilierungsprozess inTEnvironment.Compileentfernen.Myc.Ast.RTL.Coreum statisch typisierte Funktionsvarianten für alle Operatoren und gängige Funktionen (Abs, Trunc etc.) ergänzen.TRtlRegistryerweitern, um diese statischen Signaturen zu indizieren.TStaticSpecializerals neuenTAstTransformer-Pass implementieren.TStaticSpecializer.VisitFunctionCallmit der Logik für statische/dynamische Pfade implementieren.- Monomorphisierungs-Cache (Lookup und "Cache Miss"-Rekursion) im
TStaticSpecializerimplementieren. TEnvironment.Compileaktualisieren, um denTStaticSpecializeranstelle desTAstLowereraufzurufen.TEvaluatorVisitor.VisitStaticCallNodefür den direkten Aufruf der spezialisierten Funktionen implementieren.