diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs index 73ab7f20..9b263559 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs @@ -8,6 +8,7 @@ use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::ExitMeta; use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding; +use crate::mir::ValueId; // Phase 228-8: For ConditionOnly placeholder /// ExitMetaCollector: A Box that builds exit_bindings from ExitMeta /// @@ -58,9 +59,16 @@ impl ExitMetaCollector { /// 2. Create LoopExitBinding with carrier_name, join_exit_value, host_slot /// 3. Collect into Vec /// + /// # Phase 228-8: ConditionOnly carrier handling + /// + /// ConditionOnly carriers are included in exit_bindings even if they're not + /// in variable_map, because they need latch incoming values for header PHI. + /// The host_slot is set to ValueId(0) as a placeholder since ConditionOnly + /// carriers don't participate in exit PHI. + /// /// # Skipped carriers /// - /// Carriers not found in variable_map are silently skipped (filter_map behavior). + /// Carriers not found in variable_map AND not in carrier_info are silently skipped. /// This is intentional: some carriers may not be relevant to the current pattern. /// /// # Logging @@ -69,6 +77,7 @@ impl ExitMetaCollector { pub fn collect( builder: &MirBuilder, exit_meta: &ExitMeta, + carrier_info: Option<&crate::mir::join_ir::lowering::carrier_info::CarrierInfo>, // Phase 228-8: Added carrier_info debug: bool, ) -> Vec { let mut bindings = Vec::new(); @@ -90,24 +99,62 @@ impl ExitMetaCollector { // Look up host slot from variable_map if let Some(&host_slot) = builder.variable_map.get(carrier_name) { use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + + // Phase 228-8: Look up role from carrier_info if available + let role = if let Some(ci) = carrier_info { + ci.carriers.iter() + .find(|c| c.name == *carrier_name) + .map(|c| c.role) + .unwrap_or(CarrierRole::LoopState) + } else { + CarrierRole::LoopState + }; + let binding = LoopExitBinding { carrier_name: carrier_name.clone(), join_exit_value: *join_exit_value, host_slot, - role: CarrierRole::LoopState, // Phase 227: Default to LoopState (caller should update if needed) + role, }; eprintln!( - "[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}", - carrier_name, join_exit_value, host_slot + "[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}, role={:?}", + carrier_name, join_exit_value, host_slot, role ); bindings.push(binding); } else { - eprintln!( - "[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map (skip)", - carrier_name - ); + // Phase 228-8: Check if this is a ConditionOnly carrier + use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + let is_condition_only = if let Some(ci) = carrier_info { + ci.carriers.iter() + .any(|c| c.name == *carrier_name && c.role == CarrierRole::ConditionOnly) + } else { + false + }; + + if is_condition_only { + // Phase 228-8: Include ConditionOnly carrier in exit_bindings + // (needed for latch incoming, not for exit PHI) + let binding = LoopExitBinding { + carrier_name: carrier_name.clone(), + join_exit_value: *join_exit_value, + host_slot: ValueId(0), // Placeholder - not used for ConditionOnly + role: CarrierRole::ConditionOnly, + }; + + eprintln!( + "[cf_loop/exit_line] Phase 228-8: Collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_map)", + carrier_name, join_exit_value + ); + + bindings.push(binding); + } else { + eprintln!( + "[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map and not ConditionOnly (skip)", + carrier_name + ); + } } } diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs index f4325c52..557a5774 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs @@ -104,6 +104,18 @@ impl ExitLineReconnector { // Process each exit binding for binding in &boundary.exit_bindings { + // Phase 228-8: Skip ConditionOnly carriers (no variable_map update needed) + use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + if binding.role == CarrierRole::ConditionOnly { + if debug { + eprintln!( + "[cf_loop/joinir/exit_line] Phase 228-8: Skipping ConditionOnly carrier '{}' (no variable_map update)", + binding.carrier_name + ); + } + continue; + } + // Phase 33-13: Look up the PHI dst for this carrier let phi_dst = carrier_phis.get(&binding.carrier_name); @@ -173,7 +185,18 @@ impl ExitLineReconnector { carrier_phis: &BTreeMap, variable_map: &BTreeMap, ) { + use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + for binding in &boundary.exit_bindings { + // Phase 228-8: Skip ConditionOnly carriers (not in variable_map by design) + if binding.role == CarrierRole::ConditionOnly { + eprintln!( + "[JoinIR/ExitLine/Contract] Phase 228-8: Skipping ConditionOnly carrier '{}' (not in variable_map)", + binding.carrier_name + ); + continue; + } + // Contract 1: carrier_phis must contain this carrier let phi_dst = carrier_phis.get(&binding.carrier_name); if phi_dst.is_none() { diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index d5b14ced..fb3ad270 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -546,8 +546,9 @@ impl MirBuilder { let exit_meta = &fragment_meta.exit_meta; // Phase 33-10: Collect exit bindings from ExitMeta using Box + // Phase 228-8: Pass carrier_info to include ConditionOnly carriers use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector; - let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, debug); + let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, Some(&carrier_info), debug); // Phase 176-3: Build input mappings for all carriers // JoinIR main() params: [ValueId(0), ValueId(1), ValueId(2), ...] for (i, carrier1, carrier2, ...) diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs index 3873f191..f711f149 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs @@ -154,7 +154,8 @@ impl MirBuilder { } // Build exit bindings using ExitMetaCollector - let exit_bindings = ExitMetaCollector::collect(self, exit_meta, debug); + // Phase 228-8: Pass carrier_info to include ConditionOnly carriers + let exit_bindings = ExitMetaCollector::collect(self, exit_meta, Some(&ctx.carrier_info), debug); // Build boundary with carrier inputs use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; @@ -297,9 +298,11 @@ impl MirBuilder { // Phase 213: Use ExitMetaCollector for dynamic exit binding generation // Note: ExitMetaCollector internally validates that all exit carriers in ExitMeta // have corresponding variable_map entries. No additional validation needed here. + // Phase 228-8: Pass None for carrier_info (legacy path doesn't have ConditionOnly carriers) let exit_bindings = ExitMetaCollector::collect( self, exit_meta, + None, // Phase 228-8: No carrier_info in legacy path debug, ); diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs index d0b9a9b1..a8d33baa 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs @@ -330,10 +330,12 @@ impl MirBuilder { // Phase 179 Task 2: Use ExitMetaCollector for unified exit binding generation // Replaces manual loop (Phase 196 implementation) with modular Box approach + // Phase 228-8: Pass carrier_info to include ConditionOnly carriers use super::super::merge::exit_line::meta_collector::ExitMetaCollector; let exit_bindings = ExitMetaCollector::collect( self, &exit_meta, + Some(&carrier_info), // Phase 228-8: Include ConditionOnly carriers debug, );