Zum Inhalt

JDL — Intrinsic API: Die stabile Schnittstelle zwischen JDL und der Jade VM

Status: Normativ
Geltungsbereich: VM-Implementierung (D), rohe Runtime-Brücke in JDL (jade::vm) und darauf aufbauende Stdlib-Frontends (jdl::...)
Abhängigkeiten: 07-cast-effekte-refinements.md, 12-design-constitution.md, jade-compiler-db-spec.md


Präambel

Die Intrinsic API ist der einzige Berührungspunkt zwischen der in D implementierten Jade VM und dem in JDL geschriebenen Rest des Systems. Sie ist eine stabile, versionierte Grenze – solange diese 23 Funktionen ihre Semantik behalten, kann alles darüber – die Stdlib, die Effect-Engine, Aktoren, async, Signal/Slot – ohne D-Änderungen weiterentwickelt werden.

Das ist kein Detail. Es ist das Fundament auf dem die Selbst-Hosting-Vision steht: Lexer, Parser, Teile des Compilers können irgendwann in JDL geschrieben werden, weil die VM ihnen über diese API alle notwendigen Fähigkeiten bereitstellt.

Jeder Intrinsic wird in D mit dem @intrinsic-Mechanismus implementiert und vom Compiler zur Compile-Zeit durch einen INTRINSIC subcode, a, b, c Opcode ersetzt. Kein CallFrame-Overhead, kein Stack-Aufbau, direkte Dispatch-Table-Auflösung in der VM-Loop.

In JDL sind Runtime-Intrinsics gewöhnliche Funktionsdeklarationen im jade::vm-Namespace mit der @intrinsic-Annotation. Sie bilden die rohe Runtime-Brücke. User-facing APIs leben darüber in jdl::task, jdl::signal, jdl::messaging und verwandten Stdlib-Modulen.


RuntimeBus — Das Command-System

Symmetrie zu CompilerDb

Die Jade-Architektur kennt zwei zentrale Kommunikationsschichten:

CompilerDb   → Queries   → Compile-Zeit, read-only, deterministisch, gecacht
RuntimeBus   → Commands  → Laufzeit, side-effects, reihenfolgenabhängig, nicht gecacht

CompilerDb ist das Rückgrat der Compiler-Pipeline. Alle Subsystem-Kommunikation zur Compile-Zeit läuft über typisierte Queries mit automatischem Caching und Dependency-Tracking.

RuntimeBus ist das Laufzeit-Pendant. Alle Intrinsics die Zustand verändern – Handles pinnen, Tasks spawnen, Arenas öffnen, Nachrichten senden – sind Commands die über den RuntimeBus geroutet werden. Kein Subsystem greift direkt auf ein anderes zu.

Command-Kontrakt

Query:   deterministisch, seiteneffektfrei, cachebar  → CompilerDb
Command: side-effect, reihenfolgenabhängig, nicht cachebar → RuntimeBus

Das Kommunikationsformat ist symmetrisch – typisierte Structs, zentrales Dispatch, kapselierte Subsysteme:

// D-Implementierung — RuntimeBus (normativ für Struktur)
enum CmdDomain : ubyte { JME, Scheduler, Storage, Signal, FFI, Messaging }

struct RuntimeBus {
    JmePort       jme;
    SchedulerPort scheduler;
    StoragePort   storage;
    SignalPort    signal;
    FfiPort       ffi;
    MessagingPort messaging;

    auto cmd(C)(ref C c) @nogc nothrow
        if (is(typeof(C.domain)) && is(typeof(C.Return)))
    {
        static if (C.domain == CmdDomain.JME)       return jme.exec(c);
        static if (C.domain == CmdDomain.Scheduler)  return scheduler.exec(c);
        static if (C.domain == CmdDomain.Storage)    return storage.exec(c);
        static if (C.domain == CmdDomain.Signal)     return signal.exec(c);
        static if (C.domain == CmdDomain.FFI)        return ffi.exec(c);
        static if (C.domain == CmdDomain.Messaging)  return messaging.exec(c);
        static assert(0, "Unhandled command domain");
    }
}

Jede D-Implementierung eines Intrinsics in diesem Dokument delegiert über rt.cmd(...) – nie über Direktzugriffe auf Subsystem-Interna.

Verhältnis zu Intrinsics

Intrinsics sind die JDL-seitige Schnittstelle. Commands sind die D-seitige Implementierung dahinter:

JDL-Code
    ↓  jade::vm::pin(handle)
INTRINSIC-Opcode
    ↓  VM-Loop dispatch
D-Implementierung
    ↓  rt.cmd(JmeRetain { handle })
RuntimeBus
    ↓  jme.exec(...)
JmePort

Gruppe 1 – Handle Lifetime

Motivation und Memory-Modell

Jade hat keinen Garbage Collector. Der JME verwaltet Speicher über Refcounting kombiniert mit Generation-Countern für Use-After-Free-Schutz. Jeder Handle trägt einen {index, generation}-Identifier – wenn ein Handle-Slot freigegeben und wiederverwendet wird, wird der Generation-Counter inkrementiert. Stale Handles werden erkannt und trappen anstatt silent auf falschen Speicher zuzugreifen.

Jeder JDL-Typ hat eine von vier Allokationsstrategien die der Compiler automatisch ableitet:

  • memory: Value – inline im Register, kein Handle, kein Refcount, kein JME
  • memory: Ref – Heap-Allokation, Handle, Refcount-verwaltet
  • memory: Arena[A] – Bump-Allokation in einer Arena, Bulk-Reset am Scope-Ende
  • memory: Pool[P] – Slab-Allokator, Wiederverwendung von Slots

Ref-Handles droppen automatisch wenn ihr Refcount auf 0 fällt. Arena-Handles werden beim with-Block-Ende en-bloc zurückgesetzt – ein einziger Pointer-Reset, kein Traversal. Pool-Handles kehren in den Pool zurück.

Refcount-Zyklen und own: Weak:
Zwei own: Shared-Werte die sich gegenseitig referenzieren würden ihren Refcount nie auf 0 bringen – das ist ein Leak. Das JME erkennt Zyklen nicht automatisch. Die Lösung: own: Weak – eine nicht-besitzende Referenz die keinen Refcount-Beitrag leistet. Zugriff erfolgt über upgrade() -> Option[T] – gibt None zurück wenn der referenzierte Wert bereits gedropt wurde:

// Baum mit Parent-Pointer – Parent hält strong ref auf Children,
// Children halten weak ref zurück auf Parent → kein Zyklus
type TreeNode: struct {
    value:    i32
    children: [TreeNode]         // own: Shared
    parent:   Weak[TreeNode]     // own: Weak – kein Refcount-Beitrag
}

Es gibt jedoch Situationen wo kein JDL-seitiger Besitzer mehr existiert, ein Handle aber trotzdem leben muss – globale Registries, Singletons, die Signal-Subscription-Tabelle. jade::vm::pin hält den Refcount künstlich > 0 solange der Handle extern gebraucht wird.

Zwei-Welten-Grenze: JDL-Welt (JME/RC) und D-Welt (D GC) sind strikt getrennt. Werte die die Grenze überqueren werden kopiert, nie als Pointer geteilt. Handles existieren ausschließlich in der JDL-Welt.


