ReviewOS

zig-utils/zig-js

Private ABI: VM trap notifications — watchdog check + debugger break

#304
Closed chris opened this 9 days ago ·
9 days ago

Parent: #140 Tracked by: #134, #163, #164

Symbols

SymbolHome (packages/runtime/src/jsc/VM.zig @ 7ed99c02)Bun (upstream/src/jsc/bindings/bindings.cpp)
JSC__VM__notifyNeedWatchdogCheckVM.zig:142bindings.cpp:4992 — (*arg0).notifyNeedWatchdogCheck()
JSC__VM__notifyNeedDebuggerBreakVM.zig:148bindings.cpp:4997 — (*arg0).notifyNeedDebuggerBreak()

Both are one-liners that set JSC::VMTraps bits on the VM. The traps are consumed on the executing thread at the next safepoint: NeedWatchdogCheck re-checks the armed watchdog time limit and raises termination when it has elapsed; NeedDebuggerBreak makes the debugger break at the next statement boundary (inert when no debugger is attached).

Explicitly rejected in this slice

  • JSC__VM__notifyNeedShellTimeoutCheck (VM.zig:154, bindings.cpp:5002) — jsc-shell-specific (--timeout CLI) trap with no possible zig-js consumer; same category as the already-documented SourceProvider__deref rejection on #163. Rationale recorded on the tracker.

Acceptance criteria

  • JSC__VM__notifyNeedWatchdogCheck sets a VM(CContextGroup)-wide trap flag; the interpreter's 1024-step checkpoint consumes it and re-checks the armed execution deadline (#302) on the executing thread, terminating evaluation (worker terminated) when the deadline has elapsed. No armed limit → consumed as a no-op (no spurious termination).
  • Trap + deadline plumbing uses atomics end-to-end so the notify is safe from a non-evaluating thread, matching JSC's atomic trap bits.
  • JSC__VM__notifyNeedDebuggerBreak requests a debugger pause (reason "pause") at the next statement boundary for every realm of the VM that has an enabled debugger session attached — the same path as CDP Debugger.pause. With no debugger attached the notify is inert, matching JSC.
  • Both exports tolerate a null JSC__VMRef.
  • Compiled runtime fixture tests/abi/home_private_value_shims.zig covers: trap with no limit armed (bounded loop completes), trap with future limit (no spurious termination), trap with elapsed limit (unbounded loop terminates + termination request attributed), debugger-break with no debugger attached (inert), null-VM tolerance. Fixture symbol count 288 → 290.
  • Home inventory 288/431 implemented, Bun inventory 280/421; bun-private-abi-audit + c-api-audit green; full zig build test shows zero new failures (worktree baseline-vs-candidate against the same pinned base).

Design constraints (as implemented)

  • zig-js keeps trap state on the context group (CContextGroup.need_watchdog_check) and consumes it in the tree-walker's existing step checkpoint (next to the stop_flag poll); there is no separate VMTraps word — the flag is the trap bit. Context carries watchdog_check_flag/watchdog_deadline_ns host pointers (same pattern as stop_flag), wired for the group primary and every realm.
  • The watchdog consumption and the #302 host watchdog thread are intentionally redundant (belt and suspenders), exactly as JSC's trap and Watchdog timer are.
  • CInspectorState.pause_requested is now std.atomic.Value(bool) so the cross-thread notify cannot race the runtime thread's consume; owner selection still goes through chooseInspectorPauseOwner.
  • No JSC API behavior is invented beyond the mapped semantics; deltas (termination stays terminal for the context per the #302 contract) are documented on the exports.
9 days ago

Evidence

Commits (on main):

  • 66836af0 feat(abi): notify VM watchdog-check and debugger-break traps (#304)
  • 0940549a docs(abi): publish VM trap notification evidence (#304)

Implementation

  • src/c_api.zig — CContextGroup.need_watchdog_check trap bit; both exports; watchdog_check_flag/watchdog_deadline_ns wiring for group primary, JSGlobalContextCreateInGroup realms, and test-helper groups; CInspectorState.pause_requested converted to std.atomic.Value(bool) at all six use sites.
  • src/context.zig — host-owned watchdog_check_flag/watchdog_deadline_ns fields (same pattern as stop_flag), forwarded into every built interpreter.
  • src/interpreter.zig — Machine trap fields + consumption at the 1024-step checkpoint: clears the flag, re-checks the armed monotonic deadline on the executing thread, terminates with worker terminated once elapsed.
  • tests/abi/home_private_value_shims.zig — 2 new externs (288 → 290 symbols), null tolerance, debugger-break inertness without sessions, disarmed + future-deadline no-op consumption on bounded loops, elapsed-deadline termination of while (true) {} with termination-request attribution.

Validation

  • Fixture: Home private value shims: 290/290 symbols linked; runtime matrix passed
  • Home private ABI audit: implemented-private=288, pending-private=143, unclassified=0
  • Bun private ABI audit: implemented-private=280, pending-private=141, unclassified=0
  • c-api audit: 117 declarations, 117 complete, 117 public symbols present, 296 private profile exports
  • Home public ABI: 50/50 symbols linked; runtime smoke passed
  • zig build test -Dtest-filter=c_api: 148/148
  • Full zig build test worktree baseline-vs-candidate on pinned base 1c368e84 (== origin/main at push): baseline 1137 passed; 2 failed, candidate 1137 passed; 2 failed — the identical two pre-existing failures (gc.test.Object fits the 128-byte GC slab…, wasm.decode.test.wasm.decode malformed declarations); zero new failures.

Sign in to comment on this issue.