8.9 KiB
Hier ist der vollständig aktualisierte Projektplan, der die Verwaltung des Caches als Environment-spezifische Aufgabe (statt global) korrekt abbildet.
Monomorphisierung (Revidierter Plan v2)
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. Erweiterung: IFunctionCallNode
Anstatt einen neuen Knotentyp einzuführen, wird der bestehende IFunctionCallNode in Myc.Ast.Nodes erweitert, um beide Aufrufpfade (dynamisch und statisch) zu repräsentieren.
- Er erhält ein neues Property:
StaticTarget: TDataValue.TFunc. - Dynamischer Pfad (Default): Wenn
StaticTarget = nil, wird der Knoten wie bisher über denvkMethod-Pfad ausgewertet (dynamischer Dispatch über denCallee-Knoten). - Statischer Pfad (Optimiert): Wenn
StaticTarget <> nil, ignoriert der Evaluator denCallee-Knoten und ruft stattdessen dasStaticTargetdirekt mit den ausgewerteten Argumenten auf. - Die
TAst.FunctionCall-Factory inMyc.Ast.paswird um einen optionalenAStaticTarget-Parameter erweitert.
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).
- Konstruktor: Der
TStaticSpecializererhält im Konstruktor eine Referenz auf denIEnvironment-spezifischen Monomorphisierungs-Cache (siehe Punkt 3). 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 ihm übergebenen Environment-Cache nach einer spezialisiertenTDataValue.TFuncab. Bei Erfolg klont er denIFunctionCallNode(via CoW) und setzt dessenStaticTarget-Property auf die gefundene Funktion. - Dynamischer Pfad (Nein): Der
IFunctionCallNodewird (ggf. geklont, falls Kindknoten sich änderten) mitStaticTarget = nilzurückgegeben.
- Sie prüft, ob der
3. Der Environment-spezifische Cache
Das IEnvironment verwaltet einen Instanz-spezifischen Cache (z.B. TDictionary), der bereits spezialisierte Funktionen vorhält. Dieser Cache ist nicht global oder statisch.
- Jede
IEnvironment-Instanz (z.B. eine für Produktion, eine für Tests) hat ihren eigenen, isolierten Cache, der an ihreRootScopeund RTL gebunden ist. - Die
TEnvironment-Implementierung (inMyc.Ast.Environment.pas) wird um diesesTDictionary-Feld erweitert. - Der
TStaticSpecializererhält eine Referenz auf diesen Cache bei seiner Erstellung (z.B. über den Konstruktor). - Schlüssel:
(Funktions-ID, TArray<IStaticType>).- Funktions-ID: Ein eindeutiger Bezeichner für den Callee, gebilded aus der
TResolvedAddress, die innerhalb des Environments einen eindeutigen Schlüssel darstellt.
- Funktions-ID: Ein eindeutiger Bezeichner für den Callee, gebilded aus der
- Wert: Die spezialisierte
TDataValue.TFunc.
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 Environment-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 einen neuenIFunctionCallNodeum, dessenStaticTargetauf die RTL-Funktion@TRtlFunctions.Add_Ordinal_Ordinalzeigt. - Das Ergebnis (die
TDataValue.TFunc, die den optimierten Body repräsentiert) wird im Environment-Cache gespeichert. - Der ursprüngliche
IFunctionCallNode(my-func 10)wird durch einen Klon ersetzt, dessenStaticTargetauf die soeben kompilierte Funktion zeigt.
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)
VisitFunctionCallwird modifiziert, um beide Pfade zu behandeln:- Statischer Pfad:
if Assigned(Node.StaticTarget) then- Wertet die Argument-Nodes aus (mit
Assert(arg.IsTyped)). - Marshallt die
TDataValue-Ergebnisse direkt in die erwarteten nativen Typen. - Ruft das
Node.StaticTargetdirekt auf. - Wrappt das Ergebnis zurück in ein
TDataValue. - Ruft
HandleTCOauf (falls dasStaticTargeteinrecurwar).
- Wertet die Argument-Nodes aus (mit
- Dynamischer Pfad:
else- Behält die bestehende Logik (
vkMethod-Lookup) und die TCO-Thunk-Erzeugung bei (if Node.IsTailCall then ...).
- Behält die bestehende Logik (
- Statischer Pfad:
VisitBinaryExpressionundVisitUnaryExpressionwerden entfernt.
7. Auswirkungen auf TCO (Tail Call Optimization)
- Die TCO bleibt für
recurund dynamischefn-Aufrufe (dieIFunctionCallNodemitStaticTarget = nilbleiben) voll funktionsfähig und erzeugtTThunks. - Ein
IFunctionCallNodemit gesetztemStaticTarget(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
IFunctionCallNodeinMyc.Ast.NodesumStaticTarget: TDataValue.TFuncerweitern.TFunctionCallNode(Implementierungsklasse) um Feld, Konstruktorparameter und Getter erweitern.TAst.FunctionCall-Factory inMyc.Ast.pasum optionalenAStaticTarget-Parameter erweitern.TAstTransformer.VisitFunctionCallinMyc.Ast.Visitoranpassen, umStaticTargetbei CoW zu kopieren.TBinaryExpressionNode,TUnaryExpressionNode(und ihreVisit...-Methoden) aus allen Units (Nodes,Visitor,Dumper,Evaluator,Lowerer,Json,Fmx.AstEditor.Node) 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.IEnvironment(und Implementierung) um einen Member für den Monomorphisierungs-Cache erweitern.TStaticSpecializerals neuenTAstTransformer-Pass implementieren.TStaticSpecializer-Konstruktor erweitern, um den Cache vomIEnvironmententgegenzunehmen.TStaticSpecializer.VisitFunctionCallmit der Logik für statische/dynamische Pfade implementieren (Klonen desIFunctionCallNodemit gesetztemStaticTarget).- Monomorphisierungs-Cache (Lookup und "Cache Miss"-Rekursion) im
TStaticSpecializerimplementieren (unter Verwendung des Environment-Caches). TEnvironment.Compileaktualisieren, um denTStaticSpecializeranstelle desTAstLowereraufzurufen.TEvaluatorVisitor.VisitFunctionCallmodifizieren, um den statischen Pfad (if Assigned(Node.StaticTarget)) zu implementieren.