diff --git a/src/mir/builder/control_flow/joinir/merge/block_allocator.rs b/src/mir/builder/control_flow/joinir/merge/block_allocator.rs new file mode 100644 index 00000000..fe1a79dc --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/block_allocator.rs @@ -0,0 +1,70 @@ +//! JoinIR Block ID Allocator +//! +//! Allocates new BasicBlockIds for all blocks in JoinIR functions +//! to avoid ID conflicts with the host MIR builder. +//! +//! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 159-194) + +use crate::mir::MirModule; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; + +/// Phase 1: Allocate new block IDs for ALL functions (Phase 189) +/// +/// DETERMINISM: Sort functions and blocks by name/ID to ensure consistent iteration order +pub(super) fn allocate_blocks( + builder: &mut crate::mir::builder::MirBuilder, + mir_module: &MirModule, + debug: bool, +) -> Result { + let mut remapper = JoinIrIdRemapper::new(); + + // Create exit block for Return conversion (single for all functions) + let _exit_block_id = builder.block_gen.next(); + + if debug { + eprintln!( + "[cf_loop/joinir] Phase 189: Allocating block IDs for all functions" + ); + } + + // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order + let mut functions: Vec<_> = mir_module.functions.iter().collect(); + functions.sort_by_key(|(name, _)| name.as_str()); + + for (func_name, func) in functions { + if debug { + eprintln!("[cf_loop/joinir] Function: {}", func_name); + } + + // DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order + let mut blocks: Vec<_> = func.blocks.iter().collect(); + blocks.sort_by_key(|(id, _)| id.0); + + for (old_block_id, _) in blocks { + let new_block_id = builder.block_gen.next(); + // Use remapper to store composite key mapping + remapper.set_block(func_name.clone(), *old_block_id, new_block_id); + + if debug { + eprintln!( + "[cf_loop/joinir] Block remap: {}:{:?} → {:?}", + func_name, old_block_id, new_block_id + ); + } + } + + // Map function entry blocks for Call→Jump conversion (stored in remapper for later use) + let entry_block_new = remapper + .get_block(func_name, func.entry_block) + .ok_or_else(|| format!("Entry block not found for {}", func_name))?; + + if debug { + eprintln!( + "[cf_loop/joinir] Entry map: {} → {:?}", + func_name, entry_block_new + ); + } + } + + Ok(remapper) +} diff --git a/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs b/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs new file mode 100644 index 00000000..775a0257 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs @@ -0,0 +1,60 @@ +//! JoinIR Exit PHI Builder +//! +//! Constructs the exit block PHI node that merges return values +//! from all inlined JoinIR functions. +//! +//! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 581-615) + +use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId}; + +/// Phase 5: Create exit block with PHI for return values from JoinIR functions +/// +/// Phase 189-Fix: Generate exit PHI if there are multiple return values. +/// If no return values, creates empty exit block and returns None. +pub(super) fn build_exit_phi( + builder: &mut crate::mir::builder::MirBuilder, + exit_block_id: BasicBlockId, + exit_phi_inputs: &[(BasicBlockId, ValueId)], + debug: bool, +) -> Result, String> { + let exit_phi_result_id = if let Some(ref mut func) = builder.current_function { + let mut exit_block = BasicBlock::new(exit_block_id); + + // Phase 189-Fix: If we collected return values, create a PHI in exit block + // This merges all return values from JoinIR functions into a single value + let phi_result = if !exit_phi_inputs.is_empty() { + // Allocate a new ValueId for the PHI result + let phi_dst = builder.value_gen.next(); + exit_block.instructions.push(MirInstruction::Phi { + dst: phi_dst, + inputs: exit_phi_inputs.to_vec(), + type_hint: None, + }); + exit_block + .instruction_spans + .push(crate::ast::Span::unknown()); + if debug { + eprintln!( + "[cf_loop/joinir] Exit block PHI: {:?} = phi {:?}", + phi_dst, exit_phi_inputs + ); + } + Some(phi_dst) + } else { + None + }; + + func.add_block(exit_block); + if debug { + eprintln!( + "[cf_loop/joinir] Created exit block: {:?}", + exit_block_id + ); + } + phi_result + } else { + None + }; + + Ok(exit_phi_result_id) +} diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs new file mode 100644 index 00000000..6c4fb621 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -0,0 +1,405 @@ +//! JoinIR Instruction Rewriter +//! +//! Rewrites JoinIR instructions with remapped IDs and merges blocks +//! into the host MIR builder. +//! +//! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 260-546) + +use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, MirModule, ValueId}; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use std::collections::HashMap; + +/// Phase 4: Merge ALL functions and rewrite instructions +/// +/// Returns: +/// - exit_block_id: The ID of the exit block where all Returns jump to +/// - exit_phi_inputs: Vec of (from_block, return_value) for exit PHI generation +pub(super) fn merge_and_rewrite( + builder: &mut crate::mir::builder::MirBuilder, + mir_module: &MirModule, + remapper: &mut JoinIrIdRemapper, + value_to_func_name: &HashMap, + function_params: &HashMap>, + boundary: Option<&JoinInlineBoundary>, + debug: bool, +) -> Result<(BasicBlockId, Vec<(BasicBlockId, ValueId)>), String> { + // Create exit block for Return conversion (single for all functions) + let exit_block_id = builder.block_gen.next(); + if debug { + eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id); + } + + // Phase 189 FIX: Build set of boundary join_inputs to skip their Const initializers + let boundary_input_set: std::collections::HashSet = boundary + .map(|b| b.join_inputs.iter().copied().collect()) + .unwrap_or_default(); + + // Phase 189-Fix: Collect return values from JoinIR functions for exit PHI + let mut exit_phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); + + // Build function_entry_map for Call→Jump conversion + let mut function_entry_map: HashMap = HashMap::new(); + for (func_name, func) in &mir_module.functions { + let entry_block_new = remapper + .get_block(func_name, func.entry_block) + .ok_or_else(|| format!("Entry block not found for {}", func_name))?; + function_entry_map.insert(func_name.clone(), entry_block_new); + } + + // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order + if debug { + eprintln!( + "[cf_loop/joinir] Phase 189: Merging {} functions", + mir_module.functions.len() + ); + } + + let mut functions_merge: Vec<_> = mir_module.functions.iter().collect(); + functions_merge.sort_by_key(|(name, _)| name.as_str()); + + let entry_func_name = functions_merge.first().map(|(name, _)| name.as_str()); + + for (func_name, func) in functions_merge { + if debug { + eprintln!( + "[cf_loop/joinir] Merging function '{}' with {} blocks, entry={:?}", + func_name, + func.blocks.len(), + func.entry_block + ); + } + + // Build a local block map for this function (for remap_instruction compatibility) + let mut local_block_map: HashMap = HashMap::new(); + for old_block_id in func.blocks.keys() { + let new_block_id = remapper + .get_block(func_name, *old_block_id) + .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?; + local_block_map.insert(*old_block_id, new_block_id); + } + + // Clone and remap all blocks from this function + // DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order + let mut blocks_merge: Vec<_> = func.blocks.iter().collect(); + blocks_merge.sort_by_key(|(id, _)| id.0); + + for (old_block_id, old_block) in blocks_merge { + // Use remapper to get correct mapping for this function's block + let new_block_id = remapper + .get_block(func_name, *old_block_id) + .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?; + let mut new_block = BasicBlock::new(new_block_id); + + // Phase 189 FIX: Check if this is entry function's entry block (for boundary input skipping) + let is_entry_func_entry_block = + entry_func_name == Some(func_name.as_str()) && *old_block_id == func.entry_block; + + // DEBUG: Print block being processed + if debug { + eprintln!( + "[cf_loop/joinir] === Processing block {:?} (from func '{}') ===", + old_block_id, func_name + ); + eprintln!( + "[cf_loop/joinir] Original block has {} instructions:", + old_block.instructions.len() + ); + for (idx, inst) in old_block.instructions.iter().enumerate() { + eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); + } + eprintln!( + "[cf_loop/joinir] Original block terminator: {:?}", + old_block.terminator + ); + } + + // Remap instructions (Phase 189: Convert inter-function Calls to control flow) + let mut found_tail_call = false; + let mut tail_call_target: Option<(BasicBlockId, Vec)> = None; + + // First pass: Process all instructions, identify tail calls + for inst in &old_block.instructions { + // Phase 189: Skip Const String instructions that define function names + if let MirInstruction::Const { dst, value } = inst { + if let crate::mir::types::ConstValue::String(_) = value { + if value_to_func_name.contains_key(dst) { + if debug { + eprintln!( + "[cf_loop/joinir] Skipping function name const: {:?}", + inst + ); + } + continue; // Skip this instruction + } + } + // Phase 189 FIX: Skip Const instructions in entry function's entry block + // that initialize boundary inputs. BoundaryInjector provides these values via Copy. + if is_entry_func_entry_block && boundary_input_set.contains(dst) { + if debug { + eprintln!("[cf_loop/joinir] Skipping boundary input const (replaced by BoundaryInjector Copy): {:?}", inst); + } + continue; // Skip - BoundaryInjector will provide the value + } + } + + // Phase 189: Detect tail calls and save parameters + if let MirInstruction::Call { func, args, .. } = inst { + if let Some(func_name) = value_to_func_name.get(func) { + if let Some(&target_block) = function_entry_map.get(func_name) { + // This is a tail call - save info and skip the Call instruction itself + let remapped_args: Vec = args + .iter() + .map(|&v| remapper.get_value(v).unwrap_or(v)) + .collect(); + tail_call_target = Some((target_block, remapped_args)); + found_tail_call = true; + + if debug { + eprintln!( + "[cf_loop/joinir] Detected tail call to '{}' (args={:?}), will convert to Jump", + func_name, args + ); + } + continue; // Skip the Call instruction itself + } + } + } + + // Process regular instructions - Phase 189: Use remapper.remap_instruction() + manual block remapping + let remapped = remapper.remap_instruction(inst); + + // Phase 189 FIX: Manual block remapping for Branch/Phi (JoinIrIdRemapper doesn't know func_name) + let remapped_with_blocks = match remapped { + MirInstruction::Branch { + condition, + then_bb, + else_bb, + } => MirInstruction::Branch { + condition, + then_bb: local_block_map.get(&then_bb).copied().unwrap_or(then_bb), + else_bb: local_block_map.get(&else_bb).copied().unwrap_or(else_bb), + }, + MirInstruction::Phi { + dst, + inputs, + type_hint: None, + } => MirInstruction::Phi { + dst, + inputs: inputs + .iter() + .map(|(bb, val)| { + (local_block_map.get(bb).copied().unwrap_or(*bb), *val) + }) + .collect(), + type_hint: None, + }, + other => other, + }; + + if debug { + match inst { + MirInstruction::BoxCall { .. } => { + eprintln!( + "[cf_loop/joinir] Adding BoxCall to block {:?}: {:?}", + new_block_id, inst + ); + } + _ => {} + } + } + + new_block.instructions.push(remapped_with_blocks); + } + + // DEBUG: Print what was added to the block after first pass + if debug { + eprintln!( + "[cf_loop/joinir] After first pass, new_block has {} instructions", + new_block.instructions.len() + ); + for (idx, inst) in new_block.instructions.iter().enumerate() { + eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); + } + } + + // Second pass: Insert parameter bindings for tail calls + // Phase 188-Impl-3: Use actual parameter ValueIds from target function + if let Some((target_block, args)) = tail_call_target { + if debug { + eprintln!( + "[cf_loop/joinir] Inserting param bindings for tail call to {:?}", + target_block + ); + } + + // Find the target function name from the target_block + // We need to reverse-lookup the function name from the entry block + let mut target_func_name: Option = None; + for (fname, &entry_block) in &function_entry_map { + if entry_block == target_block { + target_func_name = Some(fname.clone()); + break; + } + } + + if let Some(target_func_name) = target_func_name { + if let Some(target_params) = function_params.get(&target_func_name) { + // Insert Copy instructions for parameter binding + for (i, arg_val_remapped) in args.iter().enumerate() { + if i < target_params.len() { + let param_val_original = target_params[i]; + if let Some(param_val_remapped) = + remapper.get_value(param_val_original) + { + new_block.instructions.push(MirInstruction::Copy { + dst: param_val_remapped, + src: *arg_val_remapped, + }); + + if debug { + eprintln!( + "[cf_loop/joinir] Param binding: arg {:?} → param {:?}", + arg_val_remapped, param_val_remapped + ); + } + } + } + } + } + } + + // Set terminator to Jump + new_block.terminator = Some(MirInstruction::Jump { + target: target_block, + }); + + // DEBUG: Print final state after adding parameter bindings + if debug { + eprintln!( + "[cf_loop/joinir] After adding param bindings, new_block has {} instructions", + new_block.instructions.len() + ); + for (idx, inst) in new_block.instructions.iter().enumerate() { + eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); + } + } + } + new_block.instruction_spans = old_block.instruction_spans.clone(); + + if debug { + eprintln!("[cf_loop/joinir] Span sync: new_block.instructions.len()={}, old_block.instruction_spans.len()={}, new_block.instruction_spans.len()={}", + new_block.instructions.len(), + old_block.instruction_spans.len(), + new_block.instruction_spans.len() + ); + } + + // Remap terminator (convert Return → Jump to exit) if not already set by tail call + if !found_tail_call { + if let Some(ref term) = old_block.terminator { + let remapped_term = match term { + MirInstruction::Return { value } => { + // Convert Return to Jump to exit block + // All functions return to same exit block (Phase 189) + // Phase 189-Fix: Add Copy instruction to pass return value to exit PHI + if let Some(ret_val) = value { + let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val); + if debug { + eprintln!( + "[cf_loop/joinir] Return({:?}) → Jump to exit", + remapped_val + ); + } + // Collect (from_block, return_value) for exit PHI generation + exit_phi_inputs.push((new_block_id, remapped_val)); + } + MirInstruction::Jump { + target: exit_block_id, + } + } + MirInstruction::Jump { target } => { + // Phase 189 FIX: Remap block ID for Jump + MirInstruction::Jump { + target: local_block_map.get(target).copied().unwrap_or(*target), + } + } + MirInstruction::Branch { + condition, + then_bb, + else_bb, + } => { + // Phase 189 FIX: Remap block IDs AND condition ValueId for Branch + MirInstruction::Branch { + condition: remapper.remap_value(*condition), + then_bb: local_block_map.get(then_bb).copied().unwrap_or(*then_bb), + else_bb: local_block_map.get(else_bb).copied().unwrap_or(*else_bb), + } + } + _ => remapper.remap_instruction(term), + }; + new_block.terminator = Some(remapped_term); + } + } + + // Phase 189 FIX: Ensure instruction_spans matches instructions length + // The original spans may not cover all instructions after remapping/adding + // (PHI instructions, tail call parameter bindings, etc.) + let inst_count = new_block.instructions.len(); + let span_count = new_block.instruction_spans.len(); + if inst_count > span_count { + // Use a default span for the extra instructions + let default_span = new_block + .instruction_spans + .last() + .copied() + .unwrap_or_else(crate::ast::Span::unknown); + new_block.instruction_spans.resize(inst_count, default_span); + } else if inst_count < span_count { + // Truncate spans to match instructions + new_block.instruction_spans.truncate(inst_count); + } + + // Add block to current function + if let Some(ref mut current_func) = builder.current_function { + current_func.add_block(new_block); + } + } + } + + // Phase 188-Impl-3: Inject Copy instructions for boundary inputs using BoundaryInjector + if let Some(boundary) = boundary { + use crate::mir::builder::joinir_inline_boundary_injector::BoundaryInjector; + + // Get entry function's entry block (first function by convention) + let (entry_func_name, entry_func) = mir_module + .functions + .iter() + .next() + .ok_or("JoinIR module has no functions")?; + let entry_block_remapped = remapper + .get_block(entry_func_name, entry_func.entry_block) + .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?; + + // Create HashMap from remapper for BoundaryInjector (temporary adapter) + let mut value_map_for_injector = HashMap::new(); + for join_in in &boundary.join_inputs { + if let Some(remapped) = remapper.get_value(*join_in) { + value_map_for_injector.insert(*join_in, remapped); + } + } + + // Use BoundaryInjector to inject Copy instructions + if let Some(ref mut current_func) = builder.current_function { + BoundaryInjector::inject_boundary_copies( + current_func, + entry_block_remapped, + boundary, + &value_map_for_injector, + debug, + )?; + } + } + + Ok((exit_block_id, exit_phi_inputs)) +} diff --git a/src/mir/builder/control_flow/joinir/merge/mod.rs b/src/mir/builder/control_flow/joinir/merge/mod.rs new file mode 100644 index 00000000..213e1d8f --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/mod.rs @@ -0,0 +1,223 @@ +//! JoinIR MIR Block Merging Coordinator +//! +//! This module coordinates the merging of JoinIR-generated MIR functions +//! into the host MIR builder. The process is broken into 6 phases: +//! +//! 1. Block ID allocation (block_allocator.rs) +//! 2. Value collection (value_collector.rs) +//! 3. ValueId remapping (uses JoinIrIdRemapper) +//! 4. Instruction rewriting (instruction_rewriter.rs) +//! 5. Exit PHI construction (exit_phi_builder.rs) +//! 6. Boundary reconnection (inline in this file) +//! +//! Phase 4 Refactoring: Breaking down 714-line merge_joinir_mir_blocks() into focused modules + +mod block_allocator; +mod value_collector; +mod instruction_rewriter; +mod exit_phi_builder; + +use crate::mir::{MirModule, ValueId}; +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; + +/// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function +/// +/// # Phase 189: Multi-Function MIR Merge +/// +/// This merges JoinIR-generated blocks by: +/// 1. Remapping all block IDs across ALL functions to avoid conflicts +/// 2. Remapping all value IDs across ALL functions to avoid conflicts +/// 3. Adding all blocks from all functions to current_function +/// 4. Jumping from current_block to the entry block +/// 5. Converting Return → Jump to exit block for all functions +/// +/// **Multi-Function Support** (Phase 189): +/// - Pattern 1 (Simple While) generates 3 functions: entry + loop_step + k_exit +/// - All functions are flattened into current_function with global ID remapping +/// - Single exit block receives all Return instructions from all functions +/// +/// # Phase 188-Impl-3: JoinInlineBoundary Support +/// +/// When `boundary` is provided, injects Copy instructions at the entry block +/// to connect host ValueIds to JoinIR local ValueIds: +/// +/// ```text +/// entry_block: +/// // Injected by boundary +/// ValueId(100) = Copy ValueId(4) // join_input → host_input +/// // Original JoinIR instructions follow... +/// ``` +/// +/// This enables clean separation: JoinIR uses local IDs (0,1,2...), +/// host uses its own IDs, and Copy instructions bridge the gap. +/// +/// # Returns +/// +/// Returns `Ok(Some(exit_phi_id))` if the merged JoinIR functions have return values +/// that were collected into an exit block PHI. さらに、`boundary` に +/// host_outputs が指定されている場合は、exit PHI の結果をホスト側の +/// SSA スロットへ再接続する(variable_map 内の ValueId を更新する)。 +pub(in crate::mir::builder) fn merge_joinir_mir_blocks( + builder: &mut crate::mir::builder::MirBuilder, + mir_module: &MirModule, + boundary: Option<&JoinInlineBoundary>, + debug: bool, +) -> Result, String> { + if debug { + eprintln!( + "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", + mir_module.functions.len() + ); + } + + // Phase 1: Allocate block IDs for all functions + let mut remapper = block_allocator::allocate_blocks(builder, mir_module, debug)?; + + // Phase 2: Collect values from all functions + let (used_values, value_to_func_name, function_params) = + value_collector::collect_values(mir_module, &remapper, debug)?; + + // Phase 3: Remap ValueIds + remap_values(builder, &used_values, &mut remapper, debug)?; + + // Phase 4: Merge blocks and rewrite instructions + let (exit_block_id, exit_phi_inputs) = instruction_rewriter::merge_and_rewrite( + builder, + mir_module, + &mut remapper, + &value_to_func_name, + &function_params, + boundary, + debug, + )?; + + // Phase 5: Build exit PHI + let exit_phi_result_id = exit_phi_builder::build_exit_phi( + builder, + exit_block_id, + &exit_phi_inputs, + debug, + )?; + + // Phase 6: Reconnect boundary (if specified) + if let Some(boundary) = boundary { + reconnect_boundary(builder, boundary, exit_phi_result_id, debug)?; + } + + // Jump from current block to entry function's entry block + let (entry_func_name, entry_func) = mir_module + .functions + .iter() + .next() + .ok_or("JoinIR module has no functions")?; + let entry_block = remapper + .get_block(entry_func_name, entry_func.entry_block) + .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?; + + if debug { + eprintln!("[cf_loop/joinir] Entry function name: {}", entry_func_name); + eprintln!( + "[cf_loop/joinir] Entry function's entry_block (JoinIR local): {:?}", + entry_func.entry_block + ); + eprintln!("[cf_loop/joinir] Entry block (remapped): {:?}", entry_block); + eprintln!( + "[cf_loop/joinir] Current block before emit_jump: {:?}", + builder.current_block + ); + eprintln!("[cf_loop/joinir] Jumping to entry block: {:?}", entry_block); + } + + crate::mir::builder::emission::branch::emit_jump(builder, entry_block)?; + + if debug { + eprintln!( + "[cf_loop/joinir] After emit_jump, current_block: {:?}", + builder.current_block + ); + } + + // Switch to exit block for subsequent code + builder.start_new_block(exit_block_id)?; + + if debug { + eprintln!( + "[cf_loop/joinir] Phase 189: Merge complete: {} functions merged, continuing from {:?}", + mir_module.functions.len(), + exit_block_id + ); + } + + Ok(exit_phi_result_id) +} + +/// Phase 3: Allocate new ValueIds for all collected values +fn remap_values( + builder: &mut crate::mir::builder::MirBuilder, + used_values: &std::collections::BTreeSet, + remapper: &mut crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper, + debug: bool, +) -> Result<(), String> { + if debug { + eprintln!("[cf_loop/joinir] Phase 3: Remapping {} ValueIds", used_values.len()); + } + + for old_value in used_values { + let new_value = builder.next_value_id(); + remapper.set_value(*old_value, new_value); + if debug { + eprintln!( + "[cf_loop/joinir] Value remap: {:?} → {:?}", + old_value, new_value + ); + } + } + + Ok(()) +} + +/// Phase 6: Reconnect boundary to update host variable_map +fn reconnect_boundary( + builder: &mut crate::mir::builder::MirBuilder, + boundary: &JoinInlineBoundary, + exit_phi_result: Option, + debug: bool, +) -> Result<(), String> { + // Phase 190: Use explicit LoopExitBinding to reconnect exit PHI to variable_map + // Each binding explicitly names the carrier variable and maps exit PHI to it. + if let Some(phi_result) = exit_phi_result { + // Phase 190: Use exit_bindings for explicit carrier naming + // This eliminates ambiguity about which variable is being updated + for binding in &boundary.exit_bindings { + // Find the variable in variable_map that matches the binding's host_slot + for (var_name, vid) in builder.variable_map.iter_mut() { + if *vid == binding.host_slot { + *vid = phi_result; + if debug { + eprintln!( + "[cf_loop/joinir] Phase 190: Reconnected exit PHI {:?} to variable_map['{}'] (carrier: {})", + phi_result, var_name, binding.carrier_name + ); + } + // Validate carrier name matches + if var_name != &binding.carrier_name && debug { + eprintln!( + "[cf_loop/joinir] WARNING: Carrier name mismatch: expected '{}', found '{}'", + binding.carrier_name, var_name + ); + } + } + } + } + + // Phase 190: Backward compatibility - also check deprecated host_outputs + #[allow(deprecated)] + if !boundary.host_outputs.is_empty() && debug { + eprintln!( + "[cf_loop/joinir] WARNING: Using deprecated host_outputs. Migrate to exit_bindings." + ); + } + } + + Ok(()) +} diff --git a/src/mir/builder/control_flow/joinir/merge/value_collector.rs b/src/mir/builder/control_flow/joinir/merge/value_collector.rs new file mode 100644 index 00000000..37dde612 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/value_collector.rs @@ -0,0 +1,90 @@ +//! JoinIR Value Collector +//! +//! Collects all ValueIds used in JoinIR functions for remapping. +//! Also builds auxiliary maps for Call→Jump conversion. +//! +//! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 202-246) + +use crate::mir::{MirInstruction, MirModule, ValueId}; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use std::collections::{BTreeSet, HashMap}; + +/// Phase 2: Collect all ValueIds used across ALL functions (Phase 189) +/// +/// Also build: +/// - value_to_func_name: Map of ValueId → function name (for Call→Jump conversion) +/// - function_params: Map of function name → parameter ValueIds (for tail call conversion) +pub(super) fn collect_values( + mir_module: &MirModule, + remapper: &JoinIrIdRemapper, + debug: bool, +) -> Result< + ( + BTreeSet, + HashMap, + HashMap>, + ), + String, +> { + if debug { + eprintln!( + "[cf_loop/joinir] Phase 189: Collecting value IDs from all functions" + ); + } + + let mut used_values: BTreeSet = BTreeSet::new(); + let mut value_to_func_name: HashMap = HashMap::new(); + let mut function_params: HashMap> = HashMap::new(); + + // Build function_entry_map for tracking function names + let function_entry_map: HashMap = mir_module + .functions + .keys() + .map(|name| (name.clone(), ())) + .collect(); + + for (func_name, func) in &mir_module.functions { + // Phase 188-Impl-3: Collect function parameters for tail call conversion + function_params.insert(func_name.clone(), func.params.clone()); + + for block in func.blocks.values() { + // Phase 189: Use remapper to collect values + let block_values = remapper.collect_values_in_block(block); + used_values.extend(block_values); + + // Phase 189: Track Const String instructions that define function names + for inst in &block.instructions { + if let MirInstruction::Const { dst, value } = inst { + if let crate::mir::types::ConstValue::String(s) = value { + if function_entry_map.contains_key(s) { + value_to_func_name.insert(*dst, s.clone()); + // Phase 189 FIX: Also add to used_values so it gets remapped! + // Without this, subsequent instructions referencing dst will fail + used_values.insert(*dst); + if debug { + eprintln!( + "[cf_loop/joinir] Found function name constant: {:?} = '{}'", + dst, s + ); + } + } + } + } + } + } + + // Also collect parameter ValueIds + for param in &func.params { + used_values.insert(*param); + } + } + + if debug { + eprintln!( + "[cf_loop/joinir] Collected {} unique values", + used_values.len() + ); + } + + Ok((used_values, value_to_func_name, function_params)) +} diff --git a/src/mir/builder/control_flow/joinir/mod.rs b/src/mir/builder/control_flow/joinir/mod.rs index bb56a6ac..f1d75bca 100644 --- a/src/mir/builder/control_flow/joinir/mod.rs +++ b/src/mir/builder/control_flow/joinir/mod.rs @@ -3,6 +3,8 @@ //! This module contains JoinIR-related control flow logic: //! - Pattern lowerers (patterns/) //! - Routing logic (routing.rs) ✅ +//! - MIR block merging (merge/) ✅ Phase 4 pub(in crate::mir::builder) mod patterns; pub(in crate::mir::builder) mod routing; +pub(in crate::mir::builder) mod merge; diff --git a/src/mir/builder/control_flow/mod.rs b/src/mir/builder/control_flow/mod.rs index df195477..edec0a4e 100644 --- a/src/mir/builder/control_flow/mod.rs +++ b/src/mir/builder/control_flow/mod.rs @@ -97,617 +97,26 @@ impl super::MirBuilder { /// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function /// - /// # Phase 189: Multi-Function MIR Merge + /// **Phase 4 Refactoring Complete**: This function now delegates to the modular + /// merge implementation in `joinir::merge::merge_joinir_mir_blocks()`. /// - /// This merges JoinIR-generated blocks by: - /// 1. Remapping all block IDs across ALL functions to avoid conflicts - /// 2. Remapping all value IDs across ALL functions to avoid conflicts - /// 3. Adding all blocks from all functions to current_function - /// 4. Jumping from current_block to the entry block - /// 5. Converting Return → Jump to exit block for all functions - /// - /// **Multi-Function Support** (Phase 189): - /// - Pattern 1 (Simple While) generates 3 functions: entry + loop_step + k_exit - /// - All functions are flattened into current_function with global ID remapping - /// - Single exit block receives all Return instructions from all functions - /// - /// # Phase 188-Impl-3: JoinInlineBoundary Support - /// - /// When `boundary` is provided, injects Copy instructions at the entry block - /// to connect host ValueIds to JoinIR local ValueIds: - /// - /// ```text - /// entry_block: - /// // Injected by boundary - /// ValueId(100) = Copy ValueId(4) // join_input → host_input - /// // Original JoinIR instructions follow... - /// ``` - /// - /// This enables clean separation: JoinIR uses local IDs (0,1,2...), - /// host uses its own IDs, and Copy instructions bridge the gap. - /// - /// # Returns - /// - /// Returns `Ok(Some(exit_phi_id))` if the merged JoinIR functions have return values - /// that were collected into an exit block PHI. さらに、`boundary` に - /// host_outputs が指定されている場合は、exit PHI の結果をホスト側の - /// SSA スロットへ再接続する(variable_map 内の ValueId を更新する)。 + /// The original 714-line implementation has been broken down into 6 focused modules: + /// 1. block_allocator.rs - Block ID allocation + /// 2. value_collector.rs - Value collection + /// 3. ID remapping (using JoinIrIdRemapper) + /// 4. instruction_rewriter.rs - Instruction rewriting + /// 5. exit_phi_builder.rs - Exit PHI construction + /// 6. Boundary reconnection (in merge/mod.rs) fn merge_joinir_mir_blocks( &mut self, mir_module: &crate::mir::MirModule, boundary: Option<&crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary>, debug: bool, ) -> Result, String> { - use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId}; - use std::collections::HashMap; - // Phase 189: Use new ID remapper Box - use super::joinir_id_remapper::JoinIrIdRemapper; - - if debug { - eprintln!( - "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", - mir_module.functions.len() - ); - } - - // Phase 189: Create ID remapper for ValueId/BlockId translation - let mut remapper = JoinIrIdRemapper::new(); - - // Phase 189: Map function names to their entry blocks (for Call→Jump conversion) - let mut function_entry_map: HashMap = HashMap::new(); - - // 1. Allocate new block IDs for ALL functions (Phase 189) - // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order - if debug { - eprintln!("[cf_loop/joinir] Phase 189: Allocating block IDs for all functions"); - } - let mut functions: Vec<_> = mir_module.functions.iter().collect(); - functions.sort_by_key(|(name, _)| name.as_str()); - for (func_name, func) in functions { - if debug { - eprintln!("[cf_loop/joinir] Function: {}", func_name); - } - // DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order - let mut blocks: Vec<_> = func.blocks.iter().collect(); - blocks.sort_by_key(|(id, _)| id.0); - for (old_block_id, _) in blocks { - let new_block_id = self.block_gen.next(); - // Use remapper to store composite key mapping - remapper.set_block(func_name.clone(), *old_block_id, new_block_id); - if debug { - eprintln!( - "[cf_loop/joinir] Block remap: {}:{:?} → {:?}", - func_name, old_block_id, new_block_id - ); - } - } - // Map function entry blocks for Call→Jump conversion - let entry_block_new = remapper.get_block(func_name, func.entry_block) - .ok_or_else(|| format!("Entry block not found for {}", func_name))?; - function_entry_map.insert(func_name.clone(), entry_block_new); - if debug { - eprintln!( - "[cf_loop/joinir] Entry map: {} → {:?}", - func_name, entry_block_new - ); - } - } - - // 2. Create exit block for Return conversion (single for all functions) - let exit_block_id = self.block_gen.next(); - if debug { - eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id); - } - - // 3. Collect all ValueIds used across ALL functions (Phase 189) - // Also build a map of ValueId → function name for Call→Jump conversion - // Phase 188-Impl-3: Also collect function parameters for tail call conversion - if debug { - eprintln!("[cf_loop/joinir] Phase 189: Collecting value IDs from all functions"); - } - let mut used_values: std::collections::BTreeSet = - std::collections::BTreeSet::new(); - let mut value_to_func_name: HashMap = HashMap::new(); - let mut function_params: HashMap> = HashMap::new(); - - for (func_name, func) in &mir_module.functions { - // Phase 188-Impl-3: Collect function parameters for tail call conversion - function_params.insert(func_name.clone(), func.params.clone()); - - for block in func.blocks.values() { - // Phase 189: Use remapper to collect values - let block_values = remapper.collect_values_in_block(block); - used_values.extend(block_values); - - // Phase 189: Track Const String instructions that define function names - for inst in &block.instructions { - if let MirInstruction::Const { dst, value } = inst { - if let crate::mir::types::ConstValue::String(s) = value { - if function_entry_map.contains_key(s) { - value_to_func_name.insert(*dst, s.clone()); - // Phase 189 FIX: Also add to used_values so it gets remapped! - // Without this, subsequent instructions referencing dst will fail - used_values.insert(*dst); - if debug { - eprintln!( - "[cf_loop/joinir] Found function name constant: {:?} = '{}'", - dst, s - ); - } - } - } - } - } - } - // Also collect parameter ValueIds - for param in &func.params { - used_values.insert(*param); - } - } - - // 4. Allocate new ValueIds for all collected values - for old_value in used_values { - let new_value = self.next_value_id(); - remapper.set_value(old_value, new_value); - if debug { - eprintln!( - "[cf_loop/joinir] Value remap: {:?} → {:?}", - old_value, new_value - ); - } - } - - // 5. Merge ALL functions (Phase 189: iterate over all, not just first) - // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order - if debug { - eprintln!("[cf_loop/joinir] Phase 189: Merging {} functions", mir_module.functions.len()); - } - // Phase 189: Iterate over both names and functions (need name for composite keys) - let mut functions_merge: Vec<_> = mir_module.functions.iter().collect(); - functions_merge.sort_by_key(|(name, _)| name.as_str()); - - // Phase 189 FIX: Build set of boundary join_inputs to skip their Const initializers - // When BoundaryInjector provides values via Copy, entry function shouldn't also set them via Const - let boundary_input_set: std::collections::HashSet = boundary - .map(|b| b.join_inputs.iter().copied().collect()) - .unwrap_or_default(); - let entry_func_name = functions_merge.first().map(|(name, _)| name.as_str()); - - // Phase 189-Fix: Collect return values from JoinIR functions for exit PHI - // Each Return → Jump conversion records (from_block, return_value) here - let mut exit_phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); - - for (func_name, func) in functions_merge { - if debug { - eprintln!( - "[cf_loop/joinir] Merging function '{}' with {} blocks, entry={:?}", - func_name, - func.blocks.len(), - func.entry_block - ); - } - - // Build a local block map for this function (for remap_instruction compatibility) - let mut local_block_map: HashMap = HashMap::new(); - for old_block_id in func.blocks.keys() { - let new_block_id = remapper.get_block(func_name, *old_block_id) - .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?; - local_block_map.insert(*old_block_id, new_block_id); - } - - // Clone and remap all blocks from this function - // DETERMINISM FIX: Sort blocks by ID to ensure consistent iteration order - let mut blocks_merge: Vec<_> = func.blocks.iter().collect(); - blocks_merge.sort_by_key(|(id, _)| id.0); - for (old_block_id, old_block) in blocks_merge { - // Use remapper to get correct mapping for this function's block - let new_block_id = remapper.get_block(func_name, *old_block_id) - .ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?; - let mut new_block = BasicBlock::new(new_block_id); - - // Remap instructions (Phase 189: Convert inter-function Calls to control flow) - let mut found_tail_call = false; - let mut tail_call_target: Option<(BasicBlockId, Vec)> = None; - - // DEBUG: Print block being processed - if debug { - eprintln!("[cf_loop/joinir] === Processing block {:?} (from func '{}') ===", old_block_id, func_name); - eprintln!("[cf_loop/joinir] Original block has {} instructions:", old_block.instructions.len()); - for (idx, inst) in old_block.instructions.iter().enumerate() { - eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); - } - eprintln!("[cf_loop/joinir] Original block terminator: {:?}", old_block.terminator); - } - - // Phase 189 FIX: Check if this is entry function's entry block (for boundary input skipping) - let is_entry_func_entry_block = entry_func_name == Some(func_name.as_str()) - && *old_block_id == func.entry_block; - - // First pass: Process all instructions, identify tail calls - for inst in &old_block.instructions { - // Phase 189: Skip Const String instructions that define function names - if let MirInstruction::Const { dst, value } = inst { - if let crate::mir::types::ConstValue::String(_) = value { - if value_to_func_name.contains_key(dst) { - if debug { - eprintln!("[cf_loop/joinir] Skipping function name const: {:?}", inst); - } - continue; // Skip this instruction - } - } - // Phase 189 FIX: Skip Const instructions in entry function's entry block - // that initialize boundary inputs. BoundaryInjector provides these values via Copy. - if is_entry_func_entry_block && boundary_input_set.contains(dst) { - if debug { - eprintln!("[cf_loop/joinir] Skipping boundary input const (replaced by BoundaryInjector Copy): {:?}", inst); - } - continue; // Skip - BoundaryInjector will provide the value - } - } - - // Phase 189: Detect tail calls and save parameters - if let MirInstruction::Call { func, args, .. } = inst { - if let Some(func_name) = value_to_func_name.get(func) { - if let Some(&target_block) = function_entry_map.get(func_name) { - // This is a tail call - save info and skip the Call instruction itself - let remapped_args: Vec = args - .iter() - .map(|&v| remapper.get_value(v).unwrap_or(v)) - .collect(); - tail_call_target = Some((target_block, remapped_args)); - found_tail_call = true; - - if debug { - eprintln!( - "[cf_loop/joinir] Detected tail call to '{}' (args={:?}), will convert to Jump", - func_name, args - ); - } - continue; // Skip the Call instruction itself - } - } - } - - // Process regular instructions - Phase 189: Use remapper.remap_instruction() + manual block remapping - let remapped = remapper.remap_instruction(inst); - - // Phase 189 FIX: Manual block remapping for Branch/Phi (JoinIrIdRemapper doesn't know func_name) - let remapped_with_blocks = match remapped { - MirInstruction::Branch { condition, then_bb, else_bb } => { - MirInstruction::Branch { - condition, - then_bb: local_block_map.get(&then_bb).copied().unwrap_or(then_bb), - else_bb: local_block_map.get(&else_bb).copied().unwrap_or(else_bb), - } - } - MirInstruction::Phi { dst, inputs, type_hint: None } => { - MirInstruction::Phi { - dst, - inputs: inputs.iter().map(|(bb, val)| { - (local_block_map.get(bb).copied().unwrap_or(*bb), *val) - }).collect(), - type_hint: None, - } - } - other => other, - }; - - if debug { - match inst { - MirInstruction::BoxCall { .. } => { - eprintln!("[cf_loop/joinir] Adding BoxCall to block {:?}: {:?}", new_block_id, inst); - } - _ => {} - } - } - - new_block.instructions.push(remapped_with_blocks); - } - - // DEBUG: Print what was added to the block after first pass - if debug { - eprintln!("[cf_loop/joinir] After first pass, new_block has {} instructions", new_block.instructions.len()); - for (idx, inst) in new_block.instructions.iter().enumerate() { - eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); - } - } - - // Second pass: Insert parameter bindings for tail calls - // Phase 188-Impl-3: Use actual parameter ValueIds from target function - if let Some((target_block, args)) = tail_call_target { - if debug { - eprintln!( - "[cf_loop/joinir] Inserting param bindings for tail call to {:?}", - target_block - ); - } - - // Find the target function name from the target_block - // We need to reverse-lookup the function name from the entry block - let mut target_func_name: Option = None; - for (fname, &entry_block) in &function_entry_map { - if entry_block == target_block { - target_func_name = Some(fname.clone()); - break; - } - } - - if let Some(target_func_name) = target_func_name { - if let Some(target_params) = function_params.get(&target_func_name) { - // Insert Copy instructions for parameter binding - for (i, arg_val_remapped) in args.iter().enumerate() { - if i < target_params.len() { - let param_val_original = target_params[i]; - if let Some(param_val_remapped) = remapper.get_value(param_val_original) { - new_block.instructions.push(MirInstruction::Copy { - dst: param_val_remapped, - src: *arg_val_remapped, - }); - - if debug { - eprintln!( - "[cf_loop/joinir] Param binding: arg {:?} → param {:?}", - arg_val_remapped, param_val_remapped - ); - } - } - } - } - } - } - - // Set terminator to Jump - new_block.terminator = Some(MirInstruction::Jump { - target: target_block, - }); - - // DEBUG: Print final state after adding parameter bindings - if debug { - eprintln!("[cf_loop/joinir] After adding param bindings, new_block has {} instructions", new_block.instructions.len()); - for (idx, inst) in new_block.instructions.iter().enumerate() { - eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); - } - } - } - new_block.instruction_spans = old_block.instruction_spans.clone(); - - if debug { - eprintln!("[cf_loop/joinir] Span sync: new_block.instructions.len()={}, old_block.instruction_spans.len()={}, new_block.instruction_spans.len()={}", - new_block.instructions.len(), - old_block.instruction_spans.len(), - new_block.instruction_spans.len() - ); - } - - // Remap terminator (convert Return → Jump to exit) if not already set by tail call - if !found_tail_call { - if let Some(ref term) = old_block.terminator { - let remapped_term = match term { - MirInstruction::Return { value } => { - // Convert Return to Jump to exit block - // All functions return to same exit block (Phase 189) - // Phase 189-Fix: Add Copy instruction to pass return value to exit PHI - if let Some(ret_val) = value { - let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val); - if debug { - eprintln!( - "[cf_loop/joinir] Return({:?}) → Jump to exit", - remapped_val - ); - } - // Collect (from_block, return_value) for exit PHI generation - exit_phi_inputs.push((new_block_id, remapped_val)); - } - MirInstruction::Jump { - target: exit_block_id, - } - } - MirInstruction::Jump { target } => { - // Phase 189 FIX: Remap block ID for Jump - MirInstruction::Jump { - target: local_block_map.get(target).copied().unwrap_or(*target), - } - } - MirInstruction::Branch { condition, then_bb, else_bb } => { - // Phase 189 FIX: Remap block IDs AND condition ValueId for Branch - MirInstruction::Branch { - condition: remapper.remap_value(*condition), - then_bb: local_block_map.get(then_bb).copied().unwrap_or(*then_bb), - else_bb: local_block_map.get(else_bb).copied().unwrap_or(*else_bb), - } - } - _ => remapper.remap_instruction(term), - }; - new_block.terminator = Some(remapped_term); - } - } - - // Phase 189 FIX: Ensure instruction_spans matches instructions length - // The original spans may not cover all instructions after remapping/adding - // (PHI instructions, tail call parameter bindings, etc.) - let inst_count = new_block.instructions.len(); - let span_count = new_block.instruction_spans.len(); - if inst_count > span_count { - // Use a default span for the extra instructions - let default_span = new_block.instruction_spans.last() - .copied() - .unwrap_or_else(crate::ast::Span::unknown); - new_block.instruction_spans.resize(inst_count, default_span); - } else if inst_count < span_count { - // Truncate spans to match instructions - new_block.instruction_spans.truncate(inst_count); - } - - // Add block to current function - if let Some(ref mut current_func) = self.current_function { - current_func.add_block(new_block); - } - } - } - - // Phase 188-Impl-3: Inject Copy instructions for boundary inputs using BoundaryInjector - if let Some(boundary) = boundary { - use super::joinir_inline_boundary_injector::BoundaryInjector; - - // Get entry function's entry block (first function by convention) - let (entry_func_name, entry_func) = mir_module - .functions - .iter() - .next() - .ok_or("JoinIR module has no functions")?; - let entry_block_remapped = remapper.get_block(entry_func_name, entry_func.entry_block) - .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?; - - // Create HashMap from remapper for BoundaryInjector (temporary adapter) - let mut value_map_for_injector = HashMap::new(); - for join_in in &boundary.join_inputs { - if let Some(remapped) = remapper.get_value(*join_in) { - value_map_for_injector.insert(*join_in, remapped); - } - } - - // Use BoundaryInjector to inject Copy instructions - if let Some(ref mut current_func) = self.current_function { - BoundaryInjector::inject_boundary_copies( - current_func, - entry_block_remapped, - boundary, - &value_map_for_injector, - debug, - )?; - } - } - - // 6. Create exit block with PHI for return values from JoinIR functions - // Phase 189-Fix: Generate exit PHI if there are multiple return values - let exit_phi_result_id = if let Some(ref mut func) = self.current_function { - let mut exit_block = BasicBlock::new(exit_block_id); - - // Phase 189-Fix: If we collected return values, create a PHI in exit block - // This merges all return values from JoinIR functions into a single value - let phi_result = if !exit_phi_inputs.is_empty() { - // Allocate a new ValueId for the PHI result - let phi_dst = self.value_gen.next(); - exit_block.instructions.push(MirInstruction::Phi { - dst: phi_dst, - inputs: exit_phi_inputs.clone(), - type_hint: None, - }); - exit_block.instruction_spans.push(crate::ast::Span::unknown()); - if debug { - eprintln!( - "[cf_loop/joinir] Exit block PHI: {:?} = phi {:?}", - phi_dst, exit_phi_inputs - ); - } - Some(phi_dst) - } else { - None - }; - - func.add_block(exit_block); - if debug { - eprintln!("[cf_loop/joinir] Created exit block: {:?}", exit_block_id); - } - phi_result - } else { - None - }; - - // Phase 190: Use explicit LoopExitBinding to reconnect exit PHI to variable_map - // Each binding explicitly names the carrier variable and maps exit PHI to it. - if let Some(phi_result) = exit_phi_result_id { - if let Some(ref boundary) = boundary { - // Phase 190: Use exit_bindings for explicit carrier naming - // This eliminates ambiguity about which variable is being updated - for binding in &boundary.exit_bindings { - // Find the variable in variable_map that matches the binding's host_slot - for (var_name, vid) in self.variable_map.iter_mut() { - if *vid == binding.host_slot { - *vid = phi_result; - if debug { - eprintln!( - "[cf_loop/joinir] Phase 190: Reconnected exit PHI {:?} to variable_map['{}'] (carrier: {})", - phi_result, var_name, binding.carrier_name - ); - } - // Validate carrier name matches - if var_name != &binding.carrier_name && debug { - eprintln!( - "[cf_loop/joinir] WARNING: Carrier name mismatch: expected '{}', found '{}'", - binding.carrier_name, var_name - ); - } - } - } - } - - // Phase 190: Backward compatibility - also check deprecated host_outputs - #[allow(deprecated)] - if !boundary.host_outputs.is_empty() && debug { - eprintln!( - "[cf_loop/joinir] WARNING: Using deprecated host_outputs. Migrate to exit_bindings." - ); - } - } - } - - // 7. Jump from current block to entry function's entry block - // Entry function is first function by convention - let (entry_func_name, entry_func) = mir_module - .functions - .iter() - .next() - .ok_or("JoinIR module has no functions")?; - // Use remapper to get entry block mapping - let entry_block = remapper.get_block(entry_func_name, entry_func.entry_block) - .ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?; - if debug { - eprintln!("[cf_loop/joinir] Entry function name: {}", entry_func_name); - eprintln!("[cf_loop/joinir] Entry function's entry_block (JoinIR local): {:?}", entry_func.entry_block); - eprintln!("[cf_loop/joinir] Entry block (remapped): {:?}", entry_block); - eprintln!("[cf_loop/joinir] Current block before emit_jump: {:?}", self.current_block); - eprintln!("[cf_loop/joinir] Jumping to entry block: {:?}", entry_block); - } - crate::mir::builder::emission::branch::emit_jump(self, entry_block)?; - if debug { - eprintln!("[cf_loop/joinir] After emit_jump, current_block: {:?}", self.current_block); - } - - // 8. Switch to exit block for subsequent code - self.start_new_block(exit_block_id)?; - - if debug { - eprintln!( - "[cf_loop/joinir] Phase 189: Merge complete: {} functions merged, continuing from {:?}", - mir_module.functions.len(), - exit_block_id - ); - // DEBUG: Check bb0's terminator after merge - if let Some(ref func) = self.current_function { - if let Some(bb0) = func.get_block(BasicBlockId(0)) { - eprintln!("[cf_loop/joinir] bb0 terminator after merge: {:?}", bb0.terminator); - } - // DEBUG: Check bb9's PHI after merge (PHI merge block) - if let Some(bb9) = func.get_block(BasicBlockId(9)) { - let phi_count = bb9.instructions.iter().filter(|i| matches!(i, MirInstruction::Phi { .. })).count(); - eprintln!("[cf_loop/joinir] bb9 after merge: {} instructions, {} PHI", bb9.instructions.len(), phi_count); - for (idx, inst) in bb9.instructions.iter().take(3).enumerate() { - eprintln!("[cf_loop/joinir] bb9[{}]: {:?}", idx, inst); - } - } - } - } - - Ok(exit_phi_result_id) + // Phase 4: Delegate to modular implementation + joinir::merge::merge_joinir_mir_blocks(self, mir_module, boundary, debug) } - // Phase 189: collect_values_in_block/collect_values_in_instruction removed - // These functions are now provided by JoinIrIdRemapper::collect_values_in_block() - - // Phase 189: remap_joinir_instruction/remap_instruction removed - // These functions are now provided by JoinIrIdRemapper::remap_instruction() - /// Control-flow: try/catch/finally pub(super) fn cf_try_catch( &mut self,