refactor(joinir): Phase 287 P4 - Modularize plan stage (facade)
This commit is contained in:
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction};
|
||||
use crate::mir::{MirFunction, MirInstruction};
|
||||
|
||||
use super::super::merge_result::MergeContracts;
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user