From 835273d507f0b2ecf5bb6489f71b1bb4b22af232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 28 Aug 2026 07:38:52 +0200 Subject: [PATCH] fix(gc): restore the census-pinned carrier expression in the shape scanner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #8899 lifted `descriptor.old_carrier || descriptor.cache_carrier` into a `let is_carrier` binding for its memo key. Semantically inert, but `shape_descriptor_census.py` pins the whole two-armed expression on purpose, so `lint` has been red on main since it landed — and the census's own self-test, which sabotages that exact literal via a no-op-on-absent `str.replace`, was disarmed at the same time. --- changelog.d/8917-census-carrier-pin.md | 7 +++++++ crates/perry-runtime/src/object/shapes.rs | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 changelog.d/8917-census-carrier-pin.md diff --git a/changelog.d/8917-census-carrier-pin.md b/changelog.d/8917-census-carrier-pin.md new file mode 100644 index 0000000000..a3bd74ab34 --- /dev/null +++ b/changelog.d/8917-census-carrier-pin.md @@ -0,0 +1,7 @@ +Restored the literal `if descriptor.old_carrier || descriptor.cache_carrier` two-armed expression in `scan_shape_table_rekey_mut`. + +#8899 lifted that condition into a `let is_carrier = …` binding for its memo key. The change was semantically inert, but `scripts/shape_descriptor_census.py` deliberately pins the *whole* two-armed expression — so that a sabotage which widens the gate or swaps the arms has to be red — and the refactor stopped matching that pin. `lint` has been red on `main` since #8899 landed. + +It also silently disarmed the census's own self-test: that test sabotages this exact literal via `str.replace(old, new, 1)`, which does nothing when the string is absent, so the "un-gated into an unconditional table root" case was being replaced into nothing and proving nothing. + +The condition is now written out at the decision site, with a comment saying why it must stay literal; `is_carrier` remains and still keys the memo. diff --git a/crates/perry-runtime/src/object/shapes.rs b/crates/perry-runtime/src/object/shapes.rs index 3506f13a23..007424891a 100644 --- a/crates/perry-runtime/src/object/shapes.rs +++ b/crates/perry-runtime/src/object/shapes.rs @@ -1771,7 +1771,13 @@ pub(crate) fn scan_shape_table_rekey_mut(visitor: &mut crate::gc::RuntimeRootVis continue; } let probe_addr = addr; - let moved = if is_carrier { + // Written out rather than reusing `is_carrier` on purpose: the + // census gate (`scripts/shape_descriptor_census.py`) pins this exact + // two-armed expression so that a sabotage which widens the gate or + // swaps the arms is red, and its own self-test sabotages this very + // literal. `is_carrier` above is the same predicate, and is what + // keys the memo. + let moved = if descriptor.old_carrier || descriptor.cache_carrier { visitor.visit_usize_slot(&mut addr) } else { visitor.visit_metadata_usize_slot(&mut addr)