jade::vm::pin(handle: Handle) -> ()

Semantik: Inkrementiert den Refcount des Handles um 1 auf VM-Ebene – außerhalb des normalen JDL-Ownership-Systems. Der Handle wird nicht gedropt solange dieser Beitrag existiert. Semantisch: "die VM übernimmt Mitbesitz dieses Handles."

D-Implementierung:

struct JmeRetain {
    enum domain = CmdDomain.JME;
    alias Return = void;
    HandleId handle;
}

// VM-Loop:
void execVmPin(HandleId handle) @nogc nothrow {
    auto c = JmeRetain { handle: handle };
    rt.cmd(c);
}

JmePort.exec(JmeRetain) inkrementiert den RC-Slot deterministisch. Der Generation-Counter bleibt unverändert.

JDL-Implementierung (Stdlib):
Nicht für Anwendungsentwickler. Die Stdlib nutzt es intern:

// Signal-Registry – pinnt Handler-Handles damit sie nicht droppen
// während sie registriert sind
provide SignalRegistry {
    def register(self, signal: Handle, handler: Handle) -> () {
        jade::vm::pin(handler)
        self.table.insert(signal, handler)
    }
}

Ermöglicht: Singletons, globale Service-Registries, die Signal-Subscription-Tabelle, VM-interne Handle-Tabellen, gepinnte Konfigurationsobjekte.


jade::vm::unpin(handle: Handle) -> ()

Semantik: Dekrementiert den VM-seitigen Refcount-Beitrag. Wenn dadurch der Gesamtrefcount auf 0 fällt, führt der JME den Drop-Destruktor des Typs aus, gibt den Speicher frei, und inkrementiert den Generation-Counter des Slots – alle bisherigen Handles auf diesen Slot werden damit zu stale Handles die beim nächsten Zugriff trappen.

D-Implementierung:

struct JmeRelease {
    enum domain = CmdDomain.JME;
    alias Return = void;
    HandleId handle;
}

// VM-Loop:
void execVmUnpin(HandleId handle) @nogc nothrow {
    auto c = JmeRelease { handle: handle };
    rt.cmd(c);
}

JmePort.exec(JmeRelease) dekrementiert den RC-Slot deterministisch – kein Defer, kein Zyklus-Collector, sofortiger Drop wenn Count 0.

Wichtig: jade::vm::unpin ohne vorheriges jade::vm::pin ist ein VM-Trap. Pin/Unpin müssen strikt symmetrisch aufgerufen werden. Refcount-Zyklen zwischen gepinnten Handles sind Leaks – der JME erkennt sie nicht automatisch.

Ermöglicht: Kontrolliertes Cleanup globaler Ressourcen, Hot-Swap registrierter Services, deterministisches Shutdown, Deregistrierung von Signal-Handlern.


Gruppe 2 – Scheduling

Motivation

Alle Formen von Nebenläufigkeit in JDL – async/await, Tasks, Coroutines, Aktoren, Generators – bauen auf einer kleinen Scheduling-Gruppe auf. Der Scheduler selbst ist D-Code, aber seine Steuerung ist vollständig durch diese Intrinsics ausdrückbar. Das bedeutet: die Stdlib kann eigene Scheduling-Strategien implementieren ohne D anzufassen.

Cancellation ist dabei kooperativ, nicht präemptiv: laufender Code wird nicht an beliebigen Instruktionsgrenzen abgebrochen. Stattdessen markiert die VM eine Task als cancel_requested; beobachtbar wird das an expliziten Checkpoints wie jade::vm::await(...), jade::vm::receive() und über jade::vm::cancelled().


jade::vm::spawn(fn: Handle, args: Handle, mailbox: MailboxConfig) -> Handle

Semantik: Erzeugt eine neue leichtgewichtige Task die vom Scheduler verwaltet wird. fn ist ein Handle auf eine JDL-Funktion, args ein Handle auf ein Array von Argumenten. Die Task wird in die Scheduler-Queue eingereiht – sie beginnt nicht sofort zu laufen.

mailbox konfiguriert die Mailbox der neuen Task. Wird kein expliziter Wert übergeben, gilt MailboxConfig.default.

type MailboxPolicy: enum = | Block | Trap | DropOldest | DropNewest

type MailboxConfig: struct {
    size:   u32           // maximale Anzahl Nachrichten in der Queue
    policy: MailboxPolicy // Verhalten wenn Mailbox voll
}

provide MailboxConfig {
    def default() -> MailboxConfig = MailboxConfig { size: 256, policy: Block }
}

Der zurückgegebene Handle hat die Semantik von own: Unique, share: Send – es gibt genau einen Besitzer des Task-Handles, der Handle kann aber in eine andere Task bewegt werden. join konsumiert den Handle; danach ist er ungültig.

Policy-Enforcement an der Task-Grenze:
jade::vm::spawn ist eine Task-Grenze. Der JME und die Type Engine erzwingen share/own-Policies für alle Werte die gecaptured werden:

  • share: LocalCompile-Fehler. Dieser Wert darf die erzeugende Task nicht verlassen.
  • share: Send + Copyable – wird kopiert in die neue Task.
  • share: Send + !Copyable – wird gemoved. Die Quelle verliert den Zugriff.
  • share: Sync – wird geteilt. Beide Tasks sehen denselben Wert.

Das ist keine Laufzeit-Prüfung sondern eine statische Garantie – ungültige Captures sind Compile-Fehler.

D-Implementierung:

enum MailboxPolicy : ubyte { Block, Trap, DropOldest, DropNewest }

struct MailboxConfig {
    uint          size   = 256;
    MailboxPolicy policy = MailboxPolicy.Block;
}

struct SchedulerSpawn {
    enum domain = CmdDomain.Scheduler;
    alias Return = HandleId;  // Task-Handle
    HandleId     fn;
    HandleId     args;
    MailboxConfig mailbox;
}

// VM-Loop:
HandleId execVmSpawn(HandleId fn, HandleId args, MailboxConfig mailbox) @nogc nothrow {
    auto c = SchedulerSpawn { fn: fn, args: args, mailbox: mailbox };
    return rt.cmd(c);
}

SchedulerPort.exec(SchedulerSpawn) erzeugt einen neuen TaskDescriptor mit eigenem Register-Set, eigenem Callstack und einer Mailbox der konfigurierten Größe und Policy, und enqueued ihn. Die Wahl der Scheduling-Strategie – FIFO, Work-Stealing, Priority – ist eine D-Implementierungsentscheidung die die Semantik nicht beeinflusst.

JDL-Implementierung (Stdlib):

// JoinHandle[T] in der Stdlib – own: Unique, share: Send
def spawn[T](f: () -> T) -> JoinHandle[T] {
    val handle = jade::vm::spawn(f.handle, [].handle, MailboxConfig.default())
    JoinHandle { handle }
}

// Aktor – läuft als eigene Task mit isoliertem State, konfigurierbare Mailbox
def spawnActor[S, M](initial: S, handler: (S, M) -> S, mailbox: MailboxConfig = MailboxConfig.default()) -> ActorRef[M] {
    val handle = jade::vm::spawn(actorLoop(initial, handler).handle, [].handle, mailbox)
    ActorRef { handle }
}

