Skip to content

Fix array side-table GC lifecycles - #6330

Merged
proggeramlug merged 1 commit into
mainfrom
fix/gc-array-side-table-lifecycle
Jul 13, 2026
Merged

Fix array side-table GC lifecycles#6330
proggeramlug merged 1 commit into
mainfrom
fix/gc-array-side-table-lifecycle

Conversation

@TheHypnoo

@TheHypnoo TheHypnoo commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary

Fix raw-address array side tables so live metadata follows GC movement and dead metadata is removed before allocator address reuse. This prevents leaked expando values and stale Map/Set iterator brands.

Changes

  • prune dead array named-property owners during full/fallback and copied-minor collections
  • rekey Map/Set iterator-array brands after evacuation without keeping dead arrays alive
  • remove stale iterator brands before address reuse
  • add deterministic full-GC, movement, and exact-address reuse regressions

Related issue

n/a

Test plan

  • cargo build --release clean — not run; no build-system changes
  • cargo test --workspace --exclude perry-ui-ios --exclude perry-ui-tvos --exclude perry-ui-watchos --exclude perry-ui-gtk4 --exclude perry-ui-android --exclude perry-ui-windows passes — not run; focused runtime and root-contract suites used instead
  • Added #[test] regressions in the affected runtime crate
  • Documentation update not applicable; this changes internal GC bookkeeping only
  • Platform UI backend build not applicable

Validation performed:

  • RUST_TEST_THREADS=1 cargo test --lib -p perry-runtime array_named — 6 passed
  • RUST_TEST_THREADS=1 cargo test --lib -p perry-runtime dead_owner_side_tables — 18 passed
  • RUST_TEST_THREADS=1 cargo test --lib -p perry-runtime — 1270 passed, 1 ignored
  • python3 scripts/gc_store_site_inventory.py --self-test — passed
  • python3 scripts/gc_store_site_inventory.py — passed
  • cargo fmt --all -- --check — passed
  • cargo test -p perry-codegen --test shadow_slot_hygiene — 9 passed

Screenshots / output

Not applicable; there are no user-interface changes.

Checklist

  • I have NOT bumped the workspace version or edited CLAUDE.md / CHANGELOG.md
  • My commit follows the loose feat: / fix: / docs: / chore: prefix convention
  • I've read CONTRIBUTING.md and agree to the Code of Conduct

Summary by CodeRabbit

  • Bug Fixes
    • Improved garbage collection handling for array named properties and Map/Set iterator state.
    • Dead-owner cleanup now prevents stale expando/iterator metadata from incorrectly rooting or affecting re-used addresses.
    • Ensured side tables and iterator brands remain consistent after object relocation during garbage collection.
  • Tests
    • Expanded coverage for dead-owner pruning, address reuse, relocation rekeying, and correct iterator branding for both Map and Set across full and minor GC cycles.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

GC now updates and removes array-backed named-property and iterator-marker side tables during object movement and dead-owner cleanup. Runtime registration and six lifecycle tests cover pruning, address reuse, evacuation rekeying, pointer rewriting, and iterator branding.

Changes

Array side-table lifecycle

Layer / File(s) Summary
Named-property owner cleanup
crates/perry-runtime/src/array/header.rs, crates/perry-runtime/src/array/mod.rs
Adds dead-owner pruning for array named-property entries and test-only helpers for querying and clearing the side table.
Iterator registry GC bookkeeping
crates/perry-runtime/src/map.rs, crates/perry-runtime/src/set.rs, crates/perry-runtime/src/gc/mod.rs, crates/perry-runtime/src/gc/dead_owner.rs
Rekeys moved Map and Set iterator-array addresses, prunes dead owners, registers mutable scanners, and invokes the new pruning passes during GC fan-out.
Lifecycle regression coverage
crates/perry-runtime/src/gc/tests/dead_owner_side_tables.rs
Adds isolated tests for dead-owner cleanup, exact Eden address reuse, live moves, object-pointer rewriting, and Map/Set iterator marker behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant GC
  participant ArraySideTables
  participant MapIteratorRegistry
  participant SetIteratorRegistry
  GC->>ArraySideTables: Rewrite moved named-property roots
  GC->>MapIteratorRegistry: Rewrite moved iterator-array roots
  GC->>SetIteratorRegistry: Rewrite moved iterator-array roots
  GC->>ArraySideTables: Prune dead owners
  GC->>MapIteratorRegistry: Prune dead owners
  GC->>SetIteratorRegistry: Prune dead owners
