ReviewOS

zig-utils/zig-js

Runtime: bound cross-subsystem scratch memory

#1027
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Parent: #502 Related: #480, #985

Executed baseline

On exact main at b4f5544d61036e296ebd0046579d0b991b509025, the focused coordinator witness

zig test src/runtime_threads.zig -lc --test-filter 'host internal work uses one reservation and queues excess background work'

passes and proves the existing schema-v7 boundary correctly admits one host-reserved compiler plus one general-slot compiler while queueing the third. RuntimeInternalWorkSnapshot records CPU requests, admissions, waiters, and wait time, but neither the coordinator nor the native/Wasm compiler entry points account or bound their temporary allocator bytes. GC auxiliary scratch is measured per Context by the opt-in runtime profiler, but it likewise has no process-wide admission policy. Concurrent contexts/hosts can therefore obey the CPU-slot contract while independently growing GC/JIT/Wasm scratch.

This is a resource-policy gap, not a leak or current correctness failure. Defaults must preserve existing unlimited behavior.

Required change

  • Add one process-wide, fail-closed byte-admission contract for temporary GC auxiliary, native-compilation, and Wasm-compilation storage.
  • Keep long-lived precise-heap cells, decoded Wasm modules, and published executable mappings/metadata in their existing ownership domains; they are not compiler scratch.
  • Preserve synchronous JIT/Wasm compilation, exact claim/invalidation/publication semantics, GC abort/rollback behavior, and existing CPU-slot priority/FIFO ordering.
  • A finite host/embedder limit may reject temporary growth as OOM, but a transient JIT scratch rejection must leave the tier retryable rather than caching semantic unsupportedness.
  • Publish coherent schema-versioned current/peak/requested/rejected/released bytes and waiter/admission counters by domain plus a reconciled process total.
  • Defaults remain unlimited; no benchmark/source recognition, hidden reserve, spin/sleep admission loop, or allocation moved outside the measured boundary.

Definition of done

  • Deterministic allocator tests cover exact alloc/resize/remap/free accounting, per-domain and total caps, overflow-safe rejection, release/re-admission, nested work, OOM rollback, and teardown-zero balance.
  • GC auxiliary storage uses the shared byte contract without entering the public Context heap limit or changing collector safety.
  • Baseline and optimizing JIT temporary analysis/lowering storage use the contract; executable mappings and adopted metadata remain separately owned.
  • Wasm decode/validation temporary storage is bounded without misclassifying the retained decoded module.
  • Coherent telemetry invariants hold under concurrent readers/writers and unsuppressed TSan.
  • Focused GC/JIT/Wasm/no-GIL gates and zig build test-parallel pass. The broader threadfuzz lifecycle profile has the same four failures on the exact parent and candidate and is tracked separately in #1028.
  • Exact-parent dedicated and oversubscribed evidence records unchanged checksums plus RSS/latency/throughput and the new byte gauges before any performance claim.
  • Parent issues #502 and #480 are updated with landed commits and evidence.
9 days ago

Landed the first slice in b4d773c (feat(runtime): bound process scratch memory).

The implemented contract is deliberately fail-closed at allocator admission rather than waiter-based: allocation-time parking can deadlock when the releasing owner depends on the blocked runtime path. The telemetry therefore records admission/rejection and release/re-admission, not scratch waiters.

What is now on main:

  • schema-v8 process snapshot with exact total and per-domain byte counters for GC auxiliary, native compilation, and Wasm compilation scratch;
  • default-unlimited total/per-domain limits, atomically replaceable at runtime;
  • exact policy rejection, backing-allocation rollback, release, current, and peak accounting;
  • GC auxiliary worklists routed through the process coordinator without changing their underlying private-vs-shared allocator policy;
  • teardown asserts the wrapper owns zero bytes.

Verification:

  • zig build -j1
  • standalone coordinator: 12/12 under unsuppressed TSan
  • focused real-collector integration: 5/5 normally and 5/5 under unsuppressed TSan, zero leaks

Next slice: route temporary baseline/optimizing JIT analysis and lowering allocations through native_compilation, while keeping executable code and adopted metadata under their existing long-lived owners. Transient scratch OOM will remain retryable rather than publishing a permanent tier rejection.

9 days ago

Landed the native-compilation slice in bfd6700 (feat(jit): account temporary compilation memory).

Implementation:

  • baseline numeric analysis, representation selection, block/fixup storage, return branches, and temporary PC-map rows now allocate through the process native_compilation scratch domain;
  • optimizer planning, lowering, loop-region fixups, return branches, and temporary PC-map rows use the same domain;
  • executable mappings and all retained PC/deopt/stack-map/native-operation/OSR metadata remain under their existing long-lived owners;
  • both tier state machines treat temporary allocation denial as retryable while preserving permanent rejection for unsupported input;
  • observed PC maps are explicitly transferred out of scratch ownership before publication.

Focused evidence on the final tree:

  • zig build -j1: pass
  • baseline+optimizer denial/fallback/retry case: 4/4 normally and 4/4 under TSan, 0 failed/leaked
  • focused JIT root: 130 passed, 1 platform skip, 0 failed/leaked normally
  • focused JIT root under TSan: 130 passed, 1 platform skip, 0 failed/leaked
  • observed metadata ownership subset: 6/6 normally and under TSan, 0 failed/leaked

This completes the JIT checklist item. #1027 stays open for its separate Wasm temporary-storage integration and remaining aggregate gates/evidence.

9 days ago

Landed the WebAssembly slice in 3b55e73 (feat(wasm): account temporary compilation memory).

