Convert 10 medium-priority locations to BTreeMap for determinism: Merge System (6 locations): - instruction_rewriter.rs: value_to_func_name, function_params, function_entry_map, local_block_map - value_collector.rs: value_to_func_name, function_params, function_entry_map ID Remapper (2 locations): - joinir_id_remapper.rs: block_map, value_map Boundary Injector (2 locations): - joinir_inline_boundary_injector.rs: value_map (param & return) Impact: - Changed files: 4 - Changed lines: +36, -24 (net +12) - Tests: 849/856 PASS (no regression) - Determinism: Merge/Boundary processing now deterministic Combined with Phase 222.5-D (13 high-priority locations), JoinIR pipeline now uses BTreeMap uniformly across all critical paths (Pattern/Merge/Boundary/ValueId allocation). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
331 lines
13 KiB
Rust
331 lines
13 KiB
Rust
//! Phase 188-Impl-3: JoinInlineBoundary Copy Instruction Injector
|
||
//!
|
||
//! 責務:
|
||
//! - JoinInlineBoundary で指定された入出力の Copy instruction 生成
|
||
//! - Entry block への Copy instruction 挿入
|
||
//! - SSA 値空間の接続
|
||
|
||
use std::collections::BTreeMap; // Phase 222.5-E: HashMap → BTreeMap for determinism
|
||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||
|
||
pub struct BoundaryInjector;
|
||
|
||
impl BoundaryInjector {
|
||
/// JoinInlineBoundary で指定された入力を entry block に Copy instruction として挿入
|
||
///
|
||
/// # Arguments
|
||
///
|
||
/// * `func` - 対象の MirFunction
|
||
/// * `entry_block_id` - entry block の ID
|
||
/// * `boundary` - JoinInlineBoundary(入力マッピング情報)
|
||
/// * `value_map` - ValueId リマッピング情報
|
||
/// * `debug` - デバッグログ出力
|
||
///
|
||
/// # Returns
|
||
///
|
||
/// * `Ok(())` - 成功
|
||
/// * `Err(String)` - エラー(ブロックが見つからない等)
|
||
///
|
||
/// # Phase 33-16: Loop Variable Header PHI Support
|
||
///
|
||
/// When `boundary.loop_var_name` is set, the first join_input (loop variable)
|
||
/// is handled by the header PHI instead of a Copy instruction. We skip
|
||
/// emitting the Copy for join_inputs[0] in this case to avoid overwriting
|
||
/// the PHI result with the initial value.
|
||
///
|
||
/// # Phase 33-20: All Carriers Header PHI Support
|
||
///
|
||
/// When `boundary.loop_var_name` is set, ALL carriers (loop var + other carriers
|
||
/// from exit_bindings) are handled by header PHIs. We skip ALL join_inputs
|
||
/// Copy instructions to avoid overwriting the PHI results.
|
||
///
|
||
/// # Phase 177-3: PHI Collision Avoidance (Option B)
|
||
///
|
||
/// When `phi_dst_ids` contains ValueIds of existing PHI dsts in the header block,
|
||
/// we allocate a NEW ValueId for condition_bindings instead of skipping the copy.
|
||
/// This ensures the condition variable is available even when its remapped ValueId
|
||
/// collides with a PHI dst.
|
||
///
|
||
/// # Returns
|
||
///
|
||
/// Returns a BTreeMap mapping original ValueIds to their reallocated ValueIds
|
||
/// (only for condition_bindings that had collisions).
|
||
/// Phase 222.5-E: HashMap → BTreeMap for determinism
|
||
pub fn inject_boundary_copies(
|
||
func: &mut MirFunction,
|
||
entry_block_id: BasicBlockId,
|
||
boundary: &JoinInlineBoundary,
|
||
value_map: &BTreeMap<ValueId, ValueId>, // Phase 222.5-E: HashMap → BTreeMap for determinism
|
||
phi_dst_ids: &std::collections::HashSet<ValueId>,
|
||
debug: bool,
|
||
) -> Result<BTreeMap<ValueId, ValueId>, String> { // Phase 222.5-E: HashMap → BTreeMap for determinism
|
||
// Phase 33-20: When loop_var_name is set, ALL join_inputs are handled by header PHIs
|
||
// This includes the loop variable AND all other carriers from exit_bindings.
|
||
// We skip ALL join_inputs Copy instructions, only condition_bindings remain.
|
||
let skip_all_join_inputs = boundary.loop_var_name.is_some();
|
||
|
||
// Phase 171-fix: Check both join_inputs and condition_bindings
|
||
let effective_join_inputs = if skip_all_join_inputs {
|
||
0 // Phase 33-20: All join_inputs are handled by header PHIs
|
||
} else {
|
||
boundary.join_inputs.len()
|
||
};
|
||
let total_inputs = effective_join_inputs + boundary.condition_bindings.len();
|
||
if total_inputs == 0 {
|
||
// No inputs to process, return empty reallocations map
|
||
// Phase 222.5-E: HashMap → BTreeMap for determinism
|
||
return Ok(BTreeMap::new());
|
||
}
|
||
|
||
if debug {
|
||
eprintln!(
|
||
"[BoundaryInjector] Phase 33-20: Injecting {} Copy instructions ({} join_inputs, {} condition_bindings) at entry block {:?}{}",
|
||
total_inputs,
|
||
effective_join_inputs,
|
||
boundary.condition_bindings.len(),
|
||
entry_block_id,
|
||
if skip_all_join_inputs { " (skipping ALL join_inputs - handled by header PHIs)" } else { "" }
|
||
);
|
||
}
|
||
|
||
// Phase 177-3: Use PHI dst IDs passed from caller
|
||
if debug && !phi_dst_ids.is_empty() {
|
||
eprintln!(
|
||
"[BoundaryInjector] Phase 177-3: Received {} PHI dst IDs to avoid: {:?}",
|
||
phi_dst_ids.len(),
|
||
phi_dst_ids
|
||
);
|
||
}
|
||
|
||
// Phase 177-3 Option B: First pass - allocate all new ValueIds for PHI collisions
|
||
// We need to do this BEFORE acquiring the entry_block reference to avoid borrow conflicts
|
||
// Phase 222.5-E: HashMap → BTreeMap for determinism
|
||
let mut reallocations = BTreeMap::new();
|
||
|
||
for binding in &boundary.condition_bindings {
|
||
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
|
||
|
||
if phi_dst_ids.contains(&remapped_join) {
|
||
// Collision detected! Allocate a fresh ValueId
|
||
let fresh_dst = func.next_value_id();
|
||
|
||
if debug {
|
||
eprintln!(
|
||
"[BoundaryInjector] Phase 177-3 Option B: PHI collision for condition binding '{}': {:?} → reallocated to {:?}",
|
||
binding.name, remapped_join, fresh_dst
|
||
);
|
||
}
|
||
|
||
reallocations.insert(binding.join_value, fresh_dst);
|
||
}
|
||
}
|
||
|
||
// Now get entry block reference (after all ValueId allocations are done)
|
||
let entry_block = func
|
||
.get_block_mut(entry_block_id)
|
||
.ok_or(format!("Entry block {:?} not found", entry_block_id))?;
|
||
|
||
// Copy instructions を生成して挿入
|
||
let mut copy_instructions = Vec::new();
|
||
|
||
// Phase 171: Inject Copy instructions for join_inputs (loop parameters)
|
||
// Phase 33-20: Skip ALL join_inputs when loop_var_name is set (header PHIs handle them)
|
||
if !skip_all_join_inputs {
|
||
for (join_input, host_input) in boundary
|
||
.join_inputs
|
||
.iter()
|
||
.zip(boundary.host_inputs.iter())
|
||
{
|
||
// リマップ後の ValueId を取得
|
||
let remapped_join = value_map.get(join_input).copied().unwrap_or(*join_input);
|
||
let remapped_host = *host_input; // host_input is already in host space
|
||
|
||
// Copy instruction: remapped_join = Copy remapped_host
|
||
let copy_inst = MirInstruction::Copy {
|
||
dst: remapped_join,
|
||
src: remapped_host,
|
||
};
|
||
|
||
copy_instructions.push(copy_inst);
|
||
|
||
if debug {
|
||
eprintln!(
|
||
"[BoundaryInjector] Join input: Copy {:?} = Copy {:?}",
|
||
remapped_join, remapped_host
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Phase 177-3 DEBUG: Check value_map in BoundaryInjector
|
||
if debug {
|
||
eprintln!("[DEBUG-177] === BoundaryInjector value_map ===");
|
||
for binding in &boundary.condition_bindings {
|
||
let lookup = value_map.get(&binding.join_value);
|
||
eprintln!(
|
||
"[DEBUG-177] '{}': JoinIR {:?} → {:?}",
|
||
binding.name, binding.join_value, lookup
|
||
);
|
||
}
|
||
}
|
||
|
||
// Phase 171-fix: Inject Copy instructions for condition_bindings (condition-only variables)
|
||
// These variables are read-only and used ONLY in the loop condition.
|
||
// Each binding explicitly specifies HOST ValueId → JoinIR ValueId mapping.
|
||
// We inject Copy: remapped_join_value = Copy host_value
|
||
//
|
||
// Phase 177-3 Option B: Use pre-allocated reallocations for PHI collision cases
|
||
for binding in &boundary.condition_bindings {
|
||
// Look up the remapped JoinIR ValueId from value_map
|
||
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
|
||
|
||
// Phase 177-3 Option B: Check if this binding was reallocated (PHI collision case)
|
||
let final_dst = reallocations.get(&binding.join_value).copied().unwrap_or(remapped_join);
|
||
|
||
// Copy instruction: final_dst = Copy host_value
|
||
let copy_inst = MirInstruction::Copy {
|
||
dst: final_dst,
|
||
src: binding.host_value,
|
||
};
|
||
|
||
copy_instructions.push(copy_inst);
|
||
|
||
if debug {
|
||
eprintln!(
|
||
"[BoundaryInjector] Condition binding '{}': Copy {:?} = Copy {:?} (JoinIR {:?} → remapped {:?}{})",
|
||
binding.name, final_dst, binding.host_value, binding.join_value, remapped_join,
|
||
if final_dst != remapped_join { format!(" → reallocated {:?}", final_dst) } else { String::new() }
|
||
);
|
||
}
|
||
}
|
||
|
||
// Entry block の先頭に Copy instructions を挿入
|
||
// Reverse order to preserve original order when inserting at position 0
|
||
// Phase 189 FIX: Also insert corresponding spans
|
||
let default_span = entry_block.instruction_spans.first()
|
||
.copied()
|
||
.unwrap_or_else(crate::ast::Span::unknown);
|
||
for inst in copy_instructions.into_iter().rev() {
|
||
entry_block.instructions.insert(0, inst);
|
||
entry_block.instruction_spans.insert(0, default_span);
|
||
}
|
||
|
||
// Return reallocations map for condition_bindings that had PHI collisions
|
||
Ok(reallocations)
|
||
}
|
||
}
|
||
|
||
// TODO: These tests need to be updated to use the new MirModule API
|
||
// #[cfg(test)]
|
||
// mod tests {
|
||
// use super::*;
|
||
// use crate::mir::{BasicBlock, MirModule};
|
||
//
|
||
// #[test]
|
||
// fn test_injector_empty_boundary() {
|
||
// // 空の boundary で何もしない
|
||
// let boundary = JoinInlineBoundary::new_inputs_only(vec![], vec![]);
|
||
// let mut module = MirModule::new();
|
||
// let mut func = module.define_function("test".to_string(), vec![]);
|
||
// let entry_block = func.create_block();
|
||
// let value_map = HashMap::new();
|
||
//
|
||
// let result = BoundaryInjector::inject_boundary_copies(
|
||
// &mut func,
|
||
// entry_block,
|
||
// &boundary,
|
||
// &value_map,
|
||
// false,
|
||
// );
|
||
//
|
||
// assert!(result.is_ok());
|
||
// }
|
||
//
|
||
// #[test]
|
||
// fn test_injector_single_copy() {
|
||
// // 単一の Copy instruction を挿入
|
||
// let boundary = JoinInlineBoundary::new_inputs_only(
|
||
// vec![ValueId(0)],
|
||
// vec![ValueId(10)],
|
||
// );
|
||
//
|
||
// let mut module = MirModule::new();
|
||
// let mut func = module.define_function("test".to_string(), vec![]);
|
||
// let entry_block = func.create_block();
|
||
//
|
||
// let mut value_map = HashMap::new();
|
||
// value_map.insert(ValueId(0), ValueId(100)); // JoinIR ValueId(0) remapped to ValueId(100)
|
||
//
|
||
// let result = BoundaryInjector::inject_boundary_copies(
|
||
// &mut func,
|
||
// entry_block,
|
||
// &boundary,
|
||
// &value_map,
|
||
// false,
|
||
// );
|
||
//
|
||
// assert!(result.is_ok());
|
||
//
|
||
// // Copy instruction が挿入されたことを確認
|
||
// let block = func.get_block(entry_block).unwrap();
|
||
// assert!(!block.instructions.is_empty());
|
||
//
|
||
// // First instruction should be Copy
|
||
// match &block.instructions[0] {
|
||
// MirInstruction::Copy { dst, src } => {
|
||
// assert_eq!(*dst, ValueId(100)); // Remapped join input
|
||
// assert_eq!(*src, ValueId(10)); // Host input
|
||
// }
|
||
// _ => panic!("Expected Copy instruction"),
|
||
// }
|
||
// }
|
||
//
|
||
// #[test]
|
||
// fn test_injector_multiple_copies() {
|
||
// // 複数の Copy instruction を挿入
|
||
// let boundary = JoinInlineBoundary::new_inputs_only(
|
||
// vec![ValueId(0), ValueId(1)],
|
||
// vec![ValueId(10), ValueId(20)],
|
||
// );
|
||
//
|
||
// let mut module = MirModule::new();
|
||
// let mut func = module.define_function("test".to_string(), vec![]);
|
||
// let entry_block = func.create_block();
|
||
//
|
||
// let mut value_map = HashMap::new();
|
||
// value_map.insert(ValueId(0), ValueId(100));
|
||
// value_map.insert(ValueId(1), ValueId(101));
|
||
//
|
||
// let result = BoundaryInjector::inject_boundary_copies(
|
||
// &mut func,
|
||
// entry_block,
|
||
// &boundary,
|
||
// &value_map,
|
||
// false,
|
||
// );
|
||
//
|
||
// assert!(result.is_ok());
|
||
//
|
||
// let block = func.get_block(entry_block).unwrap();
|
||
// assert_eq!(block.instructions.len(), 2);
|
||
//
|
||
// // Check both copy instructions
|
||
// match &block.instructions[0] {
|
||
// MirInstruction::Copy { dst, src } => {
|
||
// assert_eq!(*dst, ValueId(100));
|
||
// assert_eq!(*src, ValueId(10));
|
||
// }
|
||
// _ => panic!("Expected Copy instruction"),
|
||
// }
|
||
//
|
||
// match &block.instructions[1] {
|
||
// MirInstruction::Copy { dst, src } => {
|
||
// assert_eq!(*dst, ValueId(101));
|
||
// assert_eq!(*src, ValueId(20));
|
||
// }
|
||
// _ => panic!("Expected Copy instruction"),
|
||
// }
|
||
// }
|
||
// }
|