Split out of #6983, where it was raised in review and explicitly declined rather
than silently skipped.
The gap
All of the temp_root family's codegen coverage lives in
crates/perry-codegen/tests/ — temp_root_argument_temporaries.rs (#6972) and
temp_root_operand_temporaries.rs (#6983), 10 tests between them. Per CLAUDE.md:
Integration suites under crates/*/tests/*.rs do not run per-PR (nightly/tag
only) — a regression there can land green and sit red for days. Prefer putting
acceptance coverage in cargo-test-visible unit tests (#5960).
So the assertions pinning the rooting emission contract — push → re-read →
consuming call → release, and the interleaving requirement — are CI-invisible on
the PRs most likely to break them. The runtime half (gc::tests::temp_roots,
in src/) does run per-PR; the codegen half does not.
That matters more than usual for this family, because the failure mode is
silent. A lowering path that quietly goes back to threading a value through an
SSA register produces correct output under the default configuration — the
conservative native-stack scan pins it by accident — and only diverges under
PERRY_CONSERVATIVE_STACK_SCAN=off or an evacuating minor. The emission-contract
tests are the fast signal; the corpus arms are the slow one.
Why it was not fixed in #6983
The assertions need a built Module plus compile_module, so moving them into
src/ unit tests means either constructing a FnCtx by hand or duplicating the
~90 lines of entry_opts() / module_with_init() harness. Doing that once for
the whole family is worth it; doing it piecemeal for one PR's three tests is not,
and would leave the two files inconsistent.
Suggested shape
Extract the harness (entry_opts, module_with_init, ir_for, plus the
ir_for_new class builder from #6983) into something both a src/ unit-test
module and the existing integration tests can call — a #[cfg(test)] helper
module in perry-codegen, or a small pub(crate) test-support module — then
move at least the ordering/interleaving assertions across:
The negative gates matter as much as the positive ones — they are what keeps the
fix from silently becoming a cost on hot paths.
Related: #6951, #6972, #6975, #6983, #5960.
Split out of #6983, where it was raised in review and explicitly declined rather
than silently skipped.
The gap
All of the
temp_rootfamily's codegen coverage lives incrates/perry-codegen/tests/—temp_root_argument_temporaries.rs(#6972) andtemp_root_operand_temporaries.rs(#6983), 10 tests between them. Per CLAUDE.md:So the assertions pinning the rooting emission contract — push → re-read →
consuming call → release, and the interleaving requirement — are CI-invisible on
the PRs most likely to break them. The runtime half (
gc::tests::temp_roots,in
src/) does run per-PR; the codegen half does not.That matters more than usual for this family, because the failure mode is
silent. A lowering path that quietly goes back to threading a value through an
SSA register produces correct output under the default configuration — the
conservative native-stack scan pins it by accident — and only diverges under
PERRY_CONSERVATIVE_STACK_SCAN=offor an evacuating minor. The emission-contracttests are the fast signal; the corpus arms are the slow one.
Why it was not fixed in #6983
The assertions need a built
Modulepluscompile_module, so moving them intosrc/unit tests means either constructing aFnCtxby hand or duplicating the~90 lines of
entry_opts()/module_with_init()harness. Doing that once forthe whole family is worth it; doing it piecemeal for one PR's three tests is not,
and would leave the two files inconsistent.
Suggested shape
Extract the harness (
entry_opts,module_with_init,ir_for, plus their_for_newclass builder from #6983) into something both asrc/unit-testmodule and the existing integration tests can call — a
#[cfg(test)]helpermodule in
perry-codegen, or a smallpub(crate)test-support module — thenmove at least the ordering/interleaving assertions across:
MapSet: receiver rooted before the key is lowered (fix(gc): operand temporaries are precise roots — collection methods, constructor args, string-method receiver (#6970, #6969, #6971) #6983)push(arg0)before argument 1's own allocation (fix(gc): operand temporaries are precise roots — collection methods, constructor args, string-method receiver (#6970, #6969, #6971) #6983)The negative gates matter as much as the positive ones — they are what keeps the
fix from silently becoming a cost on hot paths.
Related: #6951, #6972, #6975, #6983, #5960.