Files
hakorune/src/mir/builder/joinir_inline_boundary_injector.rs
nyash-codex 9dbf053781 refactor(joinir): Phase 222.5-E - Merge/Boundary HashMap → BTreeMap
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>
2025-12-10 14:09:18 +09:00

331 lines
13 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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"),
// }
// }
// }