Zum Inhalt

Jade VM-Scheduler & Intrinsic-Grenze

Status: Konsolidierter normativer Entwurf
Version: 0.2.0
Datum: 2026-09-05
Geltungsbereich: Tasks, Scheduling, Suspension, I/O-Wakeup, Timer, Engine-Runtime-Konfiguration und Scheduler-Intrinsics
Bezieht sich auf: Runtime-Modell (03), VM Instruction Set (05), Intrinsics & VM API (08), JadeValue/Register-Layout (11), IR (13), Effect Rows & Throw[E]
Ring-Zugehörigkeit: Scheduler Ring 0; JDL-/Stdlib-Abstraktionen darüber Ring 1/2
Host: Zig; konkrete Zig-Strukturen sind nicht Teil des Vertrags


1. Architektur

Der Scheduler ist Teil der Jade VM. Er führt M leichtgewichtige Tasks auf N konfigurierbaren Worker-Threads aus.

Zwei Fragen bleiben getrennt:

Task wird wieder lauffähig:
    Event oder Zeit

Lauffähige Task bekommt CPU:
    Run Queue / Worker / Reduction-Budget
 Event-/I/O-Poller        Master Clock
         │                     │
         ▼                     ▼
   Event-Wakeup          Engine Timer-Wheel
         │                     │
         └─────────┬───────────┘
                Runnable
            Worker / Run Queue
                 VM Loop

1.1 M:N und Work-Stealing

Work-Stealing ist die vorgesehene Mehrworker-Strategie. Eine Referenzreihenfolge für Worker ist:

  1. lokale Run Queue,
  2. globale Inject-/Wakeup-Queue,
  3. Work-Stealing,
  4. Worker parken.

Deque-Typ, Steal-Größe und Victim-Auswahl sind Implementierungsdetails. Die beobachtbare Semantik darf davon nicht abhängen.

1.2 Event-Poller und Clock

Der Event-/I/O-Poller übersetzt externe Completions in Scheduler-Wakeups. Die konkrete OS-Anbindung (epoll, io_uring, kqueue, IOCP oder äquivalent) ist Plattformdetail.

Die Master Clock bedient ausschließlich aktive Timer-Wheels. Der Scheduler besitzt keinen globalen Heartbeat. Ohne Arbeit, Events oder aktive Timer darf die Runtime schlafen.


2. Task-Modell

Eine Task ist die kleinste vom Scheduler verwaltete, suspendierbare VM-Ausführungseinheit.

Konzeptionell:

Task
    id
    schedulingState
    executionState      // IP, Frames, Register
    reductions
    engineId
    waitCondition?
    cancellationRequested
    outcome?

2.1 Schedulerzustand und Ausgang

Schedulerzustand:

Runnable
Running
Waiting
Completed

Task-Ausgang:

Success(value)
Failure(error)      // materialisierter Throw[E]
Trap(info)
Cancelled

Beides sind getrennte Dimensionen. Completed bedeutet nicht automatisch Erfolg.

stateDiagram-v2
    [*] --> Runnable: spawn
    Runnable --> Running: dispatch
    Running --> Runnable: preemption / yield
    Running --> Waiting: event / timer
    Waiting --> Runnable: wakeup / cancellation
    Running --> Completed: return / throw / trap / cancellation
    Completed --> [*]

2.2 Task-Grenze und Throw[E]

Ein Throw[E] darf nicht durch den Stack einer anderen Task unwinden.

Throw[E] im Child
Task-Grenze
materialisierter Failure-Ausgang

Eine Join-/Wait-Abstraktion darf den Fehler im wartenden Task wieder lokal als Throw[E] erzeugen. Die konkrete JDL-Task-API wird separat spezifiziert.

Throw[E], Trap und Cancellation bleiben getrennt.

2.3 Task vs. Actor

Tasks besitzen keine eingebaute Mailbox und keine Supervision.

Task
    VM-Ausführungseinheit

Actor
    Task + State + Mailbox + Supervision

Actors, Channels und Mailboxes dürfen generische Wait-/Wakeup-Mechanismen verwenden, gehören aber nicht zum Scheduler-Core.


3. Scheduling und Suspension

3.1 Reduction-basierte Fairness

Jede laufende Task besitzt ein Reduction-Budget aus ihrer EngineRuntimeConfig.

Bei erschöpftem Budget wird die Task an einem VM-sicheren Preemption-Point gespeichert und erneut als Runnable eingereiht.

Das ist keine willkürliche OS-Präemption in nativen Code. Eine dauerhaft runnable Task darf andere runnable Tasks nicht dauerhaft verdrängen.

3.2 Event-getriebene Suspension

Muss eine Operation warten, registriert die Runtime eine Wait-Condition und setzt die aktuelle Task auf Waiting.

Operation
Ergebnis nicht verfügbar
Wait-Condition registrieren
Waiting
   ↓ Event
Runnable

Der Scheduler kennt nur die Wait-/Wakeup-Identität, nicht die fachliche Bedeutung des Events.

3.3 Suspension ist kein Result

VM-interner Ausführungskontrollfluss muss mindestens unterscheiden können zwischen:

Completed(value)
Suspended(waitCondition)
Trap(info)

Die konkrete Struktur ist Implementierungsdetail.

Die frühere Form JadeResult.yield() ist kein JDL-Result[T,E] und wird nicht als Fehlersemantik verwendet.

3.4 Transparente Suspension in JDL

JDL besitzt keine spezielle async-Funktionsklasse und benötigt für normale Service-Aufrufe keine async/await-Schlüsselwörter.

def loadConfig(path: Path) ->{Fs, Throw[IoError]} Config {
    val data = Fs.read(path)
    parseConfig(data)
}

Fs.read darf die aktuelle Task suspendieren. Die Funktion bleibt eine normale JDL-Funktion.

Effect Row:
    semantische Anforderungen und propagierbare Effekte

Scheduler:
    wann die Berechnung weiterlaufen kann

Suspendierbarkeit ist kein Row-Element; es gibt keinen Async-Effekt.

3.5 Explizite Parallelität

Suspension erzeugt keine neue Task. Parallele Arbeit wird explizit durch Task-Erzeugung gestartet, konzeptionell über spawn.

// exemplarisch; Oberfläche separat zu spezifizieren
val first = spawn { load("a") }
val b = load("b")
val a = first.join()

Task-Captures unterliegen den normalen Ownership-/Sharing-Regeln der Task-Grenze.

3.6 Cancellation

Cancellation ist kooperativer Scheduler-Kontrollfluss und kein Throw[E].

  • die Anforderung bleibt sticky,
  • wartende Tasks werden geweckt,
  • laufende Tasks beobachten sie an definierten Checkpoints,
  • es gibt keine willkürliche Unterbrechung beliebigen VM-/FFI-Codes.

Eine höhere Abstraktion darf Cancellation in einen Wert oder Domain-Fehler übersetzen.


4. Engine-Konfiguration und Zeit

4.1 Trennung der Ebenen

EngineDefinition
EngineDescriptor
EngineRuntimeConfig
Scheduler

Der Scheduler kennt keine semantische JDL-Engine. Er konsumiert ausschließlich EngineRuntimeConfig.

Tasks speichern ihre engineId.

4.2 EngineRuntimeConfig

Scheduler-relevante Konfiguration umfasst mindestens:

engineId
reductionLimit
optional timerWheelConfig
resourceLimits
overrunPolicy
metricsConfig

Capabilities gehören nicht zur Scheduler-Semantik. Enforcement erfolgt an RuntimeBus-/VM-Grenzen.

Registrierung und Änderungen der Runtime-Konfiguration laufen über den RuntimeBus.

4.3 Engine-lokale Timer-Wheels

Nicht jede Engine besitzt ein Timer-Wheel.

typisch zeitgetrieben:
    Rendering, periodische Jobs, Health Checks, Simulation

typisch eventgetrieben:
    HTTP-Transport, Socket-Completion, viele Actor-/Channel-Wakeups

Ein hierarchisches Timer-Wheel ist die vorgesehene Referenzimplementierung. Anzahl der Ebenen und Slots sind kein Sprachvertrag.

4.4 Overrun-Policies

Für periodische Arbeit existieren drei Policies:

Drift   nächster Lauf relativ zum tatsächlichen Abschluss; kein Aufstauen
Skip    verpasste Intervalle überspringen
Queue   verpasste Intervalle nachholen

Drift ist Default, sofern die Engine nichts anderes verlangt.

4.5 Beobachtbarkeit

Scheduler und Timer-Wheels sollen mindestens Größen wie folgende bereitstellen:

runnableTasks
waitingTasks
activeWorkers
queuePressure
targetRate
actualRate
handlerDuration
overrunCount

Das konkrete Metrikformat ist nicht normativ.


5. RuntimeBus und Intrinsic-Grenze

5.1 Mutationsgrenze

Zustandsändernde Scheduler-Operationen laufen ausschließlich über den RuntimeBus.

JDL / VM Intrinsic
RuntimeBus Command
Scheduler

Typische Operationen:

spawn
wait / yield
resume / wake
cancel
query cancellation
register / update EngineRuntimeConfig

Mailbox-, Channel- und Actor-Logik liegen darüber.

5.2 Intrinsic-Encoding

Die Intrinsic-Grenze ist stabil und versioniert. Diese Scheduler-Spezifikation friert jedoch keine 16-Bit-ID-Bereiche oder Opcode-Nummern ein.

Gemäß VM-Spec bleiben zwei kompatible Lowering-Strategien zulässig:

  1. compiler-known Builtin/Intrinsic über den normalen Call-Pfad,
  2. ein generischer Intrinsic-Opcode mit Subcode.

Konkrete IDs und Encoding gehören in die zentrale VM-/Intrinsic-Spezifikation.

5.3 JadeValue

Intrinsics umgehen das aktuelle Grenzmodell nicht.

Gemäß 11-jadevalue-und-register-layout ist JadeValue das Transportformat an Call-, Return-, Intrinsic- und FFI-Grenzen. Innerhalb einer Funktion arbeitet die VM auf dem statisch angelegten Register-Buffer.

Fast Paths sind zulässig, sofern sie dieselbe Semantik und dieselben Verifier-/ABI-Invarianten erhalten.

5.4 Capabilities

Capability-Prüfung ist keine Scheduler-Policy.

Runtime-Capabilities werden an RuntimeBus-/VM-Grenzen erzwungen. Der Scheduler erhält nur autorisierte Scheduler-Kommandos.


6. Normative Invarianten

S-1 Der Scheduler ist Teil der VM, kein Actor und kein Userland-Service.
S-2 Tasks werden M:N auf Worker-Threads ausgeführt; die Workerzahl ist konfigurierbar.
S-3 Die Task ist die kleinste suspendierbare Scheduler-Einheit; Actors liegen darüber.
S-4 Der Scheduler besitzt keinen globalen Heartbeat.
S-5 Wartende Tasks werden durch Events oder Zeitbedingungen wieder runnable.
S-6 Reduction-basierte Preemption schützt vor unbegrenzter Worker-Monopolisierung.
S-7 Preemption erfolgt nur an VM-definierten sicheren Punkten.
S-8 Suspension ist Runtime-Eigenschaft, keine eigene JDL-Funktionsklasse.
S-9 Für normale suspendierbare Aufrufe gibt es kein Async-Row-Element und keine notwendige async/await-Färbung.
S-10 Neue parallele Ausführung entsteht explizit durch Task-Erzeugung.
S-11 Schedulerzustand und Task-Ausgang sind getrennte Dimensionen.
S-12 Throw[E] unwindet nicht über Task-Grenzen; es wird beim Task-Ausgang materialisiert.
S-13 Cancellation ist weder Throw[E] noch Trap.
S-14 Der Scheduler kennt nur EngineRuntimeConfig, nicht die semantische Engine.
S-15 Timer-Wheels werden durch Engine-Runtime-Konfiguration aktiviert und parametrisiert.
S-16 Scheduler-Zustand wird ausschließlich über den RuntimeBus mutiert.
S-17 Tasks besitzen keine Scheduler-Mailbox; Messaging liegt oberhalb des Scheduler-Cores.
S-18 VM-Suspensionssignale sind keine JDL-Result[T,E]-Werte.
S-19 Intrinsic-Encoding und IDs werden zentral in VM-/Intrinsic-Spezifikationen festgelegt.
S-20 Capability-Enforcement liegt an RuntimeBus-/VM-Grenzen, nicht in der Scheduling-Policy.


7. Bewusst offene Implementierungsdetails

Diese Punkte können später entschieden werden, ohne das semantische Modell zu ändern:

Punkt Offen
Worker-Default Ableitung aus CPU-/Environment-Ressourcen
Work-Stealing Deque, Steal-Größe, Victim-Auswahl
Event-Poller Plattform-Backend
Wait-Registry interne Datenstruktur
Timer-Wheel Ebenen, Slots, Speicherlayout
Master Clock eigener Thread oder äquivalente Integration
Task-Outcome konkrete Repräsentation / Error-Envelope
Task-Handle Join/Detach/Structured-Concurrency-Lifetime
Shutdown Drain, Graceful Stop, Forced Stop
Intrinsic-ABI ID-Breite, Nummern, Encoding
Metrics genaue Struktur und Export

Diese Fragen sind keine Blocker für das Scheduler-Grundmodell.


8. Inkrementeller Implementierungsweg

V1
    ein Worker
    eine Run Queue
    Task-Zustandsautomat
    Reduction-Counter
    generisches wait / wake
    Event-Poller

V2
    mehrere Worker
    Work-Stealing
    Worker-Parking

V3
    Master Clock
    optionales Timer-Wheel
    Sleep / Deadline / Timeout

V4
    EngineRuntimeConfig
    Engine-lokale Timer-Wheels
    Resource Limits
    Overrun-Policies

V5
    Metrics
    Scheduler-Introspection
    Queue-/Fast-Path-Optimierungen

Task-Outcome-Materialisierung für Throw[E], Trap und Cancellation muss spätestens mit Task-Join/Wait integriert werden.


Änderungsprotokoll

v0.2.0

  • D-spezifische Implementierungsdetails entfernt; Host auf Zig aktualisiert.
  • Effect[R,E,D] und die alte Trennung „imperativer Stil / Effect-Stil“ entfernt.
  • Effect Rows ->{...} übernommen; Suspendierbarkeit davon ausdrücklich getrennt.
  • Task-State und Task-Outcome getrennt.
  • Throw[E]-Materialisierung an Task-Grenzen ergänzt.
  • Cancellation von Throw[E] und Trap getrennt.
  • Task-Mailbox aus dem Scheduler-Core entfernt.
  • JadeResult.yield() als veraltete Vermischung von VM-Control und JDL-Result beseitigt.
  • JadeValue als Intrinsic-Grenzformat berücksichtigt.
  • Capability-Enforcement vom Scheduler an RuntimeBus-/VM-Grenzen verschoben.
  • feste Intrinsic-ID-Bereiche aus dieser Spezifikation entfernt.
  • Timer-Wheel-Slotzahlen, Queue-Strukturen und Plattform-I/O zu Implementierungsdetails gemacht.
  • Aufbau auf acht kompakte Abschnitte reduziert.