Files
MycLib/Doc/Monomorphisierung.md
Michael Schimmel 1394314a57 New visualizer
2025-10-30 13:48:14 +01:00

5.7 KiB

Projektplan: Hybride Monomorphisierung

  • Datum: 30.10.2025
  • Version: 1.0

1. Motivation

Das aktuelle Typsystem (Myc.Ast.Types) ist mächtig, aber die Inferenz für Lambda-Parameter endet bei stUnknown. Dies erzwingt Laufzeit-Typüberprüfungen im TEvaluatorVisitor und verkompliziert AOT/JIT-Kompilierung.

2. Ziel

Implementierung einer hybriden Monomorphisierungsstrategie zur Compile-Zeit (innerhalb des TAstBinder).

  1. Statische Spezialisierung: Alle Funktionsaufrufe, deren Callee und Argumenttypen zur Compile-Zeit statisch bekannt sind, werden spezialisiert. stUnknown wird für diese Pfade eliminiert.
  2. Dynamischer Fallback: Alle dynamischen Aufrufe (z.B. Higher-Order-Funktionen, deren Callee aus einer Variable stammt) nutzen weiterhin den bestehenden dynamischen Pfad (vkMethod mit TDataValue-Wrapper).
  3. Optimierungs-Vorbereitung: Der statisch spezialisierte AST (der "Entrypoint") dient als saubere Basis für optionale Optimierungen (z.B. LLVM-Codegen).

3. Ergebnis: Die Strategie

Die Implementierung erfordert eine Erweiterung des TAstBinder, um einen Spezialisierungs-Cache zu verwalten und bei Bedarf rekursive Binding-Pässe durchzuführen.

Phase 1: Modifikation der Kern-Typen

  1. TBoundLambdaExpressionNode (Generic):
    • Muss seinen originalen, ungebundenen Body (IAstNode) behalten, um als Vorlage für das Re-Binding zu dienen.
    • Führt einen Spezialisierungs-Cache (z.B. TDictionary<TSignatureHash, ISpecializedBody>).
  2. ISpecializedBody (Interface):
    • Repräsentiert einen erfolgreich monomorphisierten Entrypoint.
    • function GetBodyAst: IAstNode;
    • function GetScopeDescriptor: IScopeDescriptor;
    • function GetReturnType: IStaticType;
  3. TBoundFunctionCallNode:
    • Benötigt ein neues Feld, um entweder den dynamischen Callee (wie bisher) oder den statischen ISpecializedBody (den Entrypoint) zu halten.

Phase 2: Anpassung des TAstBinder (Trigger)

TAstBinder.VisitFunctionCall wird zur zentralen Weichenstellung:

  1. Binde alle Argumente und ermittle ihre statischen Typen (die Aufrufsignatur).
  2. Binde den Callee.
  3. Fallunterscheidung:
    • Fall A (Dynamischer Aufruf): Der Callee ist nicht als TBoundLambdaExpressionNode statisch bekannt (z.B. (map (if flag f1 f2) ...)).
      • Aktion: Der Aufruf wird als dynamisch belassen (Fallback). Es wird lediglich geprüft, ob der Callee den Typ stMethod hat. Es findet keine Spezialisierung statt.
    • Fall B (Statischer Aufruf): Der Callee ist ein TBoundLambdaExpressionNode.
      • Aktion: Trigger die Monomorphisierung.
      • Rufe Callee.GetSpecialization(Aufrufsignatur) auf.
      • Der TBoundFunctionCallNode wird modifiziert und verweist direkt auf das zurückgegebene ISpecializedBody.
      • Der StaticType des TBoundFunctionCallNode wird auf ISpecializedBody.GetReturnType gesetzt.

Phase 3: Die Monomorphisierung (Der Re-Bind Pass)

Dies ist die Kernlogik (z.B. in TBoundLambdaExpressionNode.GetSpecialization):

  1. Cache-Lookup: Prüfe, ob für die Aufrufsignatur bereits ein ISpecializedBody im Cache existiert.
  2. Cache-Miss (Re-Binding):
    • Erzeuge einen neuen, temporären IScopeDescriptor für den Sub-Bind-Prozess.
    • Populiere den Scope: Definiere die Parameter-Namen (z.B. [a b]) in diesem Scope, aber verwende die konkreten Typen der Aufrufsignatur (z.B. stFloat, stOrdinal) anstelle von stUnknown.
    • Rekursiver Binder: Erzeuge einen neuen TAstBinder (oder rufe den aktuellen re-entrant auf), der den originalen, ungebundenen Body des Lambdas mit diesem spezialisierten Scope bindet.
    • Fehlerbehandlung: Schlägt dieser Sub-Bind-Prozess fehl (z.B. ETypeException, weil a (jetzt stFloat) mit einem Text addiert wird), ist der Aufruf ungültig -> Compile-Fehler.
    • Erfolg: Das Ergebnis ist der spezialisierte IAstNode (Body) und der IScopeDescriptor. Erzeuge ein ISpecializedBody-Objekt, speichere es im Cache und gib es zurück.

Phase 4: Anpassung des TEvaluatorVisitor (Ausführung)

TEvaluatorVisitor.VisitFunctionCall muss nun zwei Arten von Calls behandeln:

  1. Dynamischer Call: Callee ist vkMethod. (Wie bisher: (calleeValue.AsMethod)(argValues)).
  2. Statischer/Monomorphisierter Call: Callee ist ein ISpecializedBody.
    • Erzeuge den IExecutionScope (via ISpecializedBody.GetScopeDescriptor).
    • Populiere den Scope mit den (bereits evaluierten) argValues.
    • Führe den ISpecializedBody.GetBodyAst direkt mit Accept(Self) aus. (TCO muss hier ebenfalls beachtet werden).

Phase 5: LLVM-Optimierung (Optional)

Der in Phase 3 erzeugte ISpecializedBody ist der "corner case" für die Optimierung:

  • Wenn ein ISpecializedBody erfolgreich erstellt wurde und dieser AST-Body die "harten" Kriterien erfüllt (keine dynamischen Internals), wird er als Kandidat für LLVM-AOT/JIT markiert.
  • Ein LLVM-Backend kann diese Kandidaten aufnehmen, LLVM-IR generieren und den ISpecializedBody im Cache durch einen nativen Funktionspointer (den FFI-Entrypoint) ersetzen.

4. TODOs

  • TBoundLambdaExpressionNode erweitern, um den originalen Body und den Spezialisierungs-Cache zu halten.
  • ISpecializedBody (oder äquivalente Struktur) definieren.
  • TAstBinder.VisitFunctionCall um die Weichenstellung (Fall A/B) erweitern.
  • Den re-entranten Monomorphisierungs-Pass (Phase 3) implementieren.
  • TEvaluatorVisitor.VisitFunctionCall für die Ausführung von ISpecializedBody anpassen.
  • Typsystem (TTypeRules) auf Robustheit für die neuen, strikten Prüfungen im Sub-Bind-Pass testen.