Skip to content

Integration suites assert the shadow-stack lowering but NativeRootsPin is #[cfg(test)] — shadow_slot_hygiene 0/12 red since #7370 #7493

Description

@proggeramlug

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_edges
  • boxed_param_slot_uses_i64_js_value_bits_until_helper_edges
  • typed_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

  1. Make the pin reachable from integration tests — e.g. #[doc(hidden)] pub behind a testing cargo feature, or move these suites' bodies into in-crate #[cfg(test)] modules (which also gets them into the per-PR cargo-test gate, per CI: PR cargo-test never executes crates/perry integration suites; main-push full run starves under merge trains (documented near-miss) #5960).
  2. 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.
  3. Triage the residue — the lowering default is the dominant cause but not the only one. Under PERRY_RS4GC=0, shadow_slot_hygiene still has 1 failure, scalar_replaced_slot_roots 6, temp_root_operand_temporaries 6, and temp_root_argument_temporaries is completely unmoved at 3/7.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions