Skip to content

Threads: nursery/generational GC and context lifecycle performance #16

Description

@chrisbbreuer

Parent: #1

Goal

Make the GC-backed threaded runtime competitive for allocation-heavy and create/destroy-heavy embedders without weakening no-GIL correctness.

Current baseline

  • GC cells use size-class slabs with lazy bump allocation, freelist reuse, per-size-class fast-path locks, tail-slab trimming, exact bucket counters, and slab-owned bulk teardown.
  • New cells enter a non-moving, one-cycle nursery. Demand-driven quiescent minor collection traces young roots plus remembered old owners, reclaims dead young cells, and immediately tenures survivors.
  • Owner-aware strong barriers cover Object, Environment, Promise, collection, prototype, generator-request, and native synchronization side-record stores. Broad maybe-managed root/barrier inputs use exact live-payload membership instead of candidate-header peeks, with zig-gc's payload index keeping that safety off the O(live-cells) path. Weak-container barriers preserve WeakRef, WeakMap/WeakSet, and FinalizationRegistry semantics. Mutable type-erased side-cell kinds are conservatively rescanned.
  • Remembered-set allocation failure falls back to the existing precise full collector. Explicit collectGarbage() remains full-heap; no-GIL mid-script collection remains the abort-safe full concurrent protocol.
  • gc-profile separates lifecycle, allocation, backing, churn, finalizer, and nursery attribution. Focused cases are available with -Dgc-profile-case='<exact table name>' so nursery and lifecycle rows can be checked without paying for the full profile matrix.
  • Nursery rows now include object-only and array/object allocation shapes, young/reclaimed/promoted counts and bytes, survival/reclamation percentages, the computed next nursery threshold, and repeated-cycle threshold drift. Sizing policy can be judged from pause, survival, reclamation, allocation shape, and threshold trajectory together.
  • zig-gc records last-minor young/reclaimed/promoted byte totals, uses gradual threshold decay after low-survival cycles, caps upward threshold growth to the observed young batch size, trims oversized empty collector scratch after post-spike collections, and serializes allocation metadata for single-mutator concurrent marking without opting that mode into the multi-mutator parallel-GC protocol.
  • Long-lived context reuse remains materially cheaper than create-per-task use; the supported guidance is a bounded pool per isolation domain, one task at a time per context unless the host intentionally shares a realm, and quiescent collectGarbage() at measured task boundaries.

Milestones

  • Design and implement a nursery/young-generation policy with remembered-set coverage.
  • Add promotion/tenuring and full-heap fallback invariants.
  • Prove write-barrier coverage for old-to-young references under no-GIL mutation.
  • Reduce intrinsic context bootstrap and teardown work further, or document a supported pooling/reuse pattern with measured bounds.
  • Add allocation/collection pause, promotion, byte-pressure, survival-rate, allocation-shape, and next-threshold metrics to gc-profile.
  • Add focused GC profile filters so small nursery/lifecycle checks do not require the full matrix.
  • Tune first-pass nursery threshold policy: gradual decay after low-survival cycles and capped growth after high-survival fixed-size bursts.
  • Trim oversized empty collector scratch so pooled contexts do not retain arbitrary one-off spike capacity.
  • Add focused unit, TSan, concurrent-GC regression, corpus, weak/finalization, and churn/fuzzer coverage for the policy.

Remaining work

  • Tune nursery sizing and pause/throughput policy across more workload shapes using the focused profile rows.
  • Decide from measurements whether multi-age, copying/moving, or parallel minor collection earns its complexity.
  • Continue Context lifecycle reductions where profile evidence shows a bottleneck; keep the pooling guidance current as those costs move.
  • Keep old-to-young barrier coverage, exact maybe-managed safety, scratch-lifetime behavior, and weak semantics regression-gated as mutable engine state evolves.

Acceptance criteria

  • No regression in full unit, PR-249, suppression-witness, test262-parallel, or concurrent fuzzer gates.
  • Allocation and lifecycle changes have before/after profile evidence.
  • The implementation docs distinguish slab allocation, nursery policy, full/concurrent collection, and embedder lifecycle guidance.

