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

89 lines
5.7 KiB
Markdown

# 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.