You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The shipped root lowering (native/statepoints) has no coverage in the suites #7493 pinned to shadow — 9 mechanics, 3 of them found passing vacuously #7502
Split out of #7493, which restored these suites' assertions by pinning them to the lowering they were written against. That was the right repair — but it leaves a hole that "the tests are green again" hides, and this issue is that hole.
The problem
Since #7370, native roots (RS4GC statepoints) are the lowering that ships on aarch64 and x86_64 — every target whose frames the runtime can walk. The shadow stack survives only as the fallback for arm64_32 watchOS and ARM64 Windows.
After #7493, shadow_slot_hygiene (12 tests) and scalar_replaced_slot_roots (11 tests) pin NativeRootsPin::shadow(). Every assertion in both files is about a mechanism the shipped configuration does not use:
mechanic asserted (shadow)
native-roots equivalent
covered today?
a pointer-typed local reserves a frame slot
the local's alloca is ptr addrspace(1) and appears in the stack map
no
a dead value's slot is cleared before the next allocation
the value is not live at the statepoint, so it is not in that statepoint's live set
no
a numeric local reserves NO slot
a numeric local's alloca stays ptr / double, out of the map
no
slot indices are not shifted by an interleaved numeric local
(no indices exist; the map is keyed by frame offset)
n/a
the entry-module frame starts AFTER the init prelude
the entry function's first statepoint is after js_gc_init
no
a loop body's slots are cleared each iteration
per-back-edge statepoint live sets do not carry the previous iteration's value
no
a scalar-replaced field holding a heap value is a precise root (#6968)
that field's alloca is addrspace(1) and relocated
no
a scalar-replaced numeric-only literal pays NO rooting (#6997)
its allocas stay non-addrspace(1)
no
duplicate var declarations keep every slot inside the frame (#7184's shape)
(no frame bound exists; the #7184 shape is unrepresentable)
n/a
So: nine distinct root-lowering mechanics have zero assertions against the lowering Perry actually emits. Six of the nine are the exact shapes docs/src/internals/gc-rooting-invariant.md lists as having already shipped broken.
gc-root-dominance.yml is not a substitute. It reads emitted IR for store dominance — it answers "is this root store ordered correctly", not "is this value a root at all". A value the statepoint lowering silently declines to put in the map has no store for it to check.
Why it matters more than the usual "add tests" issue
numeric_only_scalar_replaced_{object,array}_emits_no_rooting asserted bind_calls(&ir) == 0. Under the native default that count is zero for every program — the tests were passing vacuously, i.e. CLAUDE.md hazard 4.
a_collection_free_construction_emits_no_this_slot_root asserted !contains("@js_shadow_slot_bind"), likewise true of every program.
Three tests that read as coverage were measuring nothing. There is no reason to think those are the only three; they are the three that happened to be adjacent to a lowering pin.
What to build
A native-roots counterpart for each row above. The load-bearing design question is what to assert against, since the statepoint form has no bind call to count:
Pre-opt IR — the ptr addrspace(1) alloca set and the gc "statepoint-example" attribute are visible in what compile_module returns today. Cheapest; proves the request, not the result.
The emitted __perry_gcmap section — crates/perry-codegen/src/gc_map.rs already builds it; assert on the parsed map. Strongest, and it is what the runtime actually reads.
(3) is the one that can catch "the map says nothing lives here". Prefer it where the mechanic is about which values are roots; (1) is fine for the negative direction (a numeric local must never become addrspace(1)).
Acceptance
Each no row above has at least one native-roots assertion.
Each new test is sabotage-checked: with the rooting deliberately dropped it must fail. A negative assertion in particular must be shown to be non-vacuous under the lowering it runs on — that is the specific mistake this issue exists to stop repeating.
The shadow-pinned originals stay. Both lowerings are supported; both need coverage. The point is not to replace them.
Split out of #7493, which restored these suites' assertions by pinning them to the lowering they were written against. That was the right repair — but it leaves a hole that "the tests are green again" hides, and this issue is that hole.
The problem
Since #7370, native roots (RS4GC statepoints) are the lowering that ships on aarch64 and x86_64 — every target whose frames the runtime can walk. The shadow stack survives only as the fallback for
arm64_32watchOS and ARM64 Windows.After #7493,
shadow_slot_hygiene(12 tests) andscalar_replaced_slot_roots(11 tests) pinNativeRootsPin::shadow(). Every assertion in both files is about a mechanism the shipped configuration does not use:ptr addrspace(1)and appears in the stack mapptr/double, out of the mapjs_gc_initaddrspace(1)and relocatedaddrspace(1)vardeclarations keep every slot inside the frame (#7184's shape)So: nine distinct root-lowering mechanics have zero assertions against the lowering Perry actually emits. Six of the nine are the exact shapes
docs/src/internals/gc-rooting-invariant.mdlists as having already shipped broken.gc-root-dominance.ymlis not a substitute. It reads emitted IR for store dominance — it answers "is this root store ordered correctly", not "is this value a root at all". A value the statepoint lowering silently declines to put in the map has no store for it to check.Why it matters more than the usual "add tests" issue
Both #7493's own findings are instances of this:
numeric_only_scalar_replaced_{object,array}_emits_no_rootingassertedbind_calls(&ir) == 0. Under the native default that count is zero for every program — the tests were passing vacuously, i.e. CLAUDE.md hazard 4.a_collection_free_construction_emits_no_this_slot_rootasserted!contains("@js_shadow_slot_bind"), likewise true of every program.Three tests that read as coverage were measuring nothing. There is no reason to think those are the only three; they are the three that happened to be adjacent to a lowering pin.
What to build
A native-roots counterpart for each row above. The load-bearing design question is what to assert against, since the statepoint form has no bind call to count:
optIR — theptr addrspace(1)alloca set and thegc "statepoint-example"attribute are visible in whatcompile_modulereturns today. Cheapest; proves the request, not the result.gc.statepoint/gc.relocateand each statepoint's live-value list. Proves the result. Needs the in-process pipeline (In-process LLVM backend: native construction via the C API, opt-in (#7241, engine-plan layer 0) #7301) to be reachable from a test.__perry_gcmapsection —crates/perry-codegen/src/gc_map.rsalready builds it; assert on the parsed map. Strongest, and it is what the runtime actually reads.(3) is the one that can catch "the map says nothing lives here". Prefer it where the mechanic is about which values are roots; (1) is fine for the negative direction (a numeric local must never become
addrspace(1)).Acceptance