diff --git a/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs b/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs index f77fe430..3f4ede23 100644 --- a/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/contract_checks/mod.rs @@ -21,10 +21,7 @@ pub(super) use terminator_targets::verify_all_terminator_targets_exist; pub(super) use exit_bindings::verify_exit_bindings_have_exit_phis; pub(super) use carrier_inputs::verify_carrier_inputs_complete; pub(in crate::mir::builder::control_flow::joinir) use boundary_creation::verify_boundary_contract_at_creation; -pub(in crate::mir::builder::control_flow::joinir) use entry_params::{ - verify_boundary_entry_params, - run_all_pipeline_checks, -}; +pub(in crate::mir::builder::control_flow::joinir) use entry_params::run_all_pipeline_checks; // Note: get_entry_function is kept internal to entry_params module // Patterns use the version from patterns/common/joinir_helpers.rs instead diff --git a/src/mir/builder/control_flow/joinir/merge/contract_checks/terminator_targets.rs b/src/mir/builder/control_flow/joinir/merge/contract_checks/terminator_targets.rs index 046a7aad..5dd57c5c 100644 --- a/src/mir/builder/control_flow/joinir/merge/contract_checks/terminator_targets.rs +++ b/src/mir/builder/control_flow/joinir/merge/contract_checks/terminator_targets.rs @@ -1,4 +1,4 @@ -use crate::mir::{BasicBlockId, MirFunction, MirInstruction}; +use crate::mir::{MirFunction, MirInstruction}; use super::super::merge_result::MergeContracts; diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan.rs deleted file mode 100644 index 24f89c92..00000000 --- a/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan.rs +++ /dev/null @@ -1,740 +0,0 @@ -//! Stage 2: Plan - Transform plan into concrete rewritten blocks -//! -//! Phase 287 P3: Extracted from instruction_rewriter.rs::plan_rewrites() -//! -//! Generates new BasicBlocks based on the scan plan: -//! - Processes each function and block -//! - Filters instructions using InstructionFilterBox -//! - Converts terminators using ReturnConverterBox -//! - Builds parameter bindings using ParameterBindingBox -//! - Prepares exit PHI inputs and carrier inputs -//! -//! Updates RewriteContext but does NOT touch MirBuilder. - -// Rewriter siblings (2 levels up: stages/ → rewriter/) -use super::super::{ - plan_helpers::{build_local_block_map, sync_spans}, - rewrite_context::RewriteContext, - scan_box::RewritePlan, - plan_box::RewrittenBlocks, - instruction_filter_box::InstructionFilterBox, - return_converter_box::ReturnConverterBox, - carrier_inputs_collector::CarrierInputsCollector, - latch_incoming_recorder, - helpers::is_skippable_continuation, -}; - -// Merge level (3 levels up: stages/ → rewriter/ → merge/) -use super::super::super::{ - phi_block_remapper::remap_phi_instruction, - block_remapper::remap_block_id, - exit_args_collector::ExitArgsCollectorBox, - tail_call_classifier::{classify_tail_call, TailCallKind}, - loop_header_phi_info::LoopHeaderPhiInfo, - trace, -}; - -// Terminator remapping (rewriter sibling) -use super::super::terminator::{remap_branch, remap_jump}; - -// Crate-level imports -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::{BTreeMap, BTreeSet}; - -/// Stage 2: Plan - Transform plan into concrete rewritten blocks -/// -/// Generates new BasicBlocks based on the scan plan: -/// - Processes each function and block -/// - Filters instructions using InstructionFilterBox -/// - Converts terminators using ReturnConverterBox -/// - Builds parameter bindings using ParameterBindingBox -/// - Prepares exit PHI inputs and carrier inputs -/// -/// Updates RewriteContext but does NOT touch MirBuilder. -/// -/// # Phase 286C-4 Step 2 -/// -/// This function extracts ~550 lines from merge_and_rewrite(): -/// - Function/block initialization (lines 399-468) -/// - First pass: instruction filtering (lines 581-760) -/// - Terminator conversion (lines 1163-1435) -/// - Span synchronization (lines 1436-1452) -pub(in crate::mir::builder::control_flow::joinir::merge) fn plan_rewrites( - _plan: RewritePlan, - mir_module: &MirModule, - remapper: &mut JoinIrIdRemapper, - function_params: &BTreeMap>, - boundary: Option<&JoinInlineBoundary>, - loop_header_phi_info: &mut LoopHeaderPhiInfo, - ctx: &mut RewriteContext, - value_to_func_name: &BTreeMap, - debug: bool, -) -> Result { - let trace = trace::trace(); - // Only verbose if explicitly requested via debug flag (not env var - causes test failures) - let verbose = debug; - macro_rules! log { - ($enabled:expr, $($arg:tt)*) => { - trace.stderr_if(&format!($($arg)*), $enabled); - }; - } - - let mut result = RewrittenBlocks { - new_blocks: Vec::new(), - block_replacements: BTreeMap::new(), - phi_inputs: Vec::new(), - carrier_inputs: BTreeMap::new(), - }; - - // Phase 256 P1.7: Build continuation candidate set - let continuation_candidates: BTreeSet = boundary - .map(|b| b.continuation_func_ids.clone()) - .unwrap_or_default(); - - let skippable_continuation_func_names: BTreeSet = mir_module - .functions - .iter() - .filter_map(|(func_name, func)| { - if continuation_candidates.contains(func_name) && is_skippable_continuation(func) { - Some(func_name.clone()) - } else { - None - } - }) - .collect(); - - // Build boundary input set for filtering - let boundary_input_set: std::collections::HashSet = boundary - .map(|b| b.join_inputs.iter().copied().collect()) - .unwrap_or_default(); - - // Sort functions for deterministic iteration - let mut functions_merge: Vec<_> = mir_module.functions.iter().collect(); - functions_merge.sort_by_key(|(name, _)| name.as_str()); - - // Determine entry function (loop header) - // - // Phase 287 P2: Prefer boundary SSOT (loop_header_func_name) over heuristic. - let entry_func_name = boundary - .and_then(|b| b.loop_header_func_name.as_deref()) - .or_else(|| { - 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()) - }); - - fn resolve_target_func_name<'a>( - function_entry_map: &'a BTreeMap, - target_block: BasicBlockId, - ) -> Option<&'a str> { - function_entry_map - .iter() - .find_map(|(fname, &entry_block)| (entry_block == target_block).then(|| fname.as_str())) - } - - fn is_joinir_main_entry_block( - func_name: &str, - func: &crate::mir::MirFunction, - old_block_id: BasicBlockId, - ) -> bool { - func_name == crate::mir::join_ir::lowering::canonical_names::MAIN - && old_block_id == func.entry_block - } - - // Process each function - for (func_name, func) in functions_merge { - let is_continuation_candidate = continuation_candidates.contains(func_name); - let is_skippable_continuation = skippable_continuation_func_names.contains(func_name); - - if debug { - log!( - true, - "[plan_rewrites] Processing function '{}' with {} blocks (continuation_candidate={}, skippable={})", - func_name, - func.blocks.len(), - is_continuation_candidate, - is_skippable_continuation - ); - } - - // Skip structurally skippable continuation functions - if is_skippable_continuation { - if debug { - log!( - true, - "[plan_rewrites] Skipping skippable continuation function '{}'", - func_name - ); - } - continue; - } - - // Build local block map for this function - let local_block_map = build_local_block_map(func_name, func, remapper)?; - - // Sort blocks for deterministic iteration - let mut blocks_merge: Vec<_> = func.blocks.iter().collect(); - blocks_merge.sort_by_key(|(id, _)| id.0); - - // Determine if this is the loop header entry block (loop_step entry). - let is_loop_header_entry_block = entry_func_name == Some(func_name.as_str()) - && blocks_merge.first().map(|(id, _)| **id) == Some(func.entry_block); - - // Check if loop header has PHIs - let is_loop_header_with_phi = - is_loop_header_entry_block && !loop_header_phi_info.carrier_phis.is_empty(); - - // Collect PHI dst IDs for this block (if 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() - }; - - // Process each block in the function - for (old_block_id, old_block) in blocks_merge { - 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))?; - - if debug { - log!( - true, - "[plan_rewrites] Block mapping: func='{}' old={:?} → new={:?} (inst_count={})", - func_name, old_block_id, new_block_id, old_block.instructions.len() - ); - } - - // Initialize new block (will be populated with filtered instructions) - let mut new_block = BasicBlock::new(new_block_id); - let mut found_tail_call = false; - let mut tail_call_target: Option<(BasicBlockId, Vec)> = None; - - // First pass: Filter instructions - for inst in &old_block.instructions { - // Skip Copy instructions that overwrite PHI dsts - if is_loop_header_with_phi { - if let MirInstruction::Copy { dst, src: _ } = inst { - let dst_remapped = remapper.get_value(*dst).unwrap_or(*dst); - if InstructionFilterBox::should_skip_copy_overwriting_phi(dst_remapped, &phi_dst_ids_for_block) { - log!( - verbose, - "[plan_rewrites] Skipping loop header Copy to PHI dst {:?}", - dst_remapped - ); - continue; - } - } - } - - // Skip function name Const String instructions - if let MirInstruction::Const { dst, value } = inst { - if InstructionFilterBox::should_skip_function_name_const(value) - && value_to_func_name.contains_key(dst) - { - log!(verbose, "[plan_rewrites] Skipping function name const: {:?}", inst); - continue; - } - - // Skip boundary input Const instructions - let boundary_inputs: Vec = boundary_input_set.iter().cloned().collect(); - if InstructionFilterBox::should_skip_boundary_input_const( - *dst, - &boundary_inputs, - is_loop_header_entry_block, - ) { - log!(verbose, "[plan_rewrites] Skipping boundary input const: {:?}", inst); - continue; - } - } - - // Detect tail calls - if let MirInstruction::Call { func, args, .. } = inst { - if let Some(callee_name) = value_to_func_name.get(func) { - if let Some(&target_block) = ctx.function_entry_map.get(callee_name) { - // This is a tail call - 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; - - log!( - verbose, - "[plan_rewrites] Detected tail call to '{}' (args={:?})", - callee_name, args - ); - continue; // Skip the Call instruction itself - } - } - } - - // Skip Copy instructions that overwrite header PHI dsts - if let MirInstruction::Copy { dst, src: _ } = inst { - 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 { - log!( - verbose, - "[plan_rewrites] Skipping Copy that overwrites header PHI dst {:?}", - remapped_dst - ); - continue; - } - } - - // Remap instruction - let remapped = remapper.remap_instruction(inst); - - // Remap block IDs in Branch/Phi - let remapped_with_blocks = match remapped { - MirInstruction::Branch { - condition, - then_bb, - else_bb, - then_edge_args, - else_edge_args, - } => { - 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 } => { - remap_phi_instruction(dst, &inputs, type_hint, &local_block_map) - } - other => other, - }; - - // TODO: Type propagation should be in apply stage, not plan stage - // For now, keep it here to match original behavior - // propagate_value_type_for_inst(builder, func, inst, &remapped_with_blocks); - - new_block.instructions.push(remapped_with_blocks); - } - - // Second pass: Insert parameter bindings for tail calls (if any) - if let Some((target_block, ref args)) = tail_call_target { - let target_func_name = resolve_target_func_name(&ctx.function_entry_map, target_block); - - // Check if target is continuation/recursive/loop entry - let is_target_continuation = target_func_name - .map(|name| continuation_candidates.contains(name)) - .unwrap_or(false); - - let is_recursive_call = target_func_name.map(|name| name == func_name).unwrap_or(false); - - // Phase 188.3: Define is_target_loop_entry early for latch incoming logic - let is_target_loop_entry = target_func_name - .map(|name| entry_func_name == Some(name)) - .unwrap_or(false); - - // Phase 287 P2: Calculate tail_call_kind early for latch incoming logic - // Only treat MAIN's entry block as entry-like (not loop_step's entry block) - let is_entry_like_block_for_latch = - is_joinir_main_entry_block(func_name, func, *old_block_id); - - let tail_call_kind = classify_tail_call( - is_entry_like_block_for_latch, - !loop_header_phi_info.carrier_phis.is_empty(), - boundary.is_some(), - is_target_continuation, - is_target_loop_entry, - ); - - if let Some(target_func_name) = target_func_name { - if let Some(target_params) = function_params.get(target_func_name) { - - log!( - verbose, - "[plan_rewrites] Tail call param binding: from='{}' to='{}' (recursive={}, loop_entry={}, continuation={})", - func_name, target_func_name, is_recursive_call, is_target_loop_entry, is_target_continuation - ); - - // Skip parameter binding in specific cases: - // 1. Loop entry point (header PHIs define carriers) - // 2. Recursive/entry call to loop header with PHIs (latch edge) - // 3. Continuation call (handled separately below) - // Phase 287 P1: Skip ONLY when target is loop header - // (not when source is entry func but target is non-entry like inner_step) - if is_loop_header_entry_block && is_target_loop_entry { - log!( - verbose, - "[plan_rewrites] Skip param bindings in header block (PHIs define carriers)" - ); - } else if (is_recursive_call || is_target_loop_entry) && is_loop_header_with_phi { - // Update remapper mappings for continuation 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!( - verbose, - "[plan_rewrites] Skip Copy bindings for {} call (remapper updated)", - if is_recursive_call { "recursive" } else { "entry" } - ); - } else if is_target_continuation { - // Continuation call: Copy args to original params - for (i, arg_val_remapped) in args.iter().enumerate() { - if i < target_params.len() { - let param_val_original = target_params[i]; - new_block.instructions.push(MirInstruction::Copy { - dst: param_val_original, - src: *arg_val_remapped, - }); - log!( - verbose, - "[plan_rewrites] Continuation param binding: {:?} = copy {:?}", - param_val_original, arg_val_remapped - ); - } - } - } else { - // Normal tail call: Insert Copy instructions - for (i, arg_val_remapped) in args.iter().enumerate() { - if i < target_params.len() { - let param_val_original = target_params[i]; - let param_remap_result = remapper.get_value(param_val_original); - let param_val_dst = param_remap_result.unwrap_or(param_val_original); - - // 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 { - log!( - verbose, - "[plan_rewrites] Skip param binding to PHI dst {:?}", - param_val_dst - ); - continue; - } - - new_block.instructions.push(MirInstruction::Copy { - dst: param_val_dst, - src: *arg_val_remapped, - }); - } - } - } - } - } - - // Record latch incoming for loop header PHI (SSOT) - // Phase 287 P2: BackEdge のみ latch 記録(LoopEntry main → loop_step を除外) - latch_incoming_recorder::record_if_backedge( - tail_call_kind, - boundary, - new_block_id, - args, - loop_header_phi_info, - ); - } - - // Synchronize spans - new_block.instruction_spans = sync_spans(&new_block.instructions, old_block); - - // Terminator conversion - if !found_tail_call { - if let Some(ref term) = old_block.terminator { - match term { - MirInstruction::Return { value } => { - // Check if we should keep Return or convert to Jump - if ReturnConverterBox::should_keep_return(is_continuation_candidate, is_skippable_continuation) { - // Non-skippable continuation: keep Return - let remapped_value = ReturnConverterBox::remap_return_value(*value, |v| remapper.remap_value(v)); - new_block.set_terminator(MirInstruction::Return { value: remapped_value }); - log!( - verbose, - "[plan_rewrites] Keeping Return for non-skippable continuation '{}' (value={:?})", - func_name, remapped_value - ); - } else { - // Convert Return to Jump to exit block - let mut exit_edge_args: Option = None; - if value.is_some() { - if let Some(b) = boundary { - // Use terminator edge-args from old block - if let Some(edge_args) = old_block.edge_args_from_terminator() { - if edge_args.layout != b.jump_args_layout { - let msg = format!( - "[plan_rewrites] 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 verbose { - log!(true, "[DEBUG] {}", msg); - } - } - - // Remap jump_args to HOST value space - let remapped_args: Vec = edge_args - .values - .iter() - .map(|&arg| remapper.remap_value(arg)) - .collect(); - - log!( - verbose, - "[plan_rewrites] Remapped exit jump_args: {:?}", - remapped_args - ); - - // Collect exit values using ExitArgsCollectorBox - 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 let Some(expr_result_val) = collection_result.expr_result_value { - result.phi_inputs.push((new_block_id, expr_result_val)); - log!( - verbose, - "[plan_rewrites] exit_phi_inputs: ({:?}, {:?})", - 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, - "[plan_rewrites] Collecting carrier '{}': from {:?} value {:?}", - carrier_name, block_id, value_id - ); - result.carrier_inputs - .entry(carrier_name) - .or_insert_with(Vec::new) - .push((block_id, value_id)); - } - } else { - // Fallback: Use header PHI dst - log!( - verbose, - "[plan_rewrites] 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) { - result.phi_inputs.push((new_block_id, phi_dst)); - log!( - verbose, - "[plan_rewrites] Using header PHI dst {:?} for exit (loop_var='{}')", - phi_dst, loop_var_name - ); - } - } - - // Phase 286C-5 Step 1: Use CarrierInputsCollector to eliminate duplication - let collector = CarrierInputsCollector::new(b, loop_header_phi_info); - let carrier_inputs = collector.collect(new_block_id); - for (carrier_name, block_id, value_id) in carrier_inputs { - result.carrier_inputs - .entry(carrier_name.clone()) - .or_insert_with(Vec::new) - .push((block_id, value_id)); - log!( - verbose, - "[plan_rewrites] Carrier '{}': from {:?} value {:?}", - carrier_name, block_id, value_id - ); - } - } - } - } - - // Set Jump terminator with edge args - if let Some(edge_args) = exit_edge_args { - new_block.set_jump_with_edge_args(ctx.exit_block_id, Some(edge_args)); - } else { - new_block.set_terminator(MirInstruction::Jump { - target: ctx.exit_block_id, - edge_args: None, - }); - } - } - } - MirInstruction::Jump { target, edge_args } => { - let remapped_term = remap_jump( - &remapper, - *target, - edge_args, - &ctx.skipped_entry_redirects, - &local_block_map, - ); - new_block.set_terminator(remapped_term); - } - MirInstruction::Branch { - condition, - then_bb, - else_bb, - then_edge_args, - else_edge_args, - } => { - let remapped_term = remap_branch( - &remapper, - *condition, - *then_bb, - *else_bb, - then_edge_args, - else_edge_args, - &ctx.skipped_entry_redirects, - &local_block_map, - ); - new_block.set_terminator(remapped_term); - } - _ => { - let remapped = remapper.remap_instruction(term); - new_block.set_terminator(remapped); - } - } - } - } else if let Some((target_block, _args)) = tail_call_target { - // Tail call: Set Jump terminator - // Classify tail call and determine actual target - let target_func_name = resolve_target_func_name(&ctx.function_entry_map, target_block); - - let is_target_continuation = target_func_name - .map(|name| continuation_candidates.contains(name)) - .unwrap_or(false); - - // Phase 287 P2: Compute is_target_loop_entry for classify_tail_call - let is_target_loop_entry = target_func_name - .map(|name| entry_func_name == Some(name)) - .unwrap_or(false); - - // Phase 287 P2: main の entry block からの呼び出しを LoopEntry 扱いにする - // Only treat MAIN's entry block as entry-like (not loop_step's entry block) - let is_entry_like_block = is_joinir_main_entry_block(func_name, func, *old_block_id); - - let tail_call_kind = classify_tail_call( - is_entry_like_block, - !loop_header_phi_info.carrier_phis.is_empty(), - boundary.is_some(), - is_target_continuation, - is_target_loop_entry, - ); - - let actual_target = match tail_call_kind { - TailCallKind::BackEdge => { - log!( - verbose, - "[plan_rewrites] BackEdge: redirecting from {:?} to header {:?}", - target_block, loop_header_phi_info.header_block - ); - loop_header_phi_info.header_block - } - TailCallKind::LoopEntry => { - log!( - verbose, - "[plan_rewrites] LoopEntry: using direct target {:?}", - target_block - ); - target_block - } - TailCallKind::ExitJump => { - // Check if target is skippable continuation - let is_target_skippable = resolve_target_func_name(&ctx.function_entry_map, target_block) - .map(|name| skippable_continuation_func_names.contains(name)) - .unwrap_or(false); - - if is_target_skippable { - log!( - verbose, - "[plan_rewrites] ExitJump (skippable): redirecting from {:?} to exit_block_id {:?}", - target_block, ctx.exit_block_id - ); - - // Phase 286C-5 Step 1: Use CarrierInputsCollector to eliminate duplication - // This replaces Phase 286C-4.1 inline code - if let Some(b) = boundary { - let collector = CarrierInputsCollector::new(b, loop_header_phi_info); - let carrier_inputs = collector.collect(new_block_id); - for (carrier_name, block_id, value_id) in carrier_inputs { - result.carrier_inputs - .entry(carrier_name.clone()) - .or_insert_with(Vec::new) - .push((block_id, value_id)); - log!( - verbose, - "[plan_rewrites] ExitJump carrier '{}': from {:?} value {:?}", - carrier_name, block_id, value_id - ); - } - } - - ctx.exit_block_id - } else { - log!( - verbose, - "[plan_rewrites] ExitJump (non-skippable): to target_block {:?}", - target_block - ); - target_block - } - } - }; - - new_block.set_terminator(MirInstruction::Jump { - target: actual_target, - edge_args: None, - }); - } - - // Add block to result - result.new_blocks.push(new_block); - } - } - - if debug { - log!( - true, - "[plan_rewrites] Generated {} new blocks", - result.new_blocks.len() - ); - } - - Ok(result) -} diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/entry_resolver.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/entry_resolver.rs new file mode 100644 index 00000000..49b5dab3 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/entry_resolver.rs @@ -0,0 +1,59 @@ +//! Phase 287 P4: Entry function resolution logic +//! +//! Extracted from plan.rs lines 122-155 +//! +//! Responsibilities: +//! - Resolve entry function name (loop header) from boundary or heuristic +//! - Reverse lookup function name by entry block +//! - Check if block is MAIN's entry block + +use crate::mir::{BasicBlockId, MirFunction}; +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use crate::mir::join_ir::lowering::canonical_names; +use std::collections::{BTreeMap, BTreeSet}; + +/// Determine entry function (loop header) +/// +/// Phase 287 P2: Prefer boundary SSOT (loop_header_func_name) over heuristic. +/// +/// Returns Option (not &str) to avoid lifetime issues. +pub(in crate::mir::builder::control_flow::joinir::merge) fn resolve_entry_func_name( + functions_merge: &Vec<(&String, &MirFunction)>, + boundary: Option<&JoinInlineBoundary>, + continuation_candidates: &BTreeSet, +) -> Option { + boundary + .and_then(|b| b.loop_header_func_name.clone()) + .or_else(|| { + functions_merge + .iter() + .find(|(name, _)| { + let name_str = name.as_str(); + let is_continuation = continuation_candidates.contains(*name); + let is_main = name_str == canonical_names::MAIN; + !is_continuation && !is_main + }) + .map(|(name, _)| (*name).clone()) + }) +} + +/// Reverse lookup: find function name by target block +/// +/// Searches function_entry_map to find which function has the given block as entry. +pub(in crate::mir::builder::control_flow::joinir::merge) fn resolve_target_func_name<'a>( + function_entry_map: &'a BTreeMap, + target_block: BasicBlockId, +) -> Option<&'a str> { + function_entry_map + .iter() + .find_map(|(fname, &entry_block)| (entry_block == target_block).then(|| fname.as_str())) +} + +/// Check if block is MAIN's entry block (pure lexical check) +pub(in crate::mir::builder::control_flow::joinir::merge) fn is_joinir_main_entry_block( + func_name: &str, + func: &MirFunction, + old_block_id: BasicBlockId, +) -> bool { + func_name == canonical_names::MAIN && old_block_id == func.entry_block +} diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/instruction_rewrite.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/instruction_rewrite.rs new file mode 100644 index 00000000..8817d88f --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/instruction_rewrite.rs @@ -0,0 +1,168 @@ +//! Phase 287 P4: Instruction filtering and remapping logic +//! +//! Extracted from plan.rs lines 228-342 +//! +//! Responsibilities: +//! - Filter instructions (skip PHI overwrites, function name consts, boundary input consts) +//! - Detect tail calls (intra-module Call instructions) +//! - Remap instruction ValueIds and BlockIds +//! - Remap Branch/Phi block references + +// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/) +use super::super::super::{ + rewrite_context::RewriteContext, + instruction_filter_box::InstructionFilterBox, +}; + +// Merge level (3 super:: up from plan/ to stages/, then 1 more to rewriter/, then 1 more to merge/) +use super::super::super::super::{ + phi_block_remapper::remap_phi_instruction, + block_remapper::remap_block_id, + loop_header_phi_info::LoopHeaderPhiInfo, + trace, +}; + +use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId}; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use std::collections::BTreeMap; + +/// Process block instructions: filter, remap, detect tail calls +/// +/// Returns (filtered_instructions, tail_call_target) +pub(in crate::mir::builder::control_flow::joinir::merge) fn process_block_instructions( + old_block: &BasicBlock, + remapper: &mut JoinIrIdRemapper, + local_block_map: &BTreeMap, + loop_header_phi_info: &LoopHeaderPhiInfo, + is_loop_header_with_phi: bool, + is_loop_header_entry_block: bool, + boundary_input_set: &std::collections::HashSet, + phi_dst_ids_for_block: &std::collections::HashSet, + value_to_func_name: &BTreeMap, + ctx: &RewriteContext, + verbose: bool, +) -> (Vec, Option<(BasicBlockId, Vec)>) { + let trace_obj = trace::trace(); + macro_rules! log { + ($enabled:expr, $($arg:tt)*) => { + trace_obj.stderr_if(&format!($($arg)*), $enabled); + }; + } + + let mut instructions = Vec::new(); + let mut tail_call_target: Option<(BasicBlockId, Vec)> = None; + + // First pass: Filter instructions + for inst in &old_block.instructions { + // Skip Copy instructions that overwrite PHI dsts + if is_loop_header_with_phi { + if let MirInstruction::Copy { dst, src: _ } = inst { + let dst_remapped = remapper.get_value(*dst).unwrap_or(*dst); + if InstructionFilterBox::should_skip_copy_overwriting_phi(dst_remapped, phi_dst_ids_for_block) { + log!( + verbose, + "[plan_rewrites] Skipping loop header Copy to PHI dst {:?}", + dst_remapped + ); + continue; + } + } + } + + // Skip function name Const String instructions + if let MirInstruction::Const { dst, value } = inst { + if InstructionFilterBox::should_skip_function_name_const(value) + && value_to_func_name.contains_key(dst) + { + log!(verbose, "[plan_rewrites] Skipping function name const: {:?}", inst); + continue; + } + + // Skip boundary input Const instructions + let boundary_inputs: Vec = boundary_input_set.iter().cloned().collect(); + if InstructionFilterBox::should_skip_boundary_input_const( + *dst, + &boundary_inputs, + is_loop_header_entry_block, + ) { + log!(verbose, "[plan_rewrites] Skipping boundary input const: {:?}", inst); + continue; + } + } + + // Detect tail calls + if let MirInstruction::Call { func, args, .. } = inst { + if let Some(callee_name) = value_to_func_name.get(func) { + if let Some(&target_block) = ctx.function_entry_map.get(callee_name) { + // This is a tail call + let remapped_args: Vec = args + .iter() + .map(|&v| remapper.get_value(v).unwrap_or(v)) + .collect(); + tail_call_target = Some((target_block, remapped_args)); + + log!( + verbose, + "[plan_rewrites] Detected tail call to '{}' (args={:?})", + callee_name, args + ); + continue; // Skip the Call instruction itself + } + } + } + + // Skip Copy instructions that overwrite header PHI dsts + if let MirInstruction::Copy { dst, src: _ } = inst { + 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 { + log!( + verbose, + "[plan_rewrites] Skipping Copy that overwrites header PHI dst {:?}", + remapped_dst + ); + continue; + } + } + + // Remap instruction + let remapped = remapper.remap_instruction(inst); + + // Remap block IDs in Branch/Phi + let remapped_with_blocks = match remapped { + MirInstruction::Branch { + condition, + then_bb, + else_bb, + then_edge_args, + else_edge_args, + } => { + 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 } => { + remap_phi_instruction(dst, &inputs, type_hint, local_block_map) + } + other => other, + }; + + // TODO: Type propagation should be in apply stage, not plan stage + // For now, keep it here to match original behavior + // propagate_value_type_for_inst(builder, func, inst, &remapped_with_blocks); + + instructions.push(remapped_with_blocks); + } + + (instructions, tail_call_target) +} diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/mod.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/mod.rs new file mode 100644 index 00000000..b463694c --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/mod.rs @@ -0,0 +1,273 @@ +//! Stage 2: Plan - Transform plan into concrete rewritten blocks +//! +//! Phase 287 P3: Extracted from instruction_rewriter.rs::plan_rewrites() +//! +//! Generates new BasicBlocks based on the scan plan: +//! - Processes each function and block +//! - Filters instructions using InstructionFilterBox +//! - Converts terminators using ReturnConverterBox +//! - Builds parameter bindings using ParameterBindingBox +//! - Prepares exit PHI inputs and carrier inputs +//! +//! Updates RewriteContext but does NOT touch MirBuilder. + +// Rewriter siblings (2 levels up: stages/ → rewriter/) +use super::super::{ + plan_helpers::{build_local_block_map, sync_spans}, + rewrite_context::RewriteContext, + scan_box::RewritePlan, + plan_box::RewrittenBlocks, + helpers::is_skippable_continuation, +}; + +// Merge level (3 levels up: stages/ → rewriter/ → merge/) +use super::super::super::{ + loop_header_phi_info::LoopHeaderPhiInfo, + trace, +}; + +// Crate-level imports +use crate::mir::{BasicBlock, MirModule, ValueId}; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use std::collections::{BTreeMap, BTreeSet, HashSet}; + +// Phase 287 P4: Import extracted modules +mod entry_resolver; +mod instruction_rewrite; +mod tail_call_rewrite; +mod terminator_rewrite; + +/// Stage 2: Plan - Transform plan into concrete rewritten blocks +/// +/// Generates new BasicBlocks based on the scan plan: +/// - Processes each function and block +/// - Filters instructions using InstructionFilterBox +/// - Converts terminators using ReturnConverterBox +/// - Builds parameter bindings using ParameterBindingBox +/// - Prepares exit PHI inputs and carrier inputs +/// +/// Updates RewriteContext but does NOT touch MirBuilder. +/// +/// # Phase 286C-4 Step 2 +/// +/// This function extracts ~550 lines from merge_and_rewrite(): +/// - Function/block initialization (lines 399-468) +/// - First pass: instruction filtering (lines 581-760) +/// - Terminator conversion (lines 1163-1435) +/// - Span synchronization (lines 1436-1452) +pub(in crate::mir::builder::control_flow::joinir::merge) fn plan_rewrites( + _plan: RewritePlan, + mir_module: &MirModule, + remapper: &mut JoinIrIdRemapper, + function_params: &BTreeMap>, + boundary: Option<&JoinInlineBoundary>, + loop_header_phi_info: &mut LoopHeaderPhiInfo, + ctx: &mut RewriteContext, + value_to_func_name: &BTreeMap, + debug: bool, +) -> Result { + let trace = trace::trace(); + // Only verbose if explicitly requested via debug flag (not env var - causes test failures) + let verbose = debug; + macro_rules! log { + ($enabled:expr, $($arg:tt)*) => { + trace.stderr_if(&format!($($arg)*), $enabled); + }; + } + + let mut result = RewrittenBlocks { + new_blocks: Vec::new(), + block_replacements: BTreeMap::new(), + phi_inputs: Vec::new(), + carrier_inputs: BTreeMap::new(), + }; + + // Phase 256 P1.7: Build continuation candidate set + let continuation_candidates: BTreeSet = boundary + .map(|b| b.continuation_func_ids.clone()) + .unwrap_or_default(); + + let skippable_continuation_func_names: BTreeSet = mir_module + .functions + .iter() + .filter_map(|(func_name, func)| { + if continuation_candidates.contains(func_name) && is_skippable_continuation(func) { + Some(func_name.clone()) + } else { + None + } + }) + .collect(); + + // Build boundary input set for filtering + let boundary_input_set: HashSet = boundary + .map(|b| b.join_inputs.iter().copied().collect()) + .unwrap_or_default(); + + // Sort functions for deterministic iteration + let mut functions_merge: Vec<_> = mir_module.functions.iter().collect(); + functions_merge.sort_by_key(|(name, _)| name.as_str()); + + // Phase 287 P4: Resolve entry function using extracted module + let entry_func_name_str = entry_resolver::resolve_entry_func_name( + &functions_merge, + boundary, + &continuation_candidates, + ); + let entry_func_name = entry_func_name_str.as_deref(); + + // Process each function + for (func_name, func) in functions_merge { + let is_continuation_candidate = continuation_candidates.contains(func_name); + let is_skippable_continuation = skippable_continuation_func_names.contains(func_name); + + if debug { + log!( + true, + "[plan_rewrites] Processing function '{}' with {} blocks (continuation_candidate={}, skippable={})", + func_name, + func.blocks.len(), + is_continuation_candidate, + is_skippable_continuation + ); + } + + // Skip structurally skippable continuation functions + if is_skippable_continuation { + if debug { + log!( + true, + "[plan_rewrites] Skipping skippable continuation function '{}'", + func_name + ); + } + continue; + } + + // Build local block map for this function + let local_block_map = build_local_block_map(func_name, func, remapper)?; + + // Sort blocks for deterministic iteration + let mut blocks_merge: Vec<_> = func.blocks.iter().collect(); + blocks_merge.sort_by_key(|(id, _)| id.0); + + // Determine if this is the loop header entry block (loop_step entry). + let is_loop_header_entry_block = entry_func_name == Some(func_name.as_str()) + && blocks_merge.first().map(|(id, _)| **id) == Some(func.entry_block); + + // Check if loop header has PHIs + let is_loop_header_with_phi = + is_loop_header_entry_block && !loop_header_phi_info.carrier_phis.is_empty(); + + // Collect PHI dst IDs for this block (if loop header) + let phi_dst_ids_for_block: HashSet = + if is_loop_header_with_phi { + loop_header_phi_info + .carrier_phis + .values() + .map(|entry| entry.phi_dst) + .collect() + } else { + HashSet::new() + }; + + // Process each block in the function + for (old_block_id, old_block) in blocks_merge { + 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))?; + + if debug { + log!( + true, + "[plan_rewrites] Block mapping: func='{}' old={:?} → new={:?} (inst_count={})", + func_name, old_block_id, new_block_id, old_block.instructions.len() + ); + } + + // Phase 287 P4: Initialize new block and process instructions + let mut new_block = BasicBlock::new(new_block_id); + + // PHASE 2: Instruction rewriting (extracted) + let (filtered_insts, tail_target) = instruction_rewrite::process_block_instructions( + old_block, + remapper, + &local_block_map, + loop_header_phi_info, + is_loop_header_with_phi, + is_loop_header_entry_block, + &boundary_input_set, + &phi_dst_ids_for_block, + value_to_func_name, + ctx, + verbose, + ); + new_block.instructions = filtered_insts; + let found_tail_call = tail_target.is_some(); + let tail_call_target = tail_target; + + // PHASE 3: Tail call parameter binding (extracted) + if let Some((target_block, ref args)) = tail_call_target { + tail_call_rewrite::process_tail_call_params( + &mut new_block, + (target_block, args), + func_name, + func, + *old_block_id, + function_params, + entry_func_name, + &continuation_candidates, + is_loop_header_entry_block, + is_loop_header_with_phi, + boundary, + loop_header_phi_info, + remapper, + ctx, + new_block_id, + verbose, + )?; + } + + // Span synchronization + new_block.instruction_spans = sync_spans(&new_block.instructions, old_block); + + // PHASE 4: Terminator rewriting (extracted) + terminator_rewrite::process_block_terminator( + &mut new_block, + old_block, + found_tail_call, + tail_call_target.as_ref().map(|(b, v)| (*b, v.as_slice())), + func_name, + func, + *old_block_id, + new_block_id, + remapper, + &local_block_map, + entry_func_name, + &continuation_candidates, + &skippable_continuation_func_names, + is_continuation_candidate, + is_skippable_continuation, + boundary, + loop_header_phi_info, + ctx, + &mut result, + verbose, + )?; + + // Add block to result + result.new_blocks.push(new_block); + } + } + + if debug { + log!( + true, + "[plan_rewrites] Generated {} new blocks", + result.new_blocks.len() + ); + } + + Ok(result) +} diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/tail_call_rewrite.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/tail_call_rewrite.rs new file mode 100644 index 00000000..82628db8 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/tail_call_rewrite.rs @@ -0,0 +1,191 @@ +//! Phase 287 P4: Tail call parameter binding logic +//! +//! Extracted from plan.rs lines 344-464 +//! +//! Responsibilities: +//! - Classify tail calls (continuation, recursive, loop entry, normal) +//! - Insert parameter binding Copy instructions +//! - Record latch incoming for loop header PHI + +// Import helpers from entry_resolver +use super::entry_resolver::{resolve_target_func_name, is_joinir_main_entry_block}; + +// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/) +use super::super::super::{ + rewrite_context::RewriteContext, + latch_incoming_recorder, +}; + +// Merge level (3 super:: up from plan/ to stages/, then 1 more to rewriter/, then 1 more to merge/) +use super::super::super::super::{ + tail_call_classifier::classify_tail_call, + loop_header_phi_info::LoopHeaderPhiInfo, + trace, +}; + +use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, MirFunction, ValueId}; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use std::collections::{BTreeMap, BTreeSet}; + +/// Process tail call parameter bindings +/// +/// Inserts Copy instructions to bind call arguments to target function parameters, +/// with special handling for: +/// - Loop entry blocks (skip bindings, PHIs define carriers) +/// - Recursive/loop entry calls with PHIs (update remapper only) +/// - Continuation calls (copy to original params) +/// - Normal tail calls (copy with header PHI dst checks) +/// +/// Also records latch incoming for loop header PHI updates. +#[allow(clippy::too_many_arguments)] +pub(in crate::mir::builder::control_flow::joinir::merge) fn process_tail_call_params( + new_block: &mut BasicBlock, + tail_call_target: (BasicBlockId, &[ValueId]), + func_name: &str, + func: &MirFunction, + old_block_id: BasicBlockId, + function_params: &BTreeMap>, + entry_func_name: Option<&str>, + continuation_candidates: &BTreeSet, + is_loop_header_entry_block: bool, + is_loop_header_with_phi: bool, + boundary: Option<&JoinInlineBoundary>, + loop_header_phi_info: &mut LoopHeaderPhiInfo, + remapper: &mut JoinIrIdRemapper, + ctx: &RewriteContext, + new_block_id: BasicBlockId, + verbose: bool, +) -> Result<(), String> { + let trace_obj = trace::trace(); + macro_rules! log { + ($enabled:expr, $($arg:tt)*) => { + trace_obj.stderr_if(&format!($($arg)*), $enabled); + }; + } + + let (target_block, args) = tail_call_target; + + let target_func_name = resolve_target_func_name(&ctx.function_entry_map, target_block); + + // Check if target is continuation/recursive/loop entry + let is_target_continuation = target_func_name + .map(|name| continuation_candidates.contains(name)) + .unwrap_or(false); + + let is_recursive_call = target_func_name.map(|name| name == func_name).unwrap_or(false); + + // Phase 188.3: Define is_target_loop_entry early for latch incoming logic + let is_target_loop_entry = target_func_name + .map(|name| entry_func_name == Some(name)) + .unwrap_or(false); + + // Phase 287 P2: Calculate tail_call_kind early for latch incoming logic + // Only treat MAIN's entry block as entry-like (not loop_step's entry block) + let is_entry_like_block_for_latch = + is_joinir_main_entry_block(func_name, func, old_block_id); + + // CRITICAL: Argument order must match merge level classify_tail_call() + let tail_call_kind = classify_tail_call( + is_entry_like_block_for_latch, + !loop_header_phi_info.carrier_phis.is_empty(), + boundary.is_some(), + is_target_continuation, + is_target_loop_entry, + ); + + if let Some(target_func_name) = target_func_name { + if let Some(target_params) = function_params.get(target_func_name) { + + log!( + verbose, + "[plan_rewrites] Tail call param binding: from='{}' to='{}' (recursive={}, loop_entry={}, continuation={})", + func_name, target_func_name, is_recursive_call, is_target_loop_entry, is_target_continuation + ); + + // Skip parameter binding in specific cases: + // 1. Loop entry point (header PHIs define carriers) + // 2. Recursive/entry call to loop header with PHIs (latch edge) + // 3. Continuation call (handled separately below) + // Phase 287 P1: Skip ONLY when target is loop header + // (not when source is entry func but target is non-entry like inner_step) + if is_loop_header_entry_block && is_target_loop_entry { + log!( + verbose, + "[plan_rewrites] Skip param bindings in header block (PHIs define carriers)" + ); + } else if (is_recursive_call || is_target_loop_entry) && is_loop_header_with_phi { + // Update remapper mappings for continuation 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!( + verbose, + "[plan_rewrites] Skip Copy bindings for {} call (remapper updated)", + if is_recursive_call { "recursive" } else { "entry" } + ); + } else if is_target_continuation { + // Continuation call: Copy args to original params + for (i, arg_val_remapped) in args.iter().enumerate() { + if i < target_params.len() { + let param_val_original = target_params[i]; + new_block.instructions.push(MirInstruction::Copy { + dst: param_val_original, + src: *arg_val_remapped, + }); + log!( + verbose, + "[plan_rewrites] Continuation param binding: {:?} = copy {:?}", + param_val_original, arg_val_remapped + ); + } + } + } else { + // Normal tail call: Insert Copy instructions + for (i, arg_val_remapped) in args.iter().enumerate() { + if i < target_params.len() { + let param_val_original = target_params[i]; + let param_remap_result = remapper.get_value(param_val_original); + let param_val_dst = param_remap_result.unwrap_or(param_val_original); + + // 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 { + log!( + verbose, + "[plan_rewrites] Skip param binding to PHI dst {:?}", + param_val_dst + ); + continue; + } + + new_block.instructions.push(MirInstruction::Copy { + dst: param_val_dst, + src: *arg_val_remapped, + }); + } + } + } + } + } + + // Record latch incoming for loop header PHI (SSOT) + // Phase 287 P2: BackEdge のみ latch 記録(LoopEntry main → loop_step を除外) + // CRITICAL: Do not move this call - exact location matters + latch_incoming_recorder::record_if_backedge( + tail_call_kind, + boundary, + new_block_id, + args, + loop_header_phi_info, + ); + + Ok(()) +} diff --git a/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/terminator_rewrite.rs b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/terminator_rewrite.rs new file mode 100644 index 00000000..364151af --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/rewriter/stages/plan/terminator_rewrite.rs @@ -0,0 +1,341 @@ +//! Phase 287 P4: Terminator conversion and routing logic +//! +//! Extracted from plan.rs lines 469-727 +//! +//! Responsibilities: +//! - Convert Return to Jump (with exit args collection) +//! - Remap Jump/Branch terminators +//! - Route tail calls (BackEdge → header, LoopEntry → target, ExitJump → exit/target) +//! - Collect carrier inputs for exit jumps + +// Import helpers from entry_resolver +use super::entry_resolver::{resolve_target_func_name, is_joinir_main_entry_block}; + +// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/) +use super::super::super::{ + rewrite_context::RewriteContext, + plan_box::RewrittenBlocks, + return_converter_box::ReturnConverterBox, + carrier_inputs_collector::CarrierInputsCollector, + terminator::{remap_branch, remap_jump}, +}; + +// Merge level (3 super:: up from plan/ to stages/, then 1 more to rewriter/, then 1 more to merge/) +use super::super::super::super::{ + tail_call_classifier::{classify_tail_call, TailCallKind}, + exit_args_collector::ExitArgsCollectorBox, + loop_header_phi_info::LoopHeaderPhiInfo, + trace, +}; + +use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, MirFunction, ValueId}; +use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; +use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; +use std::collections::{BTreeMap, BTreeSet}; + +/// Process block terminator: convert Return, remap Jump/Branch, or insert tail call Jump +/// +/// Handles: +/// - Return → Jump conversion (with exit args collection) +/// - Jump/Branch remapping (block ID updates) +/// - Tail call routing: +/// - BackEdge → header block +/// - LoopEntry → target block +/// - ExitJump → exit_block_id (if skippable) or target_block +/// +/// CRITICAL: CarrierInputsCollector calls at lines 574, 696 - DO NOT MOVE +#[allow(clippy::too_many_arguments)] +pub(in crate::mir::builder::control_flow::joinir::merge) fn process_block_terminator( + new_block: &mut BasicBlock, + old_block: &BasicBlock, + found_tail_call: bool, + tail_call_target: Option<(BasicBlockId, &[ValueId])>, + func_name: &str, + func: &MirFunction, + old_block_id: BasicBlockId, + new_block_id: BasicBlockId, + remapper: &JoinIrIdRemapper, + local_block_map: &BTreeMap, + entry_func_name: Option<&str>, + continuation_candidates: &BTreeSet, + skippable_continuation_func_names: &BTreeSet, + is_continuation_candidate: bool, + is_skippable_continuation: bool, + boundary: Option<&JoinInlineBoundary>, + loop_header_phi_info: &LoopHeaderPhiInfo, + ctx: &RewriteContext, + result: &mut RewrittenBlocks, + verbose: bool, +) -> Result<(), String> { + let trace_obj = trace::trace(); + macro_rules! log { + ($enabled:expr, $($arg:tt)*) => { + trace_obj.stderr_if(&format!($($arg)*), $enabled); + }; + } + + // Terminator conversion + if !found_tail_call { + if let Some(ref term) = old_block.terminator { + match term { + MirInstruction::Return { value } => { + // Check if we should keep Return or convert to Jump + if ReturnConverterBox::should_keep_return(is_continuation_candidate, is_skippable_continuation) { + // Non-skippable continuation: keep Return + let remapped_value = ReturnConverterBox::remap_return_value(*value, |v| remapper.remap_value(v)); + new_block.set_terminator(MirInstruction::Return { value: remapped_value }); + log!( + verbose, + "[plan_rewrites] Keeping Return for non-skippable continuation '{}' (value={:?})", + func_name, remapped_value + ); + } else { + // Convert Return to Jump to exit block + let mut exit_edge_args: Option = None; + if value.is_some() { + if let Some(b) = boundary { + // Use terminator edge-args from old block + if let Some(edge_args) = old_block.edge_args_from_terminator() { + if edge_args.layout != b.jump_args_layout { + let msg = format!( + "[plan_rewrites] 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 verbose { + log!(true, "[DEBUG] {}", msg); + } + } + + // Remap jump_args to HOST value space + let remapped_args: Vec = edge_args + .values + .iter() + .map(|&arg| remapper.remap_value(arg)) + .collect(); + + log!( + verbose, + "[plan_rewrites] Remapped exit jump_args: {:?}", + remapped_args + ); + + // Collect exit values using ExitArgsCollectorBox + 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 let Some(expr_result_val) = collection_result.expr_result_value { + result.phi_inputs.push((new_block_id, expr_result_val)); + log!( + verbose, + "[plan_rewrites] exit_phi_inputs: ({:?}, {:?})", + 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, + "[plan_rewrites] Collecting carrier '{}': from {:?} value {:?}", + carrier_name, block_id, value_id + ); + result.carrier_inputs + .entry(carrier_name) + .or_insert_with(Vec::new) + .push((block_id, value_id)); + } + } else { + // Fallback: Use header PHI dst + log!( + verbose, + "[plan_rewrites] 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) { + result.phi_inputs.push((new_block_id, phi_dst)); + log!( + verbose, + "[plan_rewrites] Using header PHI dst {:?} for exit (loop_var='{}')", + phi_dst, loop_var_name + ); + } + } + + // Phase 286C-5 Step 1: Use CarrierInputsCollector to eliminate duplication + // CRITICAL: Do not move this call - exact location matters (line 574 equivalent) + let collector = CarrierInputsCollector::new(b, loop_header_phi_info); + let carrier_inputs = collector.collect(new_block_id); + for (carrier_name, block_id, value_id) in carrier_inputs { + result.carrier_inputs + .entry(carrier_name.clone()) + .or_insert_with(Vec::new) + .push((block_id, value_id)); + log!( + verbose, + "[plan_rewrites] Carrier '{}': from {:?} value {:?}", + carrier_name, block_id, value_id + ); + } + } + } + } + + // Set Jump terminator with edge args + if let Some(edge_args) = exit_edge_args { + new_block.set_jump_with_edge_args(ctx.exit_block_id, Some(edge_args)); + } else { + new_block.set_terminator(MirInstruction::Jump { + target: ctx.exit_block_id, + edge_args: None, + }); + } + } + } + MirInstruction::Jump { target, edge_args } => { + let remapped_term = remap_jump( + remapper, + *target, + edge_args, + &ctx.skipped_entry_redirects, + local_block_map, + ); + new_block.set_terminator(remapped_term); + } + MirInstruction::Branch { + condition, + then_bb, + else_bb, + then_edge_args, + else_edge_args, + } => { + let remapped_term = remap_branch( + remapper, + *condition, + *then_bb, + *else_bb, + then_edge_args, + else_edge_args, + &ctx.skipped_entry_redirects, + local_block_map, + ); + new_block.set_terminator(remapped_term); + } + _ => { + let remapped = remapper.remap_instruction(term); + new_block.set_terminator(remapped); + } + } + } + } else if let Some((target_block, _args)) = tail_call_target { + // Tail call: Set Jump terminator + // Classify tail call and determine actual target + let target_func_name = resolve_target_func_name(&ctx.function_entry_map, target_block); + + let is_target_continuation = target_func_name + .map(|name| continuation_candidates.contains(name)) + .unwrap_or(false); + + // Phase 287 P2: Compute is_target_loop_entry for classify_tail_call + let is_target_loop_entry = target_func_name + .map(|name| entry_func_name == Some(name)) + .unwrap_or(false); + + // Phase 287 P2: main の entry block からの呼び出しを LoopEntry 扱いにする + // Only treat MAIN's entry block as entry-like (not loop_step's entry block) + let is_entry_like_block = is_joinir_main_entry_block(func_name, func, old_block_id); + + // CRITICAL: Argument order must match merge level classify_tail_call() + let tail_call_kind = classify_tail_call( + is_entry_like_block, + !loop_header_phi_info.carrier_phis.is_empty(), + boundary.is_some(), + is_target_continuation, + is_target_loop_entry, + ); + + let actual_target = match tail_call_kind { + TailCallKind::BackEdge => { + log!( + verbose, + "[plan_rewrites] BackEdge: redirecting from {:?} to header {:?}", + target_block, loop_header_phi_info.header_block + ); + loop_header_phi_info.header_block + } + TailCallKind::LoopEntry => { + log!( + verbose, + "[plan_rewrites] LoopEntry: using direct target {:?}", + target_block + ); + target_block + } + TailCallKind::ExitJump => { + // Check if target is skippable continuation + let is_target_skippable = resolve_target_func_name(&ctx.function_entry_map, target_block) + .map(|name| skippable_continuation_func_names.contains(name)) + .unwrap_or(false); + + if is_target_skippable { + log!( + verbose, + "[plan_rewrites] ExitJump (skippable): redirecting from {:?} to exit_block_id {:?}", + target_block, ctx.exit_block_id + ); + + // Phase 286C-5 Step 1: Use CarrierInputsCollector to eliminate duplication + // This replaces Phase 286C-4.1 inline code + // CRITICAL: Do not move this call - exact location matters (line 696 equivalent) + if let Some(b) = boundary { + let collector = CarrierInputsCollector::new(b, loop_header_phi_info); + let carrier_inputs = collector.collect(new_block_id); + for (carrier_name, block_id, value_id) in carrier_inputs { + result.carrier_inputs + .entry(carrier_name.clone()) + .or_insert_with(Vec::new) + .push((block_id, value_id)); + log!( + verbose, + "[plan_rewrites] ExitJump carrier '{}': from {:?} value {:?}", + carrier_name, block_id, value_id + ); + } + } + + ctx.exit_block_id + } else { + log!( + verbose, + "[plan_rewrites] ExitJump (non-skippable): to target_block {:?}", + target_block + ); + target_block + } + } + }; + + new_block.set_terminator(MirInstruction::Jump { + target: actual_target, + edge_args: None, + }); + } + + Ok(()) +}