refactor(joinir): Phase 287 P3 - Split instruction_rewriter into stages

This commit is contained in:
2025-12-27 12:17:34 +09:00
parent fe895e8838
commit 3224d83a7b
6 changed files with 1091 additions and 1044 deletions

View File

@ -46,10 +46,13 @@ pub(super) mod terminator; // Phase 260 P0.1 Step 5: Terminator remapping extrac
pub(super) mod type_propagation; // Phase 260 P0.1 Step 4: Type propagation extracted ✅
// Phase 286C-2.1: 3-stage pipeline (Scan → Plan → Apply) ✅
pub(super) mod scan_box; // Stage 1: Read-only scanning for rewrite planning
pub(super) mod plan_box; // Stage 2: Pure transformation (scan → blocks)
pub(super) mod apply_box; // Stage 3: Builder mutation only
pub(super) mod plan_helpers; // Phase 286C-4 Step 1: Helper functions for plan_rewrites()
pub(super) mod scan_box; // Stage 1: Data structures
pub(super) mod plan_box; // Stage 2: Data structures
pub(super) mod apply_box; // Stage 3: Data structures (stub)
pub(super) mod plan_helpers; // Helper functions for plan_rewrites()
// Phase 287 P3: Pipeline functions extracted to separate files
pub(super) mod stages; // scan_blocks(), plan_rewrites(), apply_rewrites()
// Re-export public API
// Phase 260 P0.1 Step 3: From helpers ✅

View File

