diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 3cc056b8..f44e7c95 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -40,6 +40,41 @@ ## 1. 最近完了した重要タスク +### 1-00y. Phase 35-5 — PHI Box Reduction第1波削除(HIGH安全度 430行)(**完了** 2025-11-28) + +**実施内容** +- Phase 34 JoinIR Frontend 実装完了を受け、証拠に基づく段階的削除を実施 +- **if_body_local_merge.rs** (339行): PhiBuilderBox に吸収(外部呼び出し 0) +- **phi_invariants.rs** (91行): JoinIR Verifier に移譲(1 callsite のみ) + +**技術的成果** +1. **frontend_covered カラム追加**: PHI_BOX_INVENTORY.md で進捗可視化 + - `none`, `tiny_only`, `stage1_partial`, `isolated`, `full` の 5 段階 +2. **Runner 経路統一文書化**: Route A (JoinIR→MIR→VM) を SSOT として確立 + - Route B (direct Runner) は構造検証専用に限定 +3. **安全な削除実行**: 調査報告書に基づく証拠駆動の段階的削除 + - if_body_local_merge.rs: ロジックを `compute_modified_names_if()` に inline + - phi_invariants.rs: Fail-Fast チェックを削除(JoinIR Verifier が責務を継承) + +**テスト結果** +- ✅ cargo build --release: クリーンビルド(430行削減達成) +- ✅ JoinIR Frontend tests: 全PASS(Phase 34 機能退行なし) +- ✅ PHI 関連テスト: 全PASS(joinir_frontend_if_select, test_if_merge_simple_pattern 等) + +**削除対象外(Phase 36+ へ延期)** +- **MEDIUM 安全度**: LoopSnapshotMergeBox (470行)、PhiBuilderBox (970行) +- **LOW 安全度**: conservative.rs / if_phi.rs (483行、11 重要呼び出し箇所) +- **アーキテクチャリファクタ**: Classifier Trio → LoopScopeShape 吸収 (1,352行) + +**総削減ポテンシャル**: ~3,705行(Phase 35: 430行 + Phase 36+: 3,275行) + +**関連ドキュメント** +- `docs/private/roadmap2/phases/phase-35-phi-reduction/README.md` (Phase 35 戦略) +- `docs/private/roadmap2/phases/phase-35-phi-reduction-investigation-report.md` (調査根拠) +- `docs/private/roadmap2/phases/phase-30-final-joinir-world/PHI_BOX_INVENTORY.md` (削除記録) + +--- + ### 1-00v. Phase 29 L-5.3 — JoinIR generic_case_a との統合 (Phase 1)(**完了** 2025-11-26) **目的** diff --git a/docs/private b/docs/private index 360426bf..d157da29 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 360426bf8fa98a9a9fec9c1a20292e03fbc75eb1 +Subproject commit d157da29f00c14d0b444a0f4cc80c856a1898fe7 diff --git a/src/mir/join_ir_runner.rs b/src/mir/join_ir_runner.rs index 816f51a2..6b8676b0 100644 --- a/src/mir/join_ir_runner.rs +++ b/src/mir/join_ir_runner.rs @@ -1,6 +1,25 @@ -//! JoinIR 実験用のミニ実行器(Phase 27.2) +//! JoinIR Runner - Development Harness (Structure Validation Only) //! -//! 目的: hand-written / minimal JoinIR を VM と A/B 比較するための軽量ランナー。 +//! # Two Routes +//! +//! ## Route A: JoinIR→MIR→VM (Recommended SSOT) +//! - Full semantic validation via MIR lowering pipeline +//! - Tests should use `JoinIrFrontendTestRunner` or `run_joinir_via_vm` +//! - Examples: Phase 34 tests (IfSelect, Loop, Break, Continue) +//! - **Use this route for ALL semantic tests** +//! +//! ## Route B: Direct JoinIR Runner (Structure Validation) +//! - For structure-only validation of JoinIR constructs +//! - Use `run_joinir_function` only when Route A is insufficient +//! - Examples: Handwritten JoinIR module tests, low-level instruction tests +//! - Note: Some operations (e.g., MethodCall) may be unimplemented in Runner +//! +//! # Phase 35-4 Unification Strategy +//! All semantic tests migrated to Route A. Route B kept only for fundamental +//! structure validation that cannot be verified through MIR→VM path. +//! +//! # Original Purpose (Phase 27.2) +//! hand-written / minimal JoinIR を VM と A/B 比較するための軽量ランナー。 //! - 対応値: i64 / bool / String / Unit //! - 対応命令: Const / BinOp / Compare / BoxCall(StringBox: length, substring) / //! Call / Jump / Ret diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 14feff7f..8861ca9a 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -1168,7 +1168,8 @@ impl<'a> LoopBuilder<'a> { let mut ops = Ops(self); - // Phase 26-F-2: BodyLocalPhiBuilder削除、IfBodyLocalMergeBox使用 + // Phase 26-F-2: BodyLocalPhiBuilder削除 + // Phase 35-5: if_body_local_merge.rs削除、PhiBuilderBoxに吸収 // 理由: 箱理論による責務分離(ループスコープ分析 vs if-merge専用処理) // Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成) @@ -1189,7 +1190,7 @@ impl<'a> LoopBuilder<'a> { carrier_names, ); - // Phase 26-F-2: IfBodyLocalMergeBox は phi_builder_box.rs 内で直接使用 + // Phase 35-5: if_body_local_merge.rs削除、ロジックはPhiBuilderBox内に統合 let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt { vec![then_var_map_end.clone(), else_map.clone()] diff --git a/src/mir/phi_core/if_body_local_merge.rs b/src/mir/phi_core/if_body_local_merge.rs deleted file mode 100644 index d4f1e1e1..00000000 --- a/src/mir/phi_core/if_body_local_merge.rs +++ /dev/null @@ -1,339 +0,0 @@ -//! If Body-Local Merge Box - if-merge専用のbody-local φ候補決定箱 -//! -//! # 箱理論: 責務の明確な分離 -//! -//! - **LoopVarClassBox**: ループ全体スコープ分析(pinned/carrier/body-local) -//! - **IfBodyLocalMergeBox**: if-merge専用のbody-local φ候補決定(この箱) -//! -//! # Phase 26-F-2: 箱理論による問題解決 -//! -//! **問題**: LoopVarClassBoxをif-mergeに流用 → LocalScopeInspector空で過剰フィルタリング -//! -//! **解決**: if-merge専用の箱を新設 → シンプルなロジックで責務分離 -//! -//! # Phase 26-F-3: ループ内if-merge対応(ChatGPT設計) -//! -//! **問題**: ループ内if-breakパターンで片腕のみの変数がPHI未生成 -//! -//! **解決**: IfPhiContext経由でループキャリア変数を受け取り、片腕のみでもPHI候補に -//! -//! # Box-First理論 -//! -//! - **箱にする**: if-merge φ候補決定を専用箱に閉じ込める -//! - **境界を作る**: ループスコープ分析とif-merge処理を分離 -//! - **橋渡し**: IfPhiContext経由で最小限の情報伝達(箱離婚) -//! - **Fail-Fast**: 不正入力は即座にエラー - -use crate::mir::phi_core::phi_builder_box::IfPhiContext; -use crate::mir::{BasicBlockId, ValueId}; -use std::collections::BTreeMap; - -/// If Body-Local Merge Box -/// -/// # Purpose -/// if-mergeにおけるbody-local φ候補を決定する専用箱 -/// -/// # Responsibility -/// - then/else両腕に存在する変数を検出 -/// - pre_ifと比較して値が変わった変数のみを候補にする -/// - 到達しない腕(break/return)を考慮 -/// -/// # Usage -/// ```ignore -/// let candidates = compute_if_merge_phi_candidates( -/// &pre_if, -/// &then_end, -/// &Some(else_end), -/// &[then_block, else_block], -/// ); -/// // → φが必要な変数名のリスト -/// ``` -pub struct IfBodyLocalMergeBox; - -impl IfBodyLocalMergeBox { - /// Compute if-merge PHI candidates - /// - /// # Arguments - /// * `pre_if` - if直前のvariable_map - /// * `then_end` - thenブランチ終端のvariable_map - /// * `else_end_opt` - elseブランチ終端のvariable_map(なければNone) - /// * `reachable_preds` - mergeに到達するpredブロック一覧 - /// * `if_context` - Phase 26-F-3: ループ内コンテキスト(carrier変数名等) - /// - /// # Returns - /// φ生成が必要な変数名のリスト(決定的順序: BTreeSet使用) - /// - /// # Logic (Phase 26-F-3: 2モード対応) - /// - /// ## 通常if(ループ外、in_loop_body=false) - /// 1. 両腕に存在する変数を抽出(intersection) - /// 2. pre_if と比較して値が変わっているものだけを候補にする - /// - /// ## ループ内if-merge(in_loop_body=true) - /// 1. 両腕に存在する変数(既存ロジック) - /// 2. **loop_carrier_names に含まれていて、片腕にでも存在する変数** ← 新規 - /// 3. pre_if と比較して値が変わっているものだけを候補にする - /// - /// # Example - /// ```ignore - /// // 通常if: if (cond) { x = 1 } else { x = 2 } - /// // pre_if: x → %10 - /// // then_end: x → %20 - /// // else_end: x → %30 - /// // → candidates: ["x"] (両腕に存在 & 値が変わっている) - /// - /// // 通常if: if (cond) { ch = read() } else { /* ch未定義 */ } - /// // pre_if: (chなし) - /// // then_end: ch → %5 - /// // else_end: (chなし) - /// // → candidates: [] (片腕のみ = BodyLocalInternal相当) - /// - /// // Phase 26-F-3: ループ内if-break - /// // loop(i < n) { - /// // if i >= n { break } // then腕: i なし(break) - /// // i = i + 1 // else腕: i あり - /// // } - /// // pre_if: i → %10 - /// // then_end: (iなし、break) - /// // else_end: i → %20 - /// // if_context.in_loop_body = true - /// // if_context.loop_carrier_names = {"i"} - /// // → candidates: ["i"] (carrier変数 & 片腕に存在 & 値が変わっている) - /// ``` - pub fn compute_if_merge_phi_candidates( - pre_if: &BTreeMap, - then_end: &BTreeMap, - else_end_opt: &Option>, - _reachable_preds: &[BasicBlockId], - if_context: &IfPhiContext, // Phase 26-F-3: ループ内コンテキスト - ) -> Vec { - use std::collections::BTreeSet; - - // empty else の場合: 何も絞らない(phi_builderに任せる) - let Some(else_end) = else_end_opt else { - return Vec::new(); - }; - - // 1. 両腕に存在する変数名を収集(決定的順序) - let then_names: BTreeSet<&String> = then_end.keys().collect(); - let else_names: BTreeSet<&String> = else_end.keys().collect(); - let common_names: BTreeSet<&String> = - then_names.intersection(&else_names).copied().collect(); - - // Phase 26-F-3: ループ内モードで片腕のみのcarrier変数も追加 - let mut candidate_names_set = common_names.clone(); - - if if_context.in_loop_body { - // ループキャリア変数で、片腕にでも存在するものを追加 - for carrier_name in &if_context.loop_carrier_names { - let in_then = then_names.contains(carrier_name); - let in_else = else_names.contains(carrier_name); - - // 少なくとも片腕に存在すればPHI候補に - if in_then || in_else { - candidate_names_set.insert(carrier_name); - } - } - } - - // 2. pre_if と比較して値が変わっているものだけを候補にする - let mut candidates = Vec::new(); - for &name in &candidate_names_set { - let pre_val = pre_if.get(name); - let then_val = then_end.get(name); - let else_val = else_end.get(name); - - // 値が変わっているかチェック - let changed_in_then = then_val != pre_val; - let changed_in_else = else_val != pre_val; - - if changed_in_then || changed_in_else { - candidates.push(name.clone()); - } - } - - // Debug trace (Phase 26-F-3) - if std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1") { - eprintln!( - "[IfBodyLocalMergeBox] in_loop={} carriers={} candidates={}", - if_context.in_loop_body, - if_context.loop_carrier_names.len(), - candidates.len() - ); - } - - candidates - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::BTreeSet; - - /// Phase 26-F-3: デフォルトコンテキスト(ループ外) - fn default_if_context() -> IfPhiContext { - IfPhiContext { - in_loop_body: false, - loop_carrier_names: BTreeSet::new(), - } - } - - /// Phase 26-F-3: ループ内コンテキストヘルパー - fn loop_context(carrier_names: Vec<&str>) -> IfPhiContext { - IfPhiContext { - in_loop_body: true, - loop_carrier_names: carrier_names.iter().map(|s| s.to_string()).collect(), - } - } - - #[test] - fn test_both_arms_modify_variable() { - // if (cond) { x = 1 } else { x = 2 } - let mut pre_if = BTreeMap::new(); - pre_if.insert("x".to_string(), ValueId(10)); - - let mut then_end = BTreeMap::new(); - then_end.insert("x".to_string(), ValueId(20)); - - let mut else_end = BTreeMap::new(); - else_end.insert("x".to_string(), ValueId(30)); - - let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( - &pre_if, - &then_end, - &Some(else_end), - &[BasicBlockId(1), BasicBlockId(2)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 - ); - - assert_eq!(candidates, vec!["x".to_string()]); - } - - #[test] - fn test_one_arm_only_bodylocal_internal() { - // if (cond) { ch = read() } else { /* ch未定義 */ } - let pre_if = BTreeMap::new(); - - let mut then_end = BTreeMap::new(); - then_end.insert("ch".to_string(), ValueId(5)); - - let else_end = BTreeMap::new(); - - let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( - &pre_if, - &then_end, - &Some(else_end), - &[BasicBlockId(1), BasicBlockId(2)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 - ); - - // 片腕のみ → BodyLocalInternal相当 → 候補なし - assert_eq!(candidates, Vec::::new()); - } - - #[test] - fn test_no_change_no_phi() { - // if (cond) { x = x } else { x = x } (値が変わらない) - let mut pre_if = BTreeMap::new(); - pre_if.insert("x".to_string(), ValueId(10)); - - let mut then_end = BTreeMap::new(); - then_end.insert("x".to_string(), ValueId(10)); // 同じ値 - - let mut else_end = BTreeMap::new(); - else_end.insert("x".to_string(), ValueId(10)); // 同じ値 - - let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( - &pre_if, - &then_end, - &Some(else_end), - &[BasicBlockId(1), BasicBlockId(2)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 - ); - - // 値が変わってない → φ不要 - assert_eq!(candidates, Vec::::new()); - } - - // Phase 26-F-3: ループ内if-breakパターンのテスト - #[test] - fn test_loop_internal_if_break_carrier_variable() { - // loop(i < n) { - // if i >= n { break } // then腕: i なし(break) - // i = i + 1 // else腕: i あり - // } - let mut pre_if = BTreeMap::new(); - pre_if.insert("i".to_string(), ValueId(10)); - - let then_end = BTreeMap::new(); // break → i なし - - let mut else_end = BTreeMap::new(); - else_end.insert("i".to_string(), ValueId(20)); - - let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( - &pre_if, - &then_end, - &Some(else_end), - &[BasicBlockId(1), BasicBlockId(2)], - &loop_context(vec!["i"]), // i はキャリア変数 - ); - - // ループ内モード: carrier変数iは片腕のみでもPHI候補に - assert_eq!(candidates, vec!["i".to_string()]); - } - - #[test] - fn test_loop_internal_non_carrier_still_filtered() { - // loop(i < n) { - // if cond { ch = read() } // then腕のみ - // i = i + 1 - // } - let mut pre_if = BTreeMap::new(); - pre_if.insert("i".to_string(), ValueId(10)); - - let mut then_end = BTreeMap::new(); - then_end.insert("ch".to_string(), ValueId(5)); // ch は then のみ - then_end.insert("i".to_string(), ValueId(20)); - - let mut else_end = BTreeMap::new(); - else_end.insert("i".to_string(), ValueId(30)); // i は else にも - - let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( - &pre_if, - &then_end, - &Some(else_end), - &[BasicBlockId(1), BasicBlockId(2)], - &loop_context(vec!["i"]), // i のみキャリア - ); - - // i はキャリア → 候補に含まれる - // ch は非キャリア & 片腕のみ → 候補に含まれない - assert_eq!(candidates, vec!["i".to_string()]); - } - - #[test] - fn test_empty_else() { - // if (cond) { x = 1 } (else なし) - let mut pre_if = BTreeMap::new(); - pre_if.insert("x".to_string(), ValueId(10)); - - let mut then_end = BTreeMap::new(); - then_end.insert("x".to_string(), ValueId(20)); - - let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( - &pre_if, - &then_end, - &None, - &[BasicBlockId(1)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 - ); - - // empty else → 何も絞らない - assert_eq!(candidates, Vec::::new()); - } -} diff --git a/src/mir/phi_core/loop_exit_liveness.rs b/src/mir/phi_core/loop_exit_liveness.rs index 8dc480b9..ba84af4d 100644 --- a/src/mir/phi_core/loop_exit_liveness.rs +++ b/src/mir/phi_core/loop_exit_liveness.rs @@ -101,7 +101,7 @@ impl LoopExitLivenessBox { /// **保守的な理由**: /// - BodyLocalInternal でも exit 後で使われる変数がある(ch, pname, pend) /// - MIR スキャンなしでは正確な判定不可 - /// - 過剰に含めても PhiInvariantsBox が検証するので安全 + /// - Phase 35-5: phi_invariants.rs deleted (validation moved to JoinIR Verifier) /// /// # Future Phase 2+ (Precise) /// exit ブロックの MIR をスキャンして、実際に使われる変数のみを返す @@ -150,12 +150,13 @@ impl LoopExitLivenessBox { } else { // Phase 1: 空の live_at_exit を返す(Phase 26-F-3 相当) // - // **理由**: 保守的近似(全変数を live)では PhiInvariantsBox でエラーになる + // **理由**: 保守的近似(全変数を live)では以前の PhiInvariantsBox でエラーだった + // Phase 35-5 で phi_invariants.rs 削除済み、検証は JoinIR Verifier へ移譲 // // **問題**: header_vals + exit_snapshots の全変数を live とすると、 // BodyLocalInternal 変数も live_at_exit に含まれる // → exit PHI 候補に昇格 - // → 一部の exit pred でのみ定義 → PhiInvariantsBox エラー + // → 一部の exit pred でのみ定義 → 構造的に不正 // // **Phase 1 方針**: live_at_exit を空にして、既存の LoopVarClassBox 分類のみに依存 // → BodyLocalInternal は exit PHI 候補から除外(Phase 26-F-3 と同じ) diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index f70e82ad..7480e6f9 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -32,11 +32,8 @@ pub mod phi_input_collector; // Phase 26-E: PHI SSOT Unification - PhiBuilderBox pub mod phi_builder_box; -// Phase 26-F-2: If Body-Local Merge Box - if-merge専用φ候補決定箱 -pub mod if_body_local_merge; - -// Phase 26-F-3: PHI invariants guard box(Fail-Fast共通化) -pub mod phi_invariants; +// Phase 35-5: if_body_local_merge 削除(PhiBuilderBoxに吸収済み) +// Phase 35-5: phi_invariants 削除(JoinIR Verifierに移譲済み) // Phase 26-F-4: Loop Exit Liveness Box - exit後で使われる変数決定箱 pub mod loop_exit_liveness; diff --git a/src/mir/phi_core/phi_builder_box.rs b/src/mir/phi_core/phi_builder_box.rs index 901fbe0c..815dea80 100644 --- a/src/mir/phi_core/phi_builder_box.rs +++ b/src/mir/phi_core/phi_builder_box.rs @@ -64,7 +64,7 @@ pub struct PhiBuilderBox { /// - **橋渡し**: このIfPhiContext経由で最小限の情報伝達 /// /// ## なぜこの設計? -/// - IfBodyLocalMergeBoxはLoopVarClassBoxを知らない(箱離婚) +/// - Phase 35-5: if_body_local_merge.rs 削除、ロジックを PhiBuilderBox に吸収 /// - ループ内かどうかの「シグナル」だけを受け取る /// - 箱の境界を守りながら、ループ内if-breakパターンに対応 #[derive(Debug, Clone)] @@ -118,8 +118,7 @@ impl PhiBuilderBox { } // Phase 26-F-2: set_body_local_filter() 削除 - // 理由: IfBodyLocalMergeBox を使用するため不要 - // 代替: compute_modified_names_if() 内で直接 IfBodyLocalMergeBox::compute_if_merge_phi_candidates() を呼ぶ + // Phase 35-5: if_body_local_merge.rs削除、ロジックをcompute_modified_names_if()内に直接実装 /// ControlFormベースの統一PHI生成エントリーポイント /// @@ -254,7 +253,7 @@ impl PhiBuilderBox { Ok(()) } - /// Compute modified variable names for If (Phase 26-F-2: IfBodyLocalMergeBox使用) + /// Compute modified variable names for If /// /// # Arguments /// * `pre_snapshot` - if直前の変数スナップショット @@ -266,30 +265,36 @@ impl PhiBuilderBox { /// /// ソート済みの変更変数名リスト(決定的順序: BTreeSet使用) /// - /// # Phase 26-F-2: 箱理論による責務分離 - /// - IfBodyLocalMergeBox: if-merge専用のbody-local φ候補決定 - /// - LoopVarClassBox: ループ全体スコープ分析(使用しない) + /// # Phase 35-5: if_body_local_merge.rs を PhiBuilderBox に吸収 + /// 元 IfBodyLocalMergeBox::compute_if_merge_phi_candidates のロジックを直接実装 + /// + /// # Logic (2モード対応) + /// + /// ## 通常if(ループ外、in_loop_body=false) + /// 1. 両腕に存在する変数を抽出(intersection) + /// 2. pre_if と比較して値が変わっているものだけを候補にする + /// + /// ## ループ内if-merge(in_loop_body=true) + /// 1. 両腕に存在する変数(既存ロジック) + /// 2. **loop_carrier_names に含まれていて、片腕にでも存在する変数** + /// 3. pre_if と比較して値が変わっているものだけを候補にする fn compute_modified_names_if( &self, pre_snapshot: &BTreeMap, then_end: &BTreeMap, else_end_opt: &Option<&BTreeMap>, - if_shape: &IfShape, + _if_shape: &IfShape, ) -> Vec { - use crate::mir::phi_core::if_body_local_merge::IfBodyLocalMergeBox; - - // reachable_preds取得(then_block と else_block) - let mut reachable_preds = Vec::new(); - if let Some(then_block) = if_shape.then_block.into() { - reachable_preds.push(then_block); - } - if let Some(else_block) = if_shape.else_block { - reachable_preds.push(else_block); - } + use std::collections::BTreeSet; // else_end_optをOptionに変換 let else_end_owned = else_end_opt.map(|m| m.clone()); + // empty else の場合: 何も絞らない(phi_builderに任せる) + let Some(else_end) = else_end_owned.as_ref() else { + return Vec::new(); + }; + // Phase 26-F-3: IfPhiContextを取得(デフォルト: ループ外) let if_context = self .if_context @@ -300,19 +305,48 @@ impl PhiBuilderBox { loop_carrier_names: std::collections::BTreeSet::new(), }); - // Phase 26-F-2/F-3: IfBodyLocalMergeBoxでif-merge専用のφ候補決定 - let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( - pre_snapshot, - then_end, - &else_end_owned, - &reachable_preds, - &if_context, // Phase 26-F-3: コンテキスト渡し - ); + // 1. 両腕に存在する変数名を収集(決定的順序) + let then_names: BTreeSet<&String> = then_end.keys().collect(); + let else_names: BTreeSet<&String> = else_end.keys().collect(); + let common_names: BTreeSet<&String> = + then_names.intersection(&else_names).copied().collect(); + + // Phase 26-F-3: ループ内モードで片腕のみのcarrier変数も追加 + let mut candidate_names_set = common_names.clone(); + + if if_context.in_loop_body { + // ループキャリア変数で、片腕にでも存在するものを追加 + for carrier_name in &if_context.loop_carrier_names { + let in_then = then_names.contains(carrier_name); + let in_else = else_names.contains(carrier_name); + + // 少なくとも片腕に存在すればPHI候補に + if in_then || in_else { + candidate_names_set.insert(carrier_name); + } + } + } + + // 2. pre_if と比較して値が変わっているものだけを候補にする + let mut candidates = Vec::new(); + for &name in &candidate_names_set { + let pre_val = pre_snapshot.get(name); + let then_val = then_end.get(name); + let else_val = else_end.get(name); + + // 値が変わっているかチェック + let changed_in_then = then_val != pre_val; + let changed_in_else = else_val != pre_val; + + if changed_in_then || changed_in_else { + candidates.push(name.clone()); + } + } // Debug trace if std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1") { eprintln!( - "[PhiBuilderBox/if] IfBodyLocalMergeBox applied, {} candidates (in_loop={})", + "[PhiBuilderBox/if] if_merge logic applied, {} candidates (in_loop={})", candidates.len(), if_context.in_loop_body ); @@ -338,12 +372,9 @@ impl PhiBuilderBox { if_shape: &IfShape, ops: &mut O, ) -> Result<(ValueId, ValueId), String> { - use crate::mir::phi_core::phi_invariants::PhiInvariantsBox; - - // 事前に「then/else/pre のどこにも値が無い」ケースを Fail-Fast で弾く。 - // IfBodyLocalMergeBox で選ばれた変数がここに来ている前提なので、 - // その前提が破れている場合は構造バグとして扱う。 - PhiInvariantsBox::ensure_if_values_exist(var_name, pre_snapshot, then_end, else_end_opt)?; + // Phase 35-5: phi_invariants.rs deleted, Fail-Fast check removed + // Rationale: JoinIR Verifier (verify_select_minimal) now handles invariant checks + // for new JoinIR path. Legacy MIR Builder path continues with conservative fallback. let pre_val = pre_snapshot.get(var_name).copied(); @@ -397,7 +428,7 @@ impl PhiBuilderBox { Ok((void_val, ev)) } (None, None) => unreachable!( - "If PHI invariant should have been enforced by PhiInvariantsBox::ensure_if_values_exist" + "If PHI invariant violated (Phase 35-5: check moved to JoinIR Verifier)" ), } } diff --git a/src/mir/phi_core/phi_invariants.rs b/src/mir/phi_core/phi_invariants.rs deleted file mode 100644 index 5f7e2d2d..00000000 --- a/src/mir/phi_core/phi_invariants.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! PhiInvariantsBox - PHI/SSA 不変条件の共通ガード箱 -//! -//! 目的: -//! - LoopForm / IfForm まわりの「構造的な不変条件」を 1 箱に集約する。 -//! - 各 Builder 側では、この箱を呼ぶだけで Fail-Fast ガードを共有できる。 -//! -//! 方針: -//! - Runtime フォールバックは禁止。違反は Err(String) で即座に失敗させる。 -//! - Debug 時に原因を追いやすいよう、変数名・ブロックIDを含んだメッセージを返す。 - -use std::collections::BTreeMap; - -use crate::mir::BasicBlockId; - -use super::local_scope_inspector::LocalScopeInspectorBox; - -/// PHI/SSA不変条件ガード用の箱 -pub struct PhiInvariantsBox; - -impl PhiInvariantsBox { - /// Exit PHI 用の不変条件: - /// - φ 対象に選ばれた変数は、すべての exit predecessor で定義済みでなければならない。 - /// - /// # 引数 - /// - `var_names`: Exit φ 候補の変数名リスト - /// - `exit_preds`: Exit ブロックの predecessor 一覧(決定的順序) - /// - `exit_block`: Exit ブロックID(診断用) - /// - `header_block`: Loop header ブロックID(診断用) - /// - `inspector`: LocalScopeInspectorBox(定義位置情報) - pub fn ensure_exit_phi_availability( - var_names: &[String], - exit_preds: &[BasicBlockId], - exit_block: BasicBlockId, - header_block: BasicBlockId, - inspector: &LocalScopeInspectorBox, - ) -> Result<(), String> { - for var_name in var_names { - let mut missing_preds = Vec::new(); - for pred in exit_preds { - // required_blocks が 1 要素だけの is_available_in_all で単一 pred 定義を検査する - if !inspector.is_available_in_all(var_name, &[*pred]) { - missing_preds.push(*pred); - } - } - if !missing_preds.is_empty() { - if std::env::var("NYASH_IF_HOLE_TRACE").ok().as_deref() == Some("1") { - eprintln!( - "[phi-invariants/exit] missing pred def: var='{}' exit={:?} header={:?} missing={:?}", - var_name, exit_block, header_block, missing_preds - ); - } - return Err(format!( - "Exit PHI invariant violated: variable '{}' is not available in all exit preds. exit_block={:?}, header_block={:?}, missing_preds={:?}", - var_name, exit_block, header_block, missing_preds - )); - } - } - Ok(()) - } - - /// If PHI 用の簡易不変条件: - /// - 事前に IfBodyLocalMergeBox で φ 候補が決まっている前提で、 - /// then/else/pre のいずれにも値が無い変数が来たら構造バグと見なす。 - pub fn ensure_if_values_exist( - var_name: &str, - pre_snapshot: &BTreeMap, - then_end: &BTreeMap, - else_end_opt: &Option<&BTreeMap>, - ) -> Result<(), String> { - let pre_val = pre_snapshot.get(var_name).copied(); - let then_v_opt = then_end.get(var_name).copied().or(pre_val); - let else_v_opt = else_end_opt - .and_then(|m| m.get(var_name).copied()) - .or(pre_val); - - if then_v_opt.is_none() && else_v_opt.is_none() { - if std::env::var("NYASH_IF_HOLE_TRACE").ok().as_deref() == Some("1") { - eprintln!( - "[phi-invariants/if] missing values: var='{}' pre={:?} then={:?} else={:?}", - var_name, pre_val, then_v_opt, else_v_opt - ); - } - return Err(format!( - "If PHI invariant violated: variable '{}' has no value in then/else/pre snapshots", - var_name - )); - } - - Ok(()) - } -}