Ermöglicht: async/await, Tasks mit JoinHandle-Semantik, Aktoren, Coroutines, Generators, parallele Map-Operationen, Worker-Pools.


type AwaitOutcome: enum = | Ready(Handle) | Yielded | Cancelled

jade::vm::await(handle: Option[Handle]) -> AwaitOutcome

Semantik: Parkt die aktuelle Ausführungseinheit und gibt Kontrolle an den Scheduler zurück. Wenn handle ein konkreter Handle ist, wartet die Task bis genau dieser Handle resumed wird – typischerweise ein Task-Handle, ein Mailbox-Handle oder ein IO-Completion-Handle. Wenn handle None ist, ist es ein kooperatives Yield – die Task wird sofort wieder in die Queue eingereiht, andere Tasks bekommen eine Chance zu laufen.

Der Rückgabewert ist explizit: - Ready(handle) wenn die Task durch jade::vm::resume mit einem Wert fortgesetzt wurde, - Yielded wenn jade::vm::await(None) nach einem kooperativen Yield wieder geplant wurde, - Cancelled wenn für die aktuelle Task vor oder waehrend des Wartens Cancellation signalisiert wurde.

D-Implementierung:

struct SchedulerAwait {
    enum domain = CmdDomain.Scheduler;
    alias Return = AwaitOutcome;
    HandleId* waitFor;  // null = kooperatives Yield
}

// VM-Loop:
AwaitOutcome execVmAwait(HandleId* handle) @nogc nothrow {
    auto c = SchedulerAwait { waitFor: handle };
    return rt.cmd(c);
}

SchedulerPort.exec(SchedulerAwait) speichert den aktuellen Instruction-Pointer und Register-State des aktuellen TaskDescriptor, markiert ihn als Suspended und – wenn ein konkreter Handle angegeben wurde – trägt ihn in eine WaitTable ein: WaitTable: HandleId → [TaskDescriptor]. Der Scheduler nimmt dann die nächste Task aus der Queue. Kooperatives Yield ist eine einfache Re-Enqueueing-Operation.

Cancellation-Semantik: - wenn cancel_requested bereits vor dem Parken gesetzt ist, kehrt SchedulerAwait sofort mit Cancelled zurück, - trifft Cancellation waehrend des Wartens ein, wird die Task aufgeweckt und ebenfalls mit Cancelled fortgesetzt, - Yielded ist nur fuer den Fall waitFor == null zulaessig.

JDL-Implementierung (Stdlib):

type TaskOutcome[T]: enum =
    | Ready(T)
    | Cancelled

// Kooperatives Yield – anderen Tasks eine Chance geben
def yield() -> bool =
    match jade::vm::await(None) {
        | Yielded   => true
        | Cancelled => false
        | Ready(_)  => false   // unzulaessig fuer await(None)
    }

// Warten auf eine Task
def await[T](task: Task[T]) -> TaskOutcome[T] =
    match jade::vm::await(Some(task.handle)) {
        | Ready(h)  => Ready(h -> T)
        | Cancelled => Cancelled
        | Yielded   => Cancelled   // unzulaessig fuer await(Some)
    }

// Warten auf IO-Completion
def awaitIo(completion: IoHandle) -> TaskOutcome[Result[Bytes, IoError]] =
    match jade::vm::await(Some(completion.handle)) {
        | Ready(h)  => Ready(h -> Result[Bytes, IoError])
        | Cancelled => Cancelled
        | Yielded   => Cancelled
    }

Ermöglicht: async/await Sugar, kooperatives Multitasking, IO-Waiting ohne Threads zu blockieren, Backpressure in Pipelines, faire Ressourcenteilung.


jade::vm::cancel(target: Handle) -> bool

Semantik: Setzt fuer die Ziel-Task das Scheduler-Flag cancel_requested = true. Cancellation ist kooperativ: laufender Code wird nicht an beliebigen Instruktionsgrenzen unterbrochen. Beobachtbar wird die Markierung an expliziten Checkpoints (jade::vm::await, jade::vm::receive) sowie ueber jade::vm::cancelled().

jade::vm::cancel ist idempotent. Wenn das Ziel kein gueltiger Task-Handle ist oder die Task bereits abgeschlossen wurde, gibt der Intrinsic false zurueck. Wenn die Zieltask blockiert in jade::vm::await(...) oder jade::vm::receive() haengt, wird sie aufgeweckt und mit Cancelled fortgesetzt.

D-Implementierung:

struct SchedulerCancel {
    enum domain = CmdDomain.Scheduler;
    alias Return = bool;
    HandleId target;
}

// VM-Loop:
bool execVmCancel(HandleId target) @nogc nothrow {
    auto c = SchedulerCancel { target: target };
    return rt.cmd(c);
}

SchedulerPort.exec(SchedulerCancel) markiert die Ziel-Task als cancel_requested. Blockierte Tasks werden aus WaitTable/Mailbox-Wartezustand geloest und wieder in die Queue eingereiht. Laeuft die Task gerade, bleibt die Cancellation sticky bis zum naechsten Checkpoint.

JDL-Implementierung (Stdlib):

// Task-API in der Stdlib
def cancel[T](task: Task[T]) -> bool =
    jade::vm::cancel(task.handle)

Ermöglicht: strukturierte Shutdown-Sequenzen, Timeouts, Supervisor-Abbruch, kooperative Unterbrechung langlaufender Tasks.


jade::vm::cancelled() -> bool

Semantik: Liefert true, wenn fuer die aktuelle Task eine kooperative Cancellation angefordert wurde. Der Zustand ist sticky bis die Task regulär endet oder eine hoehere Stdlib-Abstraktion ihn in eine definierte Fehler-/Outcome-Semantik ueberfuehrt.

D-Implementierung:

struct SchedulerCancelled {
    enum domain = CmdDomain.Scheduler;
    alias Return = bool;
}

// VM-Loop:
bool execVmCancelled() @nogc nothrow {
    auto c = SchedulerCancelled {};
    return rt.cmd(c);
}

JDL-Implementierung (Stdlib):

def checkCancelled() -> bool =
    jade::vm::cancelled()

Ermöglicht: explizite kooperative Checkpoints in Library-Code, Cancellation-aware Polling-Loops, strukturierte Wrapper wie withTimeout oder race.


jade::vm::resume(handle: Handle, value: Handle) -> ()

Semantik: Nimmt eine geparkte Task – identifiziert durch ihren Handle – und reiht sie wieder in die Scheduler-Queue ein. value ist der Handle der als Rückgabewert von jade::vm::await in der resumed Task sichtbar wird. Kann von jeder anderen Task aufgerufen werden.

D-Implementierung:

struct SchedulerResume {
    enum domain = CmdDomain.Scheduler;
    alias Return = void;
    HandleId task;
    HandleId value;
}

// VM-Loop:
void execVmResume(HandleId task, HandleId value) @nogc nothrow {
    auto c = SchedulerResume { task: task, value: value };
    rt.cmd(c);
}

SchedulerPort.exec(SchedulerResume) sucht den TaskDescriptor in der WaitTable, entfernt ihn, setzt den Rückgabewert im Register-State, und enqueued die Task wieder. Wenn die Task nicht in der WaitTable ist – also bereits läuft oder noch nie geparkt wurde – ist das ein VM-Trap.

JDL-Implementierung (Stdlib):
jade::vm::resume ist das was Promises, Futures und Channels intern verwenden um wartende Tasks zu benachrichtigen:

// Channel-Implementation intern
provide Channel[T] {
    def send(self, value: T) -> () {
        match self.waiting.pop() {
            | Some(task) => jade::vm::resume(task, value.handle)
            | None       => self.queue.push(value)
        }
    }
}

Ermöglicht: Promise-Completion, Channel-Benachrichtigung, IO-Completion-Callbacks, Timer-Wakeups, manuelle Task-Koordination.


Gruppe 3 – Signaling

Motivation

Messaging in Gruppe 6 ist gerichtet – Sender kennt Empfänger. Signaling ist ungerichtet – ein Event wird gefeuert, beliebig viele registrierte Handler reagieren. Das ist das Fundament für Observer-Pattern, Event-Bus, Signal/Slot, Pub/Sub und reaktive Systeme.

Die VM verwaltet intern eine SignalTable: HandleId → [HandleId]. Handles sind die Einheit – was in ihnen steckt ist JDL-Wissen, nicht VM-Wissen.


jade::vm::signal_register(signal: Handle, handler: Handle) -> ()

Semantik: Registriert handler als Empfänger für das Signal signal. Wenn signal gefeuert wird, wird handler aufgerufen. Mehrere Handler können auf dasselbe Signal registriert werden. Die Reihenfolge der Aufrufung ist zunächst FIFO – Policies darüber werden in JDL definiert.

D-Implementierung:

struct SignalRegister {
    enum domain = CmdDomain.Signal;
    alias Return = void;
    HandleId signal;
    HandleId handler;
}

// VM-Loop:
void execVmSignalRegister(HandleId signal, HandleId handler) @nogc nothrow {
    auto c = SignalRegister { signal: signal, handler: handler };
    rt.cmd(c);
}

SignalPort.exec(SignalRegister) appended den Handler-Handle an die SignalTable-Liste für den Signal-Handle. Beide Handles werden intern gepinnt solange die Registrierung aktiv ist – sie dürfen nicht gedropt werden solange die Subscription existiert.

JDL-Implementierung (Stdlib):

type Signal[T]: struct { handle: Handle }

provide Signal[T] {
    def connect(self, handler: Fn[T, ()]) -> Subscription {
        jade::vm::signal_register(self.handle, handler.handle)
        Subscription { signal: self.handle, handler: handler.handle }
    }
}

Ermöglicht: Observer-Pattern, reaktive Bindings, Event-Bus-Subscriptions, UI-Event-Handler, Plugin-Hooks, Lifecycle-Events.


jade::vm::signal_fire(signal: Handle, payload: Handle) -> ()

Semantik: Feuert ein Signal. Alle registrierten Handler werden mit payload aufgerufen. Die Ausführungspolicy – synchron, concurrent, race – liegt nicht in der VM sondern wird durch JDL-Wrapper um diesen Intrinsic definiert.

D-Implementierung:

struct SignalFire {
    enum domain = CmdDomain.Signal;
    alias Return = void;
    HandleId signal;
    HandleId payload;
}

// VM-Loop:
void execVmSignalFire(HandleId signal, HandleId payload) @nogc nothrow {
    auto c = SignalFire { signal: signal, payload: payload };
    rt.cmd(c);
}

SignalPort.exec(SignalFire) sucht alle Handler-Handles in der SignalTable und ruft jeden auf. In der einfachsten Implementierung sequentiell im aktuellen Task-Kontext. In der erweiterten Implementierung kann jeder Handler als SchedulerSpawn-Command gestartet werden – das ist eine Policy-Entscheidung die JDL trifft.

JDL-Implementierung (Stdlib):

// Sequential – Default
provide Signal[T] {
    def emit(self, value: T) -> () =
        jade::vm::signal_fire(self.handle, value.handle)
}

// Concurrent – jeder Handler als eigene Task
provide Signal[T] :> <{ policy: Concurrent }> {
    def emit(self, value: T) -> () {
        // Handler werden über vm_spawn parallel ausgeführt
        // vm_signal_fire intern mit spawn-Wrapper
    }
}

Ermöglicht: Event-Bus, reaktive Systeme, Signal/Slot wie Qt, Pub/Sub, Plugin-Event-Hooks, UI-Bindings, Lifecycle-Notifications.


jade::vm::signal_unregister(signal: Handle, handler: Handle) -> ()

Semantik: Entfernt handler aus der SignalTable für signal. Nach diesem Aufruf wird handler nicht mehr aufgerufen wenn signal gefeuert wird. Die intern gepinnten Handles werden freigegeben.

D-Implementierung:

struct SignalUnregister {
    enum domain = CmdDomain.Signal;
    alias Return = void;
    HandleId signal;
    HandleId handler;
}

// VM-Loop:
void execVmSignalUnregister(HandleId signal, HandleId handler) @nogc nothrow {
    auto c = SignalUnregister { signal: signal, handler: handler };
    rt.cmd(c);
}

SignalPort.exec(SignalUnregister) sucht den Handler-Handle in der SignalTable-Liste für das Signal und entfernt ihn. Die intern gepinnten Handles werden unpinned. O(n) über die Handler-Liste — in der Praxis klein.

JDL-Implementierung (Stdlib):

type Subscription: struct {
    signal:  Handle
    handler: Handle
}

provide Subscription {
    def cancel(self) -> () =
        jade::vm::signal_unregister(self.signal, self.handler)
}

Ermöglicht: Lifecycle-Management von Event-Subscriptions, sichere Deregistrierung von Pluginen und UI-Komponenten, RAII-basierte Subscription-Handles.

Motivation

Die Stdlib braucht Laufzeit-Informationen über Handles, Typen, Scheduler-Zustand und Plattform um vernünftige Entscheidungen treffen zu können – Diagnostics, Monitoring, adaptive Scheduling-Strategien, Health-Checks, Debugging-Tools. Alle diese Intrinsics sind rein lesend – sie verändern keinen Zustand.

Verbindung zu REPL-Commands: Die Introspection-Intrinsics dieser Gruppe sind die Runtime-Basis für REPL Meta-Commands. :type nutzt jade::vm::type_id, :why und das Proof-Trace-System bauen auf jade::vm::handle_policy, :cost und Scheduler-Diagnostics auf jade::vm::scheduler_stats und jade::vm::memory_stats. REPL-Commands sind damit strukturell Commands über den RuntimeBus – symmetrisch zu Compiler-Queries über die CompilerDb.


jade::vm::type_id(handle: Handle) -> TypeId

Semantik: Gibt die TypeId des Wertes zurück auf den handle zeigt. Die TypeId ist ein stabiler, ganzzahliger Identifier der zur Compile-Zeit vergeben wird und über die Laufzeit stabil bleibt.

D-Implementierung:

struct JmeTypeId {
    enum domain = CmdDomain.JME;
    alias Return = TypeId;
    HandleId handle;
}

// VM-Loop:
TypeId execVmTypeId(HandleId handle) @nogc nothrow {
    auto c = JmeTypeId { handle: handle };
    return rt.cmd(c);
}

