Skip to content

fix(gc): final-root-remark makes budgeted atomic-finalize atomic (#6180 Stage 2)#6235

Merged
proggeramlug merged 1 commit into
mainfrom
perf/gc-atomic-finalize-remark
Jul 10, 2026
Merged

fix(gc): final-root-remark makes budgeted atomic-finalize atomic (#6180 Stage 2)#6235
proggeramlug merged 1 commit into
mainfrom
perf/gc-atomic-finalize-remark

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Part of #6180 Stage 2 (follow-on to #6226). Closes the last known incremental soundness gap: the hide-in-a-local window.

Bug

A budgeted cycle's RootScan runs once, early. If a pointer's only reference migrates into a stack local mid-cycle — its heap slot overwritten (the store barrier shades the NEW value, never the old) — the object is invisible to the one-shot scan and is swept live. This is the standard reason every production incremental collector has a final remark pause.

Fix

AtomicFinalize gains a FinalRootRemark subphase for budgeted cycles only (synchronous cycles have no mutator windows — zero cost on today's default path):

  • Re-scans every root (shadow stack, globals, registered scanners) unbudgeted — bounded by root-set size, not heap size — then traces the newly discovered objects before WeakProcessing reads the marks.
  • The dispatcher now classifies subphases as sliced (BarrierSeedDrain, full-cycle RememberedSetRebuild — may return to the mutator) vs atomic tail (remark → weak → barrier-off → Sweep transition — one pause, no mutator windows). The minor path is fully atomic from remark to sweep; the full path's sliced RS-rebuild is the one post-remark window and remains store-covered by the active mark barrier. Remark runs before the full path's RS-rebuild so the rebuilt sticky set sees final marks.
  • Running unbudgeted with a stable frame_top, the remark also subsumes the count-based shadow-stack cursor staleness of the sliced initial scan.

Verification

  • Regression test atomic_finalize_remark_rescues_pointer_hidden_in_shadow_slot_after_root_scan: object allocated before the cycle (so whole-cycle allocate-black does not protect it), referenced by nothing at RootScan, planted into a shadow slot only after the cycle passes RootScan — must survive and stay off the sweep free list.
  • Full perry-runtime lib suite: 1227 passed / 0 failed.
  • Differential battery (15k-round old→young stress ± manual gc() ± force-evacuate ± PERRY_GEN_GC=0): 6/6 exact-checksum clean on release.
  • Latency probe: worst B1 frame ~118 → ~138 ms (the remark's atomic root re-scan); the initial-mark/WeakProcessing budgeting work tracked in [gc] Graduate incremental collection: budgeted scanner coverage, assists for all phases, default-on (successor to #6083) #6180 claws this back.

Summary by CodeRabbit

  • Bug Fixes
    • Improved budgeted garbage collection finalization to re-scan roots so objects that become reachable late in the cycle remain alive.
    • Fixed an issue where references placed via shadow-stack slots could be lost, leading to incorrect reclamation during incremental collection.
  • Tests
    • Added a regression test covering the late root “final remark” stage and verifying the shadow-slot scenario does not result in premature freeing.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ddaccb81-9fad-4cf2-99fb-02be6d015e5b

📥 Commits

Reviewing files that changed from the base of the PR and between 7b48029 and cd283d8.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/gc/cycle.rs
  • crates/perry-runtime/src/gc/tests/debt_pacer.rs

📝 Walkthrough

Walkthrough

Budgeted GC atomic finalization now performs a final root remark after barrier seed draining, propagates its trace seeds, and continues to weak processing or remembered-set rebuilding. A regression test covers pointers inserted into shadow-stack slots after the initial root scan.

Changes

Budgeted GC final root remark

Layer / File(s) Summary
Atomic-finalize remark flow
crates/perry-runtime/src/gc/cycle.rs
Budgeted atomic-finalize cycles add FinalRootRemark, re-scan roots after BarrierSeedDrain, drain discovered trace seeds, and continue to weak processing or remembered-set rebuilding.
Final-root-remark regression coverage
crates/perry-runtime/src/gc/tests/debt_pacer.rs
A regression test verifies that an object added to a shadow-stack slot after the initial root scan survives the completed budgeted cycle.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GcCycleState
  participant RootScanCycleState
  participant TraceWorklist
  GcCycleState->>GcCycleState: Complete BarrierSeedDrain
  GcCycleState->>RootScanCycleState: Re-scan roots
  RootScanCycleState-->>TraceWorklist: Add remark trace seeds
  GcCycleState->>TraceWorklist: Drain seeds
  TraceWorklist-->>GcCycleState: Continue to WeakProcessing or RememberedSetRebuild
Loading

Possibly related PRs

  • PerryTS/perry#6213: Updates minor-collection transitions that skip remembered-set rebuilding.
  • PerryTS/perry#6216: Modifies budgeted progression through atomic-finalize GC phases.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main change: adding final-root-remark behavior for budgeted atomic-finalize GC cycles.
Description check ✅ Passed The description covers the bug, fix, verification, and related issue context, though it does not follow the template exactly.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 perf/gc-atomic-finalize-remark

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/tests/debt_pacer.rs`:
- Around line 567-583: Update the test setup around js_shadow_slot_set so the
hidden object is introduced through a post-scan root overwrite that bypasses the
store barrier, rather than being marked by
runtime_write_barrier_root_nanbox/incremental_mark_barrier_value. Preserve the
later complete_budgeted_gc_cycle assertions and free-list check so they
specifically validate FinalRootRemark keeping the shadow-slot-only pointer
alive.
🪄 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: 453280dc-6b01-4a95-b69e-29380888e5dd

📥 Commits

Reviewing files that changed from the base of the PR and between b86605a and 7b48029.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/gc/cycle.rs
  • crates/perry-runtime/src/gc/tests/debt_pacer.rs

Comment thread crates/perry-runtime/src/gc/tests/debt_pacer.rs Outdated
… Stage 2)

The last known incremental soundness gap: a budgeted cycle's RootScan
runs ONCE, early. A pointer whose only reference migrates into a stack
local mid-cycle (heap slot overwritten - the store barrier shades the
NEW value, never the old) is invisible to the one-shot scan and was
swept live.

AtomicFinalize now runs a FinalRootRemark for budgeted cycles: re-scan
every root (shadow stack, globals, registered scanners) with marks
nearly final, unbudgeted - bounded by root-set size, not heap - then
trace the newly discovered objects before WeakProcessing reads the
marks. From remark to the Sweep transition the minor path executes
ATOMICALLY in one pause (the dispatcher classifies subphases as sliced
vs atomic-tail); the full path's sliced RememberedSetRebuild is the one
post-remark window and remains store-covered by the active barrier.
The remark also runs with a stable frame_top, subsuming the
count-based shadow-cursor staleness of the sliced initial scan.

Synchronous cycles skip the remark (no mutator windows, no staleness):
zero cost on the default STW path.

Regression test: object allocated BEFORE the cycle (not allocate-black)
referenced by nothing at RootScan, planted in a shadow slot after the
cycle passes RootScan - must survive and stay off the sweep free list.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Applied: the regression test now plants the hidden pointer via a RAW shadow-slot write (direct SHADOW stack/active mutation), bypassing js_shadow_slot_set's runtime_write_barrier_root_nanbox shading — so survival can only come from FinalRootRemark, which is the hazard shape (a migration crossing no barriered store). All assertions preserved; debt_pacer suite 11/11.

@proggeramlug proggeramlug force-pushed the perf/gc-atomic-finalize-remark branch from 7b48029 to cd283d8 Compare July 10, 2026 09:28
@proggeramlug proggeramlug merged commit 12695e6 into main Jul 10, 2026
23 of 24 checks passed
@proggeramlug proggeramlug deleted the perf/gc-atomic-finalize-remark branch July 10, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant