diff --git a/Doc/Monomorphisierung 2.md b/Doc/Monomorphisierung 2.md new file mode 100644 index 0000000..07ba913 --- /dev/null +++ b/Doc/Monomorphisierung 2.md @@ -0,0 +1,82 @@ +# 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. + +1. **Statische Spezialisierung:** Aufrufe an bekannte Funktionen (RTL oder User-Code) mit bekannten Argumenttypen werden zur Compile-Zeit an eine hochoptimierte, statisch gebundene Implementierung gemappt. +2. **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. + 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. Der Monomorphisierungs-Cache +Der `TStaticSpecializer` verwaltet einen globalen Cache (z.B. `TDictionary`), der bereits spezialisierte Funktionen vorhält. + +* **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). + +### 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): + +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. + +### 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. +* `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. + +## 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. +* `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. +* `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. +* `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 diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas index 293805c..6908fbc 100644 --- a/Src/AST/Myc.Ast.Evaluator.pas +++ b/Src/AST/Myc.Ast.Evaluator.pas @@ -220,10 +220,9 @@ begin // Create the new execution scope for this function call. lambdaScope := TScope.CreateScope(closureScope, scopeDescriptor, capturedCells); + // Capture the closure itself in slot 0 for 'recur' to find it. adr.Kind := akLocalOrParent; adr.ScopeDepth := 0; - - // Capture the closure itself in slot 0 for 'recur' to find it. adr.SlotIndex := 0; lambdaScope[adr] := TDataValue(closure); // Explicit cast