89 lines
5.7 KiB
Markdown
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. |