feat(gc): incremental collection by default — bounded pauses, RSS parity, STW as escape hatch (#6180)#6243
Conversation
Ordinary allocation pressure is now collected by the budgeted incremental stepper: debt-paced assists, sound across mutator windows (minor+full mark barrier, whole-cycle allocate-black, final root remark, drain-before-synchronous), census-free, RSS-parity on realistic workloads with ~5x lower worst pause (B1: 521 MB / 136 ms max frame vs the synchronous collector's 521 MB / 636 ms). PERRY_GC_INCREMENTAL=0 is the escape hatch (bisection, max-throughput batch); manual gc() and emergency reclaim keep their synchronous full collections. Two tests updated from the old default's gating contract (mutable sync-only scanners now run inline in the initial mark instead of blocking the stepper).
Same enumeration as the Date change (#6214): movable only gates old-page defrag, which runs solely inside moving collections at stack-unwound safepoints; the nursery copied-minor already relocates Temporal cells. The embedded temporal_rs value survives memcpy (owned allocations live on the Rust heap and move by value); from-space bulk resets skip per-object finalizers so a moved cell is never double-dropped — TemporalCleanup fires once, wherever the cell dies. ExoticExpandoOwner rekey mirrors Date (no-op without expandos).
Array growth installs a PERMANENT forwarding stub at the old address (types.rs set_forwarding_address) so stale pre-growth pointers keep resolving for reads - but the shared worklist tracer treated FORWARDED headers as zero-children, so liveness never propagated to the live post-growth array. The deforestation pass manufactures exactly such a stale pointer as the ONLY reference for direct calls (var b = build(n) with no consumer), so any collection between the 32MiB grow and the read swept the live array: length 0, NaN indices, garbage lengths for object arrays. Affected every GC mode identically (the tracer is shared); shadow frames and the conservative scan were empirically ruled out. The hop mirrors the previously dead-code trace_array path, plus MARKING the target (worklist membership alone does not protect it from the sweep). Verified: dlee's exact repro prints 4194305 0 (Node-equal) under both the incremental default and PERRY_GC_INCREMENTAL=0.
📝 WalkthroughWalkthroughIncremental GC is now enabled by default unless explicitly disabled. Synchronous-only scanners execute inline during root scanning. Forwarded headers trace their targets, Temporal cells become movable with an expando-owner hook, and regression tests cover scanner scheduling and grown-array liveness. ChangesGC behavior updates
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Environment
participant gc_incremental_enabled
participant BudgetedGCStepper
participant SynchronousScanner
Environment->>gc_incremental_enabled: Read PERRY_GC_INCREMENTAL
gc_incremental_enabled->>BudgetedGCStepper: Enable incremental collection
BudgetedGCStepper->>SynchronousScanner: Run inline during RootScan
sequenceDiagram
participant StaleArrayPointer
participant trace_one_worklist_header
participant ValidPointerSet
participant GCWorklist
StaleArrayPointer->>trace_one_worklist_header: Present forwarded header
trace_one_worklist_header->>ValidPointerSet: Validate forwarding target
trace_one_worklist_header->>GCWorklist: Mark and enqueue target
GCWorklist->>StaleArrayPointer: Preserve grown-array access
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/perry-runtime/src/gc/types.rs`:
- Around line 518-520: Update the doc comment for exotic_expando_owner_moved in
exotic_expando.rs to remove the claim that Date and Temporal cells are
non-movable or that the hook never fires for them; accurately state that the
hook handles movable Date and Temporal cells, along with RegExp as applicable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9c168b10-059e-4c9c-a96a-610d08ce13ef
📒 Files selected for processing (6)
crates/perry-runtime/src/gc/policy.rscrates/perry-runtime/src/gc/tests/budgeted_step_api.rscrates/perry-runtime/src/gc/tests/cycle_state.rscrates/perry-runtime/src/gc/tests/debt_pacer.rscrates/perry-runtime/src/gc/trace.rscrates/perry-runtime/src/gc/types.rs
| // Expando rekey on relocation, mirroring Date: no-op when the cell | ||
| // has no expando entry. | ||
| GcMoveHookKind::ExoticExpandoOwner, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the stale doc comment in exotic_expando.rs.
The doc comment on exotic_expando_owner_moved (lines 250-270 of crates/perry-runtime/src/object/exotic_expando.rs) states: "Date / RegExp / Temporal cells are non-movable so this never fires for them." With this PR marking Temporal movable (and the prior Date change in #6214), the function now fires for both. The stale comment could mislead future developers.
📝 Proposed doc update in exotic_expando.rs
/// Rekey a movable exotic cell's expando entry after the GC relocates it from
/// `old_addr` to `new_addr`. Date / RegExp / Temporal cells are non-movable so
-/// this never fires for them, but a `Promise` (`GC_TYPE_PROMISE`) is movable —
+/// this never fires for them, but a `Promise` (`GC_TYPE_PROMISE`),
+/// `Date` (`GC_TYPE_DATE_CELL`), and `Temporal` (`GC_TYPE_TEMPORAL`) are movable —
/// without this, a `.then()`-chained thenable that survives a GC move would
/// lose the `status`/`value` expandos it was gated on. Stored expando *values*
/// are already rewritten by `scan_exotic_expando_roots_mut`; this migrates the
/// owner *key*. Wired via `GcMoveHookKind::ExoticExpandoOwner`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/perry-runtime/src/gc/types.rs` around lines 518 - 520, Update the doc
comment for exotic_expando_owner_moved in exotic_expando.rs to remove the claim
that Date and Temporal cells are non-movable or that the hook never fires for
them; accurately state that the hook handles movable Date and Temporal cells,
along with RegExp as applicable.
Closes #6180. Also completes #6186 (Temporal) and fixes #6228.
Incremental collection is the default
Ordinary allocation pressure is now collected by the budgeted incremental stepper.
PERRY_GC_INCREMENTAL=0is the escape hatch (bisection, max-throughput batch); manualgc()and emergency reclaim keep their synchronous full collections.Why it's ready — the evidence chain (all merged prerequisites)
PERRY_GC_VERIFY_CLASSIFIER=1, 1227/0)./usr/bin/time -l): realistic workload at RSS parity (521 MB vs 521 MB STW) with max frame 132 ms vs 636 ms (~5×); throughput parity; extreme-churn peak attributed by vmmap to the transient mid-cycle high-water (steady-state 572 MB), the structural trade of slicing vs a pause wall.GEN_GC=0, exact checksums;VERIFY_EVACUATIONsweeps clean.perry-runtimesuite: 1227/0 as default.Also in this PR
4194305 0, Node-equal, both modes): the shared tracer treated FORWARDED array-growth stubs as zero-children — a stale pre-growth pointer as the only reference (deforestation manufactures exactly this for direct calls) let the live array be swept. The tracer now hops the forwarding edge and marks the target. Regression test included.temporal_rssurvives memcpy, bulk resets skip finalizers → no double-drop, expando rekey mirrors Date).Summary by CodeRabbit
New Features
Bug Fixes