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 d2ff5b50..da9145d6 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 @@ -100,6 +100,7 @@ impl MirBuilder { _func_name: &str, debug: bool, ) -> Result, String> { + use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierVar}; use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::BasicBlockId; @@ -121,14 +122,28 @@ impl MirBuilder { ) })?; - // Get sum variable from variable_map - let sum_var_id = self - .variable_map - .get("sum") - .copied() - .ok_or_else(|| { - format!("[cf_loop/pattern4] Sum variable 'sum' not found in variable_map") - })?; + // Phase 196: Build CarrierInfo from variable_map + // Collect all non-loop-var variables as potential carriers + let mut carriers = Vec::new(); + for (var_name, &var_id) in &self.variable_map { + if var_name != &loop_var_name { + carriers.push(CarrierVar { + name: var_name.clone(), + host_id: var_id, + }); + } + } + + let carrier_info = CarrierInfo { + loop_var_name: loop_var_name.clone(), + loop_var_id, + carriers: carriers.clone(), + }; + + eprintln!("[pattern4] CarrierInfo: loop_var={}, carriers={:?}", + carrier_info.loop_var_name, + carrier_info.carriers.iter().map(|c| &c.name).collect::>() + ); // Phase 195: Use unified trace trace::trace().varmap("pattern4_start", &self.variable_map); @@ -150,8 +165,8 @@ impl MirBuilder { }; // Call Pattern 4 lowerer - let join_module = match lower_loop_with_continue_minimal(scope) { - Some(module) => module, + let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope) { + Some(result) => result, None => { // Phase 195: Use unified trace trace::trace().debug("pattern4", "Pattern 4 lowerer returned None"); @@ -159,6 +174,11 @@ impl MirBuilder { } }; + eprintln!("[pattern4] ExitMeta: {} exit bindings", exit_meta.exit_values.len()); + for (carrier_name, join_value) in &exit_meta.exit_values { + eprintln!("[pattern4] {} → ValueId({})", carrier_name, join_value.0); + } + // Phase 195: Use unified trace trace::trace().joinir_stats( "pattern4", @@ -181,19 +201,49 @@ impl MirBuilder { mir_module.functions.values().map(|f| f.blocks.len()).sum(), ); + // Phase 196: Dynamically generate exit_bindings from ExitMeta and CarrierInfo + let mut exit_bindings = Vec::new(); + for (carrier_name, join_exit_value) in &exit_meta.exit_values { + // Find the host_slot for this carrier + let host_slot = carrier_info + .carriers + .iter() + .find(|c| &c.name == carrier_name) + .map(|c| c.host_id) + .ok_or_else(|| { + format!( + "[cf_loop/pattern4] Carrier '{}' not found in CarrierInfo", + carrier_name + ) + })?; + + exit_bindings.push( + crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding { + carrier_name: carrier_name.clone(), + join_exit_value: *join_exit_value, + host_slot, + }, + ); + + eprintln!("[pattern4] Generated binding: {} → JoinIR={} Host={}", + carrier_name, join_exit_value.0, host_slot.0); + } + + // Phase 196: Build host_inputs dynamically + // Order: [loop_var, carrier1, carrier2, ...] + let mut host_inputs = vec![carrier_info.loop_var_id]; + for carrier in &carrier_info.carriers { + host_inputs.push(carrier.host_id); + } + + eprintln!("[pattern4] host_inputs: {:?}", host_inputs.iter().map(|v| v.0).collect::>()); + // Merge JoinIR blocks into current function - // Phase 195 FIX: Use exit_bindings to connect k_exit's sum_exit to host's sum variable - // Pattern 4: k_exit(sum_exit) returns sum_exit, so we bind ValueId(15) to host's sum + // Phase 196: Use dynamically generated exit_bindings and host_inputs let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_exit_bindings( vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i_init, sum_init) - vec![loop_var_id, sum_var_id], // Host's loop variables - vec![ - crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding { - carrier_name: "sum".to_string(), - join_exit_value: ValueId(15), // k_exit's parameter (sum_exit) - host_slot: sum_var_id, // variable_map["sum"] - } - ], + host_inputs, // Host's loop variables (dynamic) + exit_bindings, ); // Phase 195: Capture exit PHI result (Pattern 4 returns sum) let result_val = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; diff --git a/src/mir/join_ir/lowering/carrier_info.rs b/src/mir/join_ir/lowering/carrier_info.rs new file mode 100644 index 00000000..72db6b44 --- /dev/null +++ b/src/mir/join_ir/lowering/carrier_info.rs @@ -0,0 +1,64 @@ +//! Carrier variable metadata for JoinIR loop lowering +//! +//! This module defines metadata structures for tracking carrier variables +//! in loop lowering. This enables dynamic generation of exit bindings +//! without hardcoded variable names or ValueIds. + +use crate::mir::ValueId; + +/// Information about a single carrier variable +#[derive(Debug, Clone)] +pub struct CarrierVar { + /// Variable name (e.g., "sum", "printed") + pub name: String, + /// Host ValueId for this variable + pub host_id: ValueId, +} + +/// Complete carrier information for a loop +#[derive(Debug, Clone)] +pub struct CarrierInfo { + /// Loop control variable name (e.g., "i") + pub loop_var_name: String, + /// Loop control variable ValueId in host + pub loop_var_id: ValueId, + /// Additional carrier variables (e.g., sum, printed) + pub carriers: Vec, +} + +/// Exit metadata returned by lowerers +/// +/// This structure captures the mapping from JoinIR exit values to +/// carrier variable names, enabling dynamic binding generation. +#[derive(Debug, Clone)] +pub struct ExitMeta { + /// Exit value bindings: (carrier_name, join_exit_value_id) + /// + /// Example for Pattern 4: + /// ``` + /// vec![("sum".to_string(), ValueId(15))] + /// ``` + /// where ValueId(15) is the k_exit parameter in JoinIR-local space. + pub exit_values: Vec<(String, ValueId)>, +} + +impl ExitMeta { + /// Create new ExitMeta with no exit values + pub fn empty() -> Self { + Self { + exit_values: vec![], + } + } + + /// Create ExitMeta with a single exit value + pub fn single(carrier_name: String, join_value: ValueId) -> Self { + Self { + exit_values: vec![(carrier_name, join_value)], + } + } + + /// Create ExitMeta with multiple exit values + pub fn multiple(exit_values: Vec<(String, ValueId)>) -> Self { + Self { exit_values } + } +} diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs index 4b60caa5..c034e2c4 100644 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs @@ -68,6 +68,7 @@ //! //! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later. +use crate::mir::join_ir::lowering::carrier_info::ExitMeta; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::{ BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, @@ -101,7 +102,7 @@ use crate::mir::ValueId; /// /// # Returns /// -/// * `Some(JoinModule)` - Successfully lowered to JoinIR +/// * `Some((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata /// * `None` - Pattern not matched (fallback to other lowerers) /// /// # Boundary Contract @@ -109,8 +110,9 @@ use crate::mir::ValueId; /// This function returns a JoinModule with: /// - **Input slots**: ValueId(0) = i_init, ValueId(1) = sum_init /// - **Output slot**: k_exit returns the final sum value +/// - **Exit metadata**: ExitMeta containing ("sum", ValueId(15)) binding /// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds -pub fn lower_loop_with_continue_minimal(_scope: LoopScopeShape) -> Option { +pub fn lower_loop_with_continue_minimal(_scope: LoopScopeShape) -> Option<(JoinModule, ExitMeta)> { // Phase 195: Use local ValueId allocator (sequential from 0) // JoinIR has NO knowledge of host ValueIds - boundary handled separately let mut value_counter = 0u32; @@ -345,5 +347,11 @@ pub fn lower_loop_with_continue_minimal(_scope: LoopScopeShape) -> Option