ReviewOS

zig-utils/zig-js

Roadmap: broad performance leadership with owned dependencies and no workarounds

#459
Open chris opened this 8 days ago ·
8 days ago

Goal

Make zig-js broadly faster than JavaScriptCore on representative JavaScript and WebAssembly workloads—not merely the current ten-kernel matrix—while preserving exact ECMAScript/WebAssembly semantics, precise/moving-GC safety, GIL-free correctness, bounded resources, and reproducible evidence.

This is the successor performance roadmap to completed #44. It does not reopen or diminish #44: the July 29 matrix already records 10/10 zig-js wins in all directly comparable modes. It expands the target to the performance surfaces that the current matrix does not cover and to the measured gaps that remain.

Current evidence boundary

Already green

The accepted clean-tree publication at 018517d2 measures runtime 14d94ec0:

  • direct warmed context: 10/10 wins, 2.32x geometric-mean throughput;
  • eight independent steady contexts: 10/10 wins, 2.53x throughput, 5.35x scaling;
  • eight independent cold lifecycles: 10/10 wins, 2.52x throughput, 5.67x scaling;
  • shared realm: 4.84x geometric-mean scaling at eight lanes, with no public-JSC-equivalent ratio.

Evidence: report and 1,540 raw samples.

Measured gaps

  • Wasm SIMD is only 0.10–0.14x JSC throughput at eight independent contexts because fixed-width SIMD currently executes through the portable interpreter rather than native vector instructions: report.
  • Shared object churn reaches only 1.48x eight-lane scaling in the latest accepted matrix, far below the near-linear compute rows.
  • Moving age-three GC is 0.63x its non-moving parent on high-survival work and 0.80x on shared mixed-survival work; shared moving pauses reach 110.56 ms with bounded rendezvous retries: report.
  • A no-GIL shape-mutation witness records 271,244 optimizer publications and 271,244 invalidations versus 40 publications and zero invalidations serialized, because Class-A invalidation is owner-wide.
  • Native optimizer publication is declared only on macOS AArch64. macOS x86-64 and Linux remain baseline/bytecode fallbacks.
  • The VM flat-slot model cannot represent block shadowing, TDZ, or per-iteration captured bindings, so substantial correct JavaScript never reaches bytecode or native tiers.
  • The comparison matrix does not cover strings, RegExp, JSON, Maps/Sets, typed arrays, promises/async, modules, classes, iterators, Intl, Temporal, proxy/accessor-heavy objects, large application graphs, warmup cost, code size, or memory efficiency.

Non-negotiable engineering contract

  • Owned dependency boundary. Production engine/package dependencies may only be zig-utils-owned sibling checkouts under ~/Code/** (currently ../zig-regex and ../zig-gc). Do not add a network package, vendored third-party runtime, system JS engine, C/C++ engine, or opaque binary dependency. External standards corpora and system JSC remain pinned, test/benchmark-only oracles, never library dependencies.
  • No workarounds. No source/function/benchmark recognition; no checksum shortcuts; no disabled safety/GC/barrier/checkpoint behavior; no benchmark-only tier selection; no silent fallback described as completion; no permanent suppressions; no threshold-only change presented as a root-cause fix; no unbounded cache/pool/reserve used to buy a score.
  • Semantics first. Every optimization preserves exceptions, evaluation order, observable effects, OOM atomicity, step/watchdog accounting, debugger/termination behavior, precise roots, moving relocation, invalidation, and no-GIL synchronization.
  • Causal evidence. Retained optimizations need exact-parent A/B evidence, profile attribution, identical work/checksums, memory/RSS accounting, and focused semantic/TSan gates. Rejected candidates stay documented and reverted.
  • No universal claims from microbenchmarks. Workload-scoped evidence remains explicit. Broader claims require broader representative matrices.
  • Portable correctness. Unsupported native backends must fail closed to exact lower tiers until they have the same differential, GC, sanitizer, and performance evidence as macOS AArch64.

Roadmap structure

Child issues are grouped into:

  1. performance evidence and anti-regression infrastructure;
  2. dependency sovereignty and owned tooling;
  3. bytecode, baseline JIT, optimizer, object model, and built-in hot paths;
  4. GC, allocation, shared-realm scaling, startup, and memory;
  5. WebAssembly native execution and SIMD/threads;
  6. backend portability and cross-tier differential verification.

Child issue map

Evidence and owned dependencies

  • #460 — representative dependency-free benchmark matrix
  • #461 — causal attribution and exact-parent regression infrastructure
  • #503 — cycles, cache behavior, energy, and thermal stability
  • #504 — independent suites and additional engines, out of tree
  • #462 — owned local dependency and offline-build enforcement
    • #463 — replace libffi with an owned native ABI dispatcher
    • #464 — consume and improve owned BunPress tooling locally
    • #497 — migrate Python/JS/shell gates and generators to owned Zig tooling

JavaScript frontend and execution tiers

  • #465 — universal semantically correct bytecode eligibility
  • #498 — fast no-JIT execution tier for restricted hosts
  • #499 — versioned bytecode, immutable realm snapshots, and AOT artifacts
  • #466 — general resumable baseline regions and precise stack maps
  • #467 — optimizer environments, closures, classes, iterators, and general control
  • #468 — call inlining, escape analysis, and scalar replacement
  • #469 — global value/loop/range/effect-aware optimization
  • #470 — unboxed representation selection
  • #471 — precise artifact invalidation and broader property ICs
  • #472 — native arrays, typed arrays, DataView, and indexed loops
  • #473 — owned string, RegExp, and JSON performance
  • #474 — promises, async/generators, microtasks, and module graphs
  • #475 — collections, Date, Intl, and Temporal
  • #496 — lexing, parsing, bytecode, and module compilation

GC, memory, startup, and concurrency

  • #476 — parallel/short shared precise collection pauses
  • #477 — survival-aware, copy-efficient moving generations
  • #478 — shared allocation/object-model scaling
  • #479 — cold context startup and teardown
  • #480 — bounded heap/cache/executable footprint
  • #481 — tiering, warmup, compilation cost, and code value
  • #482 — Thread/Worker/channel/join lifecycle
  • #502 — coordinated GC/JIT/Wasm/worker CPU and memory budgets

Native backends

  • #483 — backend-neutral codegen and W^X contract
    • #484 — macOS/Linux x86-64 baseline and optimizer backends
    • #485 — Linux AArch64/x86-64 native publication
    • #500 — Windows and restricted-JIT Darwin profiles
  • #501 — generated-code symbols, unwind metadata, and exact native stacks

WebAssembly

  • #486 — native-Wasm performance roadmap
    • #487 — owned native baseline compiler
    • #488 — native fixed-width and relaxed SIMD
    • #489 — profiling optimizing compiler
    • #490 — native atomics and shared-memory scaling

Cross-cutting release gates

  • #491 — differential fuzzing across every tier/backend
  • #492 — long-running latency, memory plateau, and cleanup
  • #493 — adversarial complexity and worst-case latency
  • #494 — compile/link/test feedback time
  • #495 — Zig/C/private/Objective-C embedding boundaries
  1. Measure and enforce first: #460, #461, #462, #503, and #504. These freeze the broader scorecard, causal/efficiency evidence format, dependency rules, and independent validation before implementation can optimize the wrong target. Begin #497 after #462 classifies the complete tooling graph.
  2. Attack proven largest gaps: #483 → #487 → #488 for the 7–10x Wasm SIMD deficit; #476 → #477/#478 for moving/shared allocation costs; #471 for the measured no-GIL invalidation storm.
  3. Broaden tier eligibility and optimization: #465 → #498 → #466 → #467, followed by #468–#472 and #481. Build #499 on the stable bytecode/no-JIT contracts. Backend work #483 → #484/#485/#500 proceeds in parallel once the shared contract is stable, with #501 required for publishable native attribution.
  4. Cover application hot paths: #473–#475, #479, #482, #495, and #496, each driven by frozen #460 rows rather than microbenchmark guesses.
  5. Prove sustained production behavior: #480, #491–#494, #502/#503, the full owned and independent matrices, cross-backend/restricted-host evidence, and only then the program-level completion claim.

An issue may start early for investigation/instrumentation, but it must not publish a performance completion result before its declared predecessors provide the required contract/evidence.

Program-level completion gate

  • Every child is complete with its own causal evidence and correctness gates.
  • The expanded representative matrix is fully defined before “win all benchmarks” is evaluated.
  • zig-js wins every directly comparable accepted row on the declared reference platforms, with no noise-qualified regression in the original #44 matrix.
  • Wasm SIMD reaches or exceeds JSC on every accepted integer, float, shuffle, and memory row.
  • Shared-realm allocation-heavy rows scale monotonically and meet their child issue floors without unbounded retained memory.
  • GC pause/throughput objectives are met on ephemeral, mixed, high-survival, and shared workloads.
  • Every declared native backend passes forced-on/off differential, moving-GC, no-GIL, TSan, termination, debugger, and artifact-lifetime gates.
  • Every declared no-JIT/restricted platform passes its fast-VM/AOT contract without attempting forbidden executable mappings.
  • Independent-suite evidence corroborates repository-owned results without adding a build/runtime dependency.
  • Throughput wins retain bounded cycles, cache traffic, energy, thermal behavior, memory, and code size on hosts that expose trustworthy measurements.
  • Production and documentation dependency audits enforce the owned-local policy.
  • README/docs claims are regenerated only from clean, committed raw evidence.

#44, #49, #52, #97, #132, #133, #144, #145, #146, #283, #426, #431, #432, #433, #434, #437, #438, #439, #440, #445, #446, #447, #448, #450, #451, #452.

8 days ago

#471 advanced in 092f34c7: Class-A shape invalidation is now artifact-local, preserves unrelated executable mappings, and retains safe lifetime across successive epoch rotations. The measured ReleaseSafe no-GIL witness moved from checked 294,094/294,094 publication/invalidation work to 40/0; suppression-free TSan needed 42 publications and only 2 exact retirements. The first two #471 acceptance boxes are complete; broader assumption classes, IC coverage, race matrix, and causal evidence remain.

8 days ago

Child #471 is complete and closed. 5e0c4023 publishes the exact-parent evidence: publications remain near necessary work (288→284), while owner invalidations and reclaimed executable mappings fall 281→4 (-98.6%), with exact-retirement/survivor/byte counters and zero post-quiescent retired mappings. The independent 1,540-sample hosted guard passed and preserved the scoped #44 contract.

Report: https://github.com/zig-utils/zig-js/blob/5e0c4023/docs/.data/jit-shape-invalidation-exact-parent-2026-08-02.md

8 days ago

#465 advanced on main with lexical TDZ/const bytecode execution in 88a3d794; full implementation boundary and evidence are recorded in the #465 update.

The slice removes the TDZ-only admission rejection for unambiguous lexical slots without flattening observable binding identities: same-name block shadowing and per-iteration captured cells remain explicit lower-tier barriers. It preserves ordinary initialized-let native eligibility and keeps the new checked operations fail-closed above bytecode. Full validation is 1,487/1,487 units, ReleaseFast test262 53,175/53,175 configured cases with 0 skipped/failures, focused TSan 1/1, and threadfuzz 400 programs/0 failures.

No dependencies or tooling scripts changed. During the full corpus evidence run, the recently broadened descriptor-invalidation path exposed a real recursive indexed-property lock; the separate root-cause fix 76fd2a06 restored Array 3,081/3,081 and Object 3,411/3,411 and is documented on #471.

8 days ago

Progress on the bytecode semantic-parity track: https://github.com/zig-utils/zig-js/commit/dbe906d7bff89aa7f5aab0686196418322dbb0c2 removes same-named lexical shadowing as an admission rejection by giving block, loop, switch, and catch bindings distinct activation slots.\n\nMeasured clean: units 1,488/1,488; ReleaseFast test262 53,175/53,175 total with zero skipped and 0 flips; threadfuzz 400/0; focused TSan differential 1/1. Fresh captured per-iteration loop cells remain the next explicit VM lexical boundary.

8 days ago

Progress on the roadmap VM flat-slot gap landed in https://github.com/zig-utils/zig-js/commit/aa6ced38be9d145b3d9126cb55d2d205f4337216.

Captured simple classic-for and for-of lexical heads can now remain in required bytecode and receive spec-correct fresh per-iteration environment cells. Uncaptured heads still use O(1) frame slots. The change covers TDZ ordering, ordinary abrupt completion, generator/async suspension, and environment-backed class capture with GC tracing and relocation.

Correctness evidence is recorded on #465: the full ReleaseFast test262 corpus stayed 53,175/53,175 with zero skipped, the 1,490-test unit suite passed, focused TSan passed, and threadfuzz completed 400 programs with zero failures. This is eligibility work only; it makes no throughput claim and does not close #459 or #465. Captured destructuring heads, captured body lexicals, labeled multi-environment jumps, and the rest of the roadmap remain.

No dependency or tooling-script changes are part of this commit. The owned BunPress checkout remains the docs renderer, and #497 already records the clarified durable-tool direction: selective TypeScript on the real Home engine, not blanket Python-to-Zig translation.

8 days ago

The roadmap VM eligibility gap narrowed again in https://github.com/zig-utils/zig-js/commit/c1160fa: captured yield-free array/object destructuring loop heads without default/computed evaluation now retain required bytecode with spec-correct fresh iteration cells, including generator and async suspension.

The full correctness evidence stayed green at 53,175/53,175 ReleaseFast test262 cases with zero skipped, 1,491/1,491 units, focused TSan, and 400/400 threadfuzz programs. This remains eligibility work, not a throughput claim. #465 stays open for destructuring default/computed evaluation, captured body lexicals, labeled multi-environment jumps, and its wider scope.

No dependencies or tooling scripts changed; BunPress and Home remain owned dependencies with the selective TypeScript/Home direction recorded in #497.

8 days ago

The roadmap VM eligibility gap narrowed again in https://github.com/zig-utils/zig-js/commit/7c8b5eacdf1bb4d9862d352c10a8e3193450209e.

Captured lexicals in repeated loop bodies now use fresh block, catch, or switch environments, including yield-free destructuring. Exact activation-local depth metadata carries ordinary and labeled break/continue across nested scopes and finally, while for-of preserves IteratorClose ordering. The tree-walker baseline also now preserves consecutive iteration labels instead of leaking an outer labeled continue past the loop. Unsupported native environment state remains fail-closed.

Correctness evidence stayed green: 1,493 units; focused TSan 2/2; ReleaseFast test262 53,175/53,175 configured cases with 0 skipped/failures and 0 flips; threadfuzz 400/0; docs 49 pages and 90 links. This is eligibility and semantic-parity work, not a throughput claim, so neither #459 nor #465 closes. No dependencies or tooling scripts changed; Home and BunPress remain owned dependencies, and the selective TypeScript-on-Home direction remains unchanged.

8 days ago

#465 advanced in 6b60d843: captured destructuring defaults, computed keys, nested patterns, and object rest now execute as real bytecode in fresh per-iteration environments, including generator/async suspension. Example: for (const {[key()]: value = fallback, ...rest} of inputs) no longer rejects the enclosing function or evaluates its key/default expressions through the tree-walker.

The slice preserves TDZ/const initialization, RequireObjectCoercible and ToPropertyKey ordering, IteratorClose, anonymous-function naming, abrupt completion, and exact lower-tier fallback above bytecode. Validation is 1,494/1,494 units, focused TSan 2/2, ReleaseFast test262 53,175/53,175 with zero skipped and 0 flips, threadfuzz 400/0, plus focused JIT/VM/relocation and docs gates.

The first #465 acceptance item is now complete. #465 and the roadmap remain open for the wider VM witness/no-GIL/debugger/moving-GC and tier-attribution gates. No dependency or tooling-script changes were made.

8 days ago

VM eligibility progress: a16c5634 implements destructuring catch bindings directly in bytecode. Computed/default/rest patterns, TDZ, iterator close, repeated captured cells, finally ordering, generator/async suspension, and moving-GC relocation are covered. Full ReleaseFast test262 remains 53,175/53,175 with 0 skipped (0 flips, no regressions); full unit suite is 1,496/1,496.

8 days ago

Advanced #465 with the forced real-corpus tier gate in 7a831095.

Four Test262 cases now run tree-walker vs required bytecode, and PR-249 join semantics runs required bytecode with JIT off in serialized and no-GIL modes. Examples cover catch rest/getter effects, IteratorClose, async-finally ordering, mixed await-using SuppressedError order, and Thread join identity/side effects. Harness helpers retain automatic provenance; corpus functions fail closed on fallback. The work also fixed async disposal after awaited rejection.

Measured: 1,500/1,500 units; normal + TSan witnesses green; threadfuzz 400/400; ReleaseFast Test262 53,175/53,175 with 0 skipped and 0 flips; docs link/build green. #465 now has its corpus-witness acceptance item checked and remains open for the wider matrix plus #460 attribution.

8 days ago

Foundation issue #460 is complete in 5a8afbe0.

The frozen V16 matrix retains all 18/18 representative families and every scored V15 contract unchanged, integrates completed #503 and #504 evidence panels, and reduces pending panels from 2 to 0. Structural, dependency, documentation, and aggregate benchmark harness gates are green. This closes the definition/evidence-contract prerequisite; it does not claim that #459 has won every row. The roadmap checkbox is updated.

8 days ago

Landed the linked #891, #892, and #893 diagnostics cluster in fix(errors): preserve JSC evaluation diagnostics (#891, #892, #893).

The tree walker and bytecode VM now share JavaScriptCore's syntax-less call subjects, tagged-template near form, construction/member evaluating spans, and assignment/delete object wording. Focused unit result: 3/3 passed. A before/after test262 sweep across the seven affected expression subtrees remained 1,193/1,193 passing (0 flips, 0 regressions).

8 days ago

Closed the optimized-tier follow-ups #890 and #894 in fix(jit): retain native diagnostic source (#890, #894).

Native artifacts now own immutable call/evaluation source tables keyed by bytecode origin; lookup remains lazy on the throw path. The warmed regression covers method calls, named/computed reads and writes, and fixed/spread construction. Verification: full test-parallel 2,314 passed / 1 skipped / 0 failed / 0 leaked; 34/34 optimizer-native tests; affected test262 subtrees remained 1,193/1,193 before and after (0 flips, 0 regressions).

Sign in to comment on this issue.