Problem
Sweep currently finalizes, unlinks, and returns each dead cell through std.mem.Allocator.free. Owned slab embedders therefore repeat allocator-vtable dispatch, size classification, ownership lookup, and synchronization for every reclaimed nursery cell. A current zig-js object_churn profile attributes a large share of minor sweep time to this path.
Proposed API
Add an optional Binding batch-release contract. When present, the collector will:
- finalize and unlink white cells exactly as today;
- retain dead headers in a collector-private intrusive list;
- begin one release phase only after every finalizer has completed;
- hand each allocation base and exact byte size to the Binding;
- end the release phase after the batch.
Bindings without the complete hook retain byte-for-byte generic allocator frees.
Acceptance criteria
- Generic allocator and finalizer semantics remain covered.
- A focused owned-storage test proves finalizers precede the release phase and no per-cell backing free occurs.
- Minor and full collection accounting remains exact.
- Full zig-gc tests pass.
- Downstream zig-js implements the hook and shows a repeatable order-balanced object_churn improvement before either commit is retained.
Downstream: zig-utils/zig-js#62