ReviewOS

zig-utils/zig-js

JIT: execute calls, effects, and completion dispatch inside exception regions

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

Parent: #146 Depends on: #436 Blocks: #432, #429 Roadmap: #134

Turn the exact exception/control metadata completed by #436 into fully executable optimized regions. Effects are not claimed native until their result/exception convention and every completion path are real.

Acceptance

  • Define a backend-neutral native operation result convention that distinguishes normal values, catchable exceptions, termination, watchdog expiry, debugger/host traps, and invalidation.
  • Lower supported calls/constructors and selected property/coercion/object effects without duplicating observable work; publish normal and exceptional SSA value edges after the operation.
  • Execute end_finally dispatch for normal, throw, return, break, and continue completions, including nested finally propagation and override rules.
  • Preserve exact locals, operand stack, completion values, handler stacks, IP/steps, stack maps, GC relocation, and fail-closed fallback at every operation boundary.
  • Pass focused interpreter/baseline/optimizer differentials for successful and throwing host/JS calls, nested catch/finally, return/throw through finally, abrupt jumps, termination, watchdog/debugger/host traps, and no-GIL invalidation.
  • Pass the relevant normal, no-GIL, leak, and TSan gates; update optimizer docs, README capability wording, and benchmark evidence only when workloads change.

Implemented slices

  • The backend-neutral result ABI and owned immutable descriptor metadata are installed at managed entry and OSR.
  • to_numeric; unary -/+/!/~, typeof, increment/decrement, ToString, and ToPropertyKey; profiled dynamic arithmetic, relational, and equality operators; exponentiation; every bitwise/shift operator; named/computed property reads and writes; all three membership forms; ordinary, method, direct-eval, explicit-this, spread, and proper-tail calls; ordinary/spread construction; and every object/array literal allocation and initialization effect are executable SSA values on deterministic AArch64 paths. void lowers directly to undefined.
  • The tree walker, bytecode VM, and optimizer callback share canonical unary and binary semantic paths. Binary inputs are rooted together and coerced left-to-right, preserving Number/BigInt results, mixed-type and Symbol errors, and one-shot exceptions.
  • Race-safe per-bytecode operand profiles retain the guarded inline Number path at numeric sites and select rooted runtime semantics only for sites that observed dynamic inputs.
  • Named operations copy property/private names into artifact-owned metadata. Multi-input operations stage contiguous inputs; writes also stage and return the right-hand side. dup and swap remain zero-cost SSA aliases.
  • Writes preserve canonical accessors, proxy traps, assignment results, GC barriers, and computed-key ordering. Membership preserves canonical coercion, proxy has, Symbol.hasInstance, prototype, private-brand, and TypeError behavior.
  • Runtime operations in a common prefix own exact steps before subsequent Boolean control. Strict terminal-step proofs remain separate, preserving throw/finally dispatch.
  • Generated code publishes exact operation/deopt/origin/step state and distinguishes normal, uncaught throw, exceptional continuation, OOM, invalidation, and fail-closed operation-trap exits.
  • Callback stack maps root every live scratch recovery value and explicit operation input. The VM validates descriptor identity, payload, opcode, arity, names, and ranges before observable work.
  • Exceptional targets transactionally reconstruct locals, operand stack, completion values, and remaining handlers, then resume catch/finally bytecode without replay.
  • Native dynamic-finally dispatch now selects normal, throw, return, break, and continue in generated AArch64 code, reconstructs the exact pre-dispatch state, consumes the completion once, and propagates through remaining catch/finally handlers.

Starting point and current evidence

#436 provides exact pre-effect frame states, owned exceptional catch/finally targets, explicit throw SSA edges, deterministic native catch/finally bodies, and canonical end_finally side exits.

