refactor(joinir): Phase 287 P3 - Split instruction_rewriter into stages
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -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 ✅
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
@ -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;
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user