refactor(joinir): Phase 287 P4 - Modularize plan stage (facade)

This commit is contained in:
2025-12-27 12:48:33 +09:00
parent 2f7061e2e5
commit 3c52ba954e
8 changed files with 1034 additions and 745 deletions

View File

@ -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

View File

@ -1,4 +1,4 @@
use crate::mir::{BasicBlockId, MirFunction, MirInstruction};
use crate::mir::{MirFunction, MirInstruction};
use super::super::merge_result::MergeContracts;

View File

@ -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<String, Vec<ValueId>>,
boundary: Option<&JoinInlineBoundary>,
loop_header_phi_info: &mut LoopHeaderPhiInfo,
ctx: &mut RewriteContext,
value_to_func_name: &BTreeMap<ValueId, String>,
debug: bool,
) -> Result<RewrittenBlocks, String> {
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<String> = boundary
.map(|b| b.continuation_func_ids.clone())
.unwrap_or_default();
let skippable_continuation_func_names: BTreeSet<String> = 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<ValueId> = 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<String, BasicBlockId>,
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<ValueId> =
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<ValueId>)> = 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<ValueId> = 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<ValueId> = 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<crate::mir::EdgeArgs> = 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<ValueId> = 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)
}

View File

@ -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<String> (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<String>,
) -> Option<String> {
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<String, BasicBlockId>,
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
}

View File

@ -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<BasicBlockId, BasicBlockId>,
loop_header_phi_info: &LoopHeaderPhiInfo,
is_loop_header_with_phi: bool,
is_loop_header_entry_block: bool,
boundary_input_set: &std::collections::HashSet<ValueId>,
phi_dst_ids_for_block: &std::collections::HashSet<ValueId>,
value_to_func_name: &BTreeMap<ValueId, String>,
ctx: &RewriteContext,
verbose: bool,
) -> (Vec<MirInstruction>, Option<(BasicBlockId, Vec<ValueId>)>) {
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<ValueId>)> = 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<ValueId> = 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<ValueId> = 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)
}

View File

@ -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<String, Vec<ValueId>>,
boundary: Option<&JoinInlineBoundary>,
loop_header_phi_info: &mut LoopHeaderPhiInfo,
ctx: &mut RewriteContext,
value_to_func_name: &BTreeMap<ValueId, String>,
debug: bool,
) -> Result<RewrittenBlocks, String> {
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<String> = boundary
.map(|b| b.continuation_func_ids.clone())
.unwrap_or_default();
let skippable_continuation_func_names: BTreeSet<String> = 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<ValueId> = 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<ValueId> =
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)
}

View File

@ -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<String, Vec<ValueId>>,
entry_func_name: Option<&str>,
continuation_candidates: &BTreeSet<String>,
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(())
}

View File

@ -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<BasicBlockId, BasicBlockId>,
entry_func_name: Option<&str>,
continuation_candidates: &BTreeSet<String>,
skippable_continuation_func_names: &BTreeSet<String>,
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<crate::mir::EdgeArgs> = 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<ValueId> = 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(())
}