Loading

Possibly related PRs

  • PerryTS/perry#6208: Updates the GC dead-owner side-table pruning pipeline for weakref and finalization entries.

Suggested labels: run-extended-tests

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title is concise and accurately summarizes the main change: fixing GC lifecycle handling for array side tables.
Description check ✅ Passed The description follows the template with Summary, Changes, Related issue, Test plan, Screenshots/output, and Checklist sections filled in.
✨ 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/gc-array-side-table-lifecycle

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: 2

🧹 Nitpick comments (1)
crates/perry-runtime/src/map.rs (1)

33-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate GC-lifecycle logic with set.rs.

scan_map_iterator_array_roots_mut/prune_dead_map_iterator_array_owners are identical in structure to scan_set_iterator_array_roots_mut/prune_dead_set_iterator_array_owners in crates/perry-runtime/src/set.rs (lines 32-53), differing only by the thread-local name. Since this is subtle, correctness-critical GC rekeying code, a shared generic helper would avoid future divergence between the two copies.

♻️ Suggested consolidation (illustrative)
// e.g. in a small shared gc side-table module
pub(crate) fn rekey_metadata_address_set(
    set: &RefCell<HashSet<usize>>,
    visitor: &mut crate::gc::RuntimeRootVisitor<'_>,
) {
    let mut arrays = set.borrow_mut();
    let mut moved = Vec::new();
    for old_addr in arrays.iter().copied() {
        let mut new_addr = old_addr;
        if visitor.visit_metadata_usize_slot(&mut new_addr) {
            moved.push((old_addr, new_addr));
        }
    }
    for (old_addr, new_addr) in moved {
        arrays.remove(&old_addr);
        arrays.insert(new_addr);
    }
}

pub(crate) fn prune_dead_address_set(
    set: &RefCell<HashSet<usize>>,
    is_dead_owner: &dyn Fn(usize) -> bool,
) {
    set.borrow_mut().retain(|owner| !is_dead_owner(*owner));
}
-pub(crate) fn scan_map_iterator_array_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) {
-    MAP_ITERATOR_ARRAYS.with(|r| {
-        let mut arrays = r.borrow_mut();
-        let mut moved = Vec::new();
-        for old_addr in arrays.iter().copied() {
-            let mut new_addr = old_addr;
-            if visitor.visit_metadata_usize_slot(&mut new_addr) {
-                moved.push((old_addr, new_addr));
-            }
-        }
-        for (old_addr, new_addr) in moved {
-            arrays.remove(&old_addr);
-            arrays.insert(new_addr);
-        }
-    });
-}
-
-pub(crate) fn prune_dead_map_iterator_array_owners(is_dead_owner: &dyn Fn(usize) -> bool) {
-    MAP_ITERATOR_ARRAYS.with(|r| {
-        r.borrow_mut().retain(|owner| !is_dead_owner(*owner));
-    });
-}
+pub(crate) fn scan_map_iterator_array_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) {
+    MAP_ITERATOR_ARRAYS.with(|r| crate::gc::rekey_metadata_address_set(r, visitor));
+}
+
+pub(crate) fn prune_dead_map_iterator_array_owners(is_dead_owner: &dyn Fn(usize) -> bool) {
+    MAP_ITERATOR_ARRAYS.with(|r| crate::gc::prune_dead_address_set(r, is_dead_owner));
+}
🤖 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/map.rs` around lines 33 - 56, Consolidate the
duplicated GC side-table logic used by scan_map_iterator_array_roots_mut and
prune_dead_map_iterator_array_owners with the corresponding set.rs functions.
Add shared helpers for rekeying metadata address sets and pruning dead owners,
then update both map and set callers to use them while preserving the existing
visitor and retention behavior.
🤖 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 `@plans/002-fix-array-side-table-lifecycles.md`:
- Around line 21-24: Update the Plan 002 dependency wording in the plan document
to explicitly state that Plan 001 is recommended but not required, and that Plan
002 may proceed independently without Plan 001. Align the wording with the
dependency guidance in plans/README.md without changing the lifecycle scope.

In `@plans/README.md`:
- Around line 21-24: Update the dependency statement for Plans 002 and 003 in
the plan ordering guidance to say they may run independently, while recommending
Plan 001 be completed first. Keep the existing ordering rationale and other plan
relationships unchanged.

---

Nitpick comments:
In `@crates/perry-runtime/src/map.rs`:
- Around line 33-56: Consolidate the duplicated GC side-table logic used by
scan_map_iterator_array_roots_mut and prune_dead_map_iterator_array_owners with
the corresponding set.rs functions. Add shared helpers for rekeying metadata
address sets and pruning dead owners, then update both map and set callers to
use them while preserving the existing visitor and retention behavior.
🪄 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: f744e746-87ad-4532-bbdc-b9cbdea04c6b

📥 Commits

Reviewing files that changed from the base of the PR and between 2c32585 and 615b6e2.

📒 Files selected for processing (9)
  • crates/perry-runtime/src/array/header.rs
  • crates/perry-runtime/src/array/mod.rs
  • crates/perry-runtime/src/gc/dead_owner.rs
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/tests/dead_owner_side_tables.rs
  • crates/perry-runtime/src/map.rs
  • crates/perry-runtime/src/set.rs
  • plans/002-fix-array-side-table-lifecycles.md
  • plans/README.md

Comment on lines +21 to +24
3. remove the stale key before the old address can be reused.

The named-property table rewrites live owners and scans stored values, but does not prune dead owners. The iterator marker sets are written by production paths and currently have neither rewrite nor death hooks.

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

Clarify the Plan 002 dependency wording.

This plan says Plan 001 is recommended but not required, while plans/README.md says Plans 002 and 003 can run independently “after 001,” which implies 001 is a prerequisite. State explicitly that Plan 001 is recommended, but Plan 002 may proceed without it.

🤖 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 `@plans/002-fix-array-side-table-lifecycles.md` around lines 21 - 24, Update
the Plan 002 dependency wording in the plan document to explicitly state that
Plan 001 is recommended but not required, and that Plan 002 may proceed
independently without Plan 001. Align the wording with the dependency guidance
in plans/README.md without changing the lifecycle scope.

Comment thread plans/README.md Outdated
Comment on lines +21 to +24
- 001 is first because the following plans change unsafe GC ownership, traversal, and heap-layout boundaries; deterministic ASan coverage lowers their regression risk.
- 002 and 003 can run independently after 001.
- 004 depends on 001 and is isolated from 002/003, but should land before performance work because it closes a resource-lifetime gap.
- 005 and 006 are separate optimizations. Execute 005 first because it reuses an already-proven filtered traversal and has the lower soundness risk.

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

Clarify that Plan 001 is not a hard prerequisite.

The status table and Plan 002 state that Plan 001 is only recommended, but this sentence says Plans 002 and 003 run “after 001.” Change it to clarify that they may run independently, with Plan 001 recommended first.

🤖 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 `@plans/README.md` around lines 21 - 24, Update the dependency statement for
Plans 002 and 003 in the plan ordering guidance to say they may run
independently, while recommending Plan 001 be completed first. Keep the existing
ordering rationale and other plan relationships unchanged.

@TheHypnoo
TheHypnoo force-pushed the fix/gc-array-side-table-lifecycle branch from 615b6e2 to 796ff3d Compare July 12, 2026 21:48
@TheHypnoo
TheHypnoo force-pushed the fix/gc-array-side-table-lifecycle branch from 796ff3d to 2e40a45 Compare July 12, 2026 21:52
@proggeramlug
proggeramlug merged commit 2179e9f into main Jul 13, 2026
25 checks passed
@proggeramlug
proggeramlug deleted the fix/gc-array-side-table-lifecycle branch July 13, 2026 04:28
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.

2 participants