Zum Inhalt

JadeValue und Register-Layout

Status: Normativ (v0.1)
Geltungsbereich: VM-Implementierung (D), Generator, Calling Convention
Abhängigkeiten: 05-vm-instruction-set.md, 00-refinements.md, 08-intrinsics-vm-api.md


Präambel

JadeValue ist der uniforme Transportcontainer der Jade VM an Systemgrenzen. Er ist bewusst schmal definiert: er existiert nicht als universeller Wert-Container für alle Werte zur Laufzeit, sondern nur dort, wo statische Typinformation nicht direkt verfügbar ist — an Call-, Return-, Intrinsic- und FFI-Grenzen.

Dieses Dokument klärt:

  • was JadeValue ist und warum es existiert,
  • wo es auftaucht und wo es explizit nicht auftaucht,
  • wie das Register-Layout einer Funktion aufgebaut ist,
  • wie memory: Value-Structs behandelt werden.


Geltungsbereich: Diese Spezifikation beschreibt das Register- und Grenzformat regulärer VM-Werte an Call-, Return-, Intrinsic- und FFI-Übergängen. Dynamische Protocol-Werte (dyn P, Existential Container) fallen nicht unter dieses Modell — sie tragen zwingend Runtime-Typinformation und liegen semantisch oberhalb von JadeValue. Sie werden separat spezifiziert (siehe dispatch-modes.md § 6).


Warum

JadeValue trägt kein Laufzeit-Tag (Invariante §2). dyn P erfordert zwingend Runtime-Typinformation — das ist der fundamentale Widerspruch. Ohne diesen Scope-Hinweis könnte ein späterer Leser fälschlich annehmen, dyn P-Werte seien einfach ein erweitertes JadeValue. Das wäre falsch.

1. Warum JadeValue existiert

JDL ist statisch typisiert. Der Compiler kennt zur Compile-Zeit den exakten Typ jedes Wertes an jeder Stelle im Programm. Innerhalb einer Funktion ist JadeValue daher nicht nötig — der Generator legt das Register-Layout der Funktion anhand der bekannten Typen und Größen fest.

An Grenzen jedoch — beim Aufruf einer Funktion, bei der Rückgabe eines Wertes, beim Übergang in Intrinsics oder FFI — wird ein uniformer Container benötigt, den die VM ohne Kenntnis der konkreten Signatur übergeben kann.

JadeValue ist dieser Container. Er ist der Übergabevertrag zwischen Frames.


2. Interne Struktur

Da der Compiler den Typ eines Wertes statisch kennt, benötigt JadeValue zur Laufzeit kein Typ-Tag. Die VM weiß aufgrund des generierten Bytecodes immer, wie ein JadeValue zu interpretieren ist.

JadeValue ist ein ungetaggter Union in D:

// D-Implementierung (normativ für Struktur, nicht für Syntax)
union JadeValue {
    ulong    immediate; // primitive Immediates: i64, u64, f64, bool, ...
    HandleId handle;    // { index: u32, generation: u32 }
}

Primitive Immediates werden als rohe Bits im ulong-Slot gespeichert:

  • bool0 (false) oder 1 (true)
  • i8..i64 → direkt als Integer-Wert
  • u8..u64 → direkt als Integer-Wert
  • f32, f64 → Reinterpret-Cast der Bit-Repräsentation (keine Wertkonversion)

Handles (memory: Ref, memory: Arena[A], memory: Pool[P]) werden als HandleId gespeichert, der in die Handle-Tabelle des JME zeigt.

Invariante

Ein JadeValue trägt immer genau einen der zwei Fälle. Welcher vorliegt, ist durch den Kontext im generierten Bytecode statisch bekannt. Die VM führt keine Laufzeit-Unterscheidung durch.


3. Wo JadeValue auftaucht

JadeValue taucht ausschließlich an Grenzen auf:

Kontext Beschreibung
Return src Rückgabewert eines Frames an den Caller
Call { dst, func, argsBase, argc } Argumente werden aus Registern in JadeValue-Slots gepackt
Intrinsic-Boundary Ein- und Ausgabe von INTRINSIC-Opcodes
FFI-Boundary Übergabe an und von der Jade Value Bridge

Was JadeValue nicht ist

JadeValue ist kein universeller Slot für alle Werte im Register-File einer Funktion. Innerhalb einer Funktion arbeitet die VM direkt auf dem typsicher angelegten Register-Buffer — ohne JadeValue-Boxing.


4. Register-Layout

Jede Funktion besitzt eine Registerdatei R0..R(n-1). Der Generator legt das Layout dieser Registerdatei als flachen Byte-Buffer an, in dem jeder Register-Slot seine compile-time bekannte Größe und seinen Offset hat.

Beispiel-Funktion:
  R0: i64       → offset  0, size 8
  R1: bool      → offset  8, size 1
  R2: Vec3      → offset  9, size 12   (memory: Value, 3× f32)
  R3: HandleId  → offset 21, size 8    (memory: Ref)
  ...

Dieses Layout wird vom Generator aus den Type-Engine-Metadaten (MemLayoutQueryApi) zur Compile-Zeit berechnet und im Function Prototype (Proto) gespeichert. Die VM allokiert beim Frame-Aufbau den entsprechenden Buffer in einem Stück.

Konsequenz: Kein uniformer JadeValue-Slot pro Register. Kein Boxing von Primitiven. Kein Overhead.


5. Value-Structs und Register-Layout

memory: Value gilt nicht nur für primitive Skalare, sondern auch für Structs:

type Vec3: struct {
    x: f32
    y: f32
    z: f32
} :> <{ memory: Value }>

Ein solcher Wert ist 96 Bit groß und passt nicht in einen einzelnen ulong-Immediate. Der Generator behandelt ihn im Register-Buffer als zusammenhängenden Speicherbereich mit compile-time bekanntem Offset und Größe.

An Grenzen (Return, Call, Intrinsic) wird ein Value-Struct als einzelner JadeValue transportiert, dessen immediate-Slot die Bits des Structs trägt — sofern er ≤ 64 Bit ist. Für größere Value-Structs gilt:

  • Der Generator emittiert mehrere aufeinanderfolgende Register-Operationen.
  • Die Calling Convention behandelt große Value-Structs als indirekte Übergabe (Pointer auf den Register-Buffer-Bereich). Details sind Implementierungswahl des Generators, müssen aber konsistent sein.

Offener Punkt: Die genaue ABI für Value-Structs > 64 Bit an Funktionsgrenzen ist noch nicht normativ festgelegt. Der Generator kann hier eine Implementierungsentscheidung treffen, die später in der Calling-Convention-Spec (05-modulsystem-und-ffi.md, Phase 2) fixiert wird.


6. Zusammenhang mit Memory Policies

Memory Policy Im Register-Buffer Als JadeValue
memory: Value (primitiv) Direkte Bytes im Buffer immediate-Slot (Bit-Cast)
memory: Value (Struct ≤ 64 Bit) Direkte Bytes im Buffer immediate-Slot
memory: Value (Struct > 64 Bit) Direkte Bytes im Buffer Indirekte Übergabe (TBD)
memory: Ref HandleId im Buffer handle-Slot
memory: Arena[A] HandleId im Buffer handle-Slot
memory: Pool[P] HandleId im Buffer handle-Slot
dyn P HandleId im Buffer (zeigt auf heap-allozierten DynValue) handle-Slot

DynValue ist kein regulärer Memory-Policy-Typ, sondern eine VM-interne Struktur die vom JME verwaltet wird. Siehe dispatch-modes.md §6.3 für die normative Definition.


7. Beziehung zum Generator

Der Generator (06-generator-und-code-emission.md) ist für das konkrete Register-Layout verantwortlich. Er konsultiert die MemLayoutQueryApi der Type Engine für:

  • Größe (size) und Alignment (align) jedes Typs,
  • Feld-Offsets für Structs,
  • Policy-Informationen (memory:) zur Entscheidung Handle vs. Immediate.

Das generierte Register-Layout ist Teil des Function Prototype (Proto) und wird vom Verifier auf Konsistenz geprüft.


8. Beziehung zur Handle-Tabelle

JadeValue.handle ist ein HandleId { index: u32, generation: u32 }. Der Generation-Counter schützt vor Use-After-Free bei Slot-Wiederverwendung (ABA-Problem). Die VM prüft bei jedem Handle-Zugriff:

slot = handleTable[id.index]
if slot.generation != id.generation:
    Trap(InvalidHandle)

JadeValue selbst trägt keine Sicherheitsgarantie — die Sicherheit kommt vom JME und seiner Handle-Tabelle. JadeValue ist ein neutraler Transportcontainer.


9. Normative Invarianten

  1. JadeValue taucht im Bytecode nur an Call-, Return-, Intrinsic- und FFI-Grenzen auf.
  2. Innerhalb einer Funktion existieren keine JadeValue-Slots — nur ein typsicher angelegter Register-Buffer.
  3. JadeValue trägt kein Laufzeit-Tag. Die Interpretation ist durch den Bytecode-Kontext statisch bekannt.
  4. Das Register-Layout einer Funktion ist compile-time konstant und im Proto gespeichert.
  5. HandleId-Werte in JadeValue müssen vor Zugriff gegen die Handle-Tabelle validiert werden (Generation-Check).

Änderungsprotokoll

Version 0.1 — Initiale Definition. JadeValue als Transportcontainer, Register-Layout als flacher Byte-Buffer, Abgrenzung Value-Struct vs. Primitive. Calling Convention für Value-Structs > 64 Bit als offener Punkt markiert.