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

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.

  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<IStaticType>).
    • 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 ((+ <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 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 TThunks.
  • 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.