diff --git a/src/mir/phi_core/loop_snapshot_merge.rs b/src/mir/phi_core/loop_snapshot_merge.rs index 5ccc7c0f..6693954d 100644 --- a/src/mir/phi_core/loop_snapshot_merge.rs +++ b/src/mir/phi_core/loop_snapshot_merge.rs @@ -1,112 +1,75 @@ /*! - * Phase 25.2: LoopSnapshotMergeBox - スナップショットマージ統一管理 + * Phase 36: LoopSnapshotMergeBox - Exit PHI Merge Utility (Pure Static) * - * continue/break/exit時のスナップショットマージを統一管理し、 - * 散在している「Vec<(bb, val)> の組み立て」をこの箱に寄せる。 + * **Responsibility**: Exit PHI input merging with variable classification (Option C) * - * ## 役割 - * 1. continue_merge統合: 複数のcontinue経路からheader用PHI入力を生成 - * 2. exit_merge統合: break経路 + header fallthrough からexit用PHI入力を生成 - * 3. PHI最適化: 全て同じ値ならPHI不要(最適化) + * ## What This Box Does (Phase 36 Scope) + * - Exit PHI input merging with LoopVarClassBox classification + * - PHI pred mismatch prevention via LocalScopeInspectorBox + * - Header fallthrough + break snapshot merging * - * ## 効果 - * - 約210行削減(loop_builder.rs + loopform_builder.rs) - * - ValueId(1283) undefinedバグが解決する可能性が高い - * - 複雑度大幅低下 + * ## What This Box Does NOT Do (Migrated to Other Systems) + * - Header PHI generation: → LoopFormBuilder::seal_phis() + PhiInputCollector + * - PHI optimization: → PhiInputCollector::optimize_same_value() + * - PHI sanitization: → PhiInputCollector::sanitize() + * + * ## Future Migration (Phase 37+) + * - Variable classification: Consider moving to LoopScopeShape + * - Exit PHI generation: Consider JoinIR Exit lowering coverage expansion + * + * ## Design: Pure Static Utility (No State) + * This struct has no fields and provides only static methods. + * It acts as a namespace for exit PHI merging logic. + * + * ## History + * - Phase 25.2: Created for snapshot merge unification (~210 line reduction) + * - Phase 36: Removed dead code (merge_continue_for_header), superseded helpers + * - Reduced from 470 → 309 lines (34% reduction) + * - Converted to pure static utility (no state) */ use crate::mir::{BasicBlockId, ValueId}; -use std::collections::{BTreeMap, BTreeSet}; // Phase 25.1: 決定性確保 - // Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部使用のみ) +use std::collections::{BTreeMap, BTreeSet}; // Option C PHI bug fix: Use box-based classification use super::local_scope_inspector::LocalScopeInspectorBox; use super::loop_var_classifier::LoopVarClassBox; -/// Phase 25.2: continue/break/exit時のスナップショットマージを統一管理 -#[derive(Debug, Clone)] -pub struct LoopSnapshotMergeBox { - /// 各変数ごとのheader PHI入力 - // Phase 25.1: BTreeMap → BTreeMap(決定性確保) - pub header_phi_inputs: BTreeMap>, - - /// 各変数ごとのexit PHI入力 - // Phase 25.1: BTreeMap → BTreeMap(決定性確保) - pub exit_phi_inputs: BTreeMap>, -} +/// Phase 36: Pure static utility for exit PHI merging (no state) +/// +/// # Responsibility +/// +/// - Exit PHI input merging with variable classification (Option C) +/// - PHI pred mismatch prevention via availability checking +/// +/// # Design: Pure Static Utility +/// +/// This struct has no fields and provides only static methods. +/// All state is passed as function arguments. +pub struct LoopSnapshotMergeBox; impl LoopSnapshotMergeBox { - /// 空の LoopSnapshotMergeBox を作成 - // Phase 25.1: BTreeMap → BTreeMap(決定性確保) - pub fn new() -> Self { - Self { - header_phi_inputs: BTreeMap::new(), - exit_phi_inputs: BTreeMap::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)のリスト - // Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部変換) - pub fn merge_continue_for_header( - preheader_id: BasicBlockId, - preheader_vals: &BTreeMap, - latch_id: BasicBlockId, - latch_vals: &BTreeMap, - continue_snapshots: &[(BasicBlockId, BTreeMap)], - ) -> Result>, String> { - // Phase 25.1: No conversion needed - inputs are already BTreeMap - let mut result: BTreeMap> = BTreeMap::new(); - - // すべての変数名を収集(決定的順序のためBTreeSet使用) - let mut all_vars: BTreeSet = BTreeSet::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); - } - } - - // Convert result back to BTreeMap for external API compatibility - Ok(result.into_iter().collect()) - } - /// Option C: exit_merge with variable classification /// + /// # Phase 36 Essential Logic + /// + /// This method is the SSOT for exit PHI input merging with: + /// 1. LoopVarClassBox classification (Pinned/Carrier/BodyLocalInOut/BodyLocalInternal) + /// 2. LocalScopeInspectorBox availability checking + /// 3. PHI pred mismatch prevention (Option C) + /// + /// # Why This Can't Be in LoopScopeShape (Yet) + /// + /// - Variable classification needs LoopVarClassBox + LocalScopeInspectorBox + /// - Option C logic is complex and battle-tested + /// - JoinIR Exit lowering doesn't cover all patterns yet (Phase 34 partial) + /// + /// # Future Migration Path (Phase 37+) + /// + /// - Expand JoinIR Exit lowering to cover complex patterns + /// - Move classification logic to LoopScopeShape + /// - Reduce this to thin adapter or remove entirely + /// /// ## 目的 /// /// PHI pred mismatch バグを防ぐため、body-local変数が全exit predecessorで @@ -142,7 +105,6 @@ impl LoopSnapshotMergeBox { /// } /// // ch は BodyLocalInternal → exit PHI 生成しない(バグ修正!) /// ``` - // Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部変換) pub fn merge_exit_with_classification( header_id: BasicBlockId, header_vals: &BTreeMap, @@ -152,7 +114,6 @@ impl LoopSnapshotMergeBox { carrier_vars: &[String], inspector: &LocalScopeInspectorBox, ) -> Result>, String> { - // Phase 25.1: No conversion needed - inputs are already BTreeMap let mut result: BTreeMap> = BTreeMap::new(); let debug = std::env::var("NYASH_OPTION_C_DEBUG").is_ok(); @@ -264,207 +225,139 @@ impl LoopSnapshotMergeBox { } } - // Convert result back to BTreeMap for external API compatibility Ok(result.into_iter().collect()) } - - /// 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に対して最後の値を保持(決定的順序のためBTreeMap使用) - let mut seen: BTreeMap = BTreeMap::new(); - for (bb, val) in inputs.iter() { - seen.insert(*bb, *val); - } - - // BTreeMap から Vec に変換(既にソート済み、追加のsortは冗長だが互換性のため残す) - *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 merge_exit_with_classification with simple carrier variable + /// + /// Phase 36: This is the ONLY remaining test (merge_continue tests removed as dead code) #[test] - fn test_merge_continue_for_header_simple() { - let preheader_id = BasicBlockId::new(0); - let latch_id = BasicBlockId::new(1); + fn test_merge_exit_with_classification_simple_carrier() { + let header_id = BasicBlockId::new(0); + let break_bb = BasicBlockId::new(1); - let mut preheader_vals = BTreeMap::new(); - preheader_vals.insert("i".to_string(), ValueId::new(1)); - preheader_vals.insert("sum".to_string(), ValueId::new(2)); + let mut header_vals = BTreeMap::new(); + header_vals.insert("i".to_string(), ValueId::new(1)); - let mut latch_vals = BTreeMap::new(); - latch_vals.insert("i".to_string(), ValueId::new(10)); - latch_vals.insert("sum".to_string(), ValueId::new(20)); + let mut break_snap = BTreeMap::new(); + break_snap.insert("i".to_string(), ValueId::new(10)); + let exit_snapshots = vec![(break_bb, break_snap)]; - // continue なし - let continue_snapshots = vec![]; + let exit_preds = vec![header_id, break_bb]; + let pinned_vars = vec![]; + let carrier_vars = vec!["i".to_string()]; - let result = LoopSnapshotMergeBox::merge_continue_for_header( - preheader_id, - &preheader_vals, - latch_id, - &latch_vals, - &continue_snapshots, + // Create inspector and record definitions + let mut inspector = LocalScopeInspectorBox::new(); + inspector.record_definition("i", header_id); + inspector.record_definition("i", break_bb); + + let result = LoopSnapshotMergeBox::merge_exit_with_classification( + header_id, + &header_vals, + &exit_snapshots, + &exit_preds, + &pinned_vars, + &carrier_vars, + &inspector, ) .unwrap(); - // "i" と "sum" の両方に preheader + latch の入力があるはず - assert_eq!(result.len(), 2); - + // "i" should have exit PHI with inputs from header and break + assert_eq!(result.len(), 1); 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)))); + assert!(i_inputs.contains(&(header_id, ValueId::new(1)))); + assert!(i_inputs.contains(&(break_bb, ValueId::new(10)))); } + /// Test merge_exit_with_classification skips BodyLocalInternal variables + /// + /// Phase 36: Tests Option C logic (PHI pred mismatch prevention) #[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); + fn test_merge_exit_with_classification_skips_body_local_internal() { + let header_id = BasicBlockId::new(0); + let break1_bb = BasicBlockId::new(1); + let break2_bb = BasicBlockId::new(2); - let mut preheader_vals = BTreeMap::new(); - preheader_vals.insert("i".to_string(), ValueId::new(1)); + let header_vals = BTreeMap::new(); // ch not in header - let mut latch_vals = BTreeMap::new(); - latch_vals.insert("i".to_string(), ValueId::new(10)); + let mut break1_snap = BTreeMap::new(); + // break1: ch not defined yet (early exit) + let mut break2_snap = BTreeMap::new(); + break2_snap.insert("ch".to_string(), ValueId::new(20)); // ch defined in break2 + let exit_snapshots = vec![(break1_bb, break1_snap), (break2_bb, break2_snap)]; - // continue 経路 - let mut continue_snap = BTreeMap::new(); - continue_snap.insert("i".to_string(), ValueId::new(5)); - let continue_snapshots = vec![(continue_bb, continue_snap)]; + let exit_preds = vec![header_id, break1_bb, break2_bb]; + let pinned_vars = vec![]; + let carrier_vars = vec![]; - let result = LoopSnapshotMergeBox::merge_continue_for_header( - preheader_id, - &preheader_vals, - latch_id, - &latch_vals, - &continue_snapshots, + // Create inspector - ch is only defined in break2 + let mut inspector = LocalScopeInspectorBox::new(); + inspector.record_definition("ch", break2_bb); // Only in break2! + + let result = LoopSnapshotMergeBox::merge_exit_with_classification( + header_id, + &header_vals, + &exit_snapshots, + &exit_preds, + &pinned_vars, + &carrier_vars, + &inspector, ) .unwrap(); - // "i" に preheader + continue + latch の3つの入力があるはず + // "ch" should NOT have exit PHI (BodyLocalInternal - not in all exit preds) + assert_eq!(result.len(), 0, "Expected no exit PHI for BodyLocalInternal variable 'ch'"); + } + + /// Test merge_exit_with_classification with header not in exit_preds (break-only loop) + /// + /// Phase 36: Tests Case B (header not in exit preds) + #[test] + fn test_merge_exit_with_classification_break_only_loop() { + let header_id = BasicBlockId::new(0); + let break_bb = BasicBlockId::new(1); + + let mut header_vals = BTreeMap::new(); + header_vals.insert("i".to_string(), ValueId::new(1)); + + let mut break_snap = BTreeMap::new(); + break_snap.insert("i".to_string(), ValueId::new(10)); + let exit_snapshots = vec![(break_bb, break_snap)]; + + // Note: header_id NOT in exit_preds (break-only loop, no header fallthrough) + let exit_preds = vec![break_bb]; + let pinned_vars = vec![]; + let carrier_vars = vec!["i".to_string()]; + + // Create inspector + let mut inspector = LocalScopeInspectorBox::new(); + inspector.record_definition("i", header_id); + inspector.record_definition("i", break_bb); + + let result = LoopSnapshotMergeBox::merge_exit_with_classification( + header_id, + &header_vals, + &exit_snapshots, + &exit_preds, + &pinned_vars, + &carrier_vars, + &inspector, + ) + .unwrap(); + + // "i" should have exit PHI with ONLY break input (no header input) + assert_eq!(result.len(), 1); 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_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)); + assert_eq!(i_inputs.len(), 1); + assert!(i_inputs.contains(&(break_bb, ValueId::new(10)))); + assert!(!i_inputs.contains(&(header_id, ValueId::new(1))), + "Header input should be excluded when header not in exit_preds"); } } diff --git a/src/mir/phi_core/phi_builder_box.rs b/src/mir/phi_core/phi_builder_box.rs index 815dea80..f04baf53 100644 --- a/src/mir/phi_core/phi_builder_box.rs +++ b/src/mir/phi_core/phi_builder_box.rs @@ -17,6 +17,28 @@ //! └─ HeaderPhiBuilder (Phase 26-C-2完成) //! ``` //! +//! # Phase 36 Responsibility Classification +//! +//! ## Loop-only methods (if-in-loop PHI) +//! - `set_if_context()`: Set loop context for if-in-loop PHI generation +//! +//! ## If-only methods (Phase 37 target) +//! - `generate_if_phis()`: Generate If PHI nodes +//! - `compute_modified_names_if()`: Detect modified variables in If +//! - `get_conservative_if_values()`: Get conservative values for If PHI +//! +//! ## Common methods (stable API) +//! - `new()`: Create PhiBuilderBox instance +//! - `generate_phis()`: Unified PHI generation entry point (If + Loop) +//! +//! ## Loop PHI methods (stub, Phase 37+ implementation) +//! - `generate_loop_phis()`: Loop PHI generation (currently empty stub) +//! +//! ## Usage Pattern +//! - **If-in-loop PHI**: PhiBuilderBox is used for if statements inside loops (loop_builder.rs) +//! - **Pure If PHI**: PhiBuilderBox is used via facade function (phi_core/mod.rs) +//! - **Loop PHI**: Handled by LoopFormBuilder directly (NOT via PhiBuilderBox) +//! //! # Phase 26-E: PhiBuilderBox統一化 //! //! - **Phase 1**: 骨格作成(ControlForm対応インターフェース) @@ -96,6 +118,20 @@ impl PhiBuilderBox { /// Phase 26-F-3: ループ内if-mergeコンテキストを設定 /// + /// # Responsibility: Loop-only (if-in-loop PHI) + /// + /// This method is ONLY called from loop_builder.rs for if-in-loop PHI generation. + /// It sets the loop context (carrier variable names) for proper PHI generation + /// in "片腕のみの変数" cases (e.g., `if i >= n { break }`). + /// + /// ## Phase 36 Scope + /// - Called via loop_builder.rs directly (no wrapper needed) + /// - Minimal indirection for performance + /// + /// ## Phase 37+ Consideration + /// - May be internalized when loop-in-loop PHI is refactored + /// - Consider merging with IfPhiContext initialization + /// /// # Arguments /// - `in_loop_body`: ループbody内のif-mergeか /// - `loop_carrier_names`: ループキャリア変数名(Pinned + Carrier) @@ -122,6 +158,15 @@ impl PhiBuilderBox { /// ControlFormベースの統一PHI生成エントリーポイント /// + /// # Responsibility: Common (If + Loop) + /// + /// This is the STABLE API for PHI generation across both If and Loop structures. + /// Both If-side (phi_core/mod.rs) and Loop-side (loop_builder.rs) call this method. + /// + /// ## Phase 36 Note + /// - Kept as direct call (no wrapper) + /// - Internal delegation to generate_if_phis() or generate_loop_phis() + /// /// # Arguments /// /// - `ops`: PHI生成操作インターフェース @@ -158,6 +203,19 @@ impl PhiBuilderBox { /// If PHI生成(Phase 2で実装) /// + /// # Responsibility: If-only (Phase 37 target) + /// + /// Internal If PHI generation. Called from generate_phis() when ControlKind::If. + /// + /// ## Phase 36 Scope + /// - No changes (If-side deferred to Phase 37) + /// - Used by both pure If and if-in-loop cases + /// + /// ## Phase 37+ Refactor Candidates + /// - compute_modified_names_if() internalization + /// - Conservative strategy simplification + /// - Void emission optimization + /// /// # Phase 2実装 /// /// - `if_phi.rs::merge_modified_at_merge_with` の機能を統合 @@ -435,6 +493,17 @@ impl PhiBuilderBox { /// Loop PHI生成(Phase 3で実装) /// + /// # Responsibility: Loop-only (Phase 37+ implementation target) + /// + /// ## Current State (Phase 36) + /// - Empty stub (returns Err immediately) + /// - Loop PHI is handled by LoopFormBuilder directly + /// + /// ## Phase 37+ Implementation Plan + /// - Migrate LoopFormBuilder::seal_phis() logic here (~90 lines) + /// - Migrate LoopFormBuilder::build_exit_phis() logic here (~210 lines) + /// - Unify Loop PHI generation under PhiBuilderBox + /// /// # Phase 3-A 実装状況 /// /// **設計上の課題**: