diff --git a/src/mir/builder.rs b/src/mir/builder.rs index e8785e8d..4e53097d 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -180,6 +180,14 @@ pub struct MirBuilder { pub(super) local_ssa_map: HashMap<(BasicBlockId, ValueId, u8), ValueId>, /// BlockSchedule cache: deduplicate materialize copies per (bb, src) pub(super) schedule_mat_map: HashMap<(BasicBlockId, ValueId), ValueId>, + + /// Guard flag to prevent re-entering emit_unified_call from BoxCall fallback. + /// Used when RouterPolicyBox in emit_unified_call has already decided to + /// route a given Method call to BoxCall; emit_box_or_plugin_call must not + /// bounce back into the unified path for the same call, otherwise an + /// infinite recursion (emit_unified_call → emit_box_or_plugin_call → + /// emit_unified_call …) can occur when routing decisions disagree. + pub(super) in_unified_boxcall_fallback: bool, } impl MirBuilder { @@ -232,6 +240,8 @@ impl MirBuilder { local_ssa_map: HashMap::new(), schedule_mat_map: HashMap::new(), + + in_unified_boxcall_fallback: false, } } diff --git a/src/mir/builder/calls/emit.rs b/src/mir/builder/calls/emit.rs index b63b580c..f935a6fe 100644 --- a/src/mir/builder/calls/emit.rs +++ b/src/mir/builder/calls/emit.rs @@ -108,7 +108,16 @@ impl MirBuilder { eprintln!("[router-guard] {}.{} → BoxCall fallback (recv=%{})", box_name, method, r.0); } let effects = EffectMask::READ.add(Effect::ReadHeap); - return self.emit_box_or_plugin_call(dst, *r, method.clone(), None, args, effects); + // Prevent BoxCall helper from bouncing back into emit_unified_call + // for the same call. RouterPolicyBox has already decided on + // Route::BoxCall for this callee, so emit_box_or_plugin_call + // must not re-enter the unified path even if its own heuristics + // would otherwise choose Unified. + let prev_flag = self.in_unified_boxcall_fallback; + self.in_unified_boxcall_fallback = true; + let res = self.emit_box_or_plugin_call(dst, *r, method.clone(), None, args, effects); + self.in_unified_boxcall_fallback = prev_flag; + return res; } } diff --git a/src/mir/builder/utils.rs b/src/mir/builder/utils.rs index d11e4978..99abb223 100644 --- a/src/mir/builder/utils.rs +++ b/src/mir/builder/utils.rs @@ -181,7 +181,14 @@ impl super::MirBuilder { ); } } - if use_unified_env && matches!(route, crate::mir::builder::router::policy::Route::Unified) { + // Unified path from BoxCall helper is only allowed when we are not + // already in a BoxCall fallback originating from emit_unified_call. + // in_unified_boxcall_fallback is set by emit_unified_call's RouterPolicy + // guard when it has already decided that this call must be a BoxCall. + if use_unified_env + && matches!(route, crate::mir::builder::router::policy::Route::Unified) + && !self.in_unified_boxcall_fallback + { let target = super::builder_calls::CallTarget::Method { box_type, method: method.clone(),