At b6d1b62, #439 is complete. Generated AArch64 code executes end_finally, returns a distinct status for all five completion kinds, publishes the exact recovery point and completion value, and fails closed on malformed kinds. The VM transactionally reconstructs the frame, consumes the selected completion without replay, and propagates throws, returns, breaks, and continues through remaining catch/finally handlers. Focused full-root evidence passes 7/7 under TSan and the broad JIT gate passes 164/164 under TSan, with zero failures, skips, or leaks. README and optimizer documentation match the implementation; benchmark scores are unchanged because the published workloads do not exercise this control path. CI tracks the pushed main head. No backward-compatibility constraint applies; prefer the cleanest operation/result ABI.

9 days ago

Checkpoint 5a227e3 (implementation e66f4f4): the backend-neutral operation ABI is now concrete. NativeOperationDescriptor selects the bytecode operation, contiguous scratch inputs, origin, and exceptional target. An appended NativeFrame callback/result area preserves normal values, catchable exceptions, termination, watchdog expiry, debugger traps, host traps, OOM, and invalidation as distinct statuses without changing existing frame offsets.

The focused ABI witness passes 3/3 normally and under TSan. README and optimizer docs are current; benchmark scores are unchanged. Acceptance item 1 is complete. Next is descriptor ownership/lowering and the VM dispatcher.

9 days ago

Checkpoint 443f6f0 (implementation 6c1d86a): operation descriptors now have full artifact ownership. Lowering derives the exact required operand suffix, stages it into a contiguous scratch range, links the exceptional-target index, and transfers immutable descriptors through Program into CompiledCode. Artifact destruction and owner invalidation release the metadata with the code.

The compiled pre-call witness checks bytecode op, input range, origin, exceptional disposition, scratch sizing, and descriptor equality after publication. The combined optimizer TSan gate passes 87/87 with zero failures/leaks. README/docs are current; benchmarks are unchanged.

Next: install the VM dispatcher in NativeFrame, mark staged pointer roots at the callback boundary, then make the first selected coercion/call operation executable.

9 days ago

Checkpoint 2dcc3c9 (implementation 2e401c0): managed entry and loop OSR now install the descriptor-backed VM operation dispatcher. It validates metadata ids/ranges before reading scratch, implements the first runtime operation (to_numeric), returns normal/catchable/OOM/host-trap outcomes through the new ABI, and fails closed for malformed or unsupported descriptors.

The dispatcher witness passes 3/3 normally and under TSan; the prior descriptor/lifecycle batch passes the combined optimizer TSan gate at 87/87. Generated code still cannot invoke the callback until callback stack maps and status branches land, so no native-effect claim is made yet. README/docs are current; benchmarks are unchanged.

9 days ago

Landed the first executable runtime-operation slice in eb18ffe, documented in b638ac0. Handler-free AArch64 to_numeric now flows through SSA as a real value, publishes exact deopt/origin/step state, roots all live callback scratch values, and exits distinctly for value, uncaught throw, OOM, invalidation, and malformed/host trap. Real postfix object coercion is covered: ten successful valueOf calls are checked before a hot native call propagates the exact thrown value. Active-handler coercion still uses the pre-effect side exit by design. Focused to_numeric, caught-fallback, and pre-call batches pass; test-jit -Dtsan=true passes 148/148. Broader acceptance boxes remain open for handler continuations, calls/constructors, completion dispatch, and the full gate matrix.

9 days ago

Landed native exceptional continuation in c8639bb and documented it in 95e5675. A throwing to_numeric now selects owned catch/finally metadata, transactionally reconstructs the pre-effect state, removes the consumed handler, injects the exception or throw completion, and resumes bytecode without replay. Hot object witnesses prove valueOf executes once for catch and finally/rethrow paths. Focused batches are green and test-jit -Dtsan=true passes 149/149. Acceptance remains open for calls/constructors, more effects, native end_finally dispatch, and the complete gate matrix.

9 days ago

