ReviewOS

zig-utils/zig-js

GC: make runtime StringCells collectible and finalizable

#325
Closed chris opened this 9 days ago ·
9 days ago

Parent: #134 Required by: #324 Related: #97, #144

Problem

Value stores JavaScript strings as *StringCell, but src/gc.zig currently treats every non-object value as an immediate primitive. Runtime StringCells and their byte storage are therefore arena-permanent and cannot participate in precise liveness or exact external-buffer finalization. That blocks #324 and leaves allocation-heavy string workloads outside the real heap lifecycle.

Scope

  • Add an explicit heap-managed StringCell kind and distinguish static/intern-table/arena cells from collectible runtime cells without pointer guessing.
  • Route fallible runtime string construction through the active context heap when GC is enabled, retaining the existing explicit arena mode where configured.
  • Trace string-valued roots and edges everywhere Value is traced, including interpreter frames, environments, object slots/elements/accessors, promises, modules, workers, C-API handles, debugger state, temporary roots, and concurrent snapshots.
  • Finalize owned string bytes exactly once and provide a general external-owner finalizer record usable by #324; callbacks must run outside collector/internal object locks and remain safe under reentry.
  • Preserve canonical WTF-8 content, equality/hash behavior, atom/intern ownership, static literals, sibling-realm sharing, and no-GIL memory ordering.
  • Integrate the required cell-kind/finalizer support into ~/Code/Libraries/zig-gc as a separately committed dependency change only if the generic collector needs extension.

Acceptance

  • Focused tests prove reachable strings survive repeated collections and unreachable strings release owned bytes/external owners exactly once.
  • Cover empty/Latin-1/UTF-8/astral/lone-surrogate strings, concatenation/rope results, property keys, closures, promises, modules, C-API protection, temporary roots, explicit GC during callbacks, context teardown, and injected OOM.
  • Stress high-churn strings in arena and GC modes; demonstrate bounded live bytes after collections and no regressions in equality or observable string semantics.
  • Run leak/finalizer accounting and relevant no-GIL/TSan stress, including concurrent marking snapshots if enabled.
  • Update GC design/status documentation and link completion evidence to #324, #97, #144, and #134.
  • Accept one full CI matrix before closure.
9 days ago

Foundation pushed in 6d2af84 (feat(gc): collect runtime string cells). It adds an explicit StringCell heap kind, immutable managed/non-managed classification, managed allocation/adoption, exact canonical-byte finalization/accounting, root and edge tracing, old-to-young barriers, C-API handle reachability, C-API allocation-entry guards, and GC design/status updates. Focused results: 6/6 StringCell tests, 3/3 nursery owner-barrier tests, and 3/3 bulk-finalizer tests passed with no leaks. Fresh full CI: https://github.com/zig-utils/zig-js/actions/runs/29700993746\n\nKeeping this issue open for the remaining acceptance work: high-churn bounded-byte stress, broader closure/module/property-key/temporary-root coverage, injected OOM and teardown cases, no-GIL/TSan evidence, and the external-owner finalizer bridge required by #324.

9 days ago

Follow-up commits on main:

  • 66fa951b defers external-owner callbacks until post-sweep and proves callback GC reentry during collection and teardown.
  • 5365a405 fixes the full-matrix ArrayBuffer recovery regression exposed by managed StringCells. The root cause was the tree walker allocating a fresh managed StringCell on every visit to a string-literal AST node; a peer spinning on Atomics.load(gate, "stop") continuously grew the born list, alternating born-growth rounds and finish races until the 64-round safe abort. Literal values are now cached per interpreter and both literal/template caches are traced as roots.

The previously failing focused witness now passes in 4 consecutive local runs (3 tests each, no leaks). Fresh descendant matrix: https://github.com/zig-utils/zig-js/actions/runs/29701666901

Remaining acceptance work here is broader string churn/OOM/no-GIL/TSan lifecycle coverage and a green full matrix.

9 days ago

The superseded 5365a405 matrix reached 26 green jobs before the next push. Its prior deterministic unit+TSan ArrayBuffer recovery failures were gone. One threadfuzz-midgc job timed out on seed 1 after 300s and then failed its 30s teardown bound, with no crash/sanitizer output and no active Context available to interrupt. The exact local reproduction on descendant 74b0fe3f passed: 45 programs, seed 1, 0 failures, 30.345s under a 90s per-seed watchdog. Treating this as an unresolved CI witness until the fresh matrix repeats or clears it: https://github.com/zig-utils/zig-js/actions/runs/29702181895.

9 days ago

Accepted by descendant CI run 29703497726: 39 jobs passed, with only the intentional nightly skip. Collectible runtime StringCells, reuse, tracing, and full GC/thread coverage are green.

Sign in to comment on this issue.