//! 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; pub mod exit_line; mod loop_header_phi_info; mod loop_header_phi_builder; mod tail_call_classifier; mod merge_result; // Phase 33-17: Re-export for use by other modules pub use loop_header_phi_info::LoopHeaderPhiInfo; pub use loop_header_phi_builder::LoopHeaderPhiBuilder; 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 (mut used_values, value_to_func_name, function_params) = value_collector::collect_values(mir_module, &remapper, debug)?; // Phase 171-fix: Add condition_bindings' join_values to used_values for remapping if let Some(boundary) = boundary { for binding in &boundary.condition_bindings { if debug { eprintln!( "[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values", binding.name, binding.join_value ); } used_values.insert(binding.join_value); } // Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping for binding in &boundary.exit_bindings { if debug { eprintln!( "[cf_loop/joinir] Phase 172-3: Adding exit binding '{}' JoinIR {:?} to used_values", binding.carrier_name, binding.join_exit_value ); } used_values.insert(binding.join_exit_value); } } // Phase 3: Remap ValueIds remap_values(builder, &used_values, &mut remapper, debug)?; // Phase 3.5: Build loop header PHIs (if loop pattern with loop_var_name) // // We need to know PHI dsts before instruction_rewriter runs, so that: // 1. Tail call handling can set latch_incoming // 2. Return handling can use PHI dsts for exit values let (entry_func_name, entry_func) = mir_module .functions .iter() .next() .ok_or("JoinIR module has no functions (Phase 3.5)")?; let entry_block_remapped = remapper .get_block(entry_func_name, entry_func.entry_block) .ok_or_else(|| format!("Entry block not found for {} (Phase 3.5)", entry_func_name))?; // Phase 33-16: Get host's current block as the entry edge (the block that jumps INTO the loop) let host_entry_block = builder.current_block.ok_or( "Phase 33-16: No current block when building header PHIs" )?; let mut loop_header_phi_info = if let Some(boundary) = boundary { if let Some(loop_var_name) = &boundary.loop_var_name { // Phase 33-16: Get loop variable's initial value from HOST (not JoinIR's ValueId(0)) // boundary.host_inputs[0] is the host ValueId that holds the initial loop var value let loop_var_init = boundary.host_inputs.first().copied().ok_or( "Phase 33-16: No host_inputs in boundary for loop_var_init" )?; if debug { eprintln!( "[cf_loop/joinir] Phase 3.5: Building header PHIs for loop_var='{}' at {:?}", loop_var_name, entry_block_remapped ); eprintln!( "[cf_loop/joinir] loop_var_init={:?} (from boundary.host_inputs[0])", loop_var_init ); eprintln!( "[cf_loop/joinir] host_entry_block={:?} (where initial value comes from)", host_entry_block ); } // Phase 33-20: Extract other carriers from exit_bindings // Skip the loop variable (it's handled separately) and collect other carriers eprintln!( "[cf_loop/joinir] Phase 33-20 DEBUG: exit_bindings count={}, loop_var_name={:?}", boundary.exit_bindings.len(), loop_var_name ); for b in boundary.exit_bindings.iter() { eprintln!( "[cf_loop/joinir] Phase 33-20 DEBUG: exit_binding: carrier_name={:?}, host_slot={:?}", b.carrier_name, b.host_slot ); } let other_carriers: Vec<(String, ValueId)> = boundary.exit_bindings .iter() .filter(|b| b.carrier_name != *loop_var_name) .map(|b| (b.carrier_name.clone(), b.host_slot)) .collect(); if debug && !other_carriers.is_empty() { eprintln!( "[cf_loop/joinir] Phase 33-20: Found {} other carriers from exit_bindings: {:?}", other_carriers.len(), other_carriers.iter().map(|(n, _)| n.as_str()).collect::>() ); } let phi_info = LoopHeaderPhiBuilder::build( builder, entry_block_remapped, // header_block (JoinIR's entry block = loop header) host_entry_block, // entry_block (host's block that jumps to loop header) loop_var_name, loop_var_init, &other_carriers, // Phase 33-20: Pass other carriers from exit_bindings boundary.expr_result.is_some(), // expr_result_is_loop_var debug, )?; // Phase 33-21: Override remapper for loop_step's parameters // // JoinIR generates separate parameter ValueIds for each function: // - main(): ValueId(0), ValueId(1), ... for (i_init, carrier1_init, ...) // - loop_step(): ValueId(3), ValueId(4), ... for (i_param, carrier1_param, ...) // // The loop body uses loop_step's parameters, so we need to remap THOSE // to the header PHI dsts, not main()'s parameters. // // We get loop_step's parameters from function_params collected earlier. // Phase 33-21: Override remapper for ALL functions' parameters // // JoinIR generates separate parameter ValueIds for each function: // - main (join_func_0): ValueId(0), ValueId(1), ... for (i_init, carrier1_init, ...) // - loop_step (join_func_1): ValueId(3), ValueId(4), ... for (i_param, carrier1_param, ...) // // ALL of these need to be mapped to header PHI dsts so that: // 1. condition evaluation uses PHI result // 2. loop body uses PHI result // 3. tail call args are correctly routed // Map main's parameters // MIR function keys use join_func_N format from join_func_name() let main_func_name = "join_func_0"; if function_params.get(main_func_name).is_none() { eprintln!( "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}", main_func_name, function_params.keys().collect::>() ); } if let Some(main_params) = function_params.get(main_func_name) { if debug { eprintln!( "[cf_loop/joinir] Phase 33-21: main ({}) params: {:?}", main_func_name, main_params ); } // Map main's parameters to header PHI dsts // main params: [i_init, carrier1_init, ...] // carrier_phis: [("i", entry), ("sum", entry), ...] for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() { if let Some(&main_param) = main_params.get(idx) { if debug { eprintln!( "[cf_loop/joinir] Phase 33-21: REMAP main param {:?} → {:?} ('{}')", main_param, entry.phi_dst, carrier_name ); } remapper.set_value(main_param, entry.phi_dst); } } } // Map loop_step's parameters let loop_step_func_name = "join_func_1"; if debug { eprintln!( "[cf_loop/joinir] Phase 33-21: function_params keys: {:?}", function_params.keys().collect::>() ); } if function_params.get(loop_step_func_name).is_none() { eprintln!( "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}", loop_step_func_name, function_params.keys().collect::>() ); } if let Some(loop_step_params) = function_params.get(loop_step_func_name) { if debug { eprintln!( "[cf_loop/joinir] Phase 33-21: loop_step ({}) params: {:?}", loop_step_func_name, loop_step_params ); } // Map loop_step's parameters to header PHI dsts // loop_step params: [i_param, carrier1_param, ...] // carrier_phis: [("i", entry), ("sum", entry), ...] for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() { if let Some(&loop_step_param) = loop_step_params.get(idx) { if debug { eprintln!( "[cf_loop/joinir] Phase 33-21: REMAP loop_step param {:?} → {:?} ('{}')", loop_step_param, entry.phi_dst, carrier_name ); } remapper.set_value(loop_step_param, entry.phi_dst); } } } if function_params.get(main_func_name).is_none() && function_params.get(loop_step_func_name).is_none() { // Fallback: Use old behavior (ValueId(0), ValueId(1), ...) // This handles patterns that don't have loop_step function if let Some(phi_dst) = phi_info.get_carrier_phi(loop_var_name) { remapper.set_value(ValueId(0), phi_dst); if debug { eprintln!( "[cf_loop/joinir] Phase 33-16 fallback: Override remap ValueId(0) → {:?} (PHI dst)", phi_dst ); } } for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() { if carrier_name == loop_var_name { continue; } let join_value_id = ValueId(idx as u32); remapper.set_value(join_value_id, entry.phi_dst); if debug { eprintln!( "[cf_loop/joinir] Phase 33-20 fallback: Override remap {:?} → {:?} (carrier '{}' PHI dst)", join_value_id, entry.phi_dst, carrier_name ); } } } phi_info } else { LoopHeaderPhiInfo::empty(entry_block_remapped) } } else { LoopHeaderPhiInfo::empty(entry_block_remapped) }; // Phase 4: Merge blocks and rewrite instructions // Phase 33-16: Pass mutable loop_header_phi_info for latch_incoming tracking let merge_result = instruction_rewriter::merge_and_rewrite( builder, mir_module, &mut remapper, &value_to_func_name, &function_params, boundary, &mut loop_header_phi_info, debug, )?; // Phase 4.5: Finalize loop header PHIs (insert into header block) // // By now, instruction_rewriter has set latch_incoming for all carriers. // We can finalize the PHIs and insert them into the header block. if !loop_header_phi_info.carrier_phis.is_empty() { if debug { eprintln!( "[cf_loop/joinir] Phase 4.5: Finalizing {} header PHIs", loop_header_phi_info.carrier_phis.len() ); } LoopHeaderPhiBuilder::finalize( builder, &loop_header_phi_info, debug, )?; } // Phase 5: Build exit PHI (expr result only, not carrier PHIs) // Phase 33-20: Carrier PHIs are now taken from header PHI info, not exit block let (exit_phi_result_id, _exit_carrier_phis) = exit_phi_builder::build_exit_phi( builder, merge_result.exit_block_id, &merge_result.exit_phi_inputs, &merge_result.carrier_inputs, debug, )?; // Phase 33-20: Build carrier_phis from header PHI info instead of exit PHI builder // The header PHI dst IS the current value of each carrier, and it's SSA-valid // because it's defined at the loop header and dominates the exit block. let carrier_phis: std::collections::BTreeMap = loop_header_phi_info .carrier_phis .iter() .map(|(name, entry)| (name.clone(), entry.phi_dst)) .collect(); if debug && !carrier_phis.is_empty() { eprintln!( "[cf_loop/joinir] Phase 33-20: Using header PHI dsts for variable_map: {:?}", carrier_phis.iter().map(|(n, v)| (n.as_str(), v)).collect::>() ); } // Phase 6: Reconnect boundary (if specified) // Phase 197-B: Pass remapper to enable per-carrier exit value lookup // Phase 33-10-Refactor-P3: Delegate to ExitLineOrchestrator // Phase 33-20: Now uses header PHI dsts instead of exit PHI dsts if let Some(boundary) = boundary { exit_line::ExitLineOrchestrator::execute(builder, boundary, &carrier_phis, debug)?; } let exit_block_id = merge_result.exit_block_id; // Jump from current block to entry function's entry block // (Reuse entry_func_name and entry_block_remapped from Phase 3.5) let entry_block = entry_block_remapped; 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 ); } // Phase 200-3: Verify JoinIR contracts (debug only) #[cfg(debug_assertions)] { if let Some(boundary) = boundary { verify_joinir_contracts( builder.function(), entry_block_remapped, exit_block_id, &loop_header_phi_info, boundary, ); if debug { eprintln!("[cf_loop/joinir] Phase 200-3: Contract verification passed"); } } } 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 200-3: JoinIR Contract Verification // ============================================================================ /// Verify loop header PHI consistency /// /// # Checks /// /// 1. If loop_var_name is Some, header block must have corresponding PHI /// 2. All carriers in LoopHeaderPhiInfo should have PHIs in the header block /// /// # Panics /// /// Panics in debug mode if contract violations are detected. #[cfg(debug_assertions)] fn verify_loop_header_phis( func: &crate::mir::MirFunction, header_block: crate::mir::BasicBlockId, loop_info: &LoopHeaderPhiInfo, boundary: &JoinInlineBoundary, ) { // Check 1: Loop variable PHI existence if let Some(ref loop_var_name) = boundary.loop_var_name { let header_block_data = &func.blocks[header_block.0]; let has_loop_var_phi = header_block_data .instructions .iter() .any(|instr| matches!(instr, crate::mir::MirInstruction::Phi { .. })); if !has_loop_var_phi && !loop_info.carrier_phis.is_empty() { panic!( "[JoinIRVerifier] Loop variable '{}' in boundary but no PHI in header block {} (has {} carrier PHIs)", loop_var_name, header_block.0, loop_info.carrier_phis.len() ); } } // Check 2: Carrier PHI existence if !loop_info.carrier_phis.is_empty() { let header_block_data = &func.blocks[header_block.0]; let phi_count = header_block_data .instructions .iter() .filter(|instr| matches!(instr, crate::mir::MirInstruction::Phi { .. })) .count(); if phi_count == 0 { panic!( "[JoinIRVerifier] LoopHeaderPhiInfo has {} PHIs but header block {} has none", loop_info.carrier_phis.len(), header_block.0 ); } // Verify each carrier has a corresponding PHI for (carrier_name, entry) in &loop_info.carrier_phis { let phi_exists = header_block_data.instructions.iter().any(|instr| { if let crate::mir::MirInstruction::Phi { dst, .. } = instr { *dst == entry.phi_dst } else { false } }); if !phi_exists { panic!( "[JoinIRVerifier] Carrier '{}' has PHI dst {:?} but PHI not found in header block {}", carrier_name, entry.phi_dst, header_block.0 ); } } } } /// Verify exit line consistency /// /// # Checks /// /// 1. All exit_bindings in boundary should have corresponding values /// 2. Exit block should exist and be in range /// /// # Panics /// /// Panics in debug mode if contract violations are detected. #[cfg(debug_assertions)] fn verify_exit_line( func: &crate::mir::MirFunction, exit_block: crate::mir::BasicBlockId, boundary: &JoinInlineBoundary, ) { // Check 1: Exit block exists if exit_block.0 >= func.blocks.len() { panic!( "[JoinIRVerifier] Exit block {} out of range (func has {} blocks)", exit_block.0, func.blocks.len() ); } // Check 2: Exit bindings reference valid values if !boundary.exit_bindings.is_empty() { for binding in &boundary.exit_bindings { // Verify host_slot is reasonable (basic sanity check) // We can't verify the exact value since it's from the host's value space, // but we can check it's not obviously invalid if binding.host_slot.0 >= 1000000 { // Arbitrary large number check panic!( "[JoinIRVerifier] Exit binding '{}' has suspiciously large host_slot {:?}", binding.carrier_name, binding.host_slot ); } } } } /// Verify all loop contracts for a merged JoinIR function /// /// This is the main entry point for verification. It runs all checks /// and panics if any contract violation is found. /// /// # Panics /// /// Panics in debug mode if any contract violation is detected. #[cfg(debug_assertions)] fn verify_joinir_contracts( func: &crate::mir::MirFunction, header_block: crate::mir::BasicBlockId, exit_block: crate::mir::BasicBlockId, loop_info: &LoopHeaderPhiInfo, boundary: &JoinInlineBoundary, ) { verify_loop_header_phis(func, header_block, loop_info, boundary); verify_exit_line(func, exit_block, boundary); }