Ordinary call execution landed in 19474871; README and optimizer documentation landed in c660c72. AArch64 now lowers call as an effectful SSA value, stages a contiguous callee plus dynamic argument vector, precisely roots every input, invokes once with undefined this, and returns the value to the native suffix. Throws reuse the owned exception continuation: focused witnesses prove exact catch and canonical finally rethrow without replay, plus malformed zero-arity rejection. The batched broad optimizer gate passes 149/149 under TSan. Published benchmark scores are unchanged because this path is not used by the benchmark matrix. Main CI: https://github.com/zig-utils/zig-js/actions/runs/29891461400

9 days ago

Follow-up batch landed in 02a6aba with documentation in 7ec0384. AArch64 now executes explicit-this calls and new-call construction through the same rooted runtime-operation ABI. Focused witnesses cover a three-input receiver vector, native constructor completion, mixed native-construction plus property fallback, and a constructor throw caught once without replay. One broad gate covered the whole batch: 150/150 under TSan. Benchmark scores are unchanged. Main CI: https://github.com/zig-utils/zig-js/actions/runs/29891834824

9 days ago

Property/method batch landed in 5603c4b with docs in c0a502b. Named and computed reads are effectful SSA values; named payloads are copied into artifact-owned metadata, computed receiver/key pairs are precisely rooted, and zero-cost dup/swap enables complete named and computed method-call chains. Witnesses cover null-before-key ordering, one-shot getter/key/callee throws, malformed missing payloads, and exact catch continuation. The batched broad optimizer gate passes 152/152 under TSan. Benchmark scores are unchanged. Main CI: https://github.com/zig-utils/zig-js/actions/runs/29892528373

9 days ago

Native named/computed property writes landed in e81430d; concise capability docs landed in df57bc7.

The optimizer now models both writes as effectful SSA values, keeps receiver/key/RHS live, roots all callback inputs, returns the assignment RHS, and dispatches through the canonical VM mutation path. Focused witnesses cover ordinary writes, setters, proxy traps, key-coercion failure, exact catch resumption, and RHS-before-nullish/key ordering. The batched broad JIT gate is green under TSan: 155/155.

Remaining #439 work is still eval/spread/tail-call execution, additional object/array/coercion effects, and native dynamic-finally completion dispatch. Latest main CI: https://github.com/zig-utils/zig-js/actions/runs/29893229195

9 days ago

Native membership effects landed in 749c2aa; concise capability docs landed in d7773bf.

in, instanceof, and private-brand in are now effectful Boolean SSA values on deterministic AArch64 paths. Their inputs are precisely rooted, private names are artifact-owned, and dispatch reuses canonical VM behavior for key coercion, proxy has, Symbol.hasInstance, prototypes, private brands, TypeErrors, and exact caught exceptions. Focused checks and the batched TSan JIT gate pass; the latter is 156/156.

Remaining #439 work is eval/spread/tail-call execution, additional object/array/coercion effects, and native dynamic-finally dispatch. Latest main CI: https://github.com/zig-utils/zig-js/actions/runs/29893818314

9 days ago

Native unary/coercion effects landed in 7ce8b63; concise capability docs landed in 4f8ffa0.

Unary -/+/!/~, typeof, increment/decrement, ToString, and ToPropertyKey now execute as rooted runtime-operation SSA values; void lowers directly to undefined. Tree-walker, bytecode, and optimizer execution share the same semantic implementation. The batch also adds exact operation-step ownership in a common prefix before Boolean control while retaining strict terminal/finally step proofs.

Focused witnesses and the required broad rerun pass; the TSan JIT gate is 157/157. Remaining #439 work is binary coercions, eval/spread/tail calls, additional object/array effects, and native dynamic-finally dispatch. Latest main CI: https://github.com/zig-utils/zig-js/actions/runs/29894681886

9 days ago

Binary-coercion batch landed directly on main in edae6d74 with matching README/optimizer documentation in b169dbf5.

