diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index b511dda9..9840e531 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -1094,13 +1094,10 @@ pub(super) fn merge_and_rewrite( exit_block_id: BasicBlockId, debug: bool, ) -> Result { - // Phase 286C-2.1: TODO - This will become an orchestrator calling the 3 stages: - // 1. scan_blocks() - Read-only analysis - // 2. plan_rewrites() - Generate rewritten blocks - // 3. apply_rewrites() - Mutate builder - // - // For now, keep existing monolithic logic intact to ensure no behavior changes. - // Future steps will extract logic incrementally into the 3 functions. + // Phase 286C-4: 3-stage pipeline orchestrator + // 1. scan_blocks() - Read-only analysis (identify what to rewrite) + // 2. plan_rewrites() - Generate rewritten blocks (pure transformation) + // 3. apply_rewrites() - Mutate builder (apply changes) let trace = trace::trace(); let verbose = debug || crate::config::env::joinir_dev_enabled(); @@ -1110,16 +1107,8 @@ pub(super) fn merge_and_rewrite( }; } - // ===== STAGE 1: SCAN (Read-only analysis) ===== - // TODO Phase 286C-2.1: Extract to scan_blocks() - // This section should: - // - Build function_entry_map - // - Build skipped_entry_redirects - // - Identify tail calls, returns, PHI adjustments - // - Return RewritePlan describing what to do - - // Phase 256 P1.7: continuation_func_ids is now BTreeSet (function names) - // No need to convert with join_func_name() - use directly + // ===== METADATA SETUP ===== + // Build continuation candidates and policy let continuation_candidates: BTreeSet = boundary .map(|b| b.continuation_func_ids.clone()) .unwrap_or_default(); @@ -1136,21 +1125,15 @@ pub(super) fn merge_and_rewrite( }) .collect(); - // Phase 132 P1: Tail call lowering is driven by the continuation contract (SSOT) - // Only structurally-skippable continuation functions are lowered to exit jumps. - let tail_call_policy = TailCallLoweringPolicyBox::new(skippable_continuation_func_names.clone()); + // Phase 132 P1: Tail call lowering policy + let _tail_call_policy = TailCallLoweringPolicyBox::new(skippable_continuation_func_names.clone()); - // Phase 177-3: exit_block_id is now passed in from block_allocator log!( verbose, - "[cf_loop/joinir/instruction_rewriter] Phase 177-3: Using exit_block_id = {:?}", + "[merge_and_rewrite] Phase 286C-4: Using exit_block_id = {:?}", 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 286C-3: Use RewriteContext to consolidate scattered state let mut ctx = RewriteContext::new(exit_block_id, boundary, debug); @@ -1183,1224 +1166,79 @@ pub(super) fn merge_and_rewrite( ); } - // Phase 256 P1.10: Pre-pass removed - param mappings are set up during block processing - // The issue was that pre-pass used stale remapper values before Phase 33-21 updates. - // Instead, we now generate Copies for non-carrier params in the main block processing loop. + // ===== STAGE 1: SCAN (Read-only analysis) ===== + if debug { + log!( + true, + "[merge_and_rewrite] Phase 286C-4 Stage 1: Scanning {} functions", + mir_module.functions.len() + ); + } - // Phase 286C-2.1 Step 3: Call scan_blocks() to identify rewrites let scan_plan = scan_blocks(mir_module, remapper, value_to_func_name, &ctx, debug)?; if debug { log!( true, - "[cf_loop/joinir] Phase 286C-2.1: Scan complete - {} tail calls, {} returns", + "[merge_and_rewrite] Phase 286C-4: Scan complete - {} tail calls, {} returns", scan_plan.tail_calls.len(), scan_plan.return_conversions.len() ); } // ===== STAGE 2: PLAN (Generate rewritten blocks) ===== - // TODO Phase 286C-2.1: Extract to plan_rewrites() - // This section should: - // - Process each function and block - // - Generate Copy instructions for parameter bindings - // - Build new BasicBlocks with remapped instructions - // - Prepare PHI inputs and carrier inputs - // - Return RewrittenBlocks ready to apply - - // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order if debug { log!( true, - "[cf_loop/joinir] Phase 189: Merging {} functions", - mir_module.functions.len() + "[merge_and_rewrite] Phase 286C-4 Stage 2: Planning rewrites" ); } - let mut functions_merge: Vec<_> = mir_module.functions.iter().collect(); - functions_merge.sort_by_key(|(name, _)| name.as_str()); - - // Phase 256 P1.10: Determine actual entry function (loop header) - // - // The entry function is the one that: - // - Is NOT a continuation function (k_exit, etc.) - // - Is NOT "main" (the trampoline that calls the loop entry) - // - // This is the function that receives boundary inputs and is the loop header. - let entry_func_name = functions_merge - .iter() - .find(|(name, _)| { - let name_str = name.as_str(); - let is_continuation = continuation_candidates.contains(*name); - let is_main = name_str == crate::mir::join_ir::lowering::canonical_names::MAIN; - !is_continuation && !is_main - }) - .map(|(name, _)| name.as_str()); - - for (func_name, func) in functions_merge { - // Phase 33-15: Identify continuation functions (k_exit, etc.) - // Continuation functions receive values from Jump args, not as independent sources - // We should NOT collect their Return values for exit_phi_inputs - let is_continuation_candidate = continuation_candidates.contains(func_name); - let is_skippable_continuation = skippable_continuation_func_names.contains(func_name); - - if debug { - log!( - true, - "[cf_loop/joinir] Merging function '{}' with {} blocks, entry={:?} (continuation_candidate={}, skippable_continuation={})", - func_name, - func.blocks.len(), - func.entry_block, - is_continuation_candidate, - is_skippable_continuation - ); - } - - // Phase 132 P1: Skip only *structurally skippable* continuation functions. - // - // Continuation functions can be real code (e.g. k_exit tailcalls post_k). - // Merge must follow the explicit continuation contract and then decide - // skippability by structure only. - if is_skippable_continuation { - if debug { - log!( - true, - "[cf_loop/joinir] Phase 132 P1: Skipping skippable continuation function '{}' blocks", - func_name - ); - } - continue; - } - - // Build a local block map for this function (for remap_instruction compatibility) - // Phase 222.5-E: HashMap → BTreeMap for determinism - let mut local_block_map: BTreeMap = BTreeMap::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))?; - - // Phase 195 FIX: Reuse existing block if present (preserves PHI from JoinIR Select lowering) - // ultrathink "finalizer集約案": Don't overwrite blocks with BasicBlock::new() - let mut new_block = - if let Some(ref mut current_func) = builder.scope_ctx.current_function { - current_func - .blocks - .remove(&new_block_id) - .unwrap_or_else(|| BasicBlock::new(new_block_id)) - } else { - BasicBlock::new(new_block_id) - }; - - // Phase 33-16: Identify loop entry point - // - // The entry function's entry block is special: it IS the loop header. - // Redirecting tail calls from this block to itself would create a self-loop. - // This flag is used to prevent such incorrect redirection. - let is_loop_entry_point = - entry_func_name == Some(func_name.as_str()) && *old_block_id == func.entry_block; - - // DEBUG: Print block being processed - // Phase 256 P1.10: Always log block mapping for debugging - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10: Block mapping: func='{}' old={:?} → new={:?} (inst_count={})", - func_name, old_block_id, new_block_id, old_block.instructions.len() - ); - - // Phase 256 P1.10 DEBUG: Log first instruction for k_exit to trace content placement - if func_name == "k_exit" { - if let Some(first_inst) = old_block.instructions.first() { - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10 DEBUG: k_exit first instruction: {:?}", - first_inst - ); - } - } - if debug { - log!( - true, - "[cf_loop/joinir] === Processing block {:?} (from func '{}') ===", - old_block_id, func_name - ); - log!( - true, - "[cf_loop/joinir] Original block has {} instructions:", - old_block.instructions.len() - ); - for (idx, inst) in old_block.instructions.iter().enumerate() { - log!(true, "[cf_loop/joinir] [{}] {:?}", idx, inst); - } - log!( - true, - "[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; - // Phase 131 Task 2: Use TailCallLoweringPolicyBox to detect k_exit tail calls - // k_exit (continuation) must not jump to its entry block. - // We skip merging continuation functions, so any tail-call to k_exit must be - // lowered as an exit jump to `exit_block_id` (and contribute exit values). - let mut k_exit_lowering_decision: Option = None; - let mut k_exit_edge_args: Option = None; - - // Phase 177-3: Check if this block is the loop header with PHI nodes - let is_loop_header_with_phi = - is_loop_entry_point && !loop_header_phi_info.carrier_phis.is_empty(); - - if is_loop_entry_point { - log!( - verbose, - "[cf_loop/joinir] Phase 177-3 DEBUG: is_loop_entry_point={}, carrier_phis.len()={}, is_loop_header_with_phi={}", - is_loop_entry_point, - loop_header_phi_info.carrier_phis.len(), - is_loop_header_with_phi - ); - } - - // Phase 177-3: Collect PHI dst IDs that will be created for this block - // (if this is the loop header) - let phi_dst_ids_for_block: std::collections::HashSet = - if is_loop_header_with_phi { - loop_header_phi_info - .carrier_phis - .values() - .map(|entry| entry.phi_dst) - .collect() - } else { - std::collections::HashSet::new() - }; - - if is_loop_header_with_phi && !phi_dst_ids_for_block.is_empty() { - log!( - verbose, - "[cf_loop/joinir] Phase 177-3: Loop header with {} PHI dsts to protect: {:?}", - phi_dst_ids_for_block.len(), - phi_dst_ids_for_block - ); - } - - // First pass: Process all instructions, identify tail calls - for inst in &old_block.instructions { - // Phase 177-3: Skip Copy instructions that would overwrite PHI dsts - // The loop header PHIs will provide these values, so copies are redundant/harmful - if is_loop_header_with_phi { - if let MirInstruction::Copy { dst, src } = inst { - // Check if this copy's dst is a PHI dst (after remapping) - let dst_remapped = remapper.get_value(*dst).unwrap_or(*dst); - log!( - verbose, - "[cf_loop/joinir] Phase 177-3 DEBUG: Copy {:?} = {:?}, dst_remapped = {:?}, in phi_dsts = {}", - dst, src, dst_remapped, phi_dst_ids_for_block.contains(&dst_remapped) - ); - // Phase 286C-2: Use InstructionFilterBox for skip judgment - if InstructionFilterBox::should_skip_copy_overwriting_phi(dst_remapped, &phi_dst_ids_for_block) { - log!( - verbose, - "[cf_loop/joinir] Phase 177-3: ✅ Skipping loop header Copy to PHI dst {:?} (original {:?})", - dst_remapped, dst - ); - continue; // Skip - would overwrite PHI - } - } - } - - // Phase 189: Skip Const String instructions that define function names - if let MirInstruction::Const { dst, value } = inst { - // Phase 286C-2: Use InstructionFilterBox for function name skip judgment - if InstructionFilterBox::should_skip_function_name_const(value) - && value_to_func_name.contains_key(dst) - { - if debug { - log!(true, "[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. - // Phase 286C-2: Use InstructionFilterBox for boundary input skip judgment - let boundary_inputs: Vec = boundary_input_set.iter().cloned().collect(); - if InstructionFilterBox::should_skip_boundary_input_const(*dst, &boundary_inputs, is_loop_entry_point) { - if debug { - log!(true, "[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(callee_name) = value_to_func_name.get(func) { - // Phase 131 Task 2: Use TailCallLoweringPolicyBox to classify tail calls - // k_exit calls are "exit edges" (not normal tail calls). - // Otherwise we'd Jump to the k_exit entry block, but continuation - // blocks are intentionally not merged (skip), causing invalid BB. - let remapped_args: Vec = args - .iter() - .map(|&v| remapper.get_value(v).unwrap_or(v)) - .collect(); - - if let Some(decision) = tail_call_policy.classify_tail_call(callee_name, &remapped_args) { - // This is a k_exit tail call - policy says normalize to exit jump - k_exit_lowering_decision = Some(decision); - if let Some(b) = boundary { - if let Some(edge_args) = old_block.edge_args_from_terminator() { - if edge_args.layout != b.jump_args_layout { - let msg = format!( - "[joinir/merge] k_exit edge-args layout mismatch: block={:?} edge={:?} boundary={:?}", - old_block.id, edge_args.layout, b.jump_args_layout - ); - if ctx.strict_exit { - return Err(msg); - } else if debug { - log!(true, "[DEBUG-177] {}", msg); - } - } - k_exit_edge_args = Some(edge_args); - } - } - found_tail_call = true; - if debug { - log!( - true, - "[cf_loop/joinir] Phase 131 Task 2: Detected k_exit tail call '{}' (args={:?}), will normalize to Jump(exit_block_id={:?})", - callee_name, - args, - exit_block_id - ); - } - continue; // Skip the Call instruction itself - } - // Not a k_exit call - check if it's a normal tail call (intra-module jump) - if let Some(&target_block) = ctx.function_entry_map.get(callee_name) { - // This is a tail call - save info and skip the Call instruction itself - tail_call_target = Some((target_block, remapped_args)); - found_tail_call = true; - - if debug { - log!( - true, - "[cf_loop/joinir] Detected tail call to '{}' (args={:?}), will convert to Jump", - func_name, args - ); - } - - continue; // Skip the Call instruction itself - } - } - } - - // Phase 33-20: Skip Copy instructions that would overwrite header PHI dsts - // In the header block, carriers are defined by PHIs, not Copies. - // JoinIR function parameters get copied to local variables, but after - // inlining with header PHIs, those Copies would overwrite the PHI results. - if let MirInstruction::Copy { dst, src: _ } = inst { - // Check if this Copy's dst (after remapping) matches any header PHI dst - let remapped_dst = remapper.get_value(*dst).unwrap_or(*dst); - let is_header_phi_dst = loop_header_phi_info - .carrier_phis - .values() - .any(|entry| entry.phi_dst == remapped_dst); - - if is_header_phi_dst { - if debug { - log!( - true, - "[cf_loop/joinir] Phase 33-20: Skipping Copy that would overwrite header PHI dst {:?}", - remapped_dst - ); - } - continue; // Skip - PHI already defines this value - } - } - - // 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) - // Phase 284 P1: Use block_remapper SSOT (consolidated priority rule) - let remapped_with_blocks = match remapped { - MirInstruction::Branch { - condition, - then_bb, - else_bb, - then_edge_args, - else_edge_args, - } => { - // Phase 284 P1: Use SSOT for block remapping - let remapped_then = remap_block_id(then_bb, &local_block_map, &ctx.skipped_entry_redirects); - let remapped_else = remap_block_id(else_bb, &local_block_map, &ctx.skipped_entry_redirects); - MirInstruction::Branch { - condition, - then_bb: remapped_then, - else_bb: remapped_else, - then_edge_args, - else_edge_args, - } - } - MirInstruction::Phi { dst, inputs, type_hint } => { - use super::phi_block_remapper::remap_phi_instruction; - remap_phi_instruction(dst, &inputs, type_hint, &local_block_map) - } - other => other, - }; - - if debug { - match inst { - MirInstruction::BoxCall { .. } => { - log!( - true, - "[cf_loop/joinir] Adding BoxCall to block {:?}: {:?}", - new_block_id, inst - ); - } - _ => {} - } - } - - propagate_value_type_for_inst(builder, func, inst, &remapped_with_blocks); - new_block.instructions.push(remapped_with_blocks); - } - - // DEBUG: Print what was added to the block after first pass - if debug { - log!( - true, - "[cf_loop/joinir] After first pass, new_block has {} instructions", - new_block.instructions.len() - ); - for (idx, inst) in new_block.instructions.iter().enumerate() { - log!(true, "[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 { - log!( - true, - "[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 &ctx.function_entry_map { - if entry_block == target_block { - target_func_name = Some(fname.clone()); - break; - } - } - - // Phase 256 P1.10: Define is_target_continuation for classify_tail_call - // This must be defined before the inner if lets so it's in scope for classify_tail_call - let is_target_continuation = target_func_name - .as_ref() - .map(|name| continuation_candidates.contains(name)) - .unwrap_or(false); - - let is_recursive_call = target_func_name - .as_ref() - .map(|name| name == func_name) - .unwrap_or(false); - - if let Some(ref target_func_name) = target_func_name { - if let Some(target_params) = function_params.get(target_func_name) { - // Phase 256 P1.10: Detect call type for param binding strategy - // - Recursive call (loop_step → loop_step): Skip all param bindings (PHI handles via latch edges) - // - Exit call (loop_step → k_exit): Skip all param bindings (exit PHI handles via exit edges) - // - Header entry (main → loop_step at header): Skip all (PHI handles via entry edges) - // Phase 256 P1.10: Detect if target is the loop entry function - // When calling the loop entry function, header PHIs will define the carriers. - // We should skip param bindings in this case. - let is_target_loop_entry = entry_func_name == Some(target_func_name.as_str()); - - // Phase 256 P1.10: Detect if target is a continuation (k_exit) - // k_exit params are exit binding values, which are remapped to exit PHI dsts. - // Generating Copies for k_exit would create multiple definitions (Copy + PHI). - // Use continuation_candidates (not skippable_continuation_func_names) because - // k_exit may have instructions (non-skippable) but still needs param skip. - let is_target_continuation = continuation_candidates.contains(target_func_name); - - // Phase 256 P1.10 DEBUG: Always log call type detection - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10: Tail call from '{}' to '{}' (is_recursive={}, is_loop_entry={}, is_target_loop_entry={}, is_target_continuation={})", - func_name, target_func_name, is_recursive_call, is_loop_entry_point, is_target_loop_entry, is_target_continuation - ); - - // Phase 33-21: Skip parameter binding in header block - // - // The header block (loop entry point) has PHIs that define carriers. - // Parameter bindings are only needed for back edges (latch → header). - // In the header block, the PHI itself provides the initial values, - // so we don't need Copy instructions from tail call args. - // - // Without this check, the generated MIR would have: - // bb_header: - // %phi_dst = phi [entry_val, bb_entry], [latch_val, bb_latch] - // %phi_dst = copy %undefined ← ❌ This overwrites the PHI! - if is_loop_entry_point { - if debug { - log!( - true, - "[cf_loop/joinir] Phase 33-21: Skip param bindings in header block (PHIs define carriers)" - ); - } - } else if (is_recursive_call || is_target_loop_entry) && is_loop_header_with_phi { - // Phase 256 P1.10: Skip param bindings for: - // - Recursive call (loop_step → loop_step): latch edge - // - Entry call (main → loop_step): entry edge - // - // Header PHIs receive values from these edges via separate mechanism. - // Generating Copies here would cause multiple definitions. - // - // Phase 259 P0 FIX: Only skip if loop header HAS PHIs! - // Pattern8 has no carriers → no PHIs → MUST generate Copy bindings. - // - // Update remapper mappings for any further instructions. - for (i, arg_val_remapped) in args.iter().enumerate() { - if i < target_params.len() { - let param_val_original = target_params[i]; - remapper.set_value(param_val_original, *arg_val_remapped); - } - } - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10: Skip Copy bindings for {} call to '{}' (remapper updated)", - if is_recursive_call { "recursive" } else { "entry" }, - target_func_name - ); - } else if is_target_continuation { - // Phase 256 P1.10: Continuation call (loop_step → k_exit) - // k_exit body uses its params to compute return values. - // We must Copy call args to ORIGINAL params (not remapped). - // Params are excluded from remapping (Phase 256 P1.10 exit_binding skip), - // so using original params avoids conflict with exit PHI. - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10 DEBUG: Before continuation Copies, new_block {:?} has {} instructions", - new_block_id, new_block.instructions.len() - ); - for (i, arg_val_remapped) in args.iter().enumerate() { - if i < target_params.len() { - let param_val_original = target_params[i]; - // Use original param as dst - it won't be remapped - new_block.instructions.push(MirInstruction::Copy { - dst: param_val_original, - src: *arg_val_remapped, - }); - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10: Continuation param binding: {:?} = copy {:?}", - param_val_original, arg_val_remapped - ); - } - } - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10 DEBUG: After continuation Copies, new_block {:?} has {} instructions", - new_block_id, new_block.instructions.len() - ); - } else { - // Insert Copy instructions for parameter binding - // Phase 131-6 FIX: Skip Copy when dst is a header PHI destination - // The PHI node will receive the value as an incoming edge instead - for (i, arg_val_remapped) in args.iter().enumerate() { - if i < target_params.len() { - let param_val_original = target_params[i]; - // Phase 256 P1.10: Use original param if not remapped - // Params are excluded from used_values to prevent undefined remaps, - // so we use the original param ValueId as the Copy dst. - let param_remap_result = remapper.get_value(param_val_original); - let param_val_dst = param_remap_result - .unwrap_or(param_val_original); - - // Phase 256 P1.10 DEBUG: Log remap decision - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10 DEBUG: Param[{}] original={:?} remap_result={:?} final_dst={:?}", - i, param_val_original, param_remap_result, param_val_dst - ); - - // Phase 131-6: Check if this would overwrite a header PHI dst - let is_header_phi_dst = loop_header_phi_info - .carrier_phis - .values() - .any(|entry| entry.phi_dst == param_val_dst); - - if is_header_phi_dst { - if debug { - log!( - true, - "[cf_loop/joinir] Phase 131-6: Skip param binding to PHI dst {:?} (PHI receives value via incoming edge)", - param_val_dst - ); - } - // Skip - PHI will receive this value as incoming edge - continue; - } - - // Phase 256 P1.10 DEBUG: Always log Copy generation - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10 DEBUG: Generating Copy: {:?} = copy {:?} (func='{}', target='{}')", - param_val_dst, arg_val_remapped, func_name, target_func_name - ); - - new_block.instructions.push(MirInstruction::Copy { - dst: param_val_dst, - src: *arg_val_remapped, - }); - } - } - } - } - } - - // Phase 33-16: Record latch incoming for loop header PHI - // - // At this point, args[0] contains the updated loop variable value (i_next). - // We record this as the latch incoming so that the header PHI can reference - // the correct SSA value at loop continuation time. - if is_recursive_call { - if let Some(b) = boundary { - if let Some(loop_var_name) = &b.loop_var_name { - if !args.is_empty() { - // The first arg is the loop variable's updated value - let latch_value = args[0]; - // The current block (new_block_id) is the latch block - loop_header_phi_info.set_latch_incoming( - loop_var_name, - new_block_id, // latch block - latch_value, // updated loop var value (i_next) - ); - - if debug { - log!( - true, - "[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': block={:?}, value={:?}", - loop_var_name, new_block_id, latch_value - ); - } - } - } - - // Phase 33-20: Also set latch incoming for other carriers from exit_bindings - // - // args layout depends on whether we have a dedicated loop variable: - // - loop_var_name = Some(..): args[0] is the loop variable, args[1..] are other carriers - // - loop_var_name = None: args[0..] are carriers (no reserved loop-var slot) - // - // Phase 176-4 FIX: exit_bindings may include the loop variable itself; skip it when loop_var_name is set. - let mut carrier_arg_idx = if b.loop_var_name.is_some() { 1 } else { 0 }; - for binding in b.exit_bindings.iter() { - // Skip if this binding is for the loop variable (already handled) - if let Some(ref loop_var) = b.loop_var_name { - if &binding.carrier_name == loop_var { - if debug { - log!( - true, - "[cf_loop/joinir] Phase 176-4: Skipping loop variable '{}' in exit_bindings (handled separately)", - binding.carrier_name - ); - } - continue; // Skip loop variable - } - } - - // Process non-loop-variable carrier - if carrier_arg_idx < args.len() { - let latch_value = args[carrier_arg_idx]; - loop_header_phi_info.set_latch_incoming( - &binding.carrier_name, - new_block_id, - latch_value, - ); - - if debug { - log!( - true, - "[cf_loop/joinir] Phase 176-4: Set latch incoming for carrier '{}': block={:?}, value={:?} (arg[{}])", - binding.carrier_name, new_block_id, latch_value, carrier_arg_idx - ); - } - carrier_arg_idx += 1; - } else if debug { - log!( - true, - "[cf_loop/joinir] Phase 33-20 WARNING: No arg for carrier '{}' at index {}", - binding.carrier_name, carrier_arg_idx - ); - } - } - - // Phase 255 P2: Set latch incoming for loop invariants - // - // Loop invariants don't have corresponding tail call args because they - // are not modified by the loop. Their latch incoming is the PHI dst itself - // (same value across all iterations). - for (inv_name, _inv_host_id) in b.loop_invariants.iter() { - if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(inv_name) { - // For invariants, latch incoming is the PHI dst (same value) - loop_header_phi_info.set_latch_incoming( - inv_name, - new_block_id, // latch block - phi_dst, // same as PHI dst (invariant value) - ); - - if debug { - log!( - true, - "[cf_loop/joinir] Phase 255 P2: Set latch incoming for loop invariant '{}': block={:?}, value={:?} (PHI dst itself)", - inv_name, new_block_id, phi_dst - ); - } - } - } - } - } - - // Phase 33-16: Classify tail call to determine redirection behavior - // - // Tail calls have different semantics depending on where they occur: - // - LoopEntry: First entry (entry function's entry block) → no redirect - // - BackEdge: Loop continuation (other blocks) → redirect to header PHI - // - ExitJump: Exit to continuation → handled by Return conversion - // Phase 256 P1.10: Add is_target_continuation to prevent k_exit calls - // from being redirected to header block. - let tail_call_kind = classify_tail_call( - is_loop_entry_point, - !loop_header_phi_info.carrier_phis.is_empty(), - boundary.is_some(), - is_target_continuation, - ); - - let actual_target = match tail_call_kind { - TailCallKind::BackEdge => { - // Back edge: redirect to header block where PHIs are - if debug { - log!( - true, - "[cf_loop/joinir] Phase 33-16: BackEdge detected, redirecting from {:?} to header {:?}", - target_block, loop_header_phi_info.header_block - ); - } - loop_header_phi_info.header_block - } - TailCallKind::LoopEntry => { - // Loop entry: no redirect (entry block IS the header) - if debug { - log!( - true, - "[cf_loop/joinir] Phase 33-16: LoopEntry detected, using direct target {:?}", - target_block - ); - } - target_block - } - TailCallKind::ExitJump => { - // Exit: jump to continuation function's entry block - // Phase 284 P1: Check if the target is a skippable continuation - // - Skippable (k_exit): jump to exit_block_id (k_exit blocks are skipped) - // - Non-skippable (k_return): jump to target_block (k_return blocks are merged) - let is_target_skippable = target_func_name - .as_ref() - .map(|name| skippable_continuation_func_names.contains(name)) - .unwrap_or(false); - - if is_target_skippable { - // Phase 259 P0 FIX: Skippable continuation - use exit_block_id - if debug { - log!( - true, - "[cf_loop/joinir] Phase 259 P0: ExitJump (skippable) redirecting from {:?} to exit_block_id {:?}", - target_block, exit_block_id - ); - } - exit_block_id - } else { - // Phase 284 P1: Non-skippable continuation - use remapped target_block - if debug { - log!( - true, - "[cf_loop/joinir] Phase 284 P1: ExitJump (non-skippable) to target_block {:?}", - target_block - ); - } - target_block - } - } - }; - - new_block.set_terminator(MirInstruction::Jump { - target: actual_target, - edge_args: None, - }); - - // DEBUG: Print final state after adding parameter bindings - if debug { - log!( - true, - "[cf_loop/joinir] After adding param bindings, new_block has {} instructions", - new_block.instructions.len() - ); - for (idx, inst) in new_block.instructions.iter().enumerate() { - log!(true, "[cf_loop/joinir] [{}] {:?}", idx, inst); - } - } - } - new_block.instruction_spans = old_block.instruction_spans.clone(); - - if debug { - log!(true, "[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 { - // Phase 260 P0.1 Step 5: Use terminator module for Jump/Branch remapping - let mut remapped_term: Option = None; - match term { - MirInstruction::Return { value } => { - // Phase 284 P1: Non-skippable continuations (like k_return) should keep Return - // Only convert Return → Jump for skippable continuations and regular loop body - // Phase 286C-2: Use ReturnConverterBox for should_keep_return decision - if ReturnConverterBox::should_keep_return(is_continuation_candidate, is_skippable_continuation) { - // Non-skippable continuation (e.g., k_return): keep Return terminator - // Remap the return value to HOST value space - // Phase 286C-2: Use ReturnConverterBox for value remapping - let remapped_value = ReturnConverterBox::remap_return_value(*value, |v| remapper.remap_value(v)); - new_block.set_terminator(MirInstruction::Return { value: remapped_value }); - log!( - true, - "[cf_loop/joinir] Phase 284 P1: Keeping Return for non-skippable continuation '{}' (value={:?})", - func_name, remapped_value - ); - } else { - // Convert Return to Jump to exit block - // All functions return to same exit block (Phase 189) - // - // Phase 246-EX: Use Jump args from block metadata for exit values - // - // The JoinIR Jump instruction passes ALL carrier values in its args, - // but the JoinIR→MIR conversion in joinir_block_converter only preserved - // the first arg in the Return value. We now use the legacy jump_args metadata - // to recover all the original Jump args. - let mut exit_edge_args: Option = None; - if value.is_some() { - if let Some(b) = boundary { - // Phase 246-EX: Use terminator edge-args via BasicBlock API - if let Some(edge_args) = old_block.edge_args_from_terminator() { - if edge_args.layout != b.jump_args_layout { - let msg = format!( - "[joinir/merge] exit edge-args layout mismatch: block={:?} edge={:?} boundary={:?}", - old_block.id, edge_args.layout, b.jump_args_layout - ); - if ctx.strict_exit { - return Err(msg); - } else if debug { - log!(true, "[DEBUG-177] {}", msg); - } - } - log!( - verbose, - "[DEBUG-177] Phase 246-EX: Block {:?} has legacy jump_args metadata: {:?}", - old_block.id, edge_args.values - ); - - // The jump_args are in JoinIR value space, remap them to HOST - let remapped_args: Vec = edge_args - .values - .iter() - .map(|&arg| remapper.remap_value(arg)) - .collect(); - - log!( - verbose, - "[DEBUG-177] Phase 246-EX: Remapped jump_args: {:?}", - remapped_args - ); - - // Phase 118 P2: Use ExitArgsCollectorBox to collect exit values - let edge_args = crate::mir::EdgeArgs { - layout: edge_args.layout, - values: remapped_args, - }; - exit_edge_args = Some(edge_args.clone()); - - let collector = ExitArgsCollectorBox::new(); - let collection_result = collector.collect( - &b.exit_bindings, - &edge_args.values, - new_block_id, - ctx.strict_exit, - edge_args.layout, - )?; - - // Add expr_result to exit_phi_inputs (if present) - if let Some(expr_result_val) = - collection_result.expr_result_value - { - ctx.add_exit_phi_input(new_block_id, expr_result_val); - log!( - verbose, - "[DEBUG-177] Phase 118: exit_phi_inputs from ExitArgsCollectorBox: ({:?}, {:?})", - new_block_id, expr_result_val - ); - } - - // Add carrier values to carrier_inputs - for (carrier_name, (block_id, value_id)) in - collection_result.carrier_values - { - log!( - verbose, - "[DEBUG-177] Phase 118: Collecting carrier '{}': from {:?} value {:?}", - carrier_name, block_id, value_id - ); - ctx.add_carrier_input(carrier_name, block_id, value_id); - } - } else { - // Fallback: Use header PHI dst (old behavior for blocks without jump_args) - log!( - verbose, - "[DEBUG-177] Phase 246-EX: Block {:?} has NO jump_args, using header PHI fallback", - old_block.id - ); - - if let Some(loop_var_name) = &b.loop_var_name { - if let Some(phi_dst) = - loop_header_phi_info.get_carrier_phi(loop_var_name) - { - ctx.add_exit_phi_input(new_block_id, phi_dst); - if debug { - log!( - true, - "[cf_loop/joinir] Phase 246-EX fallback: Using header PHI dst {:?} for exit (loop_var='{}')", - phi_dst, loop_var_name - ); - } - } - } - - // Phase 227: Filter out ConditionOnly carriers from exit PHI - // Phase 131 P1.5: For DirectValue mode, if no header PHI, use host_slot (initial value) - for binding in &b.exit_bindings { - if binding.role == crate::mir::join_ir::lowering::carrier_info::CarrierRole::ConditionOnly { - continue; - } - - if let Some(phi_dst) = loop_header_phi_info - .get_carrier_phi(&binding.carrier_name) - { - ctx.add_carrier_input(binding.carrier_name.clone(), new_block_id, phi_dst); - } else if b.exit_reconnect_mode == crate::mir::join_ir::lowering::carrier_info::ExitReconnectMode::DirectValue { - // Phase 131 P1.5: DirectValue mode fallback - use host_slot (initial value) - // This handles k_exit blocks that don't have jump_args and no header PHI - ctx.add_carrier_input(binding.carrier_name.clone(), new_block_id, binding.host_slot); - log!( - verbose, - "[cf_loop/joinir] Phase 131 P1.5: DirectValue fallback for '{}': using host_slot {:?}", - binding.carrier_name, binding.host_slot - ); - } - } - } - } - } - - if let Some(edge_args) = exit_edge_args { - new_block.set_jump_with_edge_args(exit_block_id, Some(edge_args)); - } else { - new_block.set_terminator(MirInstruction::Jump { - target: exit_block_id, - edge_args: None, - }); - } - } // Phase 284 P1: Close else block for skippable/regular Return → Jump - } - MirInstruction::Jump { target, edge_args } => { - // Phase 260 P0.1 Step 5: Use terminator::remap_jump() - remapped_term = Some(remap_jump( - &remapper, - *target, - edge_args, - &ctx.skipped_entry_redirects, - &local_block_map, - )); - } - MirInstruction::Branch { - condition, - then_bb, - else_bb, - then_edge_args, - else_edge_args, - } => { - // Phase 260 P0.1 Step 5: Use terminator::remap_branch() - remapped_term = Some(remap_branch( - &remapper, - *condition, - *then_bb, - *else_bb, - then_edge_args, - else_edge_args, - &ctx.skipped_entry_redirects, - &local_block_map, - )); - } - _ => remapped_term = Some(remapper.remap_instruction(term)), - } - // Phase 260 P0.1 Step 5: Use terminator::apply_remapped_terminator() - if let Some(term) = remapped_term { - apply_remapped_terminator(&mut new_block, term); - } - } - } - - // Phase 131 Task 2: If this block tail-calls k_exit, normalize to exit jump - // Use TailCallLoweringPolicyBox to generate the correct terminator - if let Some(decision) = k_exit_lowering_decision { - match decision { - LoweringDecision::NormalizeToExitJump { args } => { - // Collect exit values from k_exit arguments - let mut exit_edge_args: Option = None; - if let Some(b) = boundary { - let collector = ExitArgsCollectorBox::new(); - let exit_values: Vec = - if let Some(ref edge_args) = k_exit_edge_args { - edge_args - .values - .iter() - .map(|&arg| remapper.remap_value(arg)) - .collect() - } else { - args.clone() - }; - let edge_args = crate::mir::EdgeArgs { - layout: b.jump_args_layout, - values: exit_values, - }; - exit_edge_args = Some(edge_args.clone()); - let collection_result = - collector.collect( - &b.exit_bindings, - &edge_args.values, - new_block_id, - ctx.strict_exit, - edge_args.layout, - )?; - if let Some(expr_result_value) = collection_result.expr_result_value { - ctx.add_exit_phi_input(collection_result.block_id, expr_result_value); - } - for (carrier_name, (block_id, value_id)) in collection_result.carrier_values { - ctx.add_carrier_input(carrier_name, block_id, value_id); - } - } else if ctx.strict_exit { - return Err(error_tags::freeze_with_hint( - "phase131/k_exit/no_boundary", - "k_exit tail call detected without JoinInlineBoundary", - "k_exit must be handled as fragment exit; ensure boundary is passed when merging JoinIR fragments", - )); - } - - // Generate exit jump using policy box - let exit_jump = if let Some(edge_args) = exit_edge_args { - new_block.set_jump_with_edge_args(exit_block_id, Some(edge_args)); - new_block.terminator.clone().unwrap_or(MirInstruction::Jump { - target: exit_block_id, - edge_args: None, - }) - } else { - let exit_jump = tail_call_policy.rewrite_to_exit_jump(exit_block_id); - new_block.set_terminator(exit_jump.clone()); - exit_jump - }; - - // Strict mode: verify the generated terminator matches contract - if ctx.strict_exit { - tail_call_policy.verify_exit_jump(&exit_jump, exit_block_id)?; - } - } - LoweringDecision::NormalTailCall => { - // Should never happen (policy only returns NormalizeToExitJump for k_exit) - unreachable!("TailCallLoweringPolicyBox returned NormalTailCall for k_exit"); - } - } - } - - // 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); - } - - // ===== STAGE 3: APPLY (Mutate builder) ===== - // TODO Phase 286C-2.1: Extract to apply_rewrites() - // This section should: - // - Add new_block to builder - // - Update function metadata - // - Apply span synchronization - - // Add block to current function - if let Some(ref mut current_func) = builder.scope_ctx.current_function { - // Phase 256 P1.10 DEBUG: Log block content before adding (for blocks with multiple instructions) - if new_block.instructions.len() >= 4 { - log!( - true, - "[cf_loop/joinir] Phase 256 P1.10 DEBUG: Adding block {:?} with {} instructions to function", - new_block_id, new_block.instructions.len() - ); - for (idx, inst) in new_block.instructions.iter().enumerate() { - log!(true, "[cf_loop/joinir] Phase 256 P1.10 DEBUG: [{}] {:?}", idx, inst); - } - } - 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. - // - // Phase 143 fix: Normalized shadow fragments emit multiple helper functions - // (e.g. condition_fn) whose names may sort before the true entry. - // Choosing `.iter().next()` can therefore pick the wrong function and skip - // host→JoinIR Copy injection. Instead, pick the function whose params match - // the boundary.join_inputs (the entry env params). - // - // Phase 256 P1.10.1: Prefer "main" if its params match the boundary join_inputs. - let (entry_func_name, entry_func) = { - use crate::mir::join_ir::lowering::canonical_names as cn; - if let Some(main) = mir_module.functions.get(cn::MAIN) { - if main.params == boundary.join_inputs { - (cn::MAIN, main) - } else { - mir_module - .functions - .iter() - .find(|(_, func)| func.params == boundary.join_inputs) - .or_else(|| mir_module.functions.iter().next()) - .map(|(name, func)| (name.as_str(), func)) - .ok_or("JoinIR module has no functions")? - } - } else { - mir_module - .functions - .iter() - .find(|(_, func)| func.params == boundary.join_inputs) - .or_else(|| mir_module.functions.iter().next()) - .map(|(name, func)| (name.as_str(), func)) - .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))?; + let blocks = plan_rewrites( + scan_plan, + mir_module, + remapper, + function_params, + boundary, + loop_header_phi_info, + &mut ctx, + value_to_func_name, + debug, + )?; + + if debug { log!( true, - "[cf_loop/joinir] Phase 256 P1.10.1: Boundary entry selection: func='{}' entry_block={:?} remapped={:?} join_inputs={:?} entry_params={:?}", - entry_func_name, - entry_func.entry_block, - entry_block_remapped, - boundary.join_inputs, - entry_func.params + "[merge_and_rewrite] Phase 286C-4: Plan complete - {} blocks generated", + blocks.new_blocks.len() ); - - // Create BTreeMap from remapper for BoundaryInjector (temporary adapter) - // Phase 222.5-E: HashMap → BTreeMap for determinism - let mut value_map_for_injector = BTreeMap::new(); - - // Phase 171-fix: Add join_inputs to value_map - for join_in in &boundary.join_inputs { - if let Some(remapped) = remapper.get_value(*join_in) { - value_map_for_injector.insert(*join_in, remapped); - } - } - - // Phase 171-fix: Add condition_bindings to value_map - // Each binding specifies JoinIR ValueId → HOST ValueId mapping - // We remap the JoinIR ValueId to a new MIR ValueId - for binding in &boundary.condition_bindings { - if let Some(remapped) = remapper.get_value(binding.join_value) { - value_map_for_injector.insert(binding.join_value, remapped); - if debug { - log!( - true, - "[cf_loop/joinir] Phase 171-fix: Condition binding '{}': JoinIR {:?} → remapped {:?} (HOST {:?})", - binding.name, binding.join_value, remapped, binding.host_value - ); - } - } - } - - // Phase 177-3: Collect PHI dst IDs from loop_header_phi_info - let phi_dst_ids: std::collections::HashSet = loop_header_phi_info - .carrier_phis - .values() - .map(|entry| entry.phi_dst) - .collect(); - - // Use BoundaryInjector to inject Copy instructions - // Phase 177-3 Option B: Returns reallocations map for condition_bindings with PHI collisions - if let Some(ref mut current_func) = builder.scope_ctx.current_function { - let _reallocations = BoundaryInjector::inject_boundary_copies( - current_func, - entry_block_remapped, - boundary, - &value_map_for_injector, - &phi_dst_ids, - debug, - )?; - - // Note: reallocations map is returned but not currently used. - // If condition variables need to be referenced after this point, - // the reallocations map would need to be applied to update references. - // For now, condition_bindings are read-only variables used only in - // the loop condition, so no further updates are needed. - } } + // ===== STAGE 3: APPLY (Mutate builder) ===== + if debug { + log!( + true, + "[merge_and_rewrite] Phase 286C-4 Stage 3: Applying rewrites" + ); + } + + apply_rewrites( + builder, + blocks, + boundary, + remapper, + loop_header_phi_info, + mir_module, + &mut ctx, + debug, + )?; + + if debug { + log!( + true, + "[merge_and_rewrite] Phase 286C-4: Apply complete" + ); + } // Phase 131 P2: DirectValue mode remapped_exit_values SSOT // Phase 286C-3: Use ctx.set_remapped_exit_value() for state management //