diff --git a/Doc/Monomorphisierung 2.md b/Doc/Monomorphisierung 2.md index 07ba913..b408fc5 100644 --- a/Doc/Monomorphisierung 2.md +++ b/Doc/Monomorphisierung 2.md @@ -1,4 +1,8 @@ -# Monomorphisierung +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 @@ -17,66 +21,78 @@ Diese Strategie macht die spezialisierten `TBinaryExpressionNode` und `TUnaryExp ## 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. +### 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 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. +* Er erhält ein neues Property: **`StaticTarget: TDataValue.TFunc`**. +* **Dynamischer Pfad (Default):** Wenn **`StaticTarget = nil`**, wird der Knoten wie bisher über den `vkMethod`-Pfad ausgewertet (dynamischer Dispatch über den `Callee`-Knoten). +* **Statischer Pfad (Optimiert):** Wenn **`StaticTarget <> nil`**, ignoriert der Evaluator den `Callee`-Knoten und ruft stattdessen das **`StaticTarget`** direkt mit den ausgewerteten Argumenten auf. +* Die `TAst.FunctionCall`-Factory in `Myc.Ast.pas` wird um einen optionalen **`AStaticTarget`**-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 `TStaticSpecializer` erhält im Konstruktor eine Referenz auf den `IEnvironment`-spezifischen Monomorphisierungs-Cache (siehe Punkt 3). * **`VisitFunctionCall`-Logik:** Dies ist die Kernmethode. 1. Sie prüft, ob der `Callee` des `IFunctionCallNode` ein `IIdentifierNode` ist (z.B. `+`, `Abs`, `my-func`). 2. Sie prüft, ob *alle* Argumenttypen (`newArgs[i].StaticType`) statisch bekannt sind (d.h. nicht `stUnknown`). - 3. **Statischer Pfad (Ja):** Der `TStaticSpecializer` fragt den "Monomorphisierungs-Cache" nach einer spezialisierten Implementierung ab. Bei Erfolg ersetzt er den `IFunctionCallNode` durch einen `IStaticCallNode`. - 4. **Dynamischer Pfad (Nein):** Der `IFunctionCallNode` wird als Fallback beibehalten. + 3. **Statischer Pfad (Ja):** Der `TStaticSpecializer` fragt den ihm übergebenen **Environment-Cache** nach einer spezialisierten `TDataValue.TFunc` ab. Bei Erfolg **klont** er den `IFunctionCallNode` (via CoW) und setzt dessen **`StaticTarget`**-Property auf die gefundene Funktion. + 4. **Dynamischer Pfad (Nein):** Der `IFunctionCallNode` wird (ggf. geklont, falls Kindknoten sich änderten) mit **`StaticTarget = nil`** zurückgegeben. -### 3. Der Monomorphisierungs-Cache -Der `TStaticSpecializer` verwaltet einen globalen Cache (z.B. `TDictionary`), der bereits spezialisierte Funktionen vorhält. +### 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 ihre `RootScope` und RTL gebunden ist. +* Die `TEnvironment`-Implementierung (in `Myc.Ast.Environment.pas`) wird um dieses `TDictionary`-Feld erweitert. +* Der `TStaticSpecializer` erhält eine Referenz auf diesen Cache bei seiner Erstellung (z.B. über den Konstruktor). * **Schlüssel:** `(Funktions-ID, TArray)`. - * *Funktions-ID*: Ein eindeutiger Bezeichner für den Callee (z.B. die `TResolvedAddress` der `def`-Deklaration oder ein Hash des `ILambdaExpressionNode`). -* **Wert:** Die `IStaticCallNode`-Implementierungsdetails (z.B. der Funktionspointer). + * *Funktions-ID*: Ein eindeutiger Bezeichner für den Callee, gebilded aus der `TResolvedAddress`, die innerhalb des Environments einen eindeutigen Schlüssel darstellt. +* **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 Cache ist (Cache Miss): +Wenn der `TStaticSpecializer` einen statisch auflösbaren Aufruf (z.B. `(my-func 10)`) findet, der noch nicht im **Environment-Cache** ist (Cache Miss): 1. Er holt den AST-Body der Zielfunktion (z.B. `(+ x x)` aus `(def my-func (fn [x] (+ x x)))`). 2. Er instanziiert diesen Body, indem er das Wissen über die Argumenttypen (z.B. `x = stOrdinal`) anwendet. 3. Er lässt diesen neuen, instanziierten AST-Body (`(+ )`) rekursiv durch die relevanten Compiler-Phasen laufen (mindestens `TypeCheck` und `Specialize`). -4. Der `Specialize`-Pass wandelt den Body (z.B. `(+ )`) rekursiv in einen `IStaticCallNode` auf die RTL-Funktion `@TRtlFunctions.Add_Ordinal_Ordinal` um. -5. Das Ergebnis (der `IStaticCallNode`, der den optimierten Body repräsentiert) wird im Cache gespeichert. -6. Der ursprüngliche `IFunctionCallNode` `(my-func 10)` wird durch den neuen `IStaticCallNode` ersetzt. +4. Der `Specialize`-Pass wandelt den Body (z.B. `(+ )`) rekursiv in einen *neuen* `IFunctionCallNode` um, dessen **`StaticTarget`** auf die RTL-Funktion `@TRtlFunctions.Add_Ordinal_Ordinal` zeigt. +5. Das Ergebnis (die `TDataValue.TFunc`, die den optimierten Body repräsentiert) wird im **Environment-Cache** gespeichert. +6. Der ursprüngliche `IFunctionCallNode` `(my-func 10)` wird durch einen Klon ersetzt, dessen **`StaticTarget`** auf die soeben kompilierte Funktion zeigt. ### 5. RTL-Erweiterung * `Myc.Ast.RTL.Core` wird um statisch typisierte Implementierungen (z.B. `class function Add_Ordinal_Ordinal(A, B: Int64): Int64; static;`) erweitert. * Die `TRtlRegistry` wird angepasst, um diese statischen Signaturen zu indizieren und als "Bootstrap" für den Monomorphisierungs-Cache bereitzustellen. ### 6. Evaluator-Anpassung (`TEvaluatorVisitor`) -* `VisitStaticCallNode` wird implementiert. Diese Methode ist hochoptimiert: - 1. Wertet die Argument-Nodes aus (die bereits statisch typgeprüft sind). - 2. Marshallt die `TDataValue`-Ergebnisse direkt in die erwarteten nativen Typen (z.B. `AsScalar.Value.AsInt64`). - 3. Ruft den im `IStaticCallNode` gespeicherten Funktionspointer direkt auf. - 4. Wrappt das native Ergebnis (z.B. `Int64`) zurück in ein `TDataValue`. -* `VisitFunctionCall` behandelt nur noch den dynamischen `vkMethod`-Fallback. +* `VisitFunctionCall` wird modifiziert, um beide Pfade zu behandeln: + 1. **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.StaticTarget`** direkt auf. + * Wrappt das Ergebnis zurück in ein `TDataValue`. + * Ruft `HandleTCO` auf (falls das **`StaticTarget`** ein `recur` war). + 2. **Dynamischer Pfad:** `else` + * Behält die bestehende Logik (`vkMethod`-Lookup) und die TCO-Thunk-Erzeugung bei (`if Node.IsTailCall then ...`). * `VisitBinaryExpression` und `VisitUnaryExpression` werden entfernt. ### 7. Auswirkungen auf TCO (Tail Call Optimization) -* Die TCO bleibt für `recur` und dynamische `fn`-Aufrufe (die `IFunctionCallNode` bleiben) voll funktionsfähig und erzeugt `TThunk`s. -* Ein `IStaticCallNode` (z.B. `(Abs x)`) in einer Tail-Position wird *nicht* per TCO optimiert. Er ist per Definition keine Rekursion. Der `TEvaluatorVisitor` führt ihn direkt aus und beendet damit korrekt die Trampolin-Schleife. +* Die TCO bleibt für `recur` und *dynamische* `fn`-Aufrufe (die `IFunctionCallNode` mit **`StaticTarget = nil`** bleiben) voll funktionsfähig und erzeugt `TThunk`s. +* Ein `IFunctionCallNode` mit gesetztem **`StaticTarget`** (z.B. `(Abs x)`) in einer Tail-Position wird *nicht* per TCO optimiert. Er ist per Definition keine Rekursion. Der `TEvaluatorVisitor` führt ihn direkt aus und beendet damit korrekt die Trampolin-Schleife. ## TODO -* `IStaticCallNode` in `Myc.Ast.Nodes` definieren und `TAst`-Factory-Methode erstellen. -* `IAstVisitor` (und alle `TAstVisitor...`-Basisklassen) um `VisitStaticCallNode` erweitern. -* `TBinaryExpressionNode`, `TUnaryExpressionNode` (und ihre `Visit...`-Methoden) aus allen Units (`Nodes`, `Visitor`, `Dumper`, `Evaluator`, `Lowerer`, `Json`) entfernen. +* `IFunctionCallNode` in `Myc.Ast.Nodes` um **`StaticTarget: TDataValue.TFunc`** erweitern. +* `TFunctionCallNode` (Implementierungsklasse) um Feld, Konstruktorparameter und Getter erweitern. +* `TAst.FunctionCall`-Factory in `Myc.Ast.pas` um optionalen **`AStaticTarget`**-Parameter erweitern. +* `TAstTransformer.VisitFunctionCall` in `Myc.Ast.Visitor` anpassen, um **`StaticTarget`** bei CoW zu kopieren. +* `TBinaryExpressionNode`, `TUnaryExpressionNode` (und ihre `Visit...`-Methoden) aus allen Units (`Nodes`, `Visitor`, `Dumper`, `Evaluator`, `Lowerer`, `Json`, `Fmx.AstEditor.Node`) entfernen. * `TAstLowerer` aus dem Kompilierungsprozess in `TEnvironment.Compile` entfernen. * `Myc.Ast.RTL.Core` um statisch typisierte Funktionsvarianten für alle Operatoren und gängige Funktionen (Abs, Trunc etc.) ergänzen. * `TRtlRegistry` erweitern, um diese statischen Signaturen zu indizieren. +* **`IEnvironment`** (und Implementierung) um einen Member für den Monomorphisierungs-Cache erweitern. * `TStaticSpecializer` als neuen `TAstTransformer`-Pass implementieren. -* `TStaticSpecializer.VisitFunctionCall` mit der Logik für statische/dynamische Pfade implementieren. -* Monomorphisierungs-Cache (Lookup und "Cache Miss"-Rekursion) im `TStaticSpecializer` implementieren. +* **`TStaticSpecializer`-Konstruktor** erweitern, um den Cache vom `IEnvironment` entgegenzunehmen. +* `TStaticSpecializer.VisitFunctionCall` mit der Logik für statische/dynamische Pfade implementieren (Klonen des `IFunctionCallNode` mit gesetztem **`StaticTarget`**). +* Monomorphisierungs-Cache (Lookup und "Cache Miss"-Rekursion) im `TStaticSpecializer` implementieren (unter Verwendung des **Environment-Caches**). * `TEnvironment.Compile` aktualisieren, um den `TStaticSpecializer` anstelle des `TAstLowerer` aufzurufen. -* `TEvaluatorVisitor.VisitStaticCallNode` für den direkten Aufruf der spezialisierten Funktionen implementieren. \ No newline at end of file +* `TEvaluatorVisitor.VisitFunctionCall` modifizieren, um den statischen Pfad (`if Assigned(Node.StaticTarget)`) zu implementieren. \ No newline at end of file diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index d4f6a95..36ad008 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -252,10 +252,12 @@ type function GetCallee: IAstNode; function GetArguments: TArray; function GetIsTailCall: Boolean; + function GetStaticTarget: TDataValue.TFunc; {$endregion} property Callee: IAstNode read GetCallee; property Arguments: TArray read GetArguments; property IsTailCall: Boolean read GetIsTailCall; + property StaticTarget: TDataValue.TFunc read GetStaticTarget; end; // A node representing a macro call. diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 86eb3bf..686c6a5 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -546,7 +546,6 @@ var newCallee: IAstNode; newArgs: TArray; begin - // No longer cast to concrete class, use interface newCallee := Accept(Node.Callee); newArgs := AcceptNodes(Node.Arguments); @@ -555,7 +554,7 @@ begin else begin // Use TAst factory and copy properties via interface getters - Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall); + Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, Node.StaticTarget); end; end; diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index 5387a38..4dfee32 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -87,7 +87,8 @@ type const ACallee: IAstNode; const AArguments: TArray; const AStaticType: IStaticType = nil; - const AIsTailCall: Boolean = False + const AIsTailCall: Boolean = False; + const AStaticTarget: TDataValue.TFunc = nil ): IFunctionCallNode; static; class function MacroExpansionNode( @@ -227,16 +228,19 @@ type FCallee: IAstNode; FArguments: TArray; FIsTailCall: Boolean; + FStaticTarget: TDataValue.TFunc; function GetCallee: IAstNode; function GetArguments: TArray; function GetIsTailCall: Boolean; + function GetStaticTarget: TDataValue.TFunc; function GetKind: TAstNodeKind; override; public constructor Create( const ACallee: IAstNode; const AArguments: TArray; const AStaticType: IStaticType; - const AIsTailCall: Boolean + const AIsTailCall: Boolean; + const AStaticTarget: TDataValue.TFunc ); function Accept(const Visitor: IAstVisitor): TDataValue; override; function AsFunctionCall: IFunctionCallNode; override; @@ -734,17 +738,18 @@ class function TAst.FunctionCall( const ACallee: IAstNode; const AArguments: TArray; const AStaticType: IStaticType = nil; - const AIsTailCall: Boolean = False + const AIsTailCall: Boolean = False; + const AStaticTarget: TDataValue.TFunc = nil ): IFunctionCallNode; begin - // Updated to call new constructor Result := TFunctionCallNode.Create( ACallee, AArguments, if AStaticType <> nil then AStaticType else TTypes.Unknown, - AIsTailCall + AIsTailCall, + AStaticTarget ); end; @@ -1593,13 +1598,15 @@ constructor TFunctionCallNode.Create( const ACallee: IAstNode; const AArguments: TArray; const AStaticType: IStaticType; - const AIsTailCall: Boolean + const AIsTailCall: Boolean; + const AStaticTarget: TDataValue.TFunc ); begin inherited Create(AStaticType); FCallee := ACallee; FArguments := AArguments; FIsTailCall := AIsTailCall; + FStaticTarget := AStaticTarget; end; function TFunctionCallNode.Accept(const Visitor: IAstVisitor): TDataValue; @@ -1632,6 +1639,11 @@ begin Result := akFunctionCall; end; +function TFunctionCallNode.GetStaticTarget: TDataValue.TFunc; +begin + Result := FStaticTarget; +end; + { TMacroExpansionNode } constructor TMacroExpansionNode.Create(const ACallNode: IFunctionCallNode; const AExpandedBody: IAstTypedNode);