JmePort.exec(JmeTypeId) liest die TypeId direkt aus dem Handle-Descriptor – O(1), kein Lookup.

JDL-Nutzen:
Basis für TypeInfo.of[T](), runtime Typprüfungen in generischen Kontexten, Serialisierung, Debugging-Output, Schema-Validierung von Dictionaries, REPL :type-Command.


jade::vm::handle_valid(handle: Handle) -> bool

Semantik: Prüft ob ein Handle noch gültig ist – also ob der referenzierte Wert noch lebt und der Handle nicht invalidiert wurde. Handles können durch Drop, Arena-Reset oder explizites Unpin ungültig werden.

D-Implementierung:

struct JmeHandleValid {
    enum domain = CmdDomain.JME;
    alias Return = bool;
    HandleId handle;
}

// VM-Loop:
bool execVmHandleValid(HandleId handle) @nogc nothrow {
    auto c = JmeHandleValid { handle: handle };
    return rt.cmd(c);
}

JmePort.exec(JmeHandleValid) vergleicht den Generation-Counter im Handle mit dem aktuellen Counter im Slot – Use-After-Free Detection in O(1).

JDL-Nutzen:
Defensive Programmierung in der Stdlib, Assertions in Debug-Mode, sichere Handle-Dereferenzierung in FFI-nahem Code.


jade::vm::handle_policy(handle: Handle) -> PolicySet

Semantik: Gibt die Memory- und Sharing-Policies des Handles zurück – memory, share, Arena-Zugehörigkeit, FFI-Flags. Ermöglicht der Stdlib Laufzeitentscheidungen auf Basis von Policies.

D-Implementierung:

struct JmeHandlePolicy {
    enum domain = CmdDomain.JME;
    alias Return = PolicySet;
    HandleId handle;
}

// VM-Loop:
PolicySet execVmHandlePolicy(HandleId handle) @nogc nothrow {
    auto c = JmeHandlePolicy { handle: handle };
    return rt.cmd(c);
}

JmePort.exec(JmeHandlePolicy) liest den PolicySet direkt aus dem TypeDescriptor des Handle-Typs – O(1).

JDL-Nutzen:
Adaptive Algorithmen die sich je nach Policy anders verhalten, Policy-Assertions in Debug-Mode, Serialisierungs-Entscheidungen in der Bridge, REPL :why-Command (Proof-Trace-Unterstützung).


jade::vm::scheduler_stats() -> SchedulerStats

Semantik: Gibt einen Snapshot des aktuellen Scheduler-Zustands zurück – Anzahl aktiver Tasks, Anzahl wartender Tasks, Queue-Längen, Thread-Auslastung.

type SchedulerStats: struct {
    activeTasks:   u32
    waitingTasks:  u32
    queueLength:   u32
    threadCount:   u32
    utilization:   f32    // 0.0 - 1.0
}

D-Implementierung:

struct SchedulerStatsQuery {
    enum domain = CmdDomain.Scheduler;
    alias Return = SchedulerStats;
}

// VM-Loop:
SchedulerStats execVmSchedulerStats() @nogc nothrow {
    auto c = SchedulerStatsQuery {};
    return rt.cmd(c);
}

Atomare Reads aus den Scheduler-Countern. Kein Lock nötig für den Snapshot – leichte Inkonsistenz zwischen Feldern ist akzeptabel da es nur Diagnostics sind.

JDL-Nutzen:
Adaptive Backpressure, Health-Checks, Monitoring-Endpoints, Autoscaling-Entscheidungen, Load-Balancing in der Stdlib, REPL :cost-Command.


jade::vm::memory_stats() -> MemoryStats

Semantik: Gibt einen Snapshot des JME-Zustands zurück – Heap-Größe, Anzahl lebender Handles, Arena-Belegungen, Pinned-Handle-Count.

type MemoryStats: struct {
    heapBytes:      u64
    liveHandles:    u32
    pinnedHandles:  u32
    arenaCount:     u32
    arenaBytes:     u64
}

D-Implementierung:

struct JmeMemoryStats {
    enum domain = CmdDomain.JME;
    alias Return = MemoryStats;
}

// VM-Loop:
MemoryStats execVmMemoryStats() @nogc nothrow {
    auto c = JmeMemoryStats {};
    return rt.cmd(c);
}

JDL-Nutzen:
Memory-Pressure-Detection, adaptive Cache-Strategien, Leak-Detection in Tests, Profiling-Tools, REPL :cost-Command.


jade::vm::platform() -> PlatformInfo

Semantik: Gibt statische Plattforminformationen zurück – OS, Architektur, CPU-Cores, verfügbarer Arbeitsspeicher, Endianness.

type PlatformInfo: struct {
    os:           str
    arch:         str
    cpuCores:     u32
    totalMemory:  u64
    pageSize:     u32
    littleEndian: bool
}

D-Implementierung:

struct VmPlatformInfo {
    enum domain = CmdDomain.VM;
    alias Return = PlatformInfo;
}

// VM-Loop:
PlatformInfo execVmPlatform() @nogc nothrow {
    auto c = VmPlatformInfo {};
    return rt.cmd(c);
}

JDL-Nutzen:
Plattform-spezifische Optimierungen in der Stdlib, Serialisierungs-Entscheidungen, Thread-Pool-Sizing, plattformabhängige Defaults.


Gruppe 5 – FFI

Motivation

JDL muss C-Bibliotheken aufrufen können – Systemaufrufe, native Libraries, OS-APIs. Das Marshalling zwischen JDL-Handles und C-Werten übernimmt vollständig die Jade Value Bridge in D. JDL sieht davon nichts. JDL übergibt nur Handles – die Bridge kümmert sich um Typkonvertierung, Pointer-Arithmetik und ABI-Compliance automatisch anhand des TypeDescriptor.


jade::vm::load_lib(path: str) -> Handle

Semantik: Lädt eine shared library (.so / .dll / .dylib) und gibt einen Handle auf das geladene Modul zurück. Schlägt fehl wenn die Library nicht gefunden wird oder nicht geladen werden kann.

D-Implementierung:

struct FfiLoadLib {
    enum domain = CmdDomain.FFI;
    alias Return = Result!(HandleId, FfiError);
    string path;
}

// VM-Loop:
Result!(HandleId, FfiError) execVmLoadLib(string path) @nogc nothrow {
    auto c = FfiLoadLib { path: path };
    return rt.cmd(c);
}

FfiPort.exec(FfiLoadLib) ist ein Wrapper um dlopen (Unix) bzw. LoadLibrary (Windows). Der Handle referenziert ein VM-internes LibDescriptor-Objekt das das native Handle hält. Das LibDescriptor wird gepinnt solange es referenziert wird.

JDL-Implementierung (Stdlib):
Wird durch extern-Blöcke abstrahiert – Anwendungsentwickler schreiben nie jade::vm::load_lib direkt:

extern SQLite {
    def open(path: str) -> Result[DbHandle, SqliteError]
} :> FFILink("sqlite3")
// Der Compiler generiert vm_load_lib intern

Ermöglicht: Dynamisches Laden von nativen Libraries, Plugin-Architektur für native Erweiterungen, OS-API-Zugriff.


jade::vm::get_symbol(lib: Handle, name: str) -> Handle