Activity

  1. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Landed a Context teardown/lifecycle reduction across the collector boundary:

    • zig-gc 921696b perf(heap): support slab-owned bulk teardown
    • zig-js 37d1881 perf(context): skip per-cell frees at teardown

    What changed

    • Added Heap.deinitRetainingCellStorage() for embedders that finalize every cell and reclaim the backing slab/arena wholesale immediately afterward.
    • The normal Heap.deinit() contract is unchanged and still individually frees cell storage.
    • Context.destroy() enters the existing single-owner GcCellBacking teardown mode, runs all finalizers and collector side-buffer cleanup, skips per-cell cell-storage frees, then releases whole slabs.
    • Bucket-shaped object/collector side allocations are still freed normally while finalizers run.
    • Added an explicit fallback to ordinary heap deinit if a GC heap ever exists without gc_cell_backing.
    • Added exact gc-profile attribution: skipfree must equal finalized cells.

    The profile witness reported:

    • empty explicit-GC context: 1,044 finalized cells / 1,044 skipped cell frees;
    • empty no-GIL context: 1,079 / 1,079;
    • object-heavy explicit-GC destroy: 11,044 / 11,044;
    • object-heavy no-GIL destroy: 11,079 / 11,079.

    Wall-clock lifecycle rows remain noisy across local runs, so this update does not overstate a timing percentage; the eliminated dispatch count is exact and regression-tested.

    Verification

    • zig-gc: zig build test
    • zig-gc: zig build test -Dtsan=true
    • zig-js: focused bulk-destroy test and focused TSan witness
    • zig-js: zig build test -Dtest-filter=enable_gc
    • zig-js: zig build test -Dtsan=true -Dtest-filter=enable_gc
    • zig-js: zig build test -Dtsan=true -Dtest-filter=parallel_js
    • zig-js: zig build test
    • zig-js: zig build threads-test — 227/227 files passed on the clean rerun
    • zig-js: waiter-storm regression case passed twice in isolation and in the clean full corpus
    • zig-js: zig build threadfuzz -Dfuzz-iters=20
    • zig-js: zig build threadfuzz -Dfuzz-verify=true -Dfuzz-iters=300
    • zig-js: zig build gc-profile
    • zig-js: bun run docs:build

    The nursery/remembered-set/promotion work and broader Context lifecycle milestone remain open.

  2. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Landed the first complete nursery/generational milestone:

    • zig-gc 2d88e78 feat(heap): add one-cycle nursery collection
    • zig-gc 9575198 fix(heap): rescan binding-selected old cells in minor GC
    • zig-gc 2b10d10 feat(heap): expose old-space collection pressure
    • zig-js e19645d feat(gc): add quiescent nursery collection

    What is complete

    • Non-moving one-cycle nursery: dead young cells are reclaimed and every survivor is tenured in one minor cycle.
    • Owner-aware remembered old containers plus a conservative child-only compatibility path.
    • Separate weak-container barriers, exact minor-cycle WeakRef/WeakMap/WeakSet/FinalizationRegistry handling, and ephemeron fixed-point processing.
    • Binding-selected rescanning for mutable type-erased old side cells, keeping Object/Environment tracing on the narrower remembered-owner path.
    • Remembered-set OOM fallback to full GC.
    • Old-space pressure separated from total heap pressure, so large young batches receive a minor cycle before forcing a full trace.
    • Demand-driven quiescent policy at script/module boundaries. Explicit collectGarbage() remains full-heap; parallel mid-script GC remains the existing abort-safe full collector.
    • Owner-aware barriers wired through properties/elements/accessors, environments, promises, Maps/Sets, prototypes, async-generator requests, weak collections, and finalization records.
    • gc-profile now reports boundary pause, young input, reclaimed cells, promoted cells/bytes, and minor/full deltas.

    The final profile witness (512 object graphs, 1/4 retained, full trigger isolated) reported:

    mode pause young reclaimed promoted promoted bytes minor/full
    explicit GC 2.94 ms 1,543 1,286 257 156,256 1 / 0
    threaded no-GIL 3.10 ms 1,543 1,284 259 157,144 1 / 0

    These are local point measurements, not a general performance guarantee; the row is now repeatable for future tuning.

    Locally verified

    • zig-gc: zig build test
    • zig-gc: zig build test -Dtsan=true
    • zig-js: focused nursery normal and TSan tests
    • zig-js: concurrent generator/iterator-helper regression test
    • zig-js: zig build test
    • zig-js: zig build test -Dtsan=true (full all-tests sweep)
    • zig-js: zig build test -Dtsan=true -Dtest-filter=parallel_js
    • zig-js: zig build threads-test — clean 227/227
    • waiter-table storm: three consecutive focused passes plus the clean full-corpus pass after making nursery collection threshold-driven
    • zig build threads-test -Dthreads-parallel-js=true -Dthreads-case=sync/condition-wait-notify.js
    • zig build threadfuzz -Dfuzz-iters=20 — 0 failures
    • zig build threadfuzz -Dfuzz-verify=true -Dfuzz-iters=300 — 0 failures
    • zig build gc-profile
    • bun run docs:build

    The sharded no-GIL PR-249 TSan corpus, suppression witness, test262-parallel slice, and longer/nightly fuzz sweeps remain CI-backed gates; this update does not claim they were newly rerun locally.

    Remaining in #16

    • Tune adaptive nursery sizing and pause/throughput behavior.
    • Measure before adding multi-age, moving/copying, or parallel minor collection.
    • Continue Context create/destroy reductions and publish reuse/pooling guidance.
  3. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Nursery follow-up landed in b9dd15d fix(gc): remember native synchronization edges.

    A focused Debug condition asyncWait profile exposed a young Promise reclaimed while reachable only from an old Condition wrapper's native queue. Lock, Condition, and ThreadLocal side records now retain their wrapper as the owner for remembered-set barriers; queued lock jobs, async condition waiters/lock edges, and ThreadLocal map values are covered.

    The new deterministic regression tenures the wrappers, stores young values only in native records, forces a minor collection, and requires condition reacquire plus ThreadLocal reads to survive. It passes normally and under TSan, the focused profile passes in Debug and repeated ReleaseFast runs, the complete profiler is green, and the clean corpus remains 227/227.

  4. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Landed 134c203 (docs(gc): document context pooling guidance (#16)).

    What changed:

    • documented the supported context reuse pattern: bounded pool per isolation domain, one task at a time per context unless the host intentionally shares a realm, and quiescent collectGarbage() at measured task boundaries;
    • documented when to destroy instead of reuse: realm/global/module reset requirements, unreleased host handles, untrusted code state pollution, or live Worker/Thread activity;
    • tied the guidance to gc-profile rather than claiming universal timings.

    Local profile evidence from this run:

    • zig build gc-profile
    • explicit GC task lifecycle: recreate/evaluate/destroy 26,015,541 ns/task, reuse+GC 3,897,863 ns/task, 6.67x ratio;
    • threaded no-GIL GC task lifecycle: recreate/evaluate/destroy 25,567,857 ns/task, reuse+GC 3,817,355 ns/task, 6.69x ratio;
    • the reuse row is one warmup evaluation, then 40 tasks in one context with collectGarbage() every 10 tasks.

    Validation:

    • bun run docs:build
    • git diff --check

    Remaining #16 work stays focused on nursery sizing/pause tuning and future lifecycle reductions backed by the profile rows, not on pretending these local ratios are portable guarantees.

  5. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Advanced the nursery tuning work in two small pushed chunks:

    • zig-gc 4fafccc (tune nursery threshold decay) now records last-minor young/reclaimed/promoted byte totals and makes the next nursery trigger decay by at most half instead of snapping directly to the 64KiB floor after a mostly-garbage young batch. This keeps low-survival cycles from over-tightening the next workload immediately while still letting the threshold shrink when pressure stays low.
    • zig-js 38832d8 (bench(gc): expose nursery byte pressure) prints those byte totals plus the computed next nursery threshold in zig build gc-profile, so future nursery sizing changes can be judged from pause + reclamation + threshold drift together.

    Validation:

    • zig build test in ~/Code/Libraries/zig-gc
    • zig build gc-profile in ~/Code/Libraries/zig-js
    • bun run docs:build
    • git diff --check

    Current local nursery row from the profile after the change:

    mode                   pause ns      young  young bytes  reclaimed   recl bytes   promoted   prom bytes  next thresh      minor       full
    gc                      3382542       1543       747928       1286       591672        257       156256       312512          1          0
    threaded no-gil         3626333       1543       747928       1284       590784        259       157144       314288          1          0
    

    Leaving the broader "tune nursery sizing/pause/throughput" item open: this gives us the measurement and first less-twitchy policy, but we still need more workload shapes before deciding whether to add aging/copying/parallel minor machinery.

  6. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Landed a zig-gc dependency fix in f33d48c and consumed it from zig-js main in 4bf4e35.

    CI root cause: tsan-threadfuzz-lifecycle reported concurrent appends to zig-gc weak_slots during GC tracing. zig-gc now has a weak_lock around weak slot append, clear, and processing paths, matching the synchronization style already used for barrier and remembered-set handoff.

    Validation included:

    • zig build test in zig-gc
    • zig build threadfuzz -Dtsan=true -Dfuzz-lifecycle=true -Dfuzz-iters=1 in zig-js
    • THREADFUZZ_SEED_TIMEOUT_MS=300000 zig build threadfuzz -Dtsan=true -Dfuzz-midgc=true -Dfuzz-iters=1 in zig-js

    This keeps the generational/weak-edge roadmap moving without changing public API.

  7. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Dependency hardening follow-up landed in zig-utils/zig-gc main as c7426a3 (fix(gc): use tsan-visible weak slot lock).

    Root cause from the failed tsan-threadfuzz-lifecycle log on run https://github.com/zig-utils/zig-js/actions/runs/29113163306 was still weak-slot ArrayList.append racing in TSan. The previous fix serialized the path with a custom atomic spinlock, but TSan does not model that as a mutex for the protected list. The new chunk wraps weak-slot registration with a POSIX pthread mutex on pthread platforms, with the old atomic fallback elsewhere, and destroys it in heap teardown.

    Local validation: zig build test in ~/Code/Libraries/zig-gc passed. The fresh zig-js push at 5631d8d should consume this dependency through the sibling checkout; CI is pending at https://github.com/zig-utils/zig-js/actions/runs/29114200905.

  8. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Follow-up dependency fix landed in zig-utils/zig-gc main as 74d7d8d (fix(gc): allocate weak slots from scratch arena).

    Root cause update from tsan-threadfuzz-lifecycle on run https://github.com/zig-utils/zig-js/actions/runs/29114200905: after switching the weak-slot lock to a pthread-visible mutex, TSan still reported two different mutexes protecting writes to the same weak-slot ArrayList backing. The weak-slot list is collector scratch, but it was still allocating from Heap.backing; in zig-js that is the reusable GC cell slab allocator, which is the wrong allocator for cross-thread collector scratch and made lifetime/reuse ambiguous to TSan.

    The weak-slot scratch list now allocates/deinits with Heap.aux, same class as the mark/barrier scratch buffers. Local dependency validation: zig build test in ~/Code/Libraries/zig-gc passed.

    A fresh zig-js push at caad91e should consume this dependency and is queued here: https://github.com/zig-utils/zig-js/actions/runs/29114710319.

  9. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    Lifecycle/GC scratch follow-up landed in zig-gc as f8205c7 (fix(gc): serialize weak scratch globally).

    The failing tsan-threadfuzz-lifecycle log on zig-js aa697df still showed weak-slot scratch appends racing under distinct per-heap mutexes during parallel multi-context create/destroy. Since weak-slot registration is collector scratch bookkeeping rather than per-heap policy, I moved it behind a single process-wide weak scratch lock. This keeps independent heap lifecycles TSan-visible even when their aux allocator reuses scratch pages across threads.

    Validation:

    • zig build test in zig-gc
    • downstream exact gate: zig build threadfuzz -Dtsan=true -Dfuzz-lifecycle=true -Dfuzz-iters=2 in zig-js passed with 112 programs from seed 1, 0 failures

    Downstream CI rerun for the paired zig-js fix: https://github.com/zig-utils/zig-js/actions/runs/29116066646

  10. chrisbbreuer commented on Jul 10, 2026

    @chrisbbreuer
    MemberAuthor

    GC dependency update pushed:

    • zig-utils/zig-gc@a2b8cb2 reserves weak-slot scratch up front for full, minor, and parallel concurrent marking.
    • This closes a sanitizer-observed allocator/scratch reuse race where markWeak could grow weak_slots during concurrent marking while no-GIL mutators grew object backing storage.
    • Validation: TSan threadfuzz midgc 1 1 passed locally with 45/45 programs after the fix. This is correctness hardening first, but it also keeps the scratch allocation policy explicit for future nursery/concurrent-GC tuning.
  11. chrisbbreuer commented on Jul 11, 2026

    @chrisbbreuer
    MemberAuthor

    Pushed a measured #16 allocation-churn tuning chunk in zig-js@42fc9c9 (perf(gc): retain bounded reusable cell slabs).

    What changed:

    • GcCellBacking.trimEmptyTailChunks() now keeps up to 8 fully empty tail chunks per live size-class bucket instead of trimming every empty tail slab after each full collection.
    • Larger one-off spikes are still returned: chunks beyond that bounded reusable tail are trimmed, and all chunks are still released at Context destroy.
    • The backing allocator tests now cover the bounded-retention contract, inner empty chunks, retained free-list nodes, and excess-tail trimming.

    Profile evidence (zig build gc-profile):

    • Before this chunk, my initial local row showed threaded no-GIL churn reuse at 0.15% (fresh=335513, reused=535, chunks=3). A one-slab experiment only reached 0.95%, which was too small to justify as the final policy.
    • With the 8-chunk cap, the same focused row reports threaded no-GIL churn reuse at 6.55% (fresh=314009, reused=22039, freed=336051, chunks=11, live=1076).
    • Plain GC in the same run reports 62.01% reuse (fresh=127638, reused=208410, chunks=11), so no-GIL reuse is still a future tuning target, but the pathological near-zero reuse under pooled-context churn is materially reduced with a bounded memory tradeoff.

    Validation:

    • zig fmt src/context.zig
    • zig build test -Dtest-filter=GC cell backing --summary none
    • zig build test -Dtest-filter=enable_gc: collectGarbage trims empty GC backing tail chunks --summary none
    • zig build gc-profile
    • git diff --check

    Leaving #16 open: this is a measured tail-slab reuse improvement, not the end of nursery/context lifecycle tuning.

  12. chrisbbreuer commented on Jul 11, 2026

    @chrisbbreuer
    MemberAuthor

    Landed eba4172 (bench(gc): compare nursery retention shapes).

    This keeps #16's nursery tuning work measurement-led: zig build gc-profile now compares four 512-object young-generation shapes instead of one fixed 1/4-retained row:

    mode shape pause ns reclaimed recl bytes promoted prom bytes next threshold
    gc ephemeral 6,998,000 1,542 747,320 1 608 131,072
    gc 1/16 retained 6,342,750 1,478 708,408 65 39,520 131,072
    gc 1/4 retained 5,276,292 1,286 591,672 257 156,256 312,512
    gc all retained 4,353,917 518 124,728 1,025 623,200 1,246,400
    threaded no-gil ephemeral 5,467,084 1,540 746,432 3 1,496 131,072
    threaded no-gil 1/16 retained 6,378,542 1,476 707,520 67 40,408 131,072
    threaded no-gil 1/4 retained 5,686,917 1,284 590,784 259 157,144 314,288
    threaded no-gil all retained 5,026,917 516 123,840 1,027 624,088 1,248,176

    Each row had young=1543, young bytes=747928, minor=1, full=0. Docs now describe the shape matrix so future threshold/pause changes can be compared against retention, reclamation, and threshold drift together.

    Validation:

    • zig build gc-profile
    • bun run docs:build
    • git diff --check

    Leaving #16 open: this is better instrumentation for tuning decisions, not the final nursery policy.

  13. chrisbbreuer commented on Jul 11, 2026

    @chrisbbreuer
    MemberAuthor

    Pushed a measurement-focused #16 chunk in 570a046 ().

    What changed:

    • now prints byte survival and reclamation percentages for each quiescent nursery retention shape.
    • The percentages are computed from the same young/reclaimed/promoted byte counters already captured by zig-gc, so the table now shows pause, byte volume, survival/reclamation rate, and next-threshold drift together.
    • Updated testing and production-readiness docs so the profiler contract mentions the new survival/reclamation percentage columns.

    Local profile evidence from this run:

    Validation:

    • �[?25l�[32m[Crosswind]�[0m CSS engine loaded

    �[36m⠋�[0m Building documentation...
    �[36m⠙�[0m Building documentation...
    �[36m⠹�[0m Building documentation...
    �[36m⠸�[0m Building documentation...
    �[36m⠼�[0m Building documentation...
    �[36m⠴�[0m Building documentation...
    �[36m⠦�[0m Building documentation...
    �[36m⠧�[0m Building documentation...
    �[36m⠇�[0m Building documentation...
    �[36m⠏�[0m Building documentation...
    �[36m⠋�[0m Building documentation...
    �[K�[?25h�[32m✓�[0m Built 21 pages to HTML in 951.1089590000001ms

    Leaving #16 open: this improves the measurement surface for nursery tuning, but does not yet decide/implement a new sizing policy across more workload shapes.

  14. 6 remaining items

  15. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    Landed a #16 nursery tuning slice across zig-gc and zig-js.

    Commits:

    • zig-utils/zig-gc@1d7c031 (gc: cap nursery threshold growth)
    • zig-utils/zig-js@a92b6fd (docs(gc): record capped nursery growth)

    What changed:

    • zig-gc keeps the existing gradual threshold decay after low-survival nursery cycles.
    • Upward nursery-threshold growth is now capped by the just-observed young batch size, so a high-survival fixed-size burst cannot jump the threshold above the next comparable batch and skip the next quiescent minor.
    • Added a direct zig-gc unit test pinning both capped growth and unchanged gradual decay.
    • Updated zig-js thread docs with the policy and the reason.

    Focused profile evidence:

    • Before this slice, zig build gc-profile -Dgc-profile-case="nursery drift" showed the all-retained 512-object row raising the threshold to about 1.28 MiB, causing alternate batches to skip minor collection (minor=0) and carry a dead young batch.
    • After zig-gc@1d7c031, the same focused row keeps the all-retained threshold at the observed 764344-byte batch size in both gc and threaded no-gil modes, and each of the four repeated batches receives a minor collection.
    • Ephemeral, 1/16 retained, and 1/4 retained rows keep their previous threshold trajectories.

    Validation:

    • In zig-gc: /Users/chris/.local/bin/zig build test --summary all (23/23 tests passed)
    • In zig-js: /Users/chris/.local/bin/zig build gc-profile -Dgc-profile-case="nursery drift"
    • In zig-js: /Users/chris/.bun/bin/bun run docs:build
    • git diff --check in both repos

    CI pending: https://github.com/zig-utils/zig-js/actions/runs/29194875744

  16. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    Follow-up: push CI for the #16 capped nursery-growth slice completed green.

    Run: https://github.com/zig-utils/zig-js/actions/runs/29194875744
    Result: 31 success, 1 expected skip, 0 failures.

    This covers the zig-js@a92b6fd docs/evidence commit while CI consumes the current local-path dependency checkout, including zig-gc@1d7c031.

  17. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    Landed a small #16 pooled-context scratch-retention slice across the GC dependency and docs.

    Commits:

    • zig-utils/zig-gc@8059de0 (gc: trim oversized collector scratch)
    • zig-utils/zig-js@045ff0e7 (docs(gc): record scratch trim policy)

    What changed:

    • zig-gc now trims empty collector scratch buffers after a collection when their retained capacity is disproportionate to the current live-cell count.
    • Normal scratch reuse is preserved up to a floor of 4096 entries, and high-live contexts can retain up to roughly 2x current live-cell pressure.
    • One-off spikes no longer force pooled contexts to carry arbitrarily large empty mark-stack / weak-slot / remembered-card / concurrent scratch buffers forever.
    • Added a direct zig-gc regression test for oversized post-spike scratch freeing and retained normal scratch.
    • zig-js docs now record the dependency policy and test coverage surface.

    Validation:

    • In zig-gc: zig build test --summary all (24/24 passed)
    • In zig-js: zig build gc-profile -Dgc-profile-case="nursery" --summary all
    • In zig-js: bun run docs:build
    • git diff --check in both repos

    CI for the downstream docs/evidence commit is pending: https://github.com/zig-utils/zig-js/actions/runs/29200182099

  18. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    CI routing note: the 045ff0e7 run for the scratch-trim docs/dependency-consumption slice was cancelled by the follow-up b8136312 push. The live main-branch gate that includes 045ff0e7 and consumes zig-gc@8059de0 is now: https://github.com/zig-utils/zig-js/actions/runs/29200325235

  19. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    CI routing note: the previous live gate for the scratch-trim/docs history was cancelled by the follow-up main push. The current main-branch gate that includes the zig-gc scratch-trim downstream docs commit and later history is now: https://github.com/zig-utils/zig-js/actions/runs/29200500880

  20. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    CI routing note: the previous current gate was superseded by the follow-up #36 push. The current main-branch gate that includes the zig-gc scratch-trim downstream docs commit and later history is now: https://github.com/zig-utils/zig-js/actions/runs/29200654888

  21. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    CI routing note: the previous current gate was superseded by the follow-up #36 iterator-helper push. The current main-branch gate that includes the zig-gc scratch-trim downstream docs commit and later history is now: https://github.com/zig-utils/zig-js/actions/runs/29200732250

  22. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    CI routing note: the previous current gate was superseded by the follow-up #36 async-generator push. The current main-branch gate that includes the zig-gc scratch-trim downstream docs commit and later history is now: https://github.com/zig-utils/zig-js/actions/runs/29200819725

  23. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    CI routing note: the previous current gate was superseded by the follow-up #36 generator-handler push. The current main-branch gate that includes the zig-gc scratch-trim downstream docs commit and later history is now: https://github.com/zig-utils/zig-js/actions/runs/29200885804

  24. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    CI routing note: the previous current gate was superseded by the follow-up mid-GC telemetry push. The current main-branch gate that includes the zig-gc scratch-trim downstream docs commit and later history is now: https://github.com/zig-utils/zig-js/actions/runs/29201026809

  25. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    Pushed b79f9774 (bench(gc): add array nursery shape) as a small #16 measurement-surface slice.

    What changed:

    • Added an array/object 1/4 retained case to the existing gc-profile nursery case list.
    • Updated the nursery and nursery-drift table headings from 512 object to 512 allocation batches.
    • Updated thread testing and production-readiness docs so the profile contract now says the nursery rows cover retention/allocation shapes, not only object graphs.

    Focused local evidence:

    • zig build gc-profile -Dgc-profile-case="nursery" --summary all now prints the array/object 1/4 row in both gc and threaded no-gil modes.
    • zig build gc-profile -Dgc-profile-case="nursery drift" --summary all includes the same shape across the four-cycle threshold trajectory.
    • In this run, the array/object row has the same young/promoted-byte trajectory as the object 1/4 retained row, which is useful evidence before policy tuning: array/object mix currently differs mainly in wall-clock pause shape, not in the nursery byte-threshold inputs.

    Other validation:

    • bun run docs:build
    • git diff --check

    This does not tune the nursery policy yet; it broadens the focused profile surface so future #16 policy decisions can compare object-only versus array/object workloads without running the full profile matrix.

    CI: https://github.com/zig-utils/zig-js/actions/runs/29201561685 is pending for b79f9774.

    Also updated the #11 issue body to match the live reference audit: 236/259 promoted executable PR-249 files, 23 executable reference-only files, and 5 helper/preload files; #35 is now recorded as resolved rather than still tracking an open promotion blocker.

  26. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    Routing note: the CI run linked for b79f9774 was cancelled by the newer #12 memory-model docs push. The #16 array/object nursery-profile row remains on main; use current combined-head CI for d0092f5e: https://github.com/zig-utils/zig-js/actions/runs/29201642565 (queued at the time of this note).

  27. chrisbbreuer commented on Jul 12, 2026

    @chrisbbreuer
    MemberAuthor

    Green gate note for d0092f5e: https://github.com/zig-utils/zig-js/actions/runs/29201642565

    The nursery profile rows, array/object allocation-shape evidence, threshold/scratch dependency sync with zig-gc, and docs updates are covered by the successful combined gate. Next work should stay measurement-driven across more workload shapes.

  28. chrisbbreuer commented on Jul 13, 2026

    @chrisbbreuer
    MemberAuthor

    Closing this tracker as completed.\n\nThe implementation now has the intended GC-backed threaded baseline: size-class slabs, a non-moving one-cycle nursery, exact remembered-set/write-barrier coverage, full-heap fallback, weak/finalization safety, adaptive threshold telemetry, scratch trimming, focused profile rows, lifecycle/pooling guidance, and normal/TSan/concurrent-GC/fuzzer regression coverage. The issue history contains before/after profile evidence for the allocation, nursery-shape, threshold, scratch-lifetime, and context-lifecycle changes, and the docs distinguish slab allocation, nursery policy, full/concurrent collection, and embedder pooling.\n\nDesign decision for the remaining research line: current focused measurements do not show that multi-age, moving/copying, or parallel minor collection earns its correctness and complexity cost. The supported policy remains the non-moving one-cycle nursery plus measured quiescent full collection and bounded context pooling. That is a positive decision, not deferred implementation.\n\nCombined acceptance gate: https://github.com/zig-utils/zig-js/actions/runs/29232877388 — 31 success, 1 expected skip, 0 failures. Future profile evidence that identifies a concrete nursery or lifecycle bottleneck should open a narrow issue with before/after counters rather than keeping this design tracker permanently open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions