JDL – Intermediate Representation (IR) v0.1¶
Status: Draft Geltungsbereich: IR-Struktur, IR-Builder-Subsystem, Desugaring, Lowering-Grenze
Warum Dieses Dokument Existiert¶
Alle Kernspezifikationen – Constitution, VM Instruction Set, Generator, CompilerDb – referenzieren den IR als zentrale Datenstruktur, ohne seine konkrete Form zu definieren. Dieses Dokument schließt diese Lücke:
- Was ist die Containerstruktur des IR?
- Wie werden SSA-Werte und Kontrollfluss repräsentiert?
- Welches Subsystem erzeugt den IR, und was sind seine Eingaben und Ausgaben?
- Welche JDL-Konstrukte werden wann und wie in IR übersetzt?
- Wo endet die Verantwortung des IR-Builders und wo beginnt Lowering?
0. Priorität¶
Ergänzt:
00–0812-design-constitution.md05-vm-instruction-set.md06-generator-und-code-emission.mdjade-compiler-db-spec.md
Bei Widerspruch gelten die genannten Kernvorgaben. Insbesondere:
- Die Constitution definiert IR-Invarianten, Pass-Contracts und die Core/Extended-Trennung.
- Die VM Instruction Set Spec definiert die Core- und Extended-Instruktionen.
- Die Generator-Spec definiert
LoweredModuleals Ausgabeformat nach Lowering. - Die CompilerDb-Spec definiert den Query-basierten Subsystem-Aufbau.
1. Einordnung in die Pipeline¶
Source
→ Parser → AST
→ Name Resolution (CompilerDb Query, auf AST)
→ Type Checking (CompilerDb Query, auf AST)
→ IR-Builder (eigenes Subsystem, CompilerDb Query)
konsumiert: typisierten AST + Typinfo + Effekte
produziert: IR-Modul mit Extended-Instruktionen
→ Pass-Pipeline (Optimierungen, Analysen — auf IR)
→ Lowering-Pässe (Extended → Core, mechanisch)
→ Verifier (prüft Core-only + alle Invarianten)
→ Generator (read-only Konsument von LoweredModule)
→ VM-Backend → Bytecode/Wordcode
→ Bridge-Backend → FFI-Artefakte
→ (optional) WASM-Backend
→ VM führt aus
Der AST bleibt über die gesamte Pipeline hinweg immutable. Der IR-Builder liest den AST und die Ergebnisse der Type Engine via CompilerDb-Queries und produziert eine neue, eigenständige Repräsentation.
2. IR-Builder als CompilerDb-Subsystem¶
2.1 Rolle¶
Der IR-Builder ist ein eigenständiges Subsystem mit eigenen Query-Handlern in der CompilerDb. Er ist weder Teil des Frontends (das Analyse macht) noch des Generators (der read-only konsumiert).
Seine Verantwortung:
- Übersetzung von JDL-Semantik in IR-Strukturen,
- Auflösung aller syntaktischen und semantischen Zucker (siehe §6),
- Emission von Extended-Instruktionen für hochlevellige Konstrukte,
- Erzeugung expliziter Kontrollfluss-Strukturen (Basic Blocks, Terminatoren).
Nicht seine Verantwortung:
- Typprüfung (abgeschlossen vor dem IR-Builder),
- Semantikprüfung und Effektprüfung (abgeschlossen vor dem IR-Builder),
- Lowering von Extended zu Core (spätere Pipeline-Phase),
- Register-Allokation und Layout-Berechnung (Generator-Aufgabe).
2.2 CompilerDb-Integration¶
Der IR-Builder registriert Query-Handler in der CompilerDb:
// spekulativ — Query-Definitionen
// Baut das IR für ein gesamtes Modul.
// Abhängigkeiten: CheckModule, GetFileAST, Type-Engine-Queries
type BuildModuleIR: struct {
moduleId: ModuleId
// → IrModule
}
// Baut den IR für eine einzelne Funktion.
// Abhängigkeiten: GetFunctionAST, ResolveType, CheckEffects
type BuildFunctionIR: struct {
functionId: FnId
// → IrFunction
}
2.3 Arena-Zuordnung¶
Der IR-Builder besitzt eine eigene Arena gemäß CompilerDb-Architektur. IR-Datenstrukturen werden in dieser Arena allokiert. Die Arena wird zurückgesetzt, wenn die IR-Zwischendarstellung nicht mehr benötigt wird (nach Generator-Emission).
3. SSA-Modell: Block-Argumente¶
3.1 Entscheidung¶
Der IR verwendet Block-Argumente statt Phi-Nodes für die Zusammenführung von SSA-Werten an Kontrollfluss-Merge-Punkten.
3.2 Begründung¶
Block-Argumente reduzieren die Anzahl der Konzepte im IR:
- Kein spezielles Phi-Pseudo-Instruktionskonstrukt nötig.
- Ein Block ist einheitlich: Parameter + Body + Terminator.
- Terminatoren übergeben Werte explizit an Zielblöcke — die Datenfluss-Kante ist am Sprung sichtbar, nicht als implizite Rückreferenz im Zielblock.
- Pass-Transformationen (Block-Splitting, Kanten-Umleitung) sind einfacher, weil nur die Sprungargumente angepasst werden müssen.
Dieses Modell folgt MLIR, Cranelift und Swift SIL.
3.3 Semantik¶
Wenn ein Block Parameter deklariert, muss jeder Terminator der zu diesem Block springt die korrekte Anzahl und Typen von Argumenten übergeben. Die Argumente werden ausgewertet, bevor der Zielblock beginnt — das garantiert die korrekte parallele Semantik bei mehreren Parametern.
Beispiel:
// spekulativ — IR-Textdarstellung
block_then:
%v1 = loadk 1 : i32
jmp block_merge(%v1)
block_else:
%v2 = loadk 2 : i32
jmp block_merge(%v2)
block_merge(%x: i32): // Block-Argument
// %x ist %v1 oder %v2, je nach Herkunft
4. Containerstruktur¶
4.1 Übersicht¶
IrModule
├── Metadaten (Name, Source, Abhängigkeiten)
├── Typ-Definitionen
├── Modul-Konstanten
├── Modul-Initialisierungsfunktion (synthetisch)
└── Funktionen
└── IrFunction
├── Signatur (Parameter, Return-Typ, Effekte)
├── Entry-Block
└── Blöcke
└── IrBlock
├── Block-Parameter (SSA-Werte)
├── Body (geordnete Instruktionsliste)
└── Terminator
4.2 IrModule¶
Ein IR-Modul entspricht einer JDL-Quelldatei. Es ist die Einheit die der IR-Builder als Query-Ergebnis produziert.
// spekulativ
type IrModule: struct {
name: ModulePath // z.B. users::service
functions: [IrFunction] // alle Funktionen inkl. synthetischer
types: [TypeDefRef] // Typ-Definitionen dieses Moduls (Verweis in Type Engine)
constants: [IrConstant] // Modul-Level-Konstanten (compile-time auswertbar)
initFn: Option[IrFunction] // synthetische Init-Funktion für Modul-Level-Ausdrücke
source: SourceFileRef // Rückverweis auf Quelldatei
}
Modul-Initialisierung: JDL erlaubt Modul-Level-Ausdrücke wie
val counter: Atomic[u64] = Atomic.new(0u64). Da solche Ausdrücke
keine Compile-Zeit-Konstanten sind, erzeugt der IR-Builder eine
synthetische Initialisierungsfunktion (__init), die beim Modul-Load
ausgeführt wird. Reine Compile-Zeit-Konstanten landen in constants.
4.3 IrFunction¶
// spekulativ
type IrFunction: struct {
name: SymbolId // Funktionsname (oder synthetischer Name)
params: [IrParam] // Funktionsparameter
returnType: TypeId // Rückgabetyp
effects: EffectSet // deklarierte Effekte
blocks: [IrBlock] // blocks[0] ist immer der Entry-Block
sourceMap: IrSourceMap // Side-Table: IrInstId → SourceLoc
}
type IrParam: struct {
name: SymbolId
type: TypeId
value: IrValueId // der SSA-Wert den dieser Parameter definiert
}
SourceMap als Side-Table: Debug-Informationen werden nicht inline auf
einzelnen Instruktionen gespeichert, sondern als separate Zuordnungstabelle
IrInstId → SourceLoc auf Funktionsebene. Das folgt dem Prinzip der
VM Instruction Set Spec (§5: „Debug-Infos werden als Side-Table
gespeichert") und hält die Instruktionsdatenstruktur kompakt.
Pässe die Instruktionen transformieren müssen die SourceMap
entsprechend aktualisieren.
4.4 IrBlock¶
// spekulativ
type IrBlockId: struct { id: u32 }
type IrBlock: struct {
id: IrBlockId
params: [IrBlockParam] // Block-Argumente (SSA, siehe §3)
body: [IrInst] // geordnete Instruktionsliste
terminator: IrTerminator // genau einer, immer am Ende
}
type IrBlockParam: struct {
value: IrValueId // der SSA-Wert den dieser Parameter definiert
type: TypeId
}
Geordnete Instruktionsliste: Der Body ist eine geordnete Liste, keine Graph-Repräsentation (Sea-of-Nodes). Reine Instruktionen könnten theoretisch in beliebiger Reihenfolge stehen, solange Datenabhängigkeiten respektiert werden, aber eine lineare Liste ist einfacher zu debuggen, zu serialisieren und zu testen. Das ist ein bewusster Tradeoff zugunsten von Verständlichkeit (Constitution §0).
4.5 IrInst¶
// spekulativ
type IrValueId: struct { id: u32 }
type IrInstId: struct { id: u32 }
type IrInst: struct {
id: IrInstId // eindeutige ID für SourceMap-Referenz
result: Option[IrValueId] // None für Instruktionen ohne Ergebnis (z.B. SetField)
op: IrOp // Opcode (Core oder Extended)
operands: [IrOperand] // Eingabewerte
type: TypeId // Ergebnistyp (redundant, aber vereinfacht Verifier/Pässe)
}
TypeId auf Instruktionen: Der Ergebnistyp ist technisch aus den Operanden ableitbar, wird aber explizit gespeichert. Das vermeidet wiederholt Typ-Queries in Pässen und Verifier, auf Kosten von Speicher. In einem Arena-basierten System ist dieser Tradeoff akzeptabel.
4.6 IrOperand¶
// spekulativ
type IrOperand: enum =
| Value { id: IrValueId } // Referenz auf einen SSA-Wert
| Const { ref: ConstRef } // Referenz in den Konstantenpool
| TypeRef { id: TypeId } // Typreferenz (z.B. für NewStruct, CastTo)
| FieldRef { name: SymbolId } // Feldname (z.B. für GetField, SetField)
| BlockRef { id: IrBlockId } // Blockreferenz (nur in Terminatoren)
4.7 IrTerminator¶
Terminatoren beenden einen Block und definieren den Kontrollfluss. Sie sind strukturell von normalen Instruktionen getrennt, damit jeder Block garantiert genau einen hat.
// spekulativ
type IrTerminator: enum =
| Jmp {
target: IrBlockId
args: [IrValueId] // Block-Argumente für den Zielblock
}
| JmpIf {
cond: IrValueId
thenBlock: IrBlockId
thenArgs: [IrValueId]
elseBlock: IrBlockId
elseArgs: [IrValueId]
}
| Return {
value: IrValueId
}
| Trap {
reason: TrapReason
}
| Halt
4.8 IrOp — Core und Extended¶
IrOp bildet die Core/Extended-Trennung der VM Instruction Set Spec ab.
Im IR dürfen beide Kategorien koexistieren. Nach dem Lowering dürfen nur
noch Core-Operationen vorhanden sein.
// spekulativ
type IrOp: enum =
// === Core (VM-Execution-Level) ===
| LoadK // Konstante laden
| LoadBool // Bool-Immediate
| Add | Sub | Mul | Div | Mod // Arithmetik
| Neg // Unäre Negation
| Eq | Lt // Vergleich (Core-Minimum)
| Not // Logische Negation
| Call // Funktionsaufruf
| Closure // Closure erzeugen
| LoadUp | StoreUp | CloseUp // Upvalue-Operationen
| NewStruct // Struct allokieren
| GetField | SetField // Feldzugriff
| NewArray // Array allokieren
| GetIndex | SetIndex // Indexzugriff
| Move // SSA-Wert verschieben, Quelle invalidieren
| Drop // End-of-Lifetime signalisieren
// === Extended (vor Lowering eliminiert) ===
| TailCall // → Call + Return
| Ne | Le | Gt | Ge // → Eq/Lt/Not-Kombinationen
| LoadNull | LoadUnit // → LoadK mit spezieller Konstante
| LoadI8 | LoadI16 | LoadI32 // → LoadK mit Immediate
| NewStructLiteral // Struct mit Feldzuweisungen in einer Op
| Match // Pattern-Match-Konstrukt
| ShortCircuitAnd // → JmpIf-Kette
| ShortCircuitOr // → JmpIf-Kette
| CollectToArray // |> [] Sugar
| CollectToMap // |> {} Sugar
| WithScope // with-Block (acquire/release)
| Interpolate // f"..." String-Interpolation
Die Liste ist nicht abschließend. Plugins dürfen Extended erweitern (Constitution §3.3), aber nicht den Core.
5. Closures im IR¶
5.1 Problem¶
Closures capturen Werte aus umgebenden Scopes. Im IR müssen diese Captures explizit dargestellt werden, da SSA-Werte grundsätzlich nur innerhalb ihrer definierenden Funktion sichtbar sind.
5.2 Darstellung¶
Der IR-Builder erzeugt Closures als eigene IrFunction mit einer
expliziten Capture-Liste. Die Closure-Erzeugung im umgebenden Kontext
wird als Closure-Instruktion emittiert, die die zu capturenden
SSA-Werte referenziert.
// spekulativ
type IrCapture: struct {
outerValue: IrValueId // der SSA-Wert im umgebenden Scope
innerParam: IrValueId // der SSA-Wert innerhalb der Closure
type: TypeId
mode: CaptureMode // ByValue oder ByRef
}
type CaptureMode: enum =
| ByValue // Wert wird kopiert
| ByRef // Referenz auf den Wert (Upvalue)
Im umgebenden Kontext:
In der Closure-Funktion selbst werden Captures als Upvalue-Loads im Entry-Block verfügbar gemacht.
6. Desugaring-Katalog¶
Der IR-Builder löst alle JDL-Zucker auf. Der AST bleibt dabei immutable. Die folgenden Tabellen dokumentieren jedes Desugaring, die benötigte Typinformation und das resultierende IR-Konstrukt.
6.1 Kategorie 0 — Compile-Time-Eliminierung (vor IR-Builder)¶
Diese Transformationen passieren im Frontend, bevor der IR-Builder läuft.
| Konstrukt | Transformation | Phase |
|---|---|---|
@cfg(predicate) |
AST-Pruning gegen Build-Kontext | Nach Parse, vor Name Resolution |
@assert(pred, msg?) |
Compile-Error wenn Predicate false | Nach Parse (early) oder nach Typecheck (late) |
6.2 Kategorie 1 — Syntaktisch (keine Typinfo nötig, trotzdem im IR-Builder)¶
Diese Transformationen könnten theoretisch auf dem AST laufen, werden aber der Einfachheit halber im IR-Builder erledigt. Das hält den AST immutable und vermeidet einen separaten Rewrite-Pass.
| Konstrukt | JDL | IR | Anmerkung |
|---|---|---|---|
| Short-Circuit And | a and b |
JmpIf auf a, dann b oder false |
VM-Spec: Extended, über JmpIf/Jmp |
| Short-Circuit Or | a or b |
JmpIf auf a, dann true oder b |
VM-Spec: Extended, über JmpIf/Jmp |
| Pipeline | a \|> f |
call @f(%a) |
Rein syntaktische Umordnung |
| Pipeline mit Args | a \|> f(x) |
call @f(%a, %x) |
Rein syntaktische Umordnung |
| String-Interpolation | f"Hallo {name}" |
Extended Interpolate oder call @str.concat Kette |
Kann als Extended erhalten bleiben |
6.3 Kategorie 2 — Semantisch (benötigt Typinfo, muss im IR-Builder geschehen)¶
| Konstrukt | JDL | IR | Benötigte Typinfo |
|---|---|---|---|
| Result-Propagation | val x =? f() |
call + isErr + JmpIf → Error-Pfad mit CastTo, Ok-Pfad mit unwrapOk |
Return-Typ der Fn, CastTo-Instanz |
| Optional Chaining | x?.field |
match auf Option: Some → GetField + Some, None → None |
Typ ist Option[T], Feldtyp |
| Nil-Coalescing | x ?? default |
match auf Option: Some(v) → v, None → default |
Typ des Option |
with-Block |
with expr as name { body } |
call @acquire + Body-Blöcke + call @release (auch bei Error-Pfaden) |
Scopeable[R]-Instanz |
| Scope-Cleanup | implizites Drop am Scope-Ende | Drop-Instruktionen für Disposable-Werte |
Drop-Policy des Typs |
| Operator-Aufruf | a + b |
call @Add.add(%a, %b) |
Protocol-Instanz Add für Typ von a |
| Span-Operator | a..b |
call @Spannable.span(%a, %b) |
Spannable[T]-Instanz |
| Implizite Konversion | kontextabhängig | call @CastTo.castTo(%x) |
CastTo[T]: Lossless-Instanz |
| Pattern Match | match expr { \| Arm => ... } |
Extended Match oder Baum aus Eq/JmpIf + Destructuring |
Typ des Scrutinee, Enum-Varianten |
| Collect-Sugar | \|> [] |
Extended CollectToArray oder call @Collect.collect |
Collect-Instanz |
| Map-Collect | \|> {} |
Extended CollectToMap oder call @Collect.collect |
Collect-Instanz |
| Default-Parameter | f(x) mit fehlendem Arg |
Fehlende Args werden mit Default-Werten eingesetzt | Default-Wert aus Signatur |
| Protocol als Param | def f(g: Generator[T]) |
Monomorphisierung: konkreter Typ statt Protocol | Concrete-Type-Resolution |
| Derive | :> Derive([Add]) |
Synthetische IrFunction für generierte provide-Blöcke |
Struct-Felder, Protocol-Anforderungen |
7. Lowering: Extended → Core¶
7.1 Abgrenzung zum IR-Builder¶
| Phase | Verantwortung | Art der Arbeit |
|---|---|---|
| IR-Builder | JDL-Semantik → IR | Semantische Übersetzung. Braucht Typinfo, Effekte, Protocol-Instanzen. Emittiert Extended. |
| Lowering | Extended → Core | Mechanische Transformation. Braucht keine JDL-Semantik, nur die Kenntnis wie eine Extended-Op in Core zerfällt. |
7.2 Lowering-Patterns¶
Die folgende Liste definiert, wie Extended-Instruktionen zu Core-Sequenzen transformiert werden. Jedes Pattern ist eine rein mechanische Ersetzung.
Arithmetik/Vergleich:
| Extended | Core-Expansion |
|---|---|
Ne dst, a, b |
Eq tmp, a, b → Not dst, tmp |
Le dst, a, b |
Lt tmp, b, a → Not dst, tmp |
Gt dst, a, b |
Lt dst, b, a |
Ge dst, a, b |
Lt tmp, a, b → Not dst, tmp |
Kontrollfluss:
| Extended | Core-Expansion |
|---|---|
TailCall dst, func, args |
Call dst, func, args → Return dst (oder VM-Optimierung) |
ShortCircuitAnd a, b → dst |
JmpIf a, L_eval_b else dst = false; Jmp L_end / L_eval_b: dst = b / L_end |
ShortCircuitOr a, b → dst |
JmpIf a, L_true else dst = b; Jmp L_end / L_true: dst = true / L_end |
Aggregates:
| Extended | Core-Expansion |
|---|---|
NewStructLiteral T { f1: v1, f2: v2 } |
NewStruct dst, T → SetField dst, f1, v1 → SetField dst, f2, v2 |
Cancellation-aware Scheduling / Messaging:
| Konstrukt | Lowering-Hinweis |
|---|---|
task.await() |
normaler Call auf den Stdlib-Wrapper, der intern jade::vm::await(Some(handle)) aufruft und das rohe AwaitOutcome auf TaskOutcome[T] abbildet |
yield() |
normaler Call auf den Stdlib-Wrapper, der intern jade::vm::await(None) aufruft und Yielded bzw. Cancelled behandelt |
receiver.recv() |
normaler Call auf den Stdlib-Wrapper, der intern jade::vm::receive() aufruft und rohe VM-Outcomes (Message / Cancelled) in Channel-Semantik (Message / Closed / Cancelled) ueberfuehrt |
Normativ: Cancellation fuehrt keine neue Core-Instruktion ein. Scheduling-/Messaging-Cancellation bleibt eine Eigenschaft der Runtime-Intrinsics und ihrer Stdlib-Wrapper. Das respektiert die Constitution (Core klein halten, High-Level-Politik nicht in die VM verlagern).
Pattern Match (informativ):
Match auf einem Enum mit N Varianten kann gesenkt werden zu:
- einer Kette von
Eq+JmpIffür kleine N, - einer Jump-Table-Instruktion für große N (falls im Core vorhanden),
- einem Baum aus binären Vergleichen für sortierbare Werte.
Die Wahl der Strategie obliegt dem Lowering-Pass und kann heuristisch oder profilgesteuert erfolgen.
7.3 Invariante nach Lowering¶
Nach Abschluss aller Lowering-Pässe gilt:
- Kein
IrInstenthält einenIrOpaus der Extended-Kategorie. - Alle Block-Argumente sind korrekt (Typ und Anzahl stimmen an jedem Sprung).
- Alle SSA-Invarianten gelten (jeder Wert wird genau einmal definiert).
Der Verifier prüft diese Invarianten vor der Übergabe an den Generator.
8. Beziehung zu Typen, Effekten und Policies¶
8.1 Grundprinzip¶
Typ-, Effekt- und Policy-Informationen werden nicht redundant im IR dupliziert. Der IR referenziert sie über IDs in die Type Engine der CompilerDb.
TypeIdverweist auf vollständige Typinformation in der Type Engine.EffectSetist eine Menge von Effekt-Deklarationen auf Funktionsebene.- Policies (Memory, Own, Share, etc.) sind Bestandteil des
TypeIdund werden über Type-Engine-Queries aufgelöst.
8.2 Konsequenz für Pässe¶
Pässe die Typinformation benötigen (z.B. für typabhängige Optimierungen) fragen die CompilerDb. Da Typ-Queries gecacht sind, ist der Overhead minimal. Der IR selbst bleibt schlank.
Ausnahme: Der Ergebnistyp (TypeId) auf jeder IrInst ist bewusst
redundant gespeichert (siehe §4.5), um den häufigsten Query-Fall
zu vermeiden.
9. IR-Textdarstellung (informativ)¶
Die IR hat keine normative Textform. Für Debugging, Tests und Dokumentation wird die folgende informelle Notation verwendet:
fn @functionName(%param0: Type0, %param1: Type1) -> ReturnType
effects: [Effect1, Effect2]
{
entry:
%v0 = loadk 42 : i32
%v1 = call @someFunction(%param0, %v0) : str
jmpif %cond, then_block(%v1), else_block()
then_block(%x: str):
%v2 = getfield %x, name : str
jmp merge(%v2)
else_block:
%v3 = loadk "default" : str
jmp merge(%v3)
merge(%result: str):
return %result
}
Konventionen:
%namekennzeichnet SSA-Werte.@namekennzeichnet globale Symbole (Funktionen, Typen).: Typenach einer Instruktion ist der Ergebnistyp.- Terminatoren stehen am Ende jedes Blocks, ohne
%result =. - Block-Argumente stehen in Klammern nach dem Blocknamen.
- Die Textform ist kein Bestandteil des JDL-Sprachstandards.
10. Vollständiges Beispiel¶
10.1 JDL-Quellcode¶
Aus Spec 03 — registerUser (vereinfacht):
def registerUser(name: str, email: str, age: i32, role: Role = Role.User)
-> Result[UserResponse, AppError] {
val validEmail =? validateEmail(email)
val user = User {
id: UserId(f"u-{Instant.now().millis()}")
name: name
email: validEmail
age: age
role: role
}
val saved =? UserDb.insertUser(user)
LogService.info(f"Registriert: {saved.name} <{saved.email}>")
match saved.role {
| Role.Admin => LogService.warn(f"Admin erstellt: {saved.name}")
| _ => ()
}
Ok(saved)
}
10.2 IR-Dump (spekulativ)¶
fn @registerUser(%name: str, %email: str, %age: i32, %role: Role)
-> Result[UserResponse, AppError]
effects: [UserDb, LogService]
{
entry:
// =? desugared: validateEmail aufrufen, Result prüfen
%result0 = call @validateEmail(%email) : Result[str, UserError]
%is_err0 = call @Result.isErr(%result0) : bool
jmpif %is_err0, err_prop0(), ok0()
ok0:
%validEmail = call @Result.unwrapOk(%result0) : str
// Struct-Literal (Extended: NewStructLiteral)
%now = call @Instant.now() : Instant
%millis = call @Instant.millis(%now) : i64
%millis_str = call @i64.toString(%millis) : str
%id_inner = interpolate "u-", %millis_str : str
%userId = newstructliteral UserId { value: %id_inner } : UserId
%user = newstructliteral User {
id: %userId, name: %name,
email: %validEmail, age: %age, role: %role
} : User
// =? desugared: UserDb.insertUser aufrufen
%result1 = call @UserDb.insertUser(%user) : Result[User, UserError]
%is_err1 = call @Result.isErr(%result1) : bool
jmpif %is_err1, err_prop1(), ok1()
err_prop0:
// =? Error-Pfad: UserError → AppError via CastTo
%err0 = call @Result.unwrapErr(%result0) : UserError
%appErr0 = call @CastTo.castTo[AppError](%err0) : AppError
%errResult0 = call @Result.Err(%appErr0) : Result[UserResponse, AppError]
return %errResult0
err_prop1:
%err1 = call @Result.unwrapErr(%result1) : UserError
%appErr1 = call @CastTo.castTo[AppError](%err1) : AppError
%errResult1 = call @Result.Err(%appErr1) : Result[UserResponse, AppError]
return %errResult1
ok1:
%saved = call @Result.unwrapOk(%result1) : User
// String-Interpolation (Extended: Interpolate)
%sName = getfield %saved, name : str
%sEmail = getfield %saved, email : str
%logMsg = interpolate "Registriert: ", %sName, " <", %sEmail, ">" : str
%_log0 = call @LogService.info(%logMsg) : ()
// Pattern Match (Extended: Match)
%sRole = getfield %saved, role : Role
match %sRole {
Role.Admin => match_admin()
_ => match_done()
}
match_admin:
%warnMsg = interpolate "Admin erstellt: ", %sName : str
%_log1 = call @LogService.warn(%warnMsg) : ()
jmp match_done()
match_done:
// Implizite Konversion: CastTo[UserResponse] for User
%response = call @CastTo.castTo[UserResponse](%saved) : UserResponse
%okResult = call @Result.Ok(%response) : Result[UserResponse, AppError]
return %okResult
}
10.3 Beobachtungen¶
=?ist zu explizitem Kontrollfluss mit Error-Pfad undCastTo-Aufruf aufgelöst.- Struct-Literale verwenden die Extended-Instruktion
NewStructLiteral, die im Lowering zuNewStruct+ N ×SetFieldexpandiert wird. - String-Interpolation verwendet die Extended-Instruktion
Interpolate. matchverwendet die Extended-InstruktionMatch, die im Lowering zuEq/JmpIf-Ketten expandiert wird.- Implizite Konversionen sind explizite
CastTo-Aufrufe — keine Magie. - Service-Calls sind normale
call-Instruktionen; der Dispatch-Mechanismus ist Sache der VM/Runtime, nicht des IR. - Block-Argumente kommen in diesem Beispiel nicht vor, da kein Kontrollfluss-Merge Werte zusammenführen muss.
11. IR-Invarianten¶
Die Constitution (§4.2) definiert IR-Invarianten. Dieses Dokument ergänzt ihre Bedeutung im Kontext der konkreten IR-Struktur:
| Invariante | Bedeutung für die IR-Struktur |
|---|---|
SSAForm |
Jeder IrValueId wird genau einmal definiert (als Block-Parameter oder Instruktions-Result). Verwendungen referenzieren existierende Definitionen. |
DominatorsValid |
Der CFG (definiert durch Terminatoren) hat einen gültigen Dominator-Baum. |
TypesResolved |
Jeder TypeId im IR verweist auf einen vollständig aufgelösten Typ in der Type Engine. |
PoliciesConsistent |
Policies (via TypeId) widersprechen sich nicht. Z.B. kein Move auf share: Sync-Werte. |
EffectsNormalized |
Effekte in EffectSet sind in normalisierter Form. |
NoDeadBlocks |
Jeder Block ist vom Entry-Block aus erreichbar. |
ExtendedEliminated |
Nach Lowering: kein IrOp aus der Extended-Kategorie mehr vorhanden. |
ExtendedEliminated ist eine neue Invariante die der Verifier nach dem
Lowering prüft, bevor der Generator das IR konsumiert.
12. Offene Punkte¶
12.1 Closure-Capture-Analyse¶
Die genaue Strategie für die Closure-Capture-Analyse (ByValue vs. ByRef, Upvalue-Flattening, Capture-Optimierungen) ist noch nicht spezifiziert. §5 definiert die Datenstruktur, nicht die Analysestrategie.
12.2 IR-Textform¶
Die informelle Notation in §9 ist weder normativ noch stabil. Eine formale, parsebare Textform wäre für Tests und Debugging wertvoll, ist aber kein MVP-Blocker. Die Constitution erlaubt das als spätere Ergänzung (§1.2: IR ist zunächst ein Implementierungsdetail).
12.3 Generics und Monomorphisierung¶
Wie generische Funktionen im IR dargestellt werden — als generisches Template das bei Instanziierung kopiert wird, oder als bereits monomorphisierte Instanzen — ist noch offen. Die Entscheidung hängt von der Monomorphisierungs-Strategie der Type Engine ab.
12.4 CallGraph und andere deklarative Runtime-Konstrukte¶
CallGraph, Actor-Patterns und weitere deklarative Runtime-Konstrukte
(Spec 01, 03, 07) erzeugen komplexere IR-Strukturen (Node-Graphen,
Receive-Loops, eventuelle Scheduling-/Reactive-Baeume). Deren genaue
IR-Darstellung ist noch nicht spezifiziert.
Bereits entschieden: Auch diese Konstrukte muessen Scheduling- und Messaging-Cancellation ueber dieselben Runtime-Intrinsics modellieren; sie erhalten keine eigene, abweichende Cancellation-Welt im IR.
12.5 IrOp-Vollständigkeit¶
Die IrOp-Enumeration in §4.8 ist nicht abschließend. Neue
Extended-Operationen können hinzukommen, insbesondere für Plugins
(Constitution §3.3) und zukünftige Sprachfeatures.
Änderungsprotokoll¶
Version 0.1 — Initiale Definition. Containerstruktur, SSA-Modell mit Block-Argumenten, IR-Builder als CompilerDb-Subsystem, Desugaring-Katalog, Lowering-Grenze, Closure-Darstellung, Invarianten.