Ownership boundary:

  • WebAssembly.Module continues to decode retained module IR into the Context/store allocator; published operand/label depth arrays remain in that module arena.
  • Only the validator's abstract operand/control/local stacks use the process wasm_compilation scratch allocator for retained modules.
  • WebAssembly.validate routes both its decoded module and validation work through wasm_compilation, because that module is destroyed before the call returns.
  • WebAssembly.validate now participates in the existing synchronous Wasm internal-work admission scope; public validity and compile-error behavior remain unchanged.

The focused denial/retry test sets the Wasm domain cap to zero and proves exactly two policy rejections (retained-module validation and validate-only decoding), zero live-byte drift, restoration/retry, successful instantiation, and a retained module whose lifetime does not remain charged as scratch.

Final-tree evidence:

  • standalone validator/decode root: 110/110 passed
  • focused Wasm scratch denial/retry test: 4/4 passed normally, 0 failed/leaked
  • same focused root under unsuppressed TSan: 4/4 passed, 0 failed/leaked
  • existing Module/validate/reflection behavior test: 4/4 passed
  • pinned wg-1.0 names.wast: 483 pass, 0 fail, 0 skip, 0 crash

This completes the Wasm decode/validation checklist item. #1027 remains open for the aggregate GC/JIT/Wasm/no-GIL/lifecycle gates, exact-parent resource/performance evidence, and parent issue updates.

9 days ago

Final evidence is now on main in four additional commits:

  • f149135f — paired schema-7 parent/schema-8 candidate compiler-pressure harness with cold, warm, and teardown phases;
  • 7b1ba792 — deterministic hardware metadata parsing in the Home runtime;
  • 24d7ff72 — four-process warmup floor and fail-closed RSD interpretation;
  • d55b4456 — 216 raw phase rows, dated report, methodology link, and checked docs manifest.

Published evidence:

The exact-parent run compares b4f5544d61036e296ebd0046579d0b991b509025 with 24d7ff72fad07fc37512fea6c09b7cca9bb9c325 using the same overlaid runner, workload checksum, Zig, zig-gc, and zig-regex revisions. It records nine fresh-process pairs after four discarded warmup processes at one lane and at 20 lanes (2× the 10-core host).

Accounting/lifecycle results:

  • exact checksums: 29,668,441 at one lane and 642,913,460 at 20 lanes;
  • candidate JIT-on native scratch requested/released p50: 24.30/24.30 MiB at one lane and 486.03/486.03 MiB at 20 lanes;
  • native peak p50: 0.60 MiB and 4.13 MiB respectively;
  • zero policy rejections and allocator failures under the default-unlimited policy;
  • native scratch is zero before and after every completed compilation phase;
  • Context-owned GC auxiliary scratch remains live through the warm control and aggregate process scratch is zero after every teardown;
  • peak RSS changes by -0.1% to -0.5% across the four cold rows.

Two dedicated timing series exceed 10% RSD (maximum 28.92%), so the report keeps every sample and explicitly makes no speed claim from those rows. The busy JIT-on series remains bounded, but this issue's acceptance evidence is resource accounting, lifecycle, checksums, and absence of a material RSS increase—not a throughput claim.

Completed final-tree gates so far:

  • coordinator allocator policy/rollback/concurrent telemetry: 3/3 normal and 3/3 unsuppressed TSan;
  • real-collector GC policy + teardown: 2/2 normal and 2/2 unsuppressed TSan;
  • JIT denial/fallback/retry: 1/1 normal and 1/1 unsuppressed TSan;
  • Wasm temporary compilation denial/retry: 1/1 normal and 1/1 unsuppressed TSan;
  • pinned wg-1.0 suite: 73 files, 18,795 pass, 0 fail, 475 declared skips, 0 crashes;
  • deterministic no-GIL verification: 8/8 normal and 4/4 unsuppressed TSan;
  • docs link gate: 378 files; checked BunPress build: 51 pages.

The lifecycle threadfuzz profile exposed a separate pre-existing property-backed waitAsync notification failure. Exact parent and current main both produce the same four failures from the same 224 programs/seeds; it is tracked without suppression in #1028.

zig build test-parallel -Dunit-jobs=1 -j1 is currently running from a clean archive snapshot against the real collector. #1027 remains open until that terminal result is recorded and parent issues #502/#480 are updated.

9 days ago

#1027 is fully implemented and verified on main.

Implementation and evidence commits:

  • b4d773c2 — shared fail-closed byte admission and schema-v8 telemetry
  • bfd6700a — JIT temporary compilation accounting and retryable denial
  • 3b55e737 — Wasm temporary decode/validation accounting
  • f149135f, 7b1ba792, 24d7ff72, d55b4456 — paired exact-parent benchmark and published evidence
  • 8794ccc5 — complete deterministic allocator accounting coverage

Final verification:

  • runtime scratch allocator: 3/3 normal and 3/3 unsuppressed TSan
  • focused real-collector GC, JIT denial/retry, and Wasm denial/retry tests: pass normally and under unsuppressed TSan
  • no-GIL deterministic threadfuzz verify: 8/8 normal and 4/4 unsuppressed TSan
  • packed wg-1.0: 18,795 pass, 0 fail, 475 declared skip, 0 crash
  • full zig build test-parallel -Dunit-jobs=4 -j1: 2,567 passed, 1 skipped, 0 failed, 0 leaked in 893.7s
  • docs links: 378 files, all resolve; BunPress checked build: 51 pages

Exact-parent evidence: report · raw JSON

The generic threadfuzz lifecycle profile reports the same four property notify woke wrong count pn0 failures across 224 programs on both exact parent and candidate. This is not a #1027 regression and is tracked in #1028; no suppression or workaround was added.

Parent updates are posted on #502 and #480. Closing this focused child while those broader umbrellas remain open.

Sign in to comment on this issue.