Consumer parent: zig-utils/zig-js#97 Related: #25, #26, zig-utils/zig-js#125
Evidence
The post-#26 shared-heap profile shows metadata-lock handoff still dominates fixed-shape allocation. Two controlled alternatives have now failed:
- #25 shortened each ~17-cell critical section with an O(1) splice but regressed 2/4-lane throughput.
- zig-js#125 retained 64 or 256 initialized objects across checkpoints, but longer publication bursts regressed 4/8 lanes by 13–40%.
Changing exclusive-section length or frequency therefore cannot solve the convoy. Owned-slab heaps already classify managed pointers without the global list or payload map; while no mark is active, allocation only needs to make new headers discoverable for the next collection.
Proposed slice
- For parallel bindings that guarantee all cells use owned storage, initialize a non-marking batch privately and publish it onto an atomic pending-header stack without taking
alloc_lock. - Use an active-publisher counter plus the existing marking transition as a gate: a collector arms marking under
alloc_lock, waits for pre-arm publishers to leave, folds the complete pending stack intoall, then whitens/traces. A publisher that observes the transition falls back to the existing locked born-grey path. - Aggregate live/byte/young accounting atomically once per fast batch. Collection threshold reads remain atomic in parallel mode; collection and teardown consume exact quiescent values.
- Retain the proven locked path for marking, concurrent born-cell handoff, non-owned storage, OOM prefixes, and single-mutator heaps.
Acceptance
- Prove no cell can publish between the collector's final pending drain and whiten/barrier arming.
- Preserve newest-first nursery prefix order, exact accounting, short-prefix/OOM ordering, and concurrent born-grey semantics.
- Add multi-mutator overlap tests that force publication/mark transitions and verify list/header/counter integrity under TSan.
- Pass the full zig-gc unit and TSan workflow.
- Exact-parent downstream A/B materially improves zig-js shared 4/8-lane object churn without regressing 1/2 lanes, direct, or independent contexts.