Parent: #140 Tracked by: #134, #163, #164
Scope
Implemented the six pending VM execution-control exports declared in the pinned Home source (packages/runtime/src/jsc/VM.zig @ 7ed99c02) and pinned Bun bindings (bindings.cpp @ 4982b91e), present in both private-ABI inventories:
JSC__VM__getAPILock—vm->apiLock().lock()(bindings.cpp:4906)JSC__VM__holdAPILock—JSLockHolder+ callback (bindings.cpp:4896)JSC__VM__releaseAPILock—vm->apiLock().unlock()(bindings.cpp:4912)JSC__VM__setExecutionTimeLimit—vm->ensureWatchdog().setTimeLimit(Seconds{limit})(bindings.cpp:4875)JSC__VM__clearExecutionTimeLimit—watchdog->setTimeLimit(noTimeLimit)(bindings.cpp:4869)JSC__VM__hasExecutionTimeLimit—vm->watchdog() && watchdog->hasTimeLimit()(bindings.cpp:2830)
Acceptance
- All six exports implemented in
src/c_api.zigwith the pinned semantics above - Compiled-fixture coverage in
tests/abi/home_private_value_shims.zig(287/287):- API lock: callback executes under
holdAPILock; recursive same-thread re-entry (JSLock is recursive per owner thread); real mutual exclusion proven by a second thread blocking until release; null-VM tolerance - Time limit:
hasreports armed state shared across sibling realms and isolated from foreign VMs; a 30 ms limit actually aborts a runningwhile (true) {}evaluation (termination_requestedset — attributable to the watchdog, not the 500M-step budget); limit stays armed after firing (JSChasTimeLimitsemantics);cleardisarms without clearing the termination request;+infmaps tonoTimeLimit; null-VM tolerance
- API lock: callback executes under
- Both inventories refreshed: Home 285/431 private implemented (146 pending), Bun 277/421 private implemented (144 pending); exactly six status flips per inventory
zig build test-home-private-abi(287/287 symbols linked; runtime matrix passed),bun-private-abi-audit,c-api-audit(117/117, 293 private profile exports),test-c-api,test-home-public-abi(50/50),zig build test -Dtest-filter=c_api(148/148) all green- Full
zig build testvalidated on a clean-HEAD worktree (baseline vs candidate, same pinned base5413726c): 1120/1121 both runs, the single failure being the pre-existing gc slab assertion from the in-flight wasm/gc track — zero new failures; sibling wasm work on the main tree untouched - README/docs counts + evidence updated; benchmark matrix unchanged
Evidence
- Commits:
7f6cbf17feat(abi): add VM API-lock and execution-time-limit exports (#302);e1b0755fdocs(abi): publish VM execution-control evidence (#302) — both pushed tomain - Fixture:
Home private value shims: 287/287 symbols linked; runtime matrix passed - Home audit:
implemented-private=285, pending-private=146, unclassified=0 - Bun audit:
implemented-private=277, pending-private=144, unclassified=0 - C-API audit:
117 declarations, 117 complete, 0 pending, 293 private profile exports
Design notes (as implemented)
- API lock maps
JSC::VM::apiLock()onto a per-CContextGrouprecursive lock (atomic mutex + owner thread id + depth). Same-thread nesting increments depth without deadlocking (JSLock recursion semantics); foreign threads get real mutual exclusion — proven in the fixture by a spawned thread blocking until release. - Execution-time limit maps
JSC::Watchdogonto a lazily spawned host-watchdog thread per context group — the contractContext.requestTermination()documents (src/context.zig:3202). On expiry it sets the grouptermination_requestedflag (existingTopExceptionScopemachinery materializes aTerminationErrorat ABI boundaries) and callsrequestTermination()so the interpreter's cooperative stop poll aborts running evaluation. The watchdog is joined inCContextGroup.destroybefore realm teardown. - Timeout mapping mirrors
JSC::Watchdog:+inf==noTimeLimit(disarmed), non-positive == fire at the next watchdog tick, NaN == armed but never fires (JSC comparison semantics), finite values saturate on overflow. One arming fires at most once (watchdog_fired_ns); re-arming fires again. - Documented platform delta: zig-js's host-watchdog contract treats termination as terminal for the context, whereas JSC allows VM reuse after
clearHasTerminationRequest. Called out in the code and indocs/abi/README.mdrather than hidden. - No symbol-only stubs (#259): the lock really serializes native threads; the watchdog really interrupts running evaluation (fixture proves both).
Deferred from the VM-lifecycle cluster (rationale recorded on #163): JSC__VM__create/deinit/deferGC and the Zig__GlobalObject__create*/destructOnExit trio.