diff --git a/crates/perry-codegen/src/function/precise_roots.rs b/crates/perry-codegen/src/function/precise_roots.rs index 401155b1cf..7aa9ac1492 100644 --- a/crates/perry-codegen/src/function/precise_roots.rs +++ b/crates/perry-codegen/src/function/precise_roots.rs @@ -315,8 +315,7 @@ fn lower_roots_for_rs4gc(lines: &[&str], root_ptrs: &[String]) -> Option if is_call && trimmed.ends_with(')') && !trimmed.contains(" asm ") { if let Some(callee) = direct_callee_name(line) { let leaf = match crate::gc_call_effects::classify_direct_callee(callee) { - crate::gc_call_effects::GcCallEffect::CannotCollect - | crate::gc_call_effects::GcCallEffect::NeverReturns => true, + crate::gc_call_effects::GcCallEffect::CannotCollect => true, crate::gc_call_effects::GcCallEffect::AllocNoReentry => { crate::codegen::helpers::gc_safepoint_only_contract_enabled() } @@ -712,7 +711,6 @@ pub(super) fn lower_precise_roots_to_native_stack( matches!( crate::gc_call_effects::classify_direct_callee(c), crate::gc_call_effects::GcCallEffect::CannotCollect - | crate::gc_call_effects::GcCallEffect::NeverReturns ) }); if let Some(report) = report.as_mut() { @@ -757,10 +755,6 @@ pub(super) fn lower_precise_roots_to_native_stack( let cannot_collect = direct_callee.is_some_and(|callee| { match crate::gc_call_effects::classify_direct_callee(callee) { crate::gc_call_effects::GcCallEffect::CannotCollect => true, - // Control never returns here: no relocation is consumed and - // the frame's roots are dead past the call. Deeper frames - // carry their own records. - crate::gc_call_effects::GcCallEffect::NeverReturns => true, // Under the explicit-safepoint contract the runtime // guarantees these helpers' triggers never consume this // frame's precise roots (they defer to a declared safepoint diff --git a/crates/perry-codegen/src/gc_call_effects.rs b/crates/perry-codegen/src/gc_call_effects.rs index 2c379dd99e..adb85f49de 100644 --- a/crates/perry-codegen/src/gc_call_effects.rs +++ b/crates/perry-codegen/src/gc_call_effects.rs @@ -23,14 +23,6 @@ pub(crate) enum GcCallEffect { /// consumed at this call site and it needs no statepoint. Without the /// contract these remain safepoints. AllocNoReentry, - /// The callee never returns to this call site (audited 2026-08-01: every - /// `js_throw*` helper funnels into `exception::js_throw`, which is - /// `-> !` — the `f64` results are unreachable ABI shape). No relocation - /// can ever be consumed downstream and the frame's roots are dead past - /// the call, so the site needs no metadata in ANY mode. Values the - /// helper itself holds are its own frame's responsibility - /// (`RuntimeHandleScope`/temp roots), exactly as for every helper call. - NeverReturns, Unknown, } @@ -113,9 +105,12 @@ pub(crate) fn classify_direct_callee(name: &str) -> GcCallEffect { | "js_validate_array_comparator" | "js_validate_array_map_callback" => GcCallEffect::AllocNoReentry, // NO `js_throw*` prefix arm. It used to classify the whole family - // `NeverReturns`, which suppresses the safepoint in every mode — the - // strongest classification in this table, and the only one applied by - // prefix rather than exact name. + // a `NeverReturns` classification that suppressed the safepoint in + // every mode — the strongest possible, and the only one that would be + // applied by prefix rather than exact name. That variant is DELETED, + // not merely unused: it was never constructed, so its three match arms + // in `precise_roots.rs` were dead, and the kill-policy in CLAUDE.md + // says an unexercised mode is a decision nobody has made. // // Two things make that unsafe. The audit it rested on is already // false: `js_throw_reference_error_tdz`, `js_throw_not_a_constructor`