Skip to content

gc: make the below-HEAP_MIN growth-stub skip loud, and split from-space offenders by snapshot coverage - #7043

Merged
proggeramlug merged 1 commit into
mainfrom
fix/7022-heap-min-diagnostic
Jul 30, 2026
Merged

gc: make the below-HEAP_MIN growth-stub skip loud, and split from-space offenders by snapshot coverage#7043
proggeramlug merged 1 commit into
mainfrom
fix/7022-heap-min-diagnostic

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Two diagnostics salvaged from the #7022 investigation. Neither is a fix for #7022 — both are landable on their own, and the second is actively in use by the ongoing work.

Depends on #7041 (merged).

1. The below-HEAP_MIN growth-stub skip is no longer silent

js_array_grow installs a forwarding stub on the old array so stale pre-grow references keep resolving (#233's mechanism). It skips that when the array sits below the platform heap floor — an address-conditional silent divergence: behaviour depends on where the allocator happened to place memory, and nothing says so.

It now fires a debug_assert and a once-per-process line on stderr (stderr specifically, so it cannot perturb a stdout parity comparison).

This has already cost real time. During #7022 an experiment replaced arena blocks with mmap'd, guard-paged blocks to catch stale accesses. mmap with a NULL hint lands well below macOS's 2 TB floor, so this branch silently disabled every growth stub — and the experiment came back falsely clean. It was caught only by re-running with a high MAP_FIXED hint. The next person gets a signal instead.

The debug_assert deliberately fires on every occurrence while the stderr line is once-per-process: hard failure in debug, one-line warning in release. Reviewers may want to weigh in on whether the assert is too aggressive for a condition an unusual-but-legitimate allocator could reach.

2. From-space offenders split by snapshot coverage

#7041's scan reports which live objects still hold from-space pointers after the rewrite pass. It now also says why each was missed:

bucket meaning
not_in_snapshot the page was never in this cycle's dirty snapshot — the in-cycle remembered-set scan never looked at it
in_snapshot the scan looked at the page and still did not rewrite the slot

Those are different defects with different fixes, and the split is what established #7022's failure as in-cycle rather than an edge recorded and later dropped. Without it, lost_dirty counts alone were consistent with both stories.

Provenance, stated plainly

Both changes were written by an agent investigating #7022 that stalled before committing them — they existed only as uncommitted edits in a worktree on a remote box. I transferred the diff, applied it to current main (clean apply), and am landing it unchanged apart from the changelog fragment. I did not write the code and have not independently re-derived the #233 reasoning behind the stub; the diagnostic is additive and cannot change behaviour except by printing, which bounds the risk.

Validation

cargo check -p perry-runtime clean; cargo fmt --all -- --check clean. No behavioural change beyond diagnostics — the only non-diagnostic edit is threading the existing &snapshot into run_fromspace_scan, which is off by default behind PERRY_GC_FROMSPACE_SCAN.

Not run: the matrix. This adds no code to any default path, and #7041's own tests still pass.

Summary by CodeRabbit

  • Bug Fixes

    • Improved garbage-collection diagnostics for skipped array growth forwarding.
    • Development builds now flag unexpected below-heap conditions, while runtime builds emit a one-time warning instead of failing silently.
    • From-space scan reports now distinguish issues missed because a page was not part of the snapshot from issues found on pages that were scanned.
  • Documentation

    • Added changelog details describing the updated diagnostic behavior and failure classification.

…ce offenders by snapshot coverage

Two diagnostics salvaged from the #7022 investigation, both landable on their own.

1. js_array_grow skips installing the growth forwarding stub when the array sits
   below the platform heap floor. That is an address-conditional silent
   divergence: behaviour depends on where the allocator happened to place memory,
   with no signal. Now a debug_assert plus a once-per-process stderr line.

   This has already cost debugging time. An experiment replacing arena blocks
   with mmap'd guard-paged blocks used a NULL hint, landed below macOS's 2 TB
   floor, silently disabled every growth stub, and came back falsely clean -- it
   was only caught by re-running with a high MAP_FIXED hint.

2. The from-space scan (#7041) now splits offenders by whether their page was in
   this cycle's dirty snapshot:
     not_in_snapshot -- the in-cycle remembered-set scan never looked at the page
     in_snapshot     -- the scan looked and still did not rewrite the slot

   That split is what established #7022's failure as in-cycle, rather than an
   edge recorded and later dropped.

Refs #7022, #7041.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds runtime diagnostics for below-HEAP_MIN array growth stub skips and extends whole-heap from-space scan reports with dirty-snapshot coverage counters.

Changes

GC diagnostics

Layer / File(s) Summary
Array growth stub diagnostic
crates/perry-runtime/src/array/push_pop.rs, changelog.d/7043-heap-min-diagnostic.md
js_array_grow now reports below-HEAP_MIN stub skips with a debug assertion and a once-per-process stderr warning.
Snapshot-aware from-space scan
crates/perry-runtime/src/gc/fromspace_scan.rs, crates/perry-runtime/src/gc/copying.rs, changelog.d/7043-heap-min-diagnostic.md
The scan receives the remembered-dirty snapshot and classifies offending slots as in_snapshot or not_in_snapshot in its report.
Estimated code review effort: 3 (Moderate) ~20 minutes

Possibly related PRs

  • PerryTS/perry#7041: Updates the whole-heap from-space scan to accept a remembered-dirty snapshot and classify slot coverage.

Suggested reviewers: andrewtdiz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes both diagnostic changes and is concise.
Description check ✅ Passed It includes a clear summary, concrete changes, issue references, and validation details, though it doesn't follow the template headings exactly.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 fix/7022-heap-min-diagnostic

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/fromspace_scan.rs`:
- Around line 317-319: The from-space scan coverage must not be initialized from
dirty_old_pages, because the scan also visits surviving malloc-tracked objects
and other slots outside dirty old-page headers. Update the coverage tracking in
fromspace_scan.rs around SNAPSHOT_PAGES and the actual scan path to record every
inspected slot or narrow not_in_snapshot wording so it does not claim unexamined
coverage; update changelog.d/7043-heap-min-diagnostic.md lines 11-15 to match
the corrected semantics.
🪄 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: ab8a7429-e1e8-4707-b532-d1f890f8f138

📥 Commits

Reviewing files that changed from the base of the PR and between 4af0e0f and 6c7022f.

📒 Files selected for processing (4)
  • changelog.d/7043-heap-min-diagnostic.md
  • crates/perry-runtime/src/array/push_pop.rs
  • crates/perry-runtime/src/gc/copying.rs
  • crates/perry-runtime/src/gc/fromspace_scan.rs

Comment on lines +317 to +319
SNAPSHOT_PAGES.with(|c| {
*c.borrow_mut() = snapshot.dirty_old_pages.iter().copied().collect();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A120 -B20 \
  'scan_remembered_dirty_slots_copying|external_dirty_entries|dirty_pages|fallback_headers' \
  crates/perry-runtime/src/gc

Repository: PerryTS/perry

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file sizes =="
wc -l crates/perry-runtime/src/gc/fromspace_scan.rs
wc -l changelog.d/7043-heap-min-diagnostic.md

echo "== fromspace_scan relevant sections =="
ast-grep outline crates/perry-runtime/src/gc/fromspace_scan.rs --match scan_remembered_dirty_slots_copying --view expanded || true
rg -n -A35 -B35 'scan_remembered_dirty_slots_copying|SNAPSHOT_PAGES|RememberedDirtySnapshot|dirty_old_pages|dirty_pages|external_dirty_entries' crates/perry-runtime/src/gc/fromspace_scan.rs

echo "== snapshot structs and construction in gc =="
rg -n -A80 -B30 'struct RememberedDirtySnapshot|create.*remembered|remembered_dirty_snapshot|dirty_old_pages:' crates/perry-runtime/src/gc

Repository: PerryTS/perry

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== fromspace_scan scan_heap_for_fromspace_refs area =="
sed -n '200,280p' crates/perry-runtime/src/gc/fromspace_scan.rs

echo "== scanner implementation around scan_remembered_dirty_slots_copying =="
rg -n -A160 -B30 'fn scan_remembered_dirty_slots_copying|scan_header|external_dirty_entries|fallback_headers|dirty_pages\.contains' crates/perry-runtime/src/gc/copying.rs

echo "== barrier scan header implementation =="
rg -n -A120 -B30 'DirtyHeaderSlotScan::step|fn next_dirty_header_addr|dirty_pages\.contains|old_object_page_overlaps|object_pages|scan.*header' crates/perry-runtime/src/gc/barrier.rs

Repository: PerryTS/perry

Length of output: 22281


Do not use dirty_old_pages as from-space scan coverage.

dirty_old_pages only covers old-page dirty headers; the from-space scan walks every surviving slot in the heap, including malloc tracked objects, so slots whose owner is not on a dirty page can still be classified as not inspected from snapshot coverage. Build coverage from the actual scan path, or narrow this wording so not_in_snapshot does not claim the slot was never examined.

📍 Affects 2 files
  • crates/perry-runtime/src/gc/fromspace_scan.rs#L317-L319 (this comment)
  • changelog.d/7043-heap-min-diagnostic.md#L11-L15
🤖 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/fromspace_scan.rs` around lines 317 - 319, The
from-space scan coverage must not be initialized from dirty_old_pages, because
the scan also visits surviving malloc-tracked objects and other slots outside
dirty old-page headers. Update the coverage tracking in fromspace_scan.rs around
SNAPSHOT_PAGES and the actual scan path to record every inspected slot or narrow
not_in_snapshot wording so it does not claim unexamined coverage; update
changelog.d/7043-heap-min-diagnostic.md lines 11-15 to match the corrected
semantics.

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