Skip to content

fix(gc): one liveness rule for minor side-allocation owners; live Map stores survive in-place promotion - #12142

Merged
proggeramlug merged 2 commits into
mainfrom
fix/12141-minor-side-owner-liveness
Oct 7, 2026
Merged

proggeramlug merged 2 commits into
mainfrom
fix/12141-minor-side-owner-liveness

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Closes #12141.

During an untraced in-place promotion, the copying minor's side-allocation finalize treated every unmarked Map in the promoted blocks as dead. Untraced promotion marks nothing, so it freed the native store of live Maps, and the next map.set wrote through a NULL store. Any program whose Maps grow past about 131k entries while young blocks are promoted in place could hit it. It predates #12132.

Fix: one liveness rule

  • gc/dead_owner.rs has minor_side_owner_is_dead(header, space). It returns dead only if the block is minor from-space (Eden or the active survivor half), the object is an arena object, and it is not MARKED, FORWARDED, PINNED or TENURED.
    • An in-place promotion retags its blocks as PromotedYoung before the finalize runs, so their objects are never "dead in this minor", whether the promotion was traced or untraced.
    • There is no untraced gate. Dead objects in a traced promotion are left to the old generation, and the traced test below shows the next full collection frees them.
  • Every from-space finalizer and prune asks this one function: the Map walk (map/store.rs), Set, Error diagnostics, lazy JSON tapes and the address-keyed side-table prune, through the existing from-space helper. The comment at gc/copying.rs:1205 points at it.
  • Behaviour change for review: promise contexts now use it too, so a pinned from-space promise keeps its context instead of having it dropped.

Tests

  • test-files/test_gap_map_store_untraced_promotion.ts: four cohorts of 160k object keys, each dropping the last. It matches Node (checksum 13419760 iterated 600319825), and before the fix it crashes 3/3.
  • an_untraced_promotion_keeps_the_store_of_a_live_map:
    • a rooted Map sits next to a dropped 131,100-entry Map during an untraced in-place promotion;
    • it asserts the untraced path was taken, the live Map didn't move and nothing was freed;
    • the live Map keeps its store, size and entries, and still grows and reads correctly.
  • a_traced_promotion_keeps_the_live_map_and_reclaims_the_dead_one: in the traced path, the dead Map is still freed (at the next full collection).
  • Negative control: with Map back on its old filter, the witness fails (2 live stores freed, 4.2 MB).
  • The map_store.rs tests now use a per-thread free counter, so they are safe in parallel. map_store_survives_tenured_evacuation_sweeps_and_exit_walk and map_index_external_bytes_balance_… used to fail with 8 test threads; they now pass.

Verification

  • cargo test --release -p perry-runtime -- --test-threads=1: 5221 passed, 0 failed. Main has 5219; the 2 extra are the new tests.
  • Filter map promote in_place copying set, with 1 and with 8 threads, 3 runs each: all pass except ic_slow::an_unresolved_cache_slot_is_never_dereferenced, which also fails on main in debug builds only.
  • The six original crash probes (200k–600k keys, string and object keys, drop and clear) all pass 3/3 with the expected output.
  • cargo fmt --all -- --check is clean.

Ralph Küpper added 2 commits October 7, 2026 07:20
An untraced in-place promotion marks nothing, but the from-space Map walk
ran before the retagged blocks left the young arenas and read every
unmarked Map on them as dead. It freed the store of live Maps, and the
next set wrote through a null store (SIGSEGV).

Add gc::minor_side_owner_is_dead(header, space): dead only when the owner
is unmarked, unforwarded, unpinned and untenured, and its block is
from-space. The Map walk, Set, Error diagnostics, lazy JSON tapes,
promise contexts and the address-keyed side-table prune all ask it.
Witness: an untraced in-place promotion with a rooted Map and a dropped
131k-entry Map must free no store and leave the live Map usable. A traced
companion checks the dead Map is still reclaimed by the next full.

Count Map side-storage frees per thread as well, so the map_store tests
assert exact deltas while other test threads free Maps in parallel.

Gap test: four 160k object-key cohorts, each dropping the last.
@proggeramlug
proggeramlug merged commit b97a17e into main Oct 7, 2026
23 of 25 checks passed
@proggeramlug
proggeramlug deleted the fix/12141-minor-side-owner-liveness branch October 7, 2026 07:21
@coderabbitai

coderabbitai Bot commented Oct 7, 2026

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f4f2d443-d4af-4e6f-b4c5-485d3a547f9f
📥 Commits

Reviewing files that changed from the base of the PR and between 52163be and ac4de60.

📒 Files selected for processing (11)
  • changelog.d/12141-minor-side-owner-liveness.md
  • crates/perry-runtime/src/gc/copying.rs
  • crates/perry-runtime/src/gc/dead_owner.rs
  • crates/perry-runtime/src/gc/mod.rs
  • crates/perry-runtime/src/gc/tests/map_store.rs
  • crates/perry-runtime/src/map.rs
  • crates/perry-runtime/src/map/store.rs
  • crates/perry-runtime/src/node_submodules/diagnostics_gc.rs
  • crates/perry-runtime/src/promise/mod.rs
  • crates/perry-runtime/src/set.rs
  • test-files/test_gap_map_store_untraced_promotion.ts
 ________________________
< EV: Electric Verifier. >
 ------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

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.

gc: a live Map's store is freed by the minor side-allocation finalize during untraced in-place promotion

1 participant