Semantik: Sucht ein Symbol – typischerweise eine Funktion – in einer geladenen Library anhand seines Namens. Gibt einen Handle auf das Symbol zurück. Schlägt fehl wenn das Symbol nicht existiert.

D-Implementierung:

struct FfiGetSymbol {
    enum domain = CmdDomain.FFI;
    alias Return = Result!(HandleId, FfiError);
    HandleId lib;
    string   name;
}

// VM-Loop:
Result!(HandleId, FfiError) execVmGetSymbol(HandleId lib, string name) @nogc nothrow {
    auto c = FfiGetSymbol { lib: lib, name: name };
    return rt.cmd(c);
}

FfiPort.exec(FfiGetSymbol) ist ein Wrapper um dlsym (Unix) bzw. GetProcAddress (Windows). Der zurückgegebene Handle referenziert einen SymbolDescriptor der den nativen Funktionszeiger und die erwartete Signatur hält.

JDL-Nutzen:
Dynamische Symbol-Auflösung zur Laufzeit, Hot-loadable Plugins, optionale native Erweiterungen.


jade::vm::call_extern(fn: Handle, args: Handle) -> Handle

Semantik: Ruft eine native C-Funktion auf. fn ist ein Symbol-Handle, args ist ein Handle auf ein Array von JDL-Werten. Die Jade Value Bridge konvertiert die JDL-Handles automatisch zu C-kompatiblen Werten, führt den Aufruf durch, und konvertiert das Ergebnis zurück zu einem JDL-Handle.

D-Implementierung:

struct FfiCallExtern {
    enum domain = CmdDomain.FFI;
    alias Return = HandleId;
    HandleId fn;
    HandleId args;
}

// VM-Loop:
HandleId execVmCallExtern(HandleId fn, HandleId args) @nogc nothrow {
    auto c = FfiCallExtern { fn: fn, args: args };
    return rt.cmd(c);
}

FfiPort.exec(FfiCallExtern) ist der komplexeste Command. Die Bridge: 1. Liest die Signatur aus dem SymbolDescriptor 2. Konvertiert jeden args-Handle über fromJadeValue zu C-Typen 3. Führt den nativen Aufruf durch – mit korrekter Calling Convention 4. Konvertiert das Ergebnis über toJadeValue zurück

ABI-Details, Calling Conventions, Stack-Alignment – alles wird von der Bridge gehandhabt. JDL sieht nur Handles.

Ermöglicht: Der gesamte FFI-Mechanismus. Ohne diesen Intrinsic kein Systemzugriff, keine nativen Libraries, kein OS-Interop.


Gruppe 6 – Messaging

Motivation

Signaling ist 1:N und ungerichtet. Messaging ist 1:1 und gerichtet – eine Task schickt einer anderen eine konkrete Nachricht. Das ist die Basis für das Aktor-Modell, typsichere Channels und direkte Task-zu-Task-Kommunikation.

Jede gespawnte Ausführungseinheit hat eine eigene Mailbox – eine interne Queue die der Scheduler beim Spawnen automatisch erstellt. Die Mailbox ist kein JDL-Handle sondern ein VM-internes Konzept das über diese Intrinsics zugänglich ist.


jade::vm::send(target: Handle, msg: Handle) -> ()

Semantik: Legt msg in die Mailbox der Task die durch target identifiziert wird. Wenn die Zieltask gerade auf jade::vm::receive wartet, wird sie sofort resumed. Ansonsten liegt die Nachricht in der Queue bis die Zieltask bereit ist.

Policy-Enforcement: jade::vm::send ist eine Task-Grenze. Nachrichten müssen share: Send oder share: Sync sein – share: Local-Werte können nicht gesendet werden und ergeben einen Compile-Fehler. Für Send + !Copyable ist das ein Move – die sendende Task verliert den Zugriff auf den Wert.

D-Implementierung:

struct MessagingSend {
    enum domain = CmdDomain.Messaging;
    alias Return = void;
    HandleId target;
    HandleId msg;
}

// VM-Loop:
void execVmSend(HandleId target, HandleId msg) @nogc nothrow {
    auto c = MessagingSend { target: target, msg: msg };
    rt.cmd(c);
}

MessagingPort.exec(MessagingSend) sucht den TaskDescriptor via target-Handle, acquired den Mailbox-Lock, appended msg zur Message-Queue, und prüft ob die Task auf Suspended steht. Falls ja, wird sie intern geweckt und in die Scheduler-Queue eingereiht. O(1) wenn die Mailbox leer ist.

JDL-Implementierung (Stdlib):

// ActorRef – send ist typsicher durch share-Policy des Nachrichtentyps
type ActorRef[M]: struct { handle: Handle }

provide ActorRef[M] {
    def send(self, msg: M) -> () =
        jade::vm::send(self.handle, msg.handle)
}

// Channel-Sender – own: Shared, share: Sync → Multi-Producer möglich
type Sender[T]: struct { handle: Handle }
:> <{ own: Shared, share: Sync }>

Ermöglicht: Aktor-Kommunikation, typsichere Channels, Request/Response-Patterns, Pipeline-Stages, Work-Distribution an Worker-Tasks.


type ReceiveOutcome: enum = | Message(Handle) | Cancelled

type TryReceiveOutcome: enum = | Message(Handle) | Empty | Cancelled

jade::vm::receive() -> ReceiveOutcome

Semantik: Blockiert die aktuelle Task bis eine Nachricht in ihrer Mailbox liegt. Gibt die Nachricht als Message(handle) zurück. Wenn bereits Nachrichten in der Mailbox sind, kehrt sofort zurück ohne zu blockieren. Wenn fuer die aktuelle Task vor oder waehrend des Wartens Cancellation gesetzt ist, kehrt der Intrinsic mit Cancelled zurück.

D-Implementierung:

struct MessagingReceive {
    enum domain = CmdDomain.Messaging;
    alias Return = ReceiveOutcome;
}

// VM-Loop:
ReceiveOutcome execVmReceive() @nogc nothrow {
    auto c = MessagingReceive {};
    return rt.cmd(c);
}

MessagingPort.exec(MessagingReceive) prüft ob die Mailbox der aktuellen Task Nachrichten enthält. Falls ja: dequeue und Message(handle) zurückgeben. Falls nein: Task auf Suspended setzen und Scheduler-Control abgeben – intern äquivalent zu einem SchedulerAwait-Command auf dem Mailbox-Handle. Der Scheduler resumed die Task wenn MessagingSend eine Nachricht einliefert.

Cancellation-Semantik: - wenn cancel_requested bereits gesetzt ist, kehrt MessagingReceive sofort mit Cancelled zurück, - trifft Cancellation waehrend des Wartens ein, wird die Task aufgeweckt und ebenfalls mit Cancelled fortgesetzt.

Normative Abgrenzung: Closed ist keine rohe Mailbox-Semantik der VM. Geschlossene Channel-Enden sind eine Stdlib-Abstraktion ueber Sender-/Receiver-Lebenszyklen, nicht ein zusatzlicher Zustand der VM-Mailbox.

JDL-Implementierung (Stdlib):

// Aktor-Loop in der Stdlib
def actorLoop[S, M](state: S, handler: (S, M) -> S) -> () {
    var current = state
    loop {
        match jade::vm::receive() {
            | Message(h) => current = handler(current, h -> M)
            | Cancelled  => break
        }
    }
}

Ermöglicht: Aktor-Receive-Loop, blockierendes Channel-Receive, Request-Handling in Service-Tasks, sequentielle Nachrichtenverarbeitung.


jade::vm::try_receive() -> TryReceiveOutcome

Semantik: Non-blocking Version von jade::vm::receive. Prüft ob eine Nachricht in der Mailbox liegt und gibt sie zurück – oder Empty wenn die Mailbox leer ist. Blockiert nie, parkt nie. Wenn die aktuelle Task bereits als cancelled markiert ist, gibt der Intrinsic Cancelled zurück.

D-Implementierung:

struct MessagingTryReceive {
    enum domain = CmdDomain.Messaging;
    alias Return = TryReceiveOutcome;
}

// VM-Loop:
TryReceiveOutcome execVmTryReceive() @nogc nothrow {
    auto c = MessagingTryReceive {};
    return rt.cmd(c);
}

MessagingPort.exec(MessagingTryReceive) ist ein atomarer Mailbox-Check ohne Suspension. Dequeue wenn verfügbar und Message(handle) zurückgeben, Empty wenn leer. Wenn cancel_requested bereits gesetzt ist, Cancelled. O(1).

JDL-Implementierung (Stdlib):

// Non-blocking Drain – verarbeite alle verfügbaren Nachrichten
def drainMailbox[M](handler: M -> ()) -> bool {
    loop {
        match jade::vm::try_receive() {
            | Message(h) => handler(h -> M)
            | Empty      => break true
            | Cancelled  => break false
        }
    }
}

// Polling mit Fallback
def receiveOrDefault[M](default: M) -> TaskOutcome[M] =
    match jade::vm::try_receive() {
        | Message(h) => Ready(h -> M)
        | Empty      => Ready(default)
        | Cancelled  => Cancelled
    }

Ermöglicht: Event-Loop-Integration, Polling-basierte Verarbeitung, nicht-blockierende Drain-Operationen, Timeout-Pattern ohne echtes Blocking.


Gruppe 7 – Closures

Motivation

FnPtr und Closure sind strukturell verschieden: ein FnPtr ist ein nackter Proto-Pointer ohne Upvalue-Array. Eine Closure ist Proto-Pointer plus Upvalue-Array. Der Compiler generiert Closure-Objekte automatisch für Lambdas mit Captures. Wenn JDL-Code explizit einen FnPtr in eine Closure konvertieren will — etwa um ihn an eine API zu übergeben die Closure erwartet — braucht das eine VM-Operation, weil das Upvalue-Array ein VM-internes Objekt ist das nicht als normales Struct konstruiert werden kann.


jade::vm::fnptr_to_closure(fnptr: Handle) -> Handle

Semantik: Konvertiert einen FnPtr-Handle in einen Closure-Handle. Liest den Proto-Pointer aus dem FnPtr-Descriptor, allokiert ein leeres Upvalue-Array und gibt einen neuen Closure-Handle zurück. Die resultierende Closure hat keine captures — sie verhält sich identisch zum ursprünglichen FnPtr, ist aber typekompatibel mit Closure-APIs.

D-Implementierung:

struct FnPtrToClosure {
    enum domain = CmdDomain.JME;
    alias Return = HandleId;
    HandleId fnptr;
}

// VM-Loop:
HandleId execVmFnptrToClosure(HandleId fnptr) @nogc nothrow {
    auto c = FnPtrToClosure { fnptr: fnptr };
    return rt.cmd(c);
}

JmePort.exec(FnPtrToClosure) liest den ProtoId aus dem FnPtrDescriptor, allokiert ein leeres UpvalueArray über den JME-Heap, und erstellt einen neuen ClosureDescriptor { proto, upvalues: [] }. O(1) plus eine Allokation.

JDL-Implementierung (Stdlib):

provide CastTo[Closure[Sig]]: Narrow for FnPtr[Sig] {
    def castTo(self) -> Closure[Sig] =
        jade::vm::fnptr_to_closure(self)
}

Ermöglicht: Explizite FnPtr → Closure Konversion für APIs die Closure erwarten, Interoperabilität zwischen capture-freien Funktionen und Closure-basierten Abstraktionen.


Das vollständige Intrinsic-Set im Überblick

Gruppe 1 – Handle Lifetime
  vm_pin(handle)                              – Handle vor RC-Drop schützen
  vm_unpin(handle)                            – Schutz aufheben

Gruppe 2 – Scheduling
  vm_spawn(fn, args) -> Handle                          – neue Ausführungseinheit
  vm_await(handle?) -> AwaitOutcome                     – parken, auf Resume oder Yield warten
  vm_resume(handle, value)                              – Task fortsetzen
  vm_cancel(target) -> bool                             – kooperative Cancellation anfordern
  vm_cancelled() -> bool                                – Cancellation-Flag der aktuellen Task lesen

Gruppe 3 – Signaling
  vm_signal_register(signal, handler)         – Handler registrieren
  vm_signal_fire(signal, payload)             – alle Handler aufrufen
  vm_signal_unregister(signal, handler)       – Handler deregistrieren

Gruppe 4 – Introspection (readonly)
  vm_type_id(handle) -> TypeId               – Laufzeit-Typinformation
  vm_handle_valid(handle) -> bool            – Handle-Gültigkeit prüfen
  vm_handle_policy(handle) -> PolicySet      – Policies lesen
  vm_scheduler_stats() -> SchedulerStats     – Scheduler-Zustand
  vm_memory_stats() -> MemoryStats           – JME-Zustand
  vm_platform() -> PlatformInfo             – Plattform-Information

Gruppe 5 – FFI
  vm_load_lib(path) -> Result[Handle, FfiError]        – native Library laden
  vm_get_symbol(lib, name) -> Result[Handle, FfiError] – Symbol auflösen
  vm_call_extern(fn, args) -> Result[Handle, FfiError] – native Funktion aufrufen

Gruppe 6 – Messaging
  vm_send(target, msg)                                 – Nachricht in Mailbox
  vm_receive() -> ReceiveOutcome                       – blockierend empfangen, cancellation-aware
  vm_try_receive() -> TryReceiveOutcome                – non-blocking empfangen, cancellation-aware

Gruppe 7 – Closures
  vm_fnptr_to_closure(fnptr) -> Handle       – FnPtr zu Closure konvertieren

Total: 23 Intrinsics

Was diese 23 Intrinsics ermöglichen

Mit diesen 23 Primitiven kann die JDL-Stdlib folgende Abstraktionen vollständig in JDL implementieren – ohne eine Zeile zusätzlichen D-Code:

Nebenläufigkeit:
async/await-Sugar, Task[T], Future[T], Promise[T], kooperative Coroutines, Generators, parallele Map/Filter-Operationen, Worker-Pools, Thread-Pool-Abstraktion.

Kommunikation:
Channel[T] (bounded und unbounded), Sender[T]/Receiver[T], Oneshot-Channel, Broadcast-Channel, typsichere Queues.

Aktorsystem:
Actor[S, M], ActorRef[M], Supervisor-Trees, Request/Response-Pattern über Oneshot-Channels, Actor-Pools.

Reaktive Systeme:
Signal[T], Slot, Observer-Pattern, Event-Bus, Pub/Sub, reaktive Bindings für UI.

Resilience:
Timeout-Pattern (vm_await mit Timer-Handle), kooperative Cancellation, Retry-Logic, Circuit-Breaker, Backpressure ueber Mailbox-Groesse.

Diagnostics:
Health-Check-Endpoints, Metrics-Collection, Memory-Pressure-Detection, Scheduler-Monitoring, adaptive Algorithmen.

Plugin-Architektur:
Dynamisches Laden von nativen Plugins über FFI, JDL-Plugins über dynamisches jade::vm::spawn, hot-reloadbare Komponenten über jade::vm::pin/jade::vm::unpin.

Self-Hosting:
Sobald Lexer und Parser in JDL implementiert sind, laufen sie als normale Tasks über jade::vm::spawn. Der Compiler wird zur JDL-Anwendung die auf der eigenen VM läuft.


Normative Invarianten

  1. Intrinsics werden vom Compiler zur Compile-Zeit durch INTRINSIC subcode, a, b, c ersetzt – kein CallFrame-Overhead zur Laufzeit.
  2. Kein Intrinsic gibt rohe Pointer zurück. Alle Rückgabewerte sind Handles oder primitive Werte.
  3. Jade hat keinen GC. Der JME verwaltet Speicher über Refcounting. Handles droppen deterministisch wenn ihr Refcount auf 0 fällt.
  4. jade::vm::pin und jade::vm::unpin müssen symmetrisch aufgerufen werden. Die Stdlib ist dafür verantwortlich – nicht der Anwendungsentwickler.
  5. Refcount-Zyklen zwischen gepinnten oder own: Shared-Handles sind Leaks. own: Weak ist der einzige Schutzmechanismus dagegen.
  6. jade::vm::spawn und jade::vm::send sind Task-Grenzen. Der Compiler erzwingt share/own-Policies statisch: Local-Typen können Task-Grenzen nicht überqueren.
  7. share: Send + !Copyable an Task-Grenzen ist ein Move – die Quelle verliert den Zugriff. share: Sync erlaubt echte gleichzeitige Nutzung.
  8. jade::vm::call_extern delegiert das gesamte Marshalling an die Jade Value Bridge. JDL übergibt nur Handles.
  9. Introspection-Intrinsics haben keine Seiteneffekte und sind aus jedem Kontext aufrufbar.
  10. jade::vm::receive und jade::vm::await(None) sind semantisch verschieden: jade::vm::receive wartet auf Mailbox-Input, jade::vm::await(None) ist kooperatives Yield.
  11. Cancellation ist kooperativ. jade::vm::cancel unterbricht keine beliebigen Instruktionsfolgen, sondern wird an Checkpoints (jade::vm::await, jade::vm::receive, explizite jade::vm::cancelled()-Checks) beobachtet.
  12. jade::vm::cancel ist idempotent. Eine blockierte Task in await oder receive wird durch Cancellation aufgeweckt und mit einem cancellation-spezifischen Outcome fortgesetzt.
  13. Die Mailbox einer Task wird vom Scheduler beim Spawnen automatisch erstellt. Beim Drop der Task wird die Mailbox bereinigt und alle noch enthaltenen Nachrichten werden gedropt.
  14. Die SignalTable pinnt Handler-Handles intern. Handles in der Table können nicht unbeabsichtigt durch RC-Drop verschwinden.
  15. Alle 23 Runtime-Intrinsics sind im jade::vm-Namespace deklariert und dokumentiert. Sie sind sichtbar, bilden aber die rohe Runtime-Brücke und sind nicht als primäre Anwendungsoberfläche gedacht.
  16. Diese API ist versioniert und stabil. Änderungen an Semantik oder Signatur sind Breaking Changes.
  17. JDL-Welt (JME/RC) und D-Welt (D GC) sind strikt getrennt. Werte die die Grenze überqueren werden kopiert, nie als Pointer geteilt.
  18. Alle D-Implementierungen von Intrinsics kommunizieren ausschließlich über rt.cmd(...) – kein Subsystem wird direkt aufgerufen. Der RuntimeBus ist die einzige Kommunikationsschicht zur Laufzeit, analog zur CompilerDb zur Compile-Zeit.

Entschiedene Punkte

  • Subcode-Dispatch: final switch über ein D-Enum. Erzwingt Exhaustiveness zur Compile-Zeit — ein fehlender Subcode ist ein Compiler-Fehler, kein stilles Durchfallen. Konkrete Subcode-Nummerierung ist deferred bis zur Implementierung.

  • Fehlerbehandlung: FFI-Intrinsics die fehlschlagen können (jade::vm::load_lib, jade::vm::get_symbol, jade::vm::call_extern) geben Result zurück — Fehler sind recoverable, kein Trap. Trap bleibt für Programmierfehler (ungültiger Handle, falscher Subcode).

  • jade::vm::signal_unregister: Ist ein Intrinsic — symmetrisch zu jade::vm::signal_register, da beide VM-internen Zustand (SignalTable) verändern.

  • Timer-Handle: Kein eigener Intrinsic. Timer sind ein Library-Konzept das auf jade::vm::await und Scheduler-Primitives aufbaut.

  • Cancellation-Cause / Cancellation-Scopes: Deferred. Dieses Dokument normiert nur die rohe kooperative Mechanik (cancel, cancelled, cancellation-aware Checkpoints). Ursachenmodelle, Supervisor-Policies, Timeout-Wrapper und Scope-Vererbungen gehoeren in die Stdlib oder spaetere Designrunden.

  • Mailbox-Closure als VM-Zustand: Nicht eingefuehrt. Closed bleibt eine Stdlib-Semantik fuer Channel-Enden; rohe VM-Mailboxen kennen fuer receive/try_receive nur Nachricht, leer/nicht leer und Cancellation.

  • Mailbox-Konfiguration: Größe und Backpressure-Policy werden beim Spawnen als MailboxConfig übergeben. Default-Policy ist Block. Verfügbare Policies: Block (default), Trap, DropOldest, DropNewest.

// spekulativ
vm_spawn(fn, args, mailbox: MailboxConfig {
    size:   256,
    policy: Block
})
  • jade::vm::fnptr_to_closure: Ist ein INTRINSIC-Subcode über CmdDomain.JME. Liest den Proto-Pointer aus dem FnPtr-Handle, allokiert ein leeres Upvalue-Array, gibt einen neuen Closure-Handle zurück. Notwendig für CastTo[Closure[Sig]]: Narrow for FnPtr[Sig] in der Stdlib.
// spekulativ
struct FnPtrToClosure {
    enum domain = CmdDomain.JME;
    alias Return = HandleId;
    HandleId fnptr;
}

HandleId execVmFnptrToClosure(HandleId fnptr) @nogc nothrow {
    auto c = FnPtrToClosure { fnptr: fnptr };
    return rt.cmd(c);
}

Offene Punkte

  • Formale Command-Struct-Definitionen fuer alle 23 Intrinsics – dieses Dokument zeigt repräsentative Beispiele; vollständige normative D-Structs stehen noch aus.