Skip to content

feat(gc): incremental collection by default — bounded pauses, RSS parity, STW as escape hatch (#6180)#6243

Merged
proggeramlug merged 3 commits into
mainfrom
feat/gc-incremental-default-on
Jul 10, 2026
Merged

feat(gc): incremental collection by default — bounded pauses, RSS parity, STW as escape hatch (#6180)#6243
proggeramlug merged 3 commits into
mainfrom
feat/gc-incremental-default-on

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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=0 is the escape hatch (bisection, max-throughput batch); manual gc() and emergency reclaim keep their synchronous full collections.

Why it's ready — the evidence chain (all merged prerequisites)

Also in this PR

Summary by CodeRabbit

  • New Features

    • Incremental garbage collection is now enabled by default.
    • Synchronous-only scanners run inline during initial garbage-collection scanning.
    • Forwarded pointers from grown arrays remain traceable, preserving access to live data.
    • Temporal objects can now move safely during garbage collection.
  • Bug Fixes

    • Improved liveness tracking for forwarded array pointers.
    • Updated relocation handling for Temporal objects.

Ralph Küpper added 3 commits July 10, 2026 13:38
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.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Incremental 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.

Changes

GC behavior updates

Layer / File(s) Summary
Default incremental root scanning
crates/perry-runtime/src/gc/policy.rs, crates/perry-runtime/src/gc/tests/budgeted_step_api.rs, crates/perry-runtime/src/gc/tests/cycle_state.rs
PERRY_GC_INCREMENTAL defaults to enabled, and synchronous-only scanners are expected to run inline during initial root scanning.
Forwarded target tracing
crates/perry-runtime/src/gc/trace.rs, crates/perry-runtime/src/gc/tests/debt_pacer.rs
Forwarded headers resolve and enqueue marked targets, with a regression test verifying stale pointers retain access to grown arrays.
Temporal relocation metadata
crates/perry-runtime/src/gc/types.rs
Temporal cells are marked movable and use the ExoticExpandoOwner move hook.

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
Loading
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
Loading

Possibly related PRs

  • PerryTS/perry#6134: Updates the incremental-mode switch and related root-scanning behavior.

Suggested labels: run-extended-tests

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Temporal movability changes in gc/types.rs target #6186, which is not one of the linked issues and appears unrelated to #6180/#6228. Split the #6186 Temporal work into a separate PR or add it as a linked issue if it is intended to be in scope.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: incremental GC becomes the default, with bounded pauses and STW fallback.
Description check ✅ Passed The description covers the summary, related issues, and verification evidence, though it doesn't follow the template headings exactly.
Linked Issues check ✅ Passed The changes align with #6180's default-on incremental GC goal and #6228's forwarded-array liveness fix, including regression tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gc-incremental-default-on

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b03223 and 090a967.

📒 Files selected for processing (6)
  • crates/perry-runtime/src/gc/policy.rs
  • crates/perry-runtime/src/gc/tests/budgeted_step_api.rs
  • crates/perry-runtime/src/gc/tests/cycle_state.rs
  • crates/perry-runtime/src/gc/tests/debt_pacer.rs
  • crates/perry-runtime/src/gc/trace.rs
  • crates/perry-runtime/src/gc/types.rs

Comment on lines +518 to +520
// Expando rekey on relocation, mirroring Date: no-op when the cell
// has no expando entry.
GcMoveHookKind::ExoticExpandoOwner,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

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

Labels

None yet

Projects

None yet

1 participant