@ -0,0 +1,188 @@
//! Stage 3: Apply - Apply rewritten blocks to MirBuilder
//!
//! Phase 287 P3: Extracted from instruction_rewriter.rs::apply_rewrites()
//!
//! Mutates the builder:
//! - Adds new blocks to current function
//! - Injects boundary copies (if boundary provided)
//! - Updates RewriteContext with carrier/phi inputs
//!
//! # Phase 286C-4 Step 3
//!
//! This function extracts ~180 lines from merge_and_rewrite():
//! - Block addition (lines 1738-1751)
//! - Boundary injection (lines 1755-1857)
//! - Context updates (carrier_inputs, exit_phi_inputs)
use super::super::{
rewrite_context::RewriteContext,
plan_box::RewrittenBlocks,
};
use super::super::super::{
loop_header_phi_info::LoopHeaderPhiInfo,
trace,
};
use crate::mir::{MirModule, ValueId};
use crate::mir::builder::{MirBuilder, joinir_id_remapper::JoinIrIdRemapper};
use crate::mir::builder::joinir_inline_boundary_injector::BoundaryInjector;
use crate::mir::join_ir::lowering::{
inline_boundary::JoinInlineBoundary,
canonical_names,
};
/// Stage 3: Apply - Apply rewritten blocks to MirBuilder
///
/// Mutates the builder:
/// - Adds new blocks to current function
/// - Injects boundary copies (if boundary provided)
/// - Updates RewriteContext with carrier/phi inputs
///
/// # Phase 286C-4 Step 3
///
/// This function extracts ~180 lines from merge_and_rewrite():
/// - Block addition (lines 1738-1751)
/// - Boundary injection (lines 1755-1857)
/// - Context updates (carrier_inputs, exit_phi_inputs)
pub(in crate::mir::builder::control_flow::joinir::merge) fn apply_rewrites(
builder: &mut MirBuilder,
blocks: RewrittenBlocks,
boundary: Option<&JoinInlineBoundary>,
remapper: &JoinIrIdRemapper,
loop_header_phi_info: &LoopHeaderPhiInfo,
mir_module: &MirModule,
ctx: &mut RewriteContext,
debug: bool,
) -> Result<(), 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);
};
}
// Add new blocks to current function
if let Some(ref mut current_func) = builder.scope_ctx.current_function {
for new_block in blocks.new_blocks {
if debug && new_block.instructions.len() >= 4 {
log!(
true,
"[apply_rewrites] Adding block {:?} with {} instructions",
new_block.id, new_block.instructions.len()
);
for (idx, inst) in new_block.instructions.iter().enumerate() {
log!(true, "[apply_rewrites] [{}] {:?}", idx, inst);
}
}
current_func.add_block(new_block);
}
}
// Inject boundary copies (if boundary provided)
if let Some(boundary) = boundary {
use canonical_names as cn;
// Get entry function's entry block
let (entry_func_name, entry_func) = {
if let Some(main) = mir_module.functions.get(cn::MAIN) {
if main.params == boundary.join_inputs {
(cn::MAIN, main)
} else {
mir_module
.functions
.iter()
.find(|(_, func)| func.params == boundary.join_inputs)
.or_else(|| mir_module.functions.iter().next())
.map(|(name, func)| (name.as_str(), func))
.ok_or("JoinIR module has no functions")?
}
} else {
mir_module
.functions
.iter()
.find(|(_, func)| func.params == boundary.join_inputs)
.or_else(|| mir_module.functions.iter().next())
.map(|(name, func)| (name.as_str(), func))
.ok_or("JoinIR module has no functions")?
}
};
let entry_block_remapped = remapper
.get_block(entry_func_name, entry_func.entry_block)
.ok_or_else(|| format!("Entry block not found for {}", entry_func_name))?;
log!(
verbose,
"[apply_rewrites] Boundary entry: func='{}' entry_block={:?} remapped={:?}",
entry_func_name, entry_func.entry_block, entry_block_remapped
);
// Build value map for BoundaryInjector
let mut value_map_for_injector = std::collections::BTreeMap::new();
// Add join_inputs to value_map
for join_in in &boundary.join_inputs {
if let Some(remapped) = remapper.get_value(*join_in) {
value_map_for_injector.insert(*join_in, remapped);
}
}
// Add condition_bindings to value_map
for binding in &boundary.condition_bindings {
if let Some(remapped) = remapper.get_value(binding.join_value) {
value_map_for_injector.insert(binding.join_value, remapped);
log!(
verbose,
"[apply_rewrites] Condition binding '{}': JoinIR {:?} → remapped {:?}",
binding.name, binding.join_value, remapped
);
}
}
// Collect PHI dst IDs from loop_header_phi_info
let phi_dst_ids: std::collections::HashSet<ValueId> = loop_header_phi_info
.carrier_phis
.values()
.map(|entry| entry.phi_dst)
.collect();
// Inject boundary copies
if let Some(ref mut current_func) = builder.scope_ctx.current_function {
let _reallocations = BoundaryInjector::inject_boundary_copies(
current_func,
entry_block_remapped,
boundary,
&value_map_for_injector,
&phi_dst_ids,
debug,
)?;
}
}
// Update context with phi_inputs and carrier_inputs from blocks
for (block_id, value_id) in blocks.phi_inputs {
ctx.add_exit_phi_input(block_id, value_id);
}
for (carrier_name, inputs) in blocks.carrier_inputs {
for (block_id, value_id) in inputs {
ctx.add_carrier_input(carrier_name.clone(), block_id, value_id);
}
}
if debug {
log!(
true,
"[apply_rewrites] Applied {} blocks, {} phi_inputs, {} carriers",
builder.scope_ctx.current_function
.as_ref()
.map(|f| f.blocks.len())
.unwrap_or(0),
ctx.exit_phi_inputs.len(),
ctx.carrier_inputs.len()
);
}
Ok(())
}

View File

@ -0,0 +1,14 @@
//! 3-Stage Pipeline: Scan → Plan → Apply
//!
//! Phase 287 P3: Modularize instruction_rewriter.rs 3-stage pipeline
//! Extracted from instruction_rewriter.rs to separate physical files.
//!
//! This module contains the three stages of JoinIR instruction rewriting:
//! 1. **Scan**: Read-only analysis to identify what needs rewriting
//! 2. **Plan**: Transform scan plan into concrete rewritten blocks
//! 3. **Apply**: Apply rewritten blocks to MirBuilder
// Make sub-modules visible to parent's parent (merge/ level) for instruction_rewriter.rs access
pub(in crate::mir::builder::control_flow::joinir::merge) mod scan;
pub(in crate::mir::builder::control_flow::joinir::merge) mod plan;
pub(in crate::mir::builder::control_flow::joinir::merge) mod apply;

