Files
MycLib/Doc/Monomorphisierung 2.md
T
2025-11-11 11:58:08 +01:00

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.

  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. 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 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 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 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<IStaticType>).
    • 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 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 ((+ <x:Ordinal> <x:Ordinal>)) rekursiv durch die relevanten Compiler-Phasen laufen (mindestens TypeCheck und Specialize).
  4. Der Specialize-Pass wandelt den Body (z.B. (+ <x:Ordinal> <x:Ordinal>)) 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)

  • 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 mit StaticTarget = nil bleiben) voll funktionsfähig und erzeugt TThunks.
  • 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

  • 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-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.VisitFunctionCall modifizieren, um den statischen Pfad (if Assigned(Node.StaticTarget)) zu implementieren.