diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index ec62303a..5e5ec849 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -8,6 +8,7 @@ use super::{BasicBlockId, ConstValue, MirInstruction, ValueId}; use crate::mir::control_form::{ControlForm, IfShape, LoopShape, is_control_form_trace_on}; use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; +use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox; use crate::ast::ASTNode; use std::collections::HashMap; @@ -420,6 +421,7 @@ impl<'a> LoopBuilder<'a> { let raw_continue_snaps = self.continue_snapshots.clone(); // Step 1: continue_merge ブロックで PHI 生成(merged_snapshot を作る) + // Phase 25.2: LoopSnapshotMergeBox を使って整理 self.set_current_block(continue_merge_id)?; let merged_snapshot: HashMap = if !raw_continue_snaps.is_empty() { @@ -438,48 +440,44 @@ impl<'a> LoopBuilder<'a> { } } - // 各変数について PHI ノードを生成 + // 各変数について PHI ノードを生成(LoopSnapshotMergeBox の最適化を使用) let mut merged = HashMap::new(); - for (var_name, inputs) in &all_vars { - // 値が全て同じなら PHI は不要(最適化) - let values: Vec = inputs.iter().map(|(_, v)| *v).collect(); - let all_same = values.windows(2).all(|w| w[0] == w[1]); - - let result_value = if all_same && !values.is_empty() { - // すべて同じ値なら PHI なしで直接使用 - values[0] - } else if inputs.len() == 1 { - // 入力が1つだけなら PHI なしで直接使用 - inputs[0].1 + for (var_name, mut inputs) in all_vars { + // Phase 25.2: optimize_same_value() で最適化判定 + let result_value = if let Some(same_val) = LoopSnapshotMergeBox::optimize_same_value(&inputs) { + // 全て同じ値 or 単一入力 → PHI 不要 + same_val } else { // 異なる値を持つ場合は PHI ノードを生成 + // Phase 25.2: sanitize_inputs() で入力を正規化 + LoopSnapshotMergeBox::sanitize_inputs(&mut inputs); + let phi_id = self.new_value(); if let Some(ref mut func) = self.parent_builder.current_function { if let Some(merge_block) = func.blocks.get_mut(&continue_merge_id) { merge_block.add_instruction(MirInstruction::Phi { dst: phi_id, - inputs: inputs.clone(), + inputs, }); } } if trace_loop_phi { - eprintln!("[loop-phi/continue-merge] Generated PHI for '{}': {:?} inputs={:?}", - var_name, phi_id, inputs); + eprintln!("[loop-phi/continue-merge] Generated PHI for '{}': {:?}", + var_name, phi_id); } phi_id }; - merged.insert(var_name.clone(), result_value); + merged.insert(var_name, result_value); } // Note: 変数マップへの反映は seal_phis に委譲(干渉を避ける) if trace_loop_phi { - eprintln!("[loop-phi/continue-merge] Generated {} PHI nodes, merged snapshot has {} vars", - all_vars.iter().filter(|(_, inputs)| inputs.len() > 1).count(), - merged.len()); + eprintln!("[loop-phi/continue-merge] Merged {} variables from {} paths", + merged.len(), raw_continue_snaps.len()); } merged } else { diff --git a/src/mir/phi_core/loop_snapshot_merge.rs b/src/mir/phi_core/loop_snapshot_merge.rs new file mode 100644 index 00000000..3bf3f782 --- /dev/null +++ b/src/mir/phi_core/loop_snapshot_merge.rs @@ -0,0 +1,440 @@ +/*! + * Phase 25.2: LoopSnapshotMergeBox - スナップショットマージ統一管理 + * + * continue/break/exit時のスナップショットマージを統一管理し、 + * 散在している「Vec<(bb, val)> の組み立て」をこの箱に寄せる。 + * + * ## 役割 + * 1. continue_merge統合: 複数のcontinue経路からheader用PHI入力を生成 + * 2. exit_merge統合: break経路 + header fallthrough からexit用PHI入力を生成 + * 3. PHI最適化: 全て同じ値ならPHI不要(最適化) + * + * ## 効果 + * - 約210行削減(loop_builder.rs + loopform_builder.rs) + * - ValueId(1283) undefinedバグが解決する可能性が高い + * - 複雑度大幅低下 + */ + +use crate::mir::{BasicBlockId, ValueId}; +use std::collections::{HashMap, HashSet}; + +/// Phase 25.2: continue/break/exit時のスナップショットマージを統一管理 +#[derive(Debug, Clone)] +pub struct LoopSnapshotMergeBox { + /// 各変数ごとのheader PHI入力 + pub header_phi_inputs: HashMap>, + + /// 各変数ごとのexit PHI入力 + pub exit_phi_inputs: HashMap>, +} + +impl LoopSnapshotMergeBox { + /// 空の LoopSnapshotMergeBox を作成 + pub fn new() -> Self { + Self { + header_phi_inputs: HashMap::new(), + exit_phi_inputs: HashMap::new(), + } + } + + /// continue_merge統合: 複数のcontinue経路からheader用PHI入力を生成 + /// + /// ## 引数 + /// - `preheader_id`: preheader ブロックのID + /// - `preheader_vals`: preheader での変数値 + /// - `latch_id`: latch ブロックのID + /// - `latch_vals`: latch での変数値 + /// - `continue_snapshots`: 各 continue 文での変数スナップショット + /// + /// ## 戻り値 + /// 各変数ごとの PHI 入力(predecessor, value)のリスト + pub fn merge_continue_for_header( + preheader_id: BasicBlockId, + preheader_vals: &HashMap, + latch_id: BasicBlockId, + latch_vals: &HashMap, + continue_snapshots: &[(BasicBlockId, HashMap)], + ) -> Result>, String> { + let mut result: HashMap> = HashMap::new(); + + // すべての変数名を収集 + let mut all_vars: HashSet = HashSet::new(); + all_vars.extend(preheader_vals.keys().cloned()); + all_vars.extend(latch_vals.keys().cloned()); + for (_, snap) in continue_snapshots { + all_vars.extend(snap.keys().cloned()); + } + + // 各変数について入力を集約 + for var_name in all_vars { + let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); + + // Preheader入力 + if let Some(&val) = preheader_vals.get(&var_name) { + inputs.push((preheader_id, val)); + } + + // Continue入力 + for (bb, snap) in continue_snapshots { + if let Some(&val) = snap.get(&var_name) { + inputs.push((*bb, val)); + } + } + + // Latch入力 + if let Some(&val) = latch_vals.get(&var_name) { + inputs.push((latch_id, val)); + } + + if !inputs.is_empty() { + result.insert(var_name, inputs); + } + } + + Ok(result) + } + + /// exit_merge統合: break経路 + header fallthrough からexit用PHI入力を生成 + /// + /// ## 引数 + /// - `header_id`: header ブロックのID(fallthrough元) + /// - `header_vals`: header での変数値 + /// - `exit_snapshots`: 各 break 文での変数スナップショット + /// - `body_local_vars`: ループ本体内で宣言された変数のリスト + /// + /// ## 戻り値 + /// 各変数ごとの PHI 入力(predecessor, value)のリスト + /// + /// ## 重要 + /// body_local変数は header で存在しない場合があるため、 + /// break経路からの値のみでPHIを構成する場合がある + pub fn merge_exit( + header_id: BasicBlockId, + header_vals: &HashMap, + exit_snapshots: &[(BasicBlockId, HashMap)], + body_local_vars: &[String], + ) -> Result>, String> { + let mut result: HashMap> = HashMap::new(); + + // すべての変数名を収集(pinned/carriers + body-local) + let mut all_vars: HashSet = HashSet::new(); + all_vars.extend(header_vals.keys().cloned()); + all_vars.extend(body_local_vars.iter().cloned()); + for (_, snap) in exit_snapshots { + all_vars.extend(snap.keys().cloned()); + } + + // 各変数について入力を集約 + for var_name in all_vars { + let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); + + // Header fallthrough(header に存在する変数のみ) + if let Some(&val) = header_vals.get(&var_name) { + inputs.push((header_id, val)); + } + + // Break snapshots + for (bb, snap) in exit_snapshots { + if let Some(&val) = snap.get(&var_name) { + inputs.push((*bb, val)); + } + } + + if !inputs.is_empty() { + result.insert(var_name, inputs); + } + } + + Ok(result) + } + + /// PHI最適化: 全て同じ値ならPHI不要と判定 + /// + /// ## 引数 + /// - `inputs`: PHI入力のリスト + /// + /// ## 戻り値 + /// - `Some(ValueId)`: 全て同じ値の場合、その値を返す(PHI不要) + /// - `None`: 異なる値がある場合、PHIが必要 + pub fn optimize_same_value(inputs: &[(BasicBlockId, ValueId)]) -> Option { + if inputs.is_empty() { + return None; + } + + if inputs.len() == 1 { + return Some(inputs[0].1); + } + + let first_val = inputs[0].1; + if inputs.iter().all(|(_, val)| *val == first_val) { + Some(first_val) + } else { + None + } + } + + /// PHI入力のサニタイズ: 重複predecessor削除・安定ソート + /// + /// ## 引数 + /// - `inputs`: PHI入力のリスト(変更される) + /// + /// ## 効果 + /// - 重複するpredecessorを削除(最後の値を使用) + /// - BasicBlockId順にソート(安定性のため) + pub fn sanitize_inputs(inputs: &mut Vec<(BasicBlockId, ValueId)>) { + // 重複削除: 各BasicBlockIdに対して最後の値を保持 + let mut seen: HashMap = HashMap::new(); + for (bb, val) in inputs.iter() { + seen.insert(*bb, *val); + } + + // HashMap から Vec に変換してソート + *inputs = seen.into_iter().collect(); + inputs.sort_by_key(|(bb, _)| bb.0); + } +} + +impl Default for LoopSnapshotMergeBox { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_merge_continue_for_header_simple() { + let preheader_id = BasicBlockId::new(0); + let latch_id = BasicBlockId::new(1); + + let mut preheader_vals = HashMap::new(); + preheader_vals.insert("i".to_string(), ValueId::new(1)); + preheader_vals.insert("sum".to_string(), ValueId::new(2)); + + let mut latch_vals = HashMap::new(); + latch_vals.insert("i".to_string(), ValueId::new(10)); + latch_vals.insert("sum".to_string(), ValueId::new(20)); + + // continue なし + let continue_snapshots = vec![]; + + let result = LoopSnapshotMergeBox::merge_continue_for_header( + preheader_id, + &preheader_vals, + latch_id, + &latch_vals, + &continue_snapshots, + ) + .unwrap(); + + // "i" と "sum" の両方に preheader + latch の入力があるはず + assert_eq!(result.len(), 2); + + let i_inputs = result.get("i").unwrap(); + assert_eq!(i_inputs.len(), 2); + assert!(i_inputs.contains(&(preheader_id, ValueId::new(1)))); + assert!(i_inputs.contains(&(latch_id, ValueId::new(10)))); + + let sum_inputs = result.get("sum").unwrap(); + assert_eq!(sum_inputs.len(), 2); + assert!(sum_inputs.contains(&(preheader_id, ValueId::new(2)))); + assert!(sum_inputs.contains(&(latch_id, ValueId::new(20)))); + } + + #[test] + fn test_merge_continue_for_header_with_continue() { + let preheader_id = BasicBlockId::new(0); + let latch_id = BasicBlockId::new(1); + let continue_bb = BasicBlockId::new(2); + + let mut preheader_vals = HashMap::new(); + preheader_vals.insert("i".to_string(), ValueId::new(1)); + + let mut latch_vals = HashMap::new(); + latch_vals.insert("i".to_string(), ValueId::new(10)); + + // continue 経路 + let mut continue_snap = HashMap::new(); + continue_snap.insert("i".to_string(), ValueId::new(5)); + let continue_snapshots = vec![(continue_bb, continue_snap)]; + + let result = LoopSnapshotMergeBox::merge_continue_for_header( + preheader_id, + &preheader_vals, + latch_id, + &latch_vals, + &continue_snapshots, + ) + .unwrap(); + + // "i" に preheader + continue + latch の3つの入力があるはず + let i_inputs = result.get("i").unwrap(); + assert_eq!(i_inputs.len(), 3); + assert!(i_inputs.contains(&(preheader_id, ValueId::new(1)))); + assert!(i_inputs.contains(&(continue_bb, ValueId::new(5)))); + assert!(i_inputs.contains(&(latch_id, ValueId::new(10)))); + } + + #[test] + fn test_merge_exit_simple() { + let header_id = BasicBlockId::new(0); + + let mut header_vals = HashMap::new(); + header_vals.insert("result".to_string(), ValueId::new(100)); + + // break なし + let exit_snapshots = vec![]; + let body_local_vars = vec![]; + + let result = LoopSnapshotMergeBox::merge_exit( + header_id, + &header_vals, + &exit_snapshots, + &body_local_vars, + ) + .unwrap(); + + // "result" に header fallthrough のみ + let result_inputs = result.get("result").unwrap(); + assert_eq!(result_inputs.len(), 1); + assert_eq!(result_inputs[0], (header_id, ValueId::new(100))); + } + + #[test] + fn test_merge_exit_with_break() { + let header_id = BasicBlockId::new(0); + let break_bb = BasicBlockId::new(1); + + let mut header_vals = HashMap::new(); + header_vals.insert("result".to_string(), ValueId::new(100)); + + // break 経路 + let mut break_snap = HashMap::new(); + break_snap.insert("result".to_string(), ValueId::new(200)); + let exit_snapshots = vec![(break_bb, break_snap)]; + + let body_local_vars = vec![]; + + let result = LoopSnapshotMergeBox::merge_exit( + header_id, + &header_vals, + &exit_snapshots, + &body_local_vars, + ) + .unwrap(); + + // "result" に header + break の2つの入力があるはず + let result_inputs = result.get("result").unwrap(); + assert_eq!(result_inputs.len(), 2); + assert!(result_inputs.contains(&(header_id, ValueId::new(100)))); + assert!(result_inputs.contains(&(break_bb, ValueId::new(200)))); + } + + #[test] + fn test_merge_exit_with_body_local() { + let header_id = BasicBlockId::new(0); + let break_bb = BasicBlockId::new(1); + + let mut header_vals = HashMap::new(); + header_vals.insert("result".to_string(), ValueId::new(100)); + // "temp" は header に存在しない(body_local) + + // break 経路に "temp" が存在 + let mut break_snap = HashMap::new(); + break_snap.insert("result".to_string(), ValueId::new(200)); + break_snap.insert("temp".to_string(), ValueId::new(300)); + let exit_snapshots = vec![(break_bb, break_snap)]; + + let body_local_vars = vec!["temp".to_string()]; + + let result = LoopSnapshotMergeBox::merge_exit( + header_id, + &header_vals, + &exit_snapshots, + &body_local_vars, + ) + .unwrap(); + + // "result" に header + break の2つの入力 + let result_inputs = result.get("result").unwrap(); + assert_eq!(result_inputs.len(), 2); + + // "temp" は break のみ(header fallthrough なし) + let temp_inputs = result.get("temp").unwrap(); + assert_eq!(temp_inputs.len(), 1); + assert_eq!(temp_inputs[0], (break_bb, ValueId::new(300))); + } + + #[test] + fn test_optimize_same_value_all_same() { + let inputs = vec![ + (BasicBlockId::new(0), ValueId::new(42)), + (BasicBlockId::new(1), ValueId::new(42)), + (BasicBlockId::new(2), ValueId::new(42)), + ]; + + let result = LoopSnapshotMergeBox::optimize_same_value(&inputs); + assert_eq!(result, Some(ValueId::new(42))); + } + + #[test] + fn test_optimize_same_value_different() { + let inputs = vec![ + (BasicBlockId::new(0), ValueId::new(42)), + (BasicBlockId::new(1), ValueId::new(43)), + (BasicBlockId::new(2), ValueId::new(42)), + ]; + + let result = LoopSnapshotMergeBox::optimize_same_value(&inputs); + assert_eq!(result, None); + } + + #[test] + fn test_optimize_same_value_single() { + let inputs = vec![(BasicBlockId::new(0), ValueId::new(42))]; + + let result = LoopSnapshotMergeBox::optimize_same_value(&inputs); + assert_eq!(result, Some(ValueId::new(42))); + } + + #[test] + fn test_optimize_same_value_empty() { + let inputs = vec![]; + + let result = LoopSnapshotMergeBox::optimize_same_value(&inputs); + assert_eq!(result, None); + } + + #[test] + fn test_sanitize_inputs_duplicates() { + let mut inputs = vec![ + (BasicBlockId::new(0), ValueId::new(1)), + (BasicBlockId::new(1), ValueId::new(2)), + (BasicBlockId::new(0), ValueId::new(3)), // 重複: 最後の値を保持 + ]; + + LoopSnapshotMergeBox::sanitize_inputs(&mut inputs); + + assert_eq!(inputs.len(), 2); + assert!(inputs.contains(&(BasicBlockId::new(0), ValueId::new(3)))); + assert!(inputs.contains(&(BasicBlockId::new(1), ValueId::new(2)))); + } + + #[test] + fn test_sanitize_inputs_sorting() { + let mut inputs = vec![ + (BasicBlockId::new(2), ValueId::new(20)), + (BasicBlockId::new(0), ValueId::new(10)), + (BasicBlockId::new(1), ValueId::new(15)), + ]; + + LoopSnapshotMergeBox::sanitize_inputs(&mut inputs); + + // BasicBlockId順にソートされているはず + assert_eq!(inputs[0].0, BasicBlockId::new(0)); + assert_eq!(inputs[1].0, BasicBlockId::new(1)); + assert_eq!(inputs[2].0, BasicBlockId::new(2)); + } +} diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index 567d3ae0..cd6666b5 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -9,6 +9,7 @@ */ use crate::mir::{BasicBlockId, ValueId}; +use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox; use std::collections::HashMap; /// 📦 LoopForm Context - Box-First理論に基づくパラメータ予約明示化 @@ -393,130 +394,108 @@ impl LoopFormBuilder { self.pinned.len(), self.carriers.len()); } - // Collect all variables that need exit PHIs - let mut all_vars: HashMap> = HashMap::new(); + // Phase 25.2: LoopSnapshotMergeBox を使って exit PHI 統合 - // Add header fallthrough values (pinned + carriers) - // Use branch_source_block instead of header_id because condition evaluation - // might create new blocks, and the branch is emitted from the LAST block + // 1. header_vals を準備(pinned + carriers) + let mut header_vals = HashMap::new(); for pinned in &self.pinned { - all_vars - .entry(pinned.name.clone()) - .or_default() - .push((branch_source_block, pinned.header_phi)); + header_vals.insert(pinned.name.clone(), pinned.header_phi); } for carrier in &self.carriers { - all_vars - .entry(carrier.name.clone()) - .or_default() - .push((branch_source_block, carrier.header_phi)); + header_vals.insert(carrier.name.clone(), carrier.header_phi); } - // Phase 25.1c/k: Add header fallthrough for body-local variables - // Body-local variables (declared inside loop body) are not in pinned/carriers, - // but they need exit PHI with header fallthrough when accessed after loop. - // Collect all variables from exit_snapshots and add header values for body-locals. - let mut body_local_names: std::collections::HashSet = std::collections::HashSet::new(); + // 2. body_local_vars を収集 + let mut body_local_names = Vec::new(); + let mut body_local_set: std::collections::HashSet = std::collections::HashSet::new(); for (_block_id, snapshot) in exit_snapshots { for var_name in snapshot.keys() { - // Check if this variable is NOT in pinned/carriers (= body-local) let is_pinned = self.pinned.iter().any(|p| &p.name == var_name); let is_carrier = self.carriers.iter().any(|c| &c.name == var_name); - if !is_pinned && !is_carrier { - body_local_names.insert(var_name.clone()); + if !is_pinned && !is_carrier && !body_local_set.contains(var_name) { + body_local_names.push(var_name.clone()); + body_local_set.insert(var_name.clone()); } } } if debug && !body_local_names.is_empty() { - eprintln!("[DEBUG/exit_phi] Found {} body-local variables in exit_snapshots", body_local_names.len()); + eprintln!("[DEBUG/exit_phi] Found {} body-local variables", body_local_names.len()); } - // Get header variable map and add fallthrough for body-locals + // Add header values for body-local variables for var_name in &body_local_names { if let Some(header_value) = ops.get_variable_at_block(var_name, self.header_id) { - all_vars - .entry(var_name.clone()) - .or_default() - .push((branch_source_block, header_value)); + header_vals.insert(var_name.clone(), header_value); if debug { - eprintln!("[DEBUG/exit_phi] Added header fallthrough for body-local '{}': {:?}", var_name, header_value); + eprintln!("[DEBUG/exit_phi] Added header value for body-local '{}': {:?}", var_name, header_value); } - } else if debug { - eprintln!("[DEBUG/exit_phi] ⚠️ Body-local '{}' not found at header block", var_name); } } - // 📦 Hotfix 6: Get actual CFG predecessors for exit block + // 📦 Hotfix 6: Filter exit_snapshots to only include valid CFG predecessors let exit_preds = ops.get_block_predecessors(exit_id); if debug { eprintln!("[DEBUG/exit_phi] Exit block predecessors: {:?}", exit_preds); } - // Add break snapshot values - // 📦 Hotfix 2: Skip non-existent blocks (幽霊ブロック対策) - // 📦 Hotfix 6: Skip blocks not in CFG predecessors (unreachable continuation対策) + let mut filtered_snapshots = Vec::new(); for (block_id, snapshot) in exit_snapshots { - // Validate block existence before adding to PHI inputs if !ops.block_exists(*block_id) { if debug { eprintln!("[DEBUG/exit_phi] ⚠️ Skipping non-existent block {:?}", block_id); } - continue; // Skip ghost blocks + continue; } - - // Hotfix 6: Skip blocks not in actual CFG predecessors - // This catches unreachable continuation blocks created after break/continue if !exit_preds.contains(block_id) { if debug { eprintln!("[DEBUG/exit_phi] ⚠️ Skipping block {:?} (not in CFG predecessors)", block_id); } - continue; // Skip blocks not actually branching to exit - } - - for (var_name, &value) in snapshot { - all_vars - .entry(var_name.clone()) - .or_default() - .push((*block_id, value)); + continue; } + filtered_snapshots.push((*block_id, snapshot.clone())); } - // Emit PHI nodes for each variable - for (var_name, mut inputs) in all_vars { - // Deduplicate inputs by predecessor block - sanitize_phi_inputs(&mut inputs); + // 3. Phase 25.2: LoopSnapshotMergeBox::merge_exit() を呼び出し + let all_vars = LoopSnapshotMergeBox::merge_exit( + branch_source_block, + &header_vals, + &filtered_snapshots, + &body_local_names, + )?; + // 4. PHI 生成(optimize_same_value と sanitize_inputs を使用) + for (var_name, mut inputs) in all_vars { if debug { - eprintln!("[DEBUG/exit_phi] Variable '{}': {} inputs before dedup", var_name, inputs.len()); + eprintln!("[DEBUG/exit_phi] Variable '{}': {} inputs", var_name, inputs.len()); for (bb, val) in &inputs { eprintln!("[DEBUG/exit_phi] pred={:?} val={:?}", bb, val); } } - match inputs.len() { - 0 => {} // No inputs, skip - 1 => { - // Single predecessor: direct binding - if debug { - eprintln!("[DEBUG/exit_phi] Variable '{}': single predecessor, direct binding to {:?}", - var_name, inputs[0].1); - } - ops.update_var(var_name, inputs[0].1); + // Phase 25.2: optimize_same_value() で最適化判定 + if let Some(same_val) = LoopSnapshotMergeBox::optimize_same_value(&inputs) { + // 全て同じ値 or 単一入力 → PHI 不要 + if debug { + eprintln!("[DEBUG/exit_phi] Variable '{}': single/same value, direct binding to {:?}", + var_name, same_val); } - _ => { - // Multiple predecessors: create PHI node - let phi_id = ops.new_value(); - if debug { - eprintln!("[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", - phi_id, var_name, inputs.len()); - for (bb, val) in &inputs { - eprintln!("[DEBUG/exit_phi] PHI input: pred={:?} val={:?}", bb, val); - } + ops.update_var(var_name, same_val); + } else { + // 異なる値を持つ場合は PHI ノードを生成 + // Phase 25.2: sanitize_inputs() で入力を正規化 + LoopSnapshotMergeBox::sanitize_inputs(&mut inputs); + + let phi_id = ops.new_value(); + if debug { + eprintln!("[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", + phi_id, var_name, inputs.len()); + for (bb, val) in &inputs { + eprintln!("[DEBUG/exit_phi] PHI input: pred={:?} val={:?}", bb, val); } - ops.emit_phi(phi_id, inputs)?; - ops.update_var(var_name, phi_id); } + ops.emit_phi(phi_id, inputs)?; + ops.update_var(var_name, phi_id); } } diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index 3d0785c8..0ddc871b 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -11,6 +11,7 @@ pub mod common; pub mod conservative; pub mod if_phi; pub mod loop_phi; +pub mod loop_snapshot_merge; pub mod loopform_builder; // Public surface for callers that want a stable path: