Repository navigation
Integration suites assert the shadow-stack lowering but NativeRootsPin is #[cfg(test)] — shadow_slot_hygiene 0/12 red since #7370 #7493
Description
Symptom
Several perry-codegen integration suites are red on main (f05ae3b), arm64 macOS, debug:
| suite | default |
|---|---|
tests/shadow_slot_hygiene.rs |
0 passed, 12 failed |
tests/scalar_replaced_slot_roots.rs |
2 passed, 9 failed |
tests/temp_root_operand_temporaries.rs |
12 passed, 7 failed |
tests/temp_root_argument_temporaries.rs |
3 passed, 4 failed |
tests/native_proof_regressions.rs |
249 passed, 4 failed (3 of them this cause) |
Every one of these failures reproduces when the test is run alone, so this is not order dependence and it is unrelated to #7490 (whose cascade was a poisoned mutex in typed_feedback.rs, fixed in #7492).
Cause
#7370 made native roots (statepoints) the default lowering. These suites assert on the shadow-stack lowering — they used to get it by accident, because there was only one default. The failure text says so directly:
box pointer slot should be stored as i64 before helper edges:
call i64 @js_box_alloc_bits(i64 9222246136947933185)
%rs4gc.s3 = inttoptr i64 %r1 to ptr addrspace(1)
That much was already understood: codegen/helpers.rs grew NativeRootsPin::shadow() for exactly this, with the comment "a test that asserts on shadow-stack IR has to SAY so … Eight tests broke on exactly this when the default flipped, and every one of them was correct about what it asserted."
But NativeRootsPin is #[cfg(test)], so it is compiled only into the crate's own unit-test build and is unreachable from tests/*.rs. The in-crate unit tests were repaired; the integration suites had no pin to reach for and were left red. They do not run per-PR (nightly/tag only — the CLAUDE.md pitfall), so nothing turned red on the merge.
Evidence that the lowering is the variable
Full --no-fail-fast -- --test-threads=1 sweeps of -p perry-codegen --tests, default vs. the process-global override:
suite default PERRY_RS4GC=0
shadow_slot_hygiene 0 / 12 pass 11 / 12 pass ← the clean signal
scalar_replaced_slot_roots 2 / 11 pass 5 / 11 pass
temp_root_operand_temporaries 12 / 19 pass 13 / 19 pass
temp_root_argument_temporaries 3 / 7 pass 3 / 7 pass ← unmoved
native_proof_regressions 249 /253 pass 236 /253 pass ← INVERSE
native_proof_buffer_views 28 / 30 pass 27 / 30 pass ← inverse
typed_feedback (after #7492) 16 / 16 pass 16 / 16 pass
shadow_slot_hygiene going 0/12 → 11/12 is the diagnosis. But native_proof_regressions loses 13 tests under PERRY_RS4GC=0 — its whole invalidation::* family asserts the native-roots lowering and is correct today. So there is no single global setting that satisfies the set: the pin has to be per test, which is precisely what NativeRootsPin is for.
Within native_proof_regressions, three of the four default failures are healed by PERRY_RS4GC=0 and belong here:
boxed_local_slot_uses_i64_js_value_bits_until_helper_edgesboxed_param_slot_uses_i64_js_value_bits_until_helper_edgestyped_f64_receiver_method_clone_raw_loads_after_composed_guards
(The fourth, integer_modulo::i32_counter_mod_unsafe_or_nonliteral_divisors_keep_frem, is unmoved by the lowering and is tracked in the sibling issue.)
PERRY_RS4GC is also the wrong instrument for a test to reach for: rs4gc_env_override() caches it in a process-global OnceLock, so the first caller in the binary pins the answer for every later test — a fresh instance of the same first-caller-wins hazard.
Suggested shape
- Make the pin reachable from integration tests — e.g.
#[doc(hidden)] pubbehind atestingcargo feature, or move these suites' bodies into in-crate#[cfg(test)]modules (which also gets them into the per-PRcargo-testgate, per CI: PR cargo-test never executes crates/perry integration suites; main-push full run starves under merge trains (documented near-miss) #5960). - Pin each suite to the lowering it actually asserts, and say so at the top of the file. Both lowerings are correct rooting mechanisms; a test asserting one has to declare which.
- Triage the residue — the lowering default is the dominant cause but not the only one. Under
PERRY_RS4GC=0,shadow_slot_hygienestill has 1 failure,scalar_replaced_slot_roots6,temp_root_operand_temporaries6, andtemp_root_argument_temporariesis completely unmoved at 3/7. - Consider whether any of these belong in a per-PR gate. A suite that can sit 0/12 red without anything going red is documentation, not a gate.
Repro
cargo test -p perry-codegen --tests --no-fail-fast -- --test-threads=1
# then the same command prefixed with PERRY_RS4GC=0 for the contrast above
Beware truncating that output — piping it through head silently drops the tail of the target list, which is how temp_root_operand_temporaries and typed_shape_descriptors were initially missed here.
Activity
fix(test): make the root-lowering pin reachable from integration suit…
fix(test): make the root-lowering pin reachable from integration suit…
test(gc): cover the root lowering that actually ships (#7502)
test(gc): cover the root lowering that actually ships — native-roots …