Native AArch64 runtime operations now execute exponentiation plus &, |, ^, <<, >>, and >>> as rooted two-input SSA values. The callback delegates to the canonical applyBinary path, so left-to-right object coercion, Number/BigInt results, mixed-type and Symbol errors, and one-shot exceptional continuation remain shared with interpreter and bytecode execution.

Evidence:

  • focused binary-coercion gate: 5/5;
  • exact exceptional-target gate: 3/3;
  • native dispatcher-validation gate: 4/4;
  • broad optimizer TSan gate: 158/158, zero skipped/failed/leaked.

The README and optimizer guide are current. Published benchmark scores are unchanged because the committed comparison workloads do not exercise this slice. Latest main CI: https://github.com/zig-utils/zig-js/actions/runs/29895064566

Remaining #439 work is dynamic arithmetic, eval/spread/tail-call forms, additional object/array effects, and native dynamic-finally dispatch.

9 days ago

Profile-selected dynamic arithmetic landed directly on main in bb28a32b, with matching README and optimizer documentation in a5981afb.

Each arithmetic/comparison/equality bytecode now owns a race-safe operand-kind profile. Observed Number-only sites retain the existing guarded inline lowering; sites that observed dynamic operands become rooted two-input runtime SSA values and delegate to canonical binary semantics. This covers +, -, *, /, %, <, <=, >, >=, loose equality, and strict equality without weakening the numeric fast path.

Evidence:

  • dynamic arithmetic: 6/6 normally and 6/6 under TSan;
  • instruction-local/race-safe profiles: 4/4;
  • exact exceptional targets: 3/3;
  • dispatcher validation: 4/4;
  • broad optimizer TSan gate: 161/161, zero skipped/failed/leaked.

String, Number, BigInt, mixed-type rejection, loose/strict equality, Boolean control, left-to-right one-shot throws, explicit input roots, and malformed arity are covered. Benchmark scores remain unchanged because the committed comparison workloads do not use these callbacks. Latest main CI: https://github.com/zig-utils/zig-js/actions/runs/29895820403

Remaining #439 work is eval/spread/tail-call forms, additional object/array effects, and native dynamic-finally dispatch.

9 days ago

Completed on main through b6d1b62 (implementation 628b262, documentation b6d1b62). Native AArch64 now dispatches normal, throw, return, break, and continue completions, preserves exact IP/steps/value/handler recovery, propagates nested catch/finally exactly once, and fails closed on malformed completion kinds. Evidence: broad zig build test-jit -Dtsan=true 164/164 and filtered full-root zig build test -Dtsan=true -Dtest-filter="native finally" 7/7, with zero failures, skips, or leaks. Published benchmarks are unchanged because their workloads do not exercise this path.

9 days ago

Reopened after the post-#132 full unit gate exposed four independently reproducible failures in the #439 test surface: the generator-spread VM test is rejected by top-level accessor lowering; the proper-tail-call test publishes call (opcode 91) instead of tail_call (93); the literal-construction test fails to publish its expected artifact; and the spread-throw test assumes a bytecode function that is absent. These reproduce individually at ba30d4f and are not caused by test ordering. I am fixing the tests/underlying lowering as the next isolated batch, then will rerun the expensive full gate once.

9 days ago

Repair complete on main through f55cb51. 75dd7a8 corrected the initial effect witnesses; 7299b7b enabled ordinary-function array-literal spread so the throwing spread path genuinely lowers through bytecode/native effects, moved strictness out of optimized bodies so directive strings are not mistaken for movable constants, and updated stale side-exit assertions to inspect the actual native descriptors/deopt points; f55cb51 isolates direct property-cache tests from process-wide parallel IC mode.

Evidence on the clean head: grouped optimizer VM gate 64/64 normal and 64/64 under suppression-free TSan; complete unit/no-GIL/GC gate 1,402/1,402 with zero skips, failures, or leaks. The reopened regressions are resolved, so #439 is complete again.

Sign in to comment on this issue.