View File

@ -0,0 +1,740 @@
//! 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,133 @@
//! Stage 1: Scan - Read-only analysis to identify what needs rewriting
//!
//! Phase 287 P3: Extracted from instruction_rewriter.rs::scan_blocks()
//!
//! Scans all blocks and instructions to identify:
//! - Tail calls to convert
//! - Returns to convert to exit jumps
//! - PHI adjustments needed
//! - Parameter bindings to generate
//!
//! This is a READ-ONLY operation - no mutations, just analysis.
use super::super::{
rewrite_context::RewriteContext,
scan_box::{RewritePlan, ReturnConversion, TailCallRewrite},
};
use super::super::super::trace;
use crate::mir::{MirInstruction, MirModule, ValueId};
use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper;
use std::collections::BTreeMap;
/// Stage 1: Scan - Read-only analysis to identify what needs rewriting
///
/// Scans all blocks and instructions to identify:
/// - Tail calls to convert
/// - Returns to convert to exit jumps
/// - PHI adjustments needed
/// - Parameter bindings to generate
///
/// This is a READ-ONLY operation - no mutations, just analysis.
pub(in crate::mir::builder::control_flow::joinir::merge) fn scan_blocks(
mir_module: &MirModule,
remapper: &JoinIrIdRemapper,
value_to_func_name: &BTreeMap<ValueId, String>,
ctx: &RewriteContext,
debug: bool,
) -> Result<RewritePlan, 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 plan = RewritePlan {
tail_calls: Vec::new(),
return_conversions: Vec::new(),
phi_adjustments: Vec::new(),
parameter_bindings: Vec::new(),
};
// Sort functions for deterministic iteration
let mut functions_merge: Vec<_> = mir_module.functions.iter().collect();
functions_merge.sort_by_key(|(name, _)| name.as_str());
for (func_name, func) in functions_merge {
// Sort blocks for deterministic iteration
let mut blocks_merge: Vec<_> = func.blocks.iter().collect();
blocks_merge.sort_by_key(|(id, _)| id.0);
for (old_block_id, old_block) in blocks_merge {
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))?;
// Scan instructions for tail calls and PHI adjustments
for inst in &old_block.instructions {
// Detect tail calls
if let MirInstruction::Call { func: callee, args, .. } = inst {
if let Some(callee_name) = value_to_func_name.get(callee) {
// Check if this is an intra-module call (tail call candidate)
if let Some(&target_block) = ctx.function_entry_map.get(callee_name) {
let remapped_args: Vec<ValueId> = args
.iter()
.map(|&v| remapper.get_value(v).unwrap_or(v))
.collect();
let is_recursive = callee_name == func_name;
// For now, mark all tail calls as non-continuation (will be refined in plan stage)
let is_continuation = false;
let is_loop_entry = false; // Will be determined in plan stage
plan.tail_calls.push(TailCallRewrite {
block_id: new_block_id,
target_func_name: callee_name.clone(),
target_block,
args: remapped_args,
is_recursive,
is_continuation,
is_loop_entry,
});
log!(
verbose,
"[scan_blocks] Detected tail call: block={:?} target='{}' args={:?}",
new_block_id, callee_name, args
);
}
}
}
}
// Check terminator for Return instructions
if let Some(MirInstruction::Return { value }) = &old_block.terminator {
plan.return_conversions.push(ReturnConversion {
block_id: new_block_id,
return_value: *value,
has_exit_edge_args: old_block.edge_args_from_terminator().is_some(),
should_keep_return: false, // Will be determined in plan stage
});
log!(
verbose,
"[scan_blocks] Detected return: block={:?} value={:?}",
new_block_id, value
);
}
}
}
if debug {
log!(
true,
"[scan_blocks] Plan summary: {} tail calls, {} returns",
plan.tail_calls.len(),
plan.return_conversions.len()
);
}
Ok(plan)
}