ReviewOS

zig-utils/zig-js

Runtime: optimize promises, async, generators, and module graphs

#474
Open chris opened this 9 days ago ·
9 days ago

Parent: #459 Depends on: #460, #465, #467, #468

Goal

Optimize promises, async functions/generators, generators, microtasks, modules, and dynamic import as real application pipelines rather than isolated semantic features.

Scope

  • Reduce promise/reaction/job allocation and queue traffic with bounded owned storage and exact FIFO/realm semantics.
  • Optimize settled-promise fast paths, await suspend/resume, async stack metadata, generator activation reuse, and async-generator request queues.
  • Compile suspend/resume states through VM/native tiers with precise roots, handler/finally state, debugger visibility, and cancellation/termination checkpoints.
  • Optimize static/dynamic module graph loading, linking, namespace access, top-level await propagation, import attributes/defer, cache lookup, and graph teardown.
  • Profile worker/module lifecycle and shared/no-GIL microtask contention separately from single-context behavior.

No-workaround rules

  • No synchronous promise shortcut that changes job ordering, swallowed rejection, missing async stack, pooled activation with observable stale state, bypassed host hook, or unbounded module/job cache.
  • Network/filesystem loading remains an embedder/provider boundary; benchmarks use deterministic in-memory owned fixtures.

Acceptance

  • #460 adds promise chains, fan-in/fan-out, async loops, generators, async generators, module graphs, dynamic import, TLA, errors, and worker rows.
  • Forced tree/VM/native differentials preserve values, errors, job ordering, stacks, hooks, and exact suspend/resume state.
  • Moving GC, finalization, OOM, teardown, debugger, no-GIL, Worker, TSan, and lifecycle fuzz gates pass.
  • Profiles prove reductions in allocations, queue locks, resumes, runtime dispatch, and graph teardown cost with bounded memory.
  • Directly comparable expanded rows meet #459 performance gates without regressing existing call/closure rows.

Foundation

Existing runtime: src/promise.zig, VM activation stack, module graph in src/context.zig, Worker infrastructure under #1.

9 days ago

Promise allocation work for the representative async row landed on main as 6b49bf45.

Each intrinsic wrapper now avoids ObjectStorageState plus ObjectColdState unless a C-API host actually needs private data. Exact eight-lane bounded evidence shows an 11.0% median wall reduction with stable checksums and a small peak-RSS reduction. The added opt-in profile makes Promise, resolving-function, queue, lock, and process counts repeatable after warmup.

The focused continuation remains #512: one bounded sample still records 576,000 Promise creations and 9,748,601 uncontended state locks, and the full publication-scale row remains too memory/time intensive.

9 days ago

Follow-up performance work landed in perf(promise): elide serialized state mutexes (#512). It preserves Promise trace-sensitive GC recovery while eliding the Promise mutex only for serialized contexts; concurrent marking and parallel JS keep the mutex.

The bounded eight-lane workload records zero Promise mutex acquisitions after the change, improves median elapsed 4.2%, and reduces maximum RSS 15.4%. The parallel settlement case passes under TSan and the full unit suite passes.

9 days ago

GC scheduling progress landed in perf(gc): amortize nursery scans over job roots (#512). It amortizes precise nursery scans against the explicit Promise-job root frontier while preserving fail-closed remembered-set recovery and exact moving-nursery behavior.

The first 60,000-job eight-lane sample is 11.6× faster with a stable checksum, and the full 1,537-test unit inventory passes. The seven-sample run still accumulates promoted/collector pressure (19.2 GB at 1,248.43s), so #512 remains open for cross-sample reclamation rather than being closed on a first-sample win.

9 days ago

The cross-sample Promise/job retention blocker is fixed on main by perf(gc): reclaim quiescent promotion debt (#512), closing #512.

The final collector slice detects promotion debt against the last quiescent live baseline and performs the required heap-level sweep at a host boundary. It does not weaken FIFO jobs, moving GC, concurrent coordination, or explicit thresholds.

The exact 60,000-job × 7-sample × 8-lane async row now finishes in 217.76 s real time with all seven checksums equal to 920578560; median measured workload time is 26.409281125 s. The bounded row improves from 1.540351500 s to 0.980488667 s median, maximum RSS falls 21.9%, and peak physical footprint falls 47.8%. The full unit inventory passes with 1,537 tests, one expected skip, and zero failures/leaks.

This completes the focused memory-lifetime work required to make the representative async panel publishable. Broader async/module optimization acceptance remains tracked here and through #460/#459.

9 days ago

The module-loader hardening slice is complete in #849 via fix(modules): seed runtime graph indexes (#849). The persistent loader registry/source store and all graph-local specifier/export indexes now use lazy realm-secret keyed hashing while preserving cache identity, deletion/reload, cycles, namespaces, TLA, dynamic import, GC traversal, and existing lock ownership. Exact-parent module Test262 stayed 1,540/1,540 across the two relevant inventories with 0 flips; required-VM parity had 0 divergences.

Sign in to comment on this issue.