diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 1a5f7f4d..41b04ee8 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -10,7 +10,7 @@ - **✅ JoinIR ラインは Phase 68 で一旦 Chapter Close!** - Phase 27-67 で JoinIR の「第1章(構造 + PHI + 型ヒント SSOT)」が完了。 - 4つの柱(Structure / Scope / JoinIR / Type Hints)が確立。 - - 今後は Trio 削除ライン、wasm/Web デモライン、最適化ラインに分岐。 + - Trio 削除ライン(Phase 70 完了)を経て、wasm/Web デモラインと最適化ラインに分岐。 - 詳細: [phase-30-final-joinir-world/README.md](docs/private/roadmap2/phases/phase-30-final-joinir-world/README.md) - **最終ゴール** @@ -19,9 +19,9 @@ - 既存の PHI 箱(if_phi.rs / PhiBuilderBox / conservative.rs / Trio 等)は、JoinIR 側のカバレッジが十分になったところから順に削っていく。 - **これから(Phase 69+)** - - Trio 削除ライン: LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox を LoopScopeShape に完全吸収。 - wasm/Web デモライン: JoinIR ベースの軽量デモ実装。 - 最適化ライン: JoinIR の最適化パスと LLVM/ny-llvmc 統合。 + - Trio 削除ライン: 完了(Phase 70、LoopScopeShape SSOT) --- @@ -94,16 +94,15 @@ - 69-2: `merge_exit_with_classification` から Inspector 引数を削除し、LoopScopeShape/ExitAnalysis 経由で必要な情報を取る形に縮退(約 42 行削減)。既存の 3 テストはすべて PASS。 - 69-3: `BasicBlock.predecessors` を `HashSet` → `BTreeSet` に変更するなど、MIR の predecessor イテレーションを決定的にし、これまで非決定性でフラッキーだった 2 つのループ系テストを安定化。loopform 14/14 / merge_exit 3/3 を含む関連テストはすべて PASS。 - 未了: - - 69-4: Trio 3 箱(LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox)の本体削除は、legacy bridge 完全移行後の Phase 67+ で扱う。 - 69-5: conservative.rs の docs/ 移設も今後の小フェーズとして残しておく。 +- 追加完了 (Phase 70): + - 69-4: Trio 3 箱(LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox)を削除し、LoopScopeShape を SSOT とする構成に移行。 ## 2. 次の一手(Phase 69+) ### 直近の候補タスク -- **Trio 削除ライン**(Phase 69 候補) - - LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox を LoopScopeShape に完全吸収 - - Phase 48-7: Trio/LoopScopeShape の将来扱い整理(docs-only) +- **Phase 70 完了**: Trio 3 箱削除(LoopScopeShape SSOT) - **P3-C 拡大 / If PHI 本体削除**(Phase 70 候補) - GenericTypeResolver 経由で全 P3-C ケースをカバー diff --git a/docs/development/current/main/phase69-4-trio-deletion-plan.md b/docs/development/current/main/phase69-4-trio-deletion-plan.md index 56026e68..110e3f66 100644 --- a/docs/development/current/main/phase69-4-trio-deletion-plan.md +++ b/docs/development/current/main/phase69-4-trio-deletion-plan.md @@ -500,3 +500,31 @@ Phase 70 実装完了と判定する条件: **Phase 70 実装開始準備完了!** 🚀 **次のステップ**: Phase 70 実装(見積もり3時間、削減見込み ~1,443行) + +--- + +## 🛠 Phase 70-1 / 70-2 実施メモ(2025-11-30) + +### 70-1: loop_form_intake.rs Trio 使用削除 ✅ + +- 変更ファイル: `src/mir/join_ir/lowering/loop_form_intake.rs` +- 内容: + - LocalScopeInspectorBox / LoopVarClassBox を使った変数分類ロジックを完全削除。 + - `pinned_hint` / `carrier_hint` から `BTreeSet` ベースで `ordered_pinned` / `ordered_carriers` を作る薄い箱に縮退。 + - 実際の pinned/carrier 判定は `LoopScopeShape::from_loop_form()` 側に一本化(二重分類問題の解消)。 +- 行数: 29 行 → 2 行(約 27 行削減)。 +- テスト: loopform 14/14 PASS。 + +### 70-2: loop_to_join.rs 呼び出し側修正 ✅ + +- 変更ファイル: `src/mir/join_ir/lowering/loop_to_join.rs` +- 内容: + - `intake_loop_form(loop_form, &Default::default(), &query, func)` を `intake_loop_form(loop_form, &query, func)` に変更。 + - Trio のダミー引数を削除し、JoinIR lowering からの Trio 依存を 0 に。 +- テスト: + - loopform テストは 70-1 と同じく 14/14 PASS。 + - `cargo test --release` 全体は既知の 39 失敗を含むが、新規エラーの追加はなし。 + +Phase 70-1/2 により、LoopToJoinLowerer 側からは完全に Trio が姿を消し、 +LoopScopeShape が pinned/carrier/exit_live の SSOT になった。 +Phase 70-3 以降では json_v0_bridge と phi_core 本体に残っている Trio を設計通りに畳んでいく。 diff --git a/src/mir/join_ir/lowering/generic_case_a.rs b/src/mir/join_ir/lowering/generic_case_a.rs index a1524fde..bb5b989d 100644 --- a/src/mir/join_ir/lowering/generic_case_a.rs +++ b/src/mir/join_ir/lowering/generic_case_a.rs @@ -3,7 +3,7 @@ //! 制約(必読): //! - 条件式の中身を解析しない(Compare/BinOp を MIR そのままコピーするだけ) //! - 多重ヘッダ/ネストループは対象外(v1 は minimal_ssa_skip_ws の単純ループ専用) -//! - pinned/carrier/exit は LoopVarClassBox / LoopExitLivenessBox から渡された前提で扱う +//! - pinned/carrier/exit は LoopScopeShape から渡された前提で扱う //! - 解析に失敗したら必ず None を返し、呼び元にフォールバックさせる use std::collections::BTreeMap; diff --git a/src/mir/join_ir/lowering/loop_form_intake.rs b/src/mir/join_ir/lowering/loop_form_intake.rs index 11e4ba93..8dbf2e63 100644 --- a/src/mir/join_ir/lowering/loop_form_intake.rs +++ b/src/mir/join_ir/lowering/loop_form_intake.rs @@ -4,9 +4,10 @@ //! - LoopForm + MirQuery + MirFunction から、JoinIR 降下に必要な情報を抽出する。 //! - pinned/carrier 推定と ValueId マッピング、header/exit スナップショット収集を一元化。 //! - generic_case_a/B など複数ロワーで再利用するための「入口箱」だよ。 +//! +//! Phase 70-1: Trio (LocalScopeInspectorBox, LoopVarClassBox) 依存削除 +//! - 変数分類は LoopScopeShape::from_loop_form() に一本化(二重分類問題解消) -use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox; -use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox}; use crate::mir::{BasicBlockId, MirFunction, MirInstruction, MirQuery, ValueId}; use std::collections::{BTreeMap, BTreeSet}; @@ -21,10 +22,12 @@ pub(crate) struct LoopFormIntake { /// LoopForm + MIR から pinned/carrier とスナップショットを抽出する。 /// +/// Phase 70-1: var_classes 引数削除(Trio 依存排除) +/// 実際の変数分類は呼び出し側が LoopScopeShape::from_loop_form() で実施 +/// /// 失敗時は None(フォールバック用)。 pub(crate) fn intake_loop_form( loop_form: &crate::mir::loop_form::LoopForm, - var_classes: &LoopVarClassBox, query: &impl MirQuery, mir_func: &MirFunction, ) -> Option { @@ -165,36 +168,10 @@ pub(crate) fn intake_loop_form( } } - // LocalScopeInspector に snapshot を登録して VarClass 判定の土台を整える - let mut inspector = LocalScopeInspectorBox::new(); - inspector.record_snapshot(loop_form.header, &header_vals_mir); - for (bb, snap) in &exit_snapshots { - inspector.record_snapshot(*bb, snap); - } - - let all_names: Vec = header_vals_mir.keys().cloned().collect(); - let classified = var_classes.classify_all( - &all_names, - &pinned_hint, - &carrier_hint, - &inspector, - &exit_preds, - ); - - let ordered_pinned: Vec = classified - .iter() - .filter(|(_, c)| matches!(c, LoopVarClass::Pinned)) - .map(|(n, _)| n.clone()) - .collect::>() - .into_iter() - .collect(); - let ordered_carriers: Vec = classified - .iter() - .filter(|(_, c)| matches!(c, LoopVarClass::Carrier)) - .map(|(n, _)| n.clone()) - .collect::>() - .into_iter() - .collect(); + // Phase 70-1: Trio 分類を削除し、pinned_hint/carrier_hint をそのまま返す + // 実際の分類は LoopScopeShape::from_loop_form() 内部で実施される(二重分類問題解消) + let ordered_pinned: Vec = pinned_hint.into_iter().collect::>().into_iter().collect(); + let ordered_carriers: Vec = carrier_hint.into_iter().collect::>().into_iter().collect(); if ordered_pinned.is_empty() || ordered_carriers.is_empty() { return None; diff --git a/src/mir/join_ir/lowering/loop_scope_shape/builder.rs b/src/mir/join_ir/lowering/loop_scope_shape/builder.rs index c1383e9f..18e007df 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/builder.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/builder.rs @@ -3,94 +3,44 @@ //! LoopForm / 既存箱から LoopScopeShape を構築し、Case-A minimal ターゲットは //! analyze_case_a パスにルーティングする。 //! -//! # Phase 48-6: Trio 依存の境界 -//! -//! このファイルは **唯一 Trio(LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspectorBox)を -//! 知らないといけない層** として設計されている。 -//! -//! ## Trio が表に出る箇所(意図的に許容) -//! -//! 1. **builder.rs**(このファイル): from_existing_boxes_legacy とその helper 関数 -//! 2. **phi_core/* 自身**: Trio の実装ファイル(保持) -//! 3. **legacy bridge**: json_v0_bridge/lowering/loop_.rs(Phase 62+ で段階廃止) -//! 4. **テスト**: loop_scope_shape/tests.rs, loop_form_intake.rs(検証用) -//! -//! ## 外部からの利用 -//! -//! 外部(JoinIR lowering, LoopToJoinLowerer 等)は **LoopScopeShape の API のみ** を使用し、 -//! Trio の存在を知らない設計になっている(Phase 48-2/48-4 完了)。 +//! Trio legacy boxes は完全に除去済み。 +//! LoopForm / LoopFormIntake から LoopScopeShape を構築し、変数分類と定義位置を +//! LoopScopeShape の内部に閉じ込める。 use std::collections::{BTreeMap, BTreeSet}; use crate::mir::control_form::LoopId; use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake; use crate::mir::loop_form::LoopForm; -// Phase 48-6: Trio 依存(builder.rs のみが知る層) -use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox; -use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox; -use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox}; use crate::mir::{BasicBlockId, MirQuery}; use super::case_a::{is_case_a_minimal_target, validate_case_a_structural}; -use super::shape::LoopScopeShape; +use super::shape::{LoopScopeShape, LoopVarClass}; impl LoopScopeShape { /// Case-A ルーティング込みで LoopScopeShape を構築 - pub(crate) fn from_existing_boxes( + pub(crate) fn from_loop_form( loop_form: &LoopForm, intake: &LoopFormIntake, - var_classes: &LoopVarClassBox, - exit_live_box: &LoopExitLivenessBox, - query: &impl MirQuery, + _query: &impl MirQuery, func_name: Option<&str>, ) -> Option { if let Some(name) = func_name { if is_case_a_minimal_target(name) { - return Self::analyze_case_a( - loop_form, - intake, - var_classes, - exit_live_box, - query, - name, - ); + return Self::analyze_case_a(loop_form, intake, name); } } - Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query) - } - - /// Trio 引数なしで LoopScopeShape を構築(Trio 内部化) - pub(crate) fn from_loop_form( - loop_form: &LoopForm, - intake: &LoopFormIntake, - query: &impl MirQuery, - func_name: Option<&str>, - ) -> Option { - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); - - Self::from_existing_boxes( - loop_form, - intake, - &var_classes, - &exit_live_box, - query, - func_name, - ) + Self::build_from_intake(loop_form, intake) } /// Case-A minimal 用の解析パス(Phase 48-5: 構造判定検証追加) fn analyze_case_a( loop_form: &LoopForm, intake: &LoopFormIntake, - var_classes: &LoopVarClassBox, - exit_live_box: &LoopExitLivenessBox, - query: &impl MirQuery, func_name: &str, ) -> Option { - let result = - Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)?; + let result = Self::build_from_intake(loop_form, intake)?; // Phase 48-5: 構造判定検証(警告のみ、将来的に厳格化) validate_case_a_structural(loop_form, &result, func_name); @@ -108,25 +58,9 @@ impl LoopScopeShape { Some(result) } - /// 既存箱ベースの従来実装(Case-A 以外のループで使用) - /// - /// # Phase 48-6: Trio を知らないといけない唯一の層 - /// - /// この関数は **Trio(LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspectorBox)を - /// 直接受け取り、LoopScopeShape に変換する責務** を持つ。 - /// - /// - **外部からは呼ばれない**: from_loop_form() が Trio を内部生成して隠蔽(Phase 48-2) - /// - **Legacy bridge のみ**: json_v0_bridge/lowering/loop_.rs が直接呼ぶ(Phase 62+ 廃止予定) - /// - **helper 関数**: 下記3関数も Trio を使用(このファイル内でのみ可視) - /// - build_inspector(): LocalScopeInspectorBox 構築 - /// - classify_body_and_exit(): LoopVarClassBox 使用 - /// - merge_exit_live_from_box(): LoopExitLivenessBox 使用 - fn from_existing_boxes_legacy( + fn build_from_intake( loop_form: &LoopForm, intake: &LoopFormIntake, - var_classes: &LoopVarClassBox, - exit_live_box: &LoopExitLivenessBox, - query: &impl MirQuery, ) -> Option { let layout = block_layout(loop_form); @@ -145,18 +79,16 @@ impl LoopScopeShape { let pinned: BTreeSet = intake.pinned_ordered.iter().cloned().collect(); let carriers: BTreeSet = intake.carrier_ordered.iter().cloned().collect(); - let inspector = build_inspector(intake, layout.exit); - let (body_locals, mut exit_live) = - classify_body_and_exit(var_classes, intake, &inspector, &layout); - - let exit_live_from_box = - merge_exit_live_from_box(exit_live_box, query, intake, layout.exit); - for name in exit_live_from_box { - exit_live.insert(name); - } + let variable_definitions = collect_variable_definitions(intake, &layout); + let (body_locals, exit_live) = classify_body_and_exit( + intake, + &pinned, + &carriers, + &variable_definitions, + &layout, + ); let progress_carrier = carriers.iter().next().cloned(); - let variable_definitions = extract_variable_definitions(&inspector); Some(Self { header: layout.header, @@ -199,43 +131,61 @@ fn block_layout(loop_form: &LoopForm) -> LoopBlockLayout { } } -/// Phase 48-6: Trio 依存 helper(LocalScopeInspectorBox 構築) -/// -/// LocalScopeInspectorBox に header/exit snapshot を登録して変数定義解析の土台を作る。 -/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。 -fn build_inspector(intake: &LoopFormIntake, exit: BasicBlockId) -> LocalScopeInspectorBox { - let mut inspector = LocalScopeInspectorBox::new(); - inspector.record_snapshot(exit, &intake.header_snapshot); - for (bb, snap) in &intake.exit_snapshots { - inspector.record_snapshot(*bb, snap); +fn collect_variable_definitions( + intake: &LoopFormIntake, + layout: &LoopBlockLayout, +) -> BTreeMap> { + let mut var_defs: BTreeMap> = BTreeMap::new(); + + for var_name in intake.header_snapshot.keys() { + var_defs + .entry(var_name.clone()) + .or_default() + .insert(layout.exit); + var_defs + .entry(var_name.clone()) + .or_default() + .insert(layout.header); } - inspector + + for (bb, snap) in &intake.exit_snapshots { + for var_name in snap.keys() { + var_defs + .entry(var_name.clone()) + .or_default() + .insert(*bb); + } + } + + var_defs } -/// Phase 48-6: Trio 依存 helper(LoopVarClassBox による変数分類) -/// -/// LoopVarClassBox::classify_all() を呼び出して変数を 4分類し、 -/// body_locals と exit_live に振り分ける。 -/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。 fn classify_body_and_exit( - var_classes: &LoopVarClassBox, intake: &LoopFormIntake, - inspector: &LocalScopeInspectorBox, + pinned: &BTreeSet, + carriers: &BTreeSet, + variable_definitions: &BTreeMap>, layout: &LoopBlockLayout, ) -> (BTreeSet, BTreeSet) { - let all_names: Vec = intake.header_snapshot.keys().cloned().collect(); - let classified = var_classes.classify_all( - &all_names, - &intake.pinned_ordered, - &intake.carrier_ordered, - inspector, - &intake.exit_preds, - ); - let mut body_locals: BTreeSet = BTreeSet::new(); let mut exit_live: BTreeSet = BTreeSet::new(); - for (name, class) in &classified { + let mut all_names: BTreeSet = intake.header_snapshot.keys().cloned().collect(); + for (_, snap) in &intake.exit_snapshots { + all_names.extend(snap.keys().cloned()); + } + all_names.extend(pinned.iter().cloned()); + all_names.extend(carriers.iter().cloned()); + + for name in all_names { + let class = classify_var( + &name, + pinned, + carriers, + &intake.exit_preds, + variable_definitions, + ); + match class { LoopVarClass::Pinned | LoopVarClass::Carrier => { exit_live.insert(name.clone()); @@ -263,40 +213,42 @@ fn classify_body_and_exit( (body_locals, exit_live) } -/// Phase 48-6: Trio 依存 helper(LoopExitLivenessBox による exit_live 計算) -/// -/// LoopExitLivenessBox::compute_live_at_exit() を呼び出して、 -/// ループ終了時に live な変数集合を取得する。 -/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。 -fn merge_exit_live_from_box( - exit_live_box: &LoopExitLivenessBox, - query: &impl MirQuery, - intake: &LoopFormIntake, - exit_block: BasicBlockId, -) -> BTreeSet { - exit_live_box.compute_live_at_exit( - query, - exit_block, - &intake.header_snapshot, - &intake.exit_snapshots, - ) +fn classify_var( + var_name: &str, + pinned: &BTreeSet, + carriers: &BTreeSet, + exit_preds: &[BasicBlockId], + variable_definitions: &BTreeMap>, +) -> LoopVarClass { + if var_name.starts_with("__pin$") && var_name.contains("$@") { + return LoopVarClass::BodyLocalInternal; + } + + if pinned.contains(var_name) { + return LoopVarClass::Pinned; + } + + if carriers.contains(var_name) { + return LoopVarClass::Carrier; + } + + if is_available_in_all(var_name, exit_preds, variable_definitions) { + LoopVarClass::BodyLocalExit + } else { + LoopVarClass::BodyLocalInternal + } } -/// Phase 48-4: LocalScopeInspectorBox から variable_definitions を抽出 -/// -/// inspector.all_variables() と get_defining_blocks() を使って、 -/// 変数名 → 定義ブロック集合のマッピングを構築する。 -/// この関数は from_existing_boxes_legacy からのみ呼ばれ、Trio を直接扱う。 -fn extract_variable_definitions( - inspector: &LocalScopeInspectorBox, -) -> BTreeMap> { - let mut var_defs = BTreeMap::new(); - for var_name in inspector.all_variables() { - let blocks: BTreeSet = inspector - .get_defining_blocks(&var_name) - .into_iter() - .collect(); - var_defs.insert(var_name, blocks); +fn is_available_in_all( + var_name: &str, + required_blocks: &[BasicBlockId], + variable_definitions: &BTreeMap>, +) -> bool { + if let Some(defining_blocks) = variable_definitions.get(var_name) { + required_blocks + .iter() + .all(|block| defining_blocks.contains(block)) + } else { + false } - var_defs } diff --git a/src/mir/join_ir/lowering/loop_scope_shape/mod.rs b/src/mir/join_ir/lowering/loop_scope_shape/mod.rs index c7d1eb47..3f1a86a5 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/mod.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/mod.rs @@ -16,7 +16,6 @@ mod structural; pub(crate) use case_a::is_case_a_minimal_target; pub(crate) use context::CaseAContext; pub(crate) use shape::LoopScopeShape; -pub(crate) use structural::LoopStructuralAnalysis; #[cfg(test)] mod tests; diff --git a/src/mir/join_ir/lowering/loop_scope_shape/shape.rs b/src/mir/join_ir/lowering/loop_scope_shape/shape.rs index 7b630de5..09d0f522 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/shape.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/shape.rs @@ -4,9 +4,30 @@ use std::collections::{BTreeMap, BTreeSet}; -use crate::mir::phi_core::loop_var_classifier::LoopVarClass; use crate::mir::BasicBlockId; +/// Variable classification for loop PHI generation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LoopVarClass { + Pinned, + Carrier, + BodyLocalExit, + BodyLocalInternal, +} + +impl LoopVarClass { + #[cfg(test)] + pub fn needs_exit_phi(self) -> bool { + matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier | LoopVarClass::BodyLocalExit) + } + + #[cfg(test)] + pub fn needs_header_phi(self) -> bool { + matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier) + } + +} + /// ループ変数スコープの統合ビュー /// /// ## 4分類の定義 @@ -32,7 +53,7 @@ use crate::mir::BasicBlockId; /// - Block IDs: `header`, `body`, `latch`, `exit` /// - Variable classification: `pinned`, `carriers`, `body_locals`, `exit_live` /// - `progress_carrier`: 進捗チェック用(将来の Verifier で使用予定) -/// - `variable_definitions`: LocalScopeInspectorBox 情報統合予定(Phase 48-5+) +/// - `variable_definitions`: definition blocks collected from LoopFormIntake snapshots #[derive(Debug, Clone)] #[allow(dead_code)] // Block IDs and progress_carrier are reserved for future F-3/F-4 use pub(crate) struct LoopScopeShape { @@ -50,12 +71,13 @@ pub(crate) struct LoopScopeShape { impl LoopScopeShape { /// header PHI が必要か判定 - #[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering + #[cfg(test)] pub fn needs_header_phi(&self, var_name: &str) -> bool { self.pinned.contains(var_name) || self.carriers.contains(var_name) } /// exit PHI が必要か判定 + #[cfg(test)] pub fn needs_exit_phi(&self, var_name: &str) -> bool { self.exit_live.contains(var_name) } @@ -70,21 +92,8 @@ impl LoopScopeShape { self.carriers.iter().cloned().collect() } - /// header PHI 対象(pinned + carriers) - #[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering - pub fn header_phi_vars(&self) -> Vec { - let mut result: Vec = self.pinned.iter().cloned().collect(); - result.extend(self.carriers.iter().cloned()); - result - } - - /// exit PHI 対象 - #[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering - pub fn exit_phi_vars(&self) -> Vec { - self.exit_live.iter().cloned().collect() - } - /// 変数を4分類 + #[cfg(test)] pub fn classify(&self, var_name: &str) -> LoopVarClass { if self.pinned.contains(var_name) { return LoopVarClass::Pinned; @@ -105,20 +114,14 @@ impl LoopScopeShape { } } - /// 複数変数を一括分類 - pub fn classify_all(&self, var_names: &[String]) -> Vec<(String, LoopVarClass)> { - var_names - .iter() - .map(|name| (name.clone(), self.classify(name))) - .collect() - } - /// ループ終了時に live な変数集合を返す + #[cfg(test)] pub fn get_exit_live(&self) -> &BTreeSet { &self.exit_live } /// 変数が required_blocks すべてで利用可能か判定 + #[cfg(test)] pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool { if let Some(def_blocks) = self.variable_definitions.get(var_name) { required_blocks.iter().all(|bid| def_blocks.contains(bid)) diff --git a/src/mir/join_ir/lowering/loop_scope_shape/structural.rs b/src/mir/join_ir/lowering/loop_scope_shape/structural.rs index 04d6d114..a4993965 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/structural.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/structural.rs @@ -108,7 +108,7 @@ impl LoopStructuralAnalysis { mod tests { use super::*; use crate::mir::control_form::{ExitEdge, ExitKind, LoopId}; - use crate::mir::{BasicBlockId, ValueId}; + use crate::mir::BasicBlockId; use std::collections::{BTreeMap, BTreeSet}; fn make_single_exit_analysis() -> ExitAnalysis { diff --git a/src/mir/join_ir/lowering/loop_scope_shape/tests.rs b/src/mir/join_ir/lowering/loop_scope_shape/tests.rs index 0b777402..92f31ca3 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/tests.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/tests.rs @@ -1,7 +1,6 @@ use super::*; use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake; -use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox; -use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox}; +use super::shape::LoopVarClass; use crate::mir::{BasicBlockId, MirQuery, ValueId}; use std::collections::{BTreeMap, BTreeSet}; @@ -49,21 +48,12 @@ impl MirQuery for EmptyQuery { } #[test] -fn test_from_existing_boxes_basic() { +fn test_from_loop_form_basic() { let loop_form = make_dummy_loop_form(); let intake = make_dummy_intake(); - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None); assert!(scope.is_some()); let scope = scope.unwrap(); @@ -87,19 +77,9 @@ fn test_from_existing_boxes_basic() { fn test_needs_header_phi() { let loop_form = make_dummy_loop_form(); let intake = make_dummy_intake(); - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ) - .unwrap(); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap(); assert!(scope.needs_header_phi("s")); assert!(scope.needs_header_phi("n")); @@ -111,19 +91,9 @@ fn test_needs_header_phi() { fn test_needs_exit_phi() { let loop_form = make_dummy_loop_form(); let intake = make_dummy_intake(); - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ) - .unwrap(); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap(); assert!(scope.needs_exit_phi("s")); assert!(scope.needs_exit_phi("n")); @@ -134,19 +104,9 @@ fn test_needs_exit_phi() { fn test_ordered_accessors() { let loop_form = make_dummy_loop_form(); let intake = make_dummy_intake(); - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ) - .unwrap(); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap(); let pinned = scope.pinned_ordered(); assert_eq!(pinned.len(), 2); @@ -197,19 +157,9 @@ fn test_block_ids_preserved() { }; let intake = make_dummy_intake(); - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ) - .unwrap(); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap(); assert_eq!(scope.header, BasicBlockId::new(200)); assert_eq!(scope.body, BasicBlockId::new(300)); @@ -245,19 +195,9 @@ fn test_deterministic_order() { fn test_needs_phi_consistency() { let loop_form = make_dummy_loop_form(); let intake = make_dummy_intake(); - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ) - .unwrap(); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap(); for var in &scope.pinned { assert!( @@ -458,7 +398,7 @@ fn test_is_available_in_all_phase48_5_future() { assert!(!scope.is_available_in_all("unknown", &[BasicBlockId::new(3)])); } -/// Phase 48-4: from_existing_boxes で variable_definitions が埋まることを確認 +/// Phase 48-4: from_loop_form で variable_definitions が埋まることを確認 #[test] fn test_variable_definitions_from_inspector() { let loop_form = make_dummy_loop_form(); @@ -481,19 +421,9 @@ fn test_variable_definitions_from_inspector() { (BasicBlockId::new(11), exit2_snap), ]; - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ) - .unwrap(); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap(); // s, n, i は両方の exit で利用可能 → is_available_in_all should be true assert!(scope.is_available_in_all("s", &[BasicBlockId::new(10), BasicBlockId::new(11)])); @@ -532,19 +462,9 @@ fn test_variable_definitions_partial_availability() { (BasicBlockId::new(22), exit3_snap), ]; - let var_classes = LoopVarClassBox::new(); - let exit_live_box = LoopExitLivenessBox::new(); let query = EmptyQuery; - let scope = LoopScopeShape::from_existing_boxes( - &loop_form, - &intake, - &var_classes, - &exit_live_box, - &query, - None, - ) - .unwrap(); + let scope = LoopScopeShape::from_loop_form(&loop_form, &intake, &query, None).unwrap(); // s は全 exit で利用可能 assert!(scope.is_available_in_all( diff --git a/src/mir/join_ir/lowering/loop_to_join.rs b/src/mir/join_ir/lowering/loop_to_join.rs index c1dff17a..4b7abd9a 100644 --- a/src/mir/join_ir/lowering/loop_to_join.rs +++ b/src/mir/join_ir/lowering/loop_to_join.rs @@ -98,8 +98,8 @@ impl LoopToJoinLowerer { let query = MirQueryBox::new(func); // Step 2: LoopFormIntake を構築 - // Phase 48-2: intake_loop_form は空の var_classes を使用(将来は from_loop_form に統合) - let intake = intake_loop_form(loop_form, &Default::default(), &query, func)?; + // Phase 70-2: var_classes 引数削除完了(Trio 依存ゼロ) + let intake = intake_loop_form(loop_form, &query, func)?; // Step 3: LoopScopeShape を構築 // Phase 48-2: from_loop_form() で Trio を内部化(LoopExitLivenessBox 依存削除) diff --git a/src/mir/join_ir/mod.rs b/src/mir/join_ir/mod.rs index 0120065b..1d0be9d0 100644 --- a/src/mir/join_ir/mod.rs +++ b/src/mir/join_ir/mod.rs @@ -114,7 +114,7 @@ pub(crate) fn env_flag_is_1(name: &str) -> bool { /// - **Pinned**: ループ中で値が変わらない変数(例: skip_ws の s, n / trim の str, b) /// - **Carrier**: ループで更新される変数(例: skip_ws の i / trim の e) /// -/// Phase 27.4 では minimal/trim 用に手動で構成するが、将来は LoopVarClassBox から自動導出する。 +/// Phase 27.4 では minimal/trim 用に手動で構成するが、将来は LoopScopeShape から自動導出する。 #[derive(Debug, Clone)] #[allow(dead_code)] // Phase 27.4-C で実際に使用予定(現在は設計の雛形) pub(crate) struct LoopHeaderShape { diff --git a/src/mir/loop_builder/loop_form.rs b/src/mir/loop_builder/loop_form.rs index c6943685..a9c173e1 100644 --- a/src/mir/loop_builder/loop_form.rs +++ b/src/mir/loop_builder/loop_form.rs @@ -316,7 +316,7 @@ impl<'a> LoopBuilder<'a> { // - BodyLocalExit: needs EXIT PHI only, NOT header PHI // - BodyLocalInternal: needs NO PHI at all // - // TODO Step 5-3: Integrate Option C classification (LoopVarClassBox) here + // TODO Step 5-3: Integrate Option C classification (LoopScopeShape) here // // TEMPORARY DISABLE to test hypothesis that header PHIs are the root cause let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1"); diff --git a/src/mir/phi_core/local_scope_inspector.rs b/src/mir/phi_core/local_scope_inspector.rs deleted file mode 100644 index 80df3e08..00000000 --- a/src/mir/phi_core/local_scope_inspector.rs +++ /dev/null @@ -1,361 +0,0 @@ -/// LocalScopeInspectorBox - Variable definition tracker -/// -/// # Phase 30: LoopScopeShape 移行中 -/// -/// このBoxは将来 LoopScopeShape に吸収される予定。 -/// 定義位置情報は LoopScopeShape::from_existing_boxes() 内部で集約され、 -/// 変数分類に使用される。 -/// -/// **新規コード**: LoopScopeShape が利用可能な場合は、 -/// `classify()` メソッドを直接使うことを推奨。 -/// -/// # Purpose -/// -/// This box tracks which variables are defined in which basic blocks. -/// Used by LoopVarClassBox to determine if a body-local variable is -/// defined in all exit predecessors (making it eligible for exit PHI). -/// -/// # Responsibility -/// -/// - Record variable definitions per block -/// - Query if a variable is defined in specific blocks -/// - Query if a variable is defined in ALL of a set of blocks -/// -/// # Design Philosophy (Box Theory) -/// -/// This box has ONE job: track definition locations. -/// It doesn't know about loops, PHI nodes, or exit blocks. -/// It's a pure data structure that other boxes can query. -use crate::mir::{BasicBlockId, ValueId}; -use std::collections::{BTreeMap, BTreeSet}; - -/// Tracks which variables are defined in which blocks -#[derive(Debug, Clone, Default)] -pub struct LocalScopeInspectorBox { - /// Variable name → Set of blocks where it's defined - var_definitions: BTreeMap>, -} - -impl LocalScopeInspectorBox { - /// Create a new empty inspector - pub fn new() -> Self { - Self { - var_definitions: BTreeMap::new(), - } - } - - /// Record that a variable is defined in a specific block - /// - /// # Example - /// ``` - /// let mut inspector = LocalScopeInspectorBox::new(); - /// inspector.record_definition("ch", BasicBlockId::new(5)); - /// inspector.record_definition("i", BasicBlockId::new(2)); - /// inspector.record_definition("i", BasicBlockId::new(5)); // i defined in multiple blocks - /// ``` - pub fn record_definition(&mut self, var_name: &str, block: BasicBlockId) { - self.var_definitions - .entry(var_name.to_string()) - .or_insert_with(BTreeSet::new) - .insert(block); - } - - /// Record definitions from a snapshot (block_id, vars) - /// - /// # Example - /// ``` - /// let snapshot = BTreeMap::from([ - /// ("i".to_string(), ValueId(1)), - /// ("n".to_string(), ValueId(2)), - /// ]); - /// inspector.record_snapshot(BasicBlockId::new(5), &snapshot); - /// ``` - pub fn record_snapshot(&mut self, block: BasicBlockId, vars: &BTreeMap) { - for var_name in vars.keys() { - self.record_definition(var_name, block); - } - } - - /// Check if a variable is defined in a specific block - /// - /// # Returns - /// - `true` if the variable is defined in the block - /// - `false` if the variable is not defined in the block, or doesn't exist - pub fn is_defined_in(&self, var_name: &str, block: BasicBlockId) -> bool { - self.var_definitions - .get(var_name) - .map(|blocks| blocks.contains(&block)) - .unwrap_or(false) - } - - /// Check if a variable is AVAILABLE in ALL of the specified blocks - /// - /// # Semantics (Important!) - /// - /// This checks if the variable is "in scope" (available) at each block, - /// NOT whether it was "newly defined" in that block. - /// - /// - A variable is "available" if it's in the snapshot at that point - /// - This includes both newly-defined variables AND inherited variables - /// - /// For Option C PHI generation: - /// - "available in all exit preds" → can generate exit PHI - /// - "NOT available in all exit preds" → BodyLocalInternal, skip exit PHI - /// - /// # Returns - /// - `true` if the variable is available in every block in `required_blocks` - /// - `false` if the variable is missing from any block, or doesn't exist - /// - /// # Example - /// ``` - /// // Variable "ch" is only available in block 5 - /// inspector.record_definition("ch", BasicBlockId::new(5)); - /// - /// let exit_preds = vec![BasicBlockId::new(2), BasicBlockId::new(5)]; - /// assert!(!inspector.is_available_in_all("ch", &exit_preds)); // false: missing block 2 - /// - /// // Variable "i" is available in both blocks - /// inspector.record_definition("i", BasicBlockId::new(2)); - /// inspector.record_definition("i", BasicBlockId::new(5)); - /// assert!(inspector.is_available_in_all("i", &exit_preds)); // true: in all blocks - /// ``` - pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool { - if let Some(defining_blocks) = self.var_definitions.get(var_name) { - required_blocks - .iter() - .all(|block| defining_blocks.contains(block)) - } else { - // Variable doesn't exist at all - false - } - } - - /// Get all blocks where a variable is defined - /// - /// # Returns - /// - Vec of BasicBlockIds where the variable is defined - /// - Empty vec if the variable doesn't exist - pub fn get_defining_blocks(&self, var_name: &str) -> Vec { - self.var_definitions - .get(var_name) - .map(|blocks| blocks.iter().copied().collect()) - .unwrap_or_default() - } - - /// Get all tracked variables - pub fn all_variables(&self) -> Vec { - self.var_definitions.keys().cloned().collect() - } - - /// Get the number of blocks where a variable is defined - pub fn definition_count(&self, var_name: &str) -> usize { - self.var_definitions - .get(var_name) - .map(|blocks| blocks.len()) - .unwrap_or(0) - } - - // ======================================================================== - // Phase 30 F-1.3: LoopScopeShape 委譲メソッド - // ======================================================================== - - /// Phase 30: LoopScopeShape を使って変数が全 exit pred で利用可能か判定 - /// - /// 新規コードでは LoopScopeShape::classify() を直接使うことを推奨。 - /// このメソッドは LoopScopeShape が既にある場合の便利メソッド。 - /// - /// # Note - /// - /// LoopScopeShape::classify() は既に exit_live 情報を含んでいるため、 - /// 直接 classify() → needs_exit_phi() を使う方が効率的。 - #[allow(dead_code)] // Phase 30 F-1.3: will be used when LocalScopeInspectorBox is absorbed - pub(crate) fn is_available_in_all_with_scope( - &self, - var_name: &str, - scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape, - ) -> bool { - // LoopScopeShape の分類を使用 - // Pinned/Carrier/BodyLocalExit → available in all (exit PHI needed) - // BodyLocalInternal → NOT available in all (no exit PHI) - scope.needs_exit_phi(var_name) - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_record_and_query_single_definition() { - let mut inspector = LocalScopeInspectorBox::new(); - - inspector.record_definition("ch", BasicBlockId::new(5)); - - assert!(inspector.is_defined_in("ch", BasicBlockId::new(5))); - assert!(!inspector.is_defined_in("ch", BasicBlockId::new(2))); - assert!(!inspector.is_defined_in("unknown", BasicBlockId::new(5))); - } - - #[test] - fn test_record_multiple_blocks() { - let mut inspector = LocalScopeInspectorBox::new(); - - inspector.record_definition("i", BasicBlockId::new(2)); - inspector.record_definition("i", BasicBlockId::new(5)); - inspector.record_definition("i", BasicBlockId::new(7)); - - assert!(inspector.is_defined_in("i", BasicBlockId::new(2))); - assert!(inspector.is_defined_in("i", BasicBlockId::new(5))); - assert!(inspector.is_defined_in("i", BasicBlockId::new(7))); - assert!(!inspector.is_defined_in("i", BasicBlockId::new(10))); - } - - #[test] - fn test_is_available_in_all_success() { - let mut inspector = LocalScopeInspectorBox::new(); - - inspector.record_definition("i", BasicBlockId::new(2)); - inspector.record_definition("i", BasicBlockId::new(5)); - inspector.record_definition("i", BasicBlockId::new(7)); - - let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)]; - - assert!(inspector.is_available_in_all("i", &required)); - } - - #[test] - fn test_is_available_in_all_failure() { - let mut inspector = LocalScopeInspectorBox::new(); - - // "ch" only defined in block 5 - inspector.record_definition("ch", BasicBlockId::new(5)); - - let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)]; - - // Should fail because block 2 is missing - assert!(!inspector.is_available_in_all("ch", &required)); - } - - #[test] - fn test_is_available_in_all_unknown_variable() { - let inspector = LocalScopeInspectorBox::new(); - - let required = vec![BasicBlockId::new(2)]; - - assert!(!inspector.is_available_in_all("unknown", &required)); - } - - #[test] - fn test_get_defining_blocks() { - let mut inspector = LocalScopeInspectorBox::new(); - - inspector.record_definition("x", BasicBlockId::new(3)); - inspector.record_definition("x", BasicBlockId::new(7)); - inspector.record_definition("x", BasicBlockId::new(1)); - - let mut blocks = inspector.get_defining_blocks("x"); - blocks.sort_by_key(|b| b.0); - - assert_eq!( - blocks, - vec![ - BasicBlockId::new(1), - BasicBlockId::new(3), - BasicBlockId::new(7), - ] - ); - } - - #[test] - fn test_get_defining_blocks_unknown() { - let inspector = LocalScopeInspectorBox::new(); - - assert_eq!( - inspector.get_defining_blocks("unknown"), - Vec::::new() - ); - } - - #[test] - fn test_record_snapshot() { - let mut inspector = LocalScopeInspectorBox::new(); - - let mut snapshot = BTreeMap::new(); - snapshot.insert("i".to_string(), ValueId(10)); - snapshot.insert("n".to_string(), ValueId(20)); - snapshot.insert("ch".to_string(), ValueId(712)); - - inspector.record_snapshot(BasicBlockId::new(5), &snapshot); - - assert!(inspector.is_defined_in("i", BasicBlockId::new(5))); - assert!(inspector.is_defined_in("n", BasicBlockId::new(5))); - assert!(inspector.is_defined_in("ch", BasicBlockId::new(5))); - assert!(!inspector.is_defined_in("i", BasicBlockId::new(2))); - } - - #[test] - fn test_all_variables() { - let mut inspector = LocalScopeInspectorBox::new(); - - inspector.record_definition("a", BasicBlockId::new(1)); - inspector.record_definition("b", BasicBlockId::new(2)); - inspector.record_definition("c", BasicBlockId::new(3)); - - let mut vars = inspector.all_variables(); - vars.sort(); - - assert_eq!(vars, vec!["a", "b", "c"]); - } - - #[test] - fn test_definition_count() { - let mut inspector = LocalScopeInspectorBox::new(); - - inspector.record_definition("x", BasicBlockId::new(1)); - inspector.record_definition("x", BasicBlockId::new(2)); - inspector.record_definition("x", BasicBlockId::new(3)); - - assert_eq!(inspector.definition_count("x"), 3); - assert_eq!(inspector.definition_count("unknown"), 0); - } - - /// Test the skip_whitespace PHI bug scenario - #[test] - fn test_skip_whitespace_scenario() { - let mut inspector = LocalScopeInspectorBox::new(); - - // Simulate skip_whitespace: - // - Variables i, n, s are defined in all blocks (header + breaks) - // - Variable ch is only defined in block 5 (break path 2) - - let block_2 = BasicBlockId::new(2); // header / break path 1 - let block_5 = BasicBlockId::new(5); // break path 2 - let block_7 = BasicBlockId::new(7); // latch (hypothetical) - - // i, n, s are in all blocks - for var in &["i", "n", "s"] { - inspector.record_definition(var, block_2); - inspector.record_definition(var, block_5); - inspector.record_definition(var, block_7); - } - - // ch is only in block 5 - inspector.record_definition("ch", block_5); - - let exit_preds = vec![block_2, block_5, block_7]; - - // i, n, s should be in all exit preds - assert!(inspector.is_available_in_all("i", &exit_preds)); - assert!(inspector.is_available_in_all("n", &exit_preds)); - assert!(inspector.is_available_in_all("s", &exit_preds)); - - // ch should NOT be in all exit preds (missing block 2 and 7) - assert!(!inspector.is_available_in_all("ch", &exit_preds)); - - // This means ch should NOT get an exit PHI! - } -} diff --git a/src/mir/phi_core/loop_exit_liveness.rs b/src/mir/phi_core/loop_exit_liveness.rs deleted file mode 100644 index ba84af4d..00000000 --- a/src/mir/phi_core/loop_exit_liveness.rs +++ /dev/null @@ -1,414 +0,0 @@ -//! Loop Exit Liveness Box - Exit後で使われる変数の決定専門Box -//! -//! Phase 26-F-4: Exit Liveness Analysis -//! - Exit後で本当に使われる変数を決定 -//! - Phase 1: 空集合(MIRスキャン実装待ち) -//! - Phase 2+: MIRスキャン実装予定(LoopFormOps拡張後) -//! -//! # 環境変数制御 -//! - `NYASH_EXIT_LIVE_ENABLE=1`: Phase 2+ MIRスキャン実装を有効化(実験用) -//! - デフォルト(未設定): Phase 26-F-3 相当の挙動(安定版) -//! -//! # Box-First理論: 責務の明確な分離 -//! -//! - **LoopVarClassBox**: スコープ分類(Pinned/Carrier/BodyLocal) -//! - **LoopExitLivenessBox**: Exit後の実際の使用(この箱) -//! - **BodyLocalPhiBuilder**: 両者を統合して exit PHI 判定 -//! -//! # Phase 26-F-4: ChatGPT設計による箱離婚 -//! -//! **問題**: LoopVarClassBox が「スコープ分類」と「exit後使用」を混在 -//! -//! **解決**: LoopExitLivenessBox 新設で完全分離 -//! - スコープ分類: LoopVarClassBox(変更なし) -//! - 実際の使用: LoopExitLivenessBox(新箱) -//! - 統合判定: BodyLocalPhiBuilder(OR論理) - -use crate::mir::{BasicBlockId, ValueId}; -use std::collections::{BTreeMap, BTreeSet}; - -/// ExitLivenessProvider -/// -/// ExitPhiBuilder が依存するインターフェース。環境や実装を差し替えやすくするために -/// trait として定義し、Legacy / MIR スキャン版のどちらでも差し込めるようにしている。 -pub trait ExitLivenessProvider: Send + Sync { - fn compute_live_at_exit( - &self, - mir_query: &dyn crate::mir::MirQuery, - exit_block: BasicBlockId, - header_vals: &BTreeMap, - exit_snapshots: &[(BasicBlockId, BTreeMap)], - ) -> BTreeSet; -} - -/// Loop Exit Liveness Box(Legacy/Phase 1) -/// -/// # Phase 30: LoopScopeShape 移行中 -/// -/// このBoxは将来 LoopScopeShape に吸収される予定。 -/// LoopScopeShape.exit_live が唯一の live_at_exit 情報源になる。 -/// -/// **新規コード**: `LoopScopeShape::exit_live` を直接参照すること。 -/// -/// # Purpose -/// Exit後で本当に使われる変数を決定する専門箱 -/// -/// # Responsibility -/// - live_at_exit 変数集合の計算 -/// - 将来: MIRスキャンによる精密liveness解析 -/// - 現在: 保守的近似(全変数をliveとみなす) -/// -/// # Phase 1 Implementation (Conservative) -/// header_vals + exit_snapshots に現れる全変数を live_at_exit に含める -/// -/// **理由**: LoopFormOps に MIR アクセスがないため、保守的近似で先行実装 -/// -/// # Future Phase 2+ (Precise) -/// exit ブロックの MIR をスキャンして、実際に read される変数のみを live とする -/// -/// **将来拡張**: LoopFormOps 拡張 or MirFunction 参照追加時に精密化 -/// -/// # Usage -/// ```ignore -/// let liveness_box = LoopExitLivenessBox::new(); -/// let live_at_exit = liveness_box.compute_live_at_exit( -/// &header_vals, -/// &exit_snapshots, -/// ); -/// // → ch, pname, pend 等が含まれる(保守的) -/// ``` -#[derive(Debug, Clone, Default)] -pub struct LoopExitLivenessBox; - -impl LoopExitLivenessBox { - /// Create new LoopExitLivenessBox - pub fn new() -> Self { - Self - } - - /// Compute live_at_exit variables (Phase 1: Conservative) - /// - /// # Arguments - /// * `header_vals` - Header variable values - /// * `exit_snapshots` - Exit predecessor snapshots - /// - /// # Returns - /// BTreeSet of variable names that are potentially live at exit - /// - /// # Phase 1 Logic (Conservative) - /// すべての header_vals と exit_snapshots に現れる変数を live とみなす - /// - /// **保守的な理由**: - /// - BodyLocalInternal でも exit 後で使われる変数がある(ch, pname, pend) - /// - MIR スキャンなしでは正確な判定不可 - /// - Phase 35-5: phi_invariants.rs deleted (validation moved to JoinIR Verifier) - /// - /// # Future Phase 2+ (Precise) - /// exit ブロックの MIR をスキャンして、実際に使われる変数のみを返す - /// - /// **精密化計画**: - /// 1. LoopFormOps に `get_block_instructions()` 追加 - /// 2. exit ブロックの Copy/BinOp/Call 等で read される ValueId を収集 - /// 3. ValueId → 変数名の逆引き(variable_map 逆マップ) - /// 4. read される変数のみを live_at_exit に含める - /// - /// # Example - /// ```ignore - /// // Phase 1: 保守的近似 - /// // skip_whitespace: ch は一部 pred でしか定義されないが、exit 後で使われる - /// let live_at_exit = liveness_box.compute_live_at_exit( - /// query, - /// exit_block, - /// &header_vals, // { i: %10, n: %20 } - /// &exit_snapshots, // [{ i: %30, ch: %40 }, { i: %50 }] - /// ); - /// // → live_at_exit = { "i", "n", "ch" }(保守的に ch も含める) - /// ``` - pub fn compute_live_at_exit( - &self, - _mir_query: &dyn crate::mir::MirQuery, - _exit_block: BasicBlockId, - _header_vals: &BTreeMap, - _exit_snapshots: &[(BasicBlockId, BTreeMap)], - ) -> BTreeSet { - // Phase 26-F-4: 環境変数制御による段階的実装 - // - // **環境変数ガード**: - // - NYASH_EXIT_LIVE_ENABLE=1: Phase 2+ MIRスキャン実装を有効化(実験用) - // - デフォルト(未設定): Phase 26-F-3 相当の挙動(安定版) - - let enable_phase2 = std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1"); - - let live_vars = if enable_phase2 { - // Phase 2+ MIRスキャン実装(未実装) - // TODO: LoopFormOps拡張後に実装 - // - exit ブロックの MIR をスキャン - // - Copy/BinOp/Call 等で read される ValueId を収集 - // - ValueId → 変数名の逆引き - // - read される変数のみを live_at_exit に含める - BTreeSet::new() - } else { - // Phase 1: 空の live_at_exit を返す(Phase 26-F-3 相当) - // - // **理由**: 保守的近似(全変数を live)では以前の PhiInvariantsBox でエラーだった - // Phase 35-5 で phi_invariants.rs 削除済み、検証は JoinIR Verifier へ移譲 - // - // **問題**: header_vals + exit_snapshots の全変数を live とすると、 - // BodyLocalInternal 変数も live_at_exit に含まれる - // → exit PHI 候補に昇格 - // → 一部の exit pred でのみ定義 → 構造的に不正 - // - // **Phase 1 方針**: live_at_exit を空にして、既存の LoopVarClassBox 分類のみに依存 - // → BodyLocalInternal は exit PHI 候補から除外(Phase 26-F-3 と同じ) - BTreeSet::new() - }; - - // Debug trace - if std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1") { - eprintln!( - "[LoopExitLivenessBox] Phase {}: live_at_exit={} vars{}", - if enable_phase2 { "2+" } else { "1" }, - live_vars.len(), - if enable_phase2 { - " (MIR scan - experimental)" - } else { - " (empty - Phase 26-F-3 equivalent)" - } - ); - } - - live_vars - } - - // ======================================================================== - // Phase 30 F-1.2: LoopScopeShape 委譲メソッド - // ======================================================================== - - /// Phase 30: LoopScopeShape の exit_live を直接取得 - /// - /// 新規コードではこのメソッドを使うことを推奨。 - /// 将来的には旧 compute_live_at_exit() メソッドを削除し、 - /// LoopScopeShape::exit_live を直接参照するのが標準になる。 - /// - /// # Example - /// - /// ```ignore - /// let scope = LoopScopeShape::from_existing_boxes(...)?; - /// let live = liveness_box.get_exit_live_from_scope(&scope); - /// ``` - #[allow(dead_code)] // Phase 30 F-1.2: will be used when LoopExitLivenessBox is absorbed - pub(crate) fn get_exit_live_from_scope( - &self, - scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape, - ) -> BTreeSet { - scope.exit_live.clone() - } -} - -impl ExitLivenessProvider for LoopExitLivenessBox { - fn compute_live_at_exit( - &self, - _mir_query: &dyn crate::mir::MirQuery, - _exit_block: BasicBlockId, - header_vals: &BTreeMap, - exit_snapshots: &[(BasicBlockId, BTreeMap)], - ) -> BTreeSet { - self.compute_live_at_exit(_mir_query, _exit_block, header_vals, exit_snapshots) - } -} - -/// MirScanExitLiveness - Phase 2+ 用のExitLivenessProviderプレースホルダ -/// -/// Phase 26-F 時点では LoopExitLivenessBox と同じ実装を呼び出すだけの薄い箱だよ。 -/// 将来、LoopFormOps や MirFunction へのアクセスが整備されたら、 -/// ここに本物の MIR スキャン実装(use/def ベースの live_at_exit 計算)を差し込む予定。 -#[derive(Debug, Clone, Default)] -pub struct MirScanExitLiveness; - -impl ExitLivenessProvider for MirScanExitLiveness { - fn compute_live_at_exit( - &self, - mir_query: &dyn crate::mir::MirQuery, - exit_block: BasicBlockId, - header_vals: &BTreeMap, - exit_snapshots: &[(BasicBlockId, BTreeMap)], - ) -> BTreeSet { - let trace = std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1"); - - // 対象ブロック集合(exit と break preds) - let mut targets: BTreeSet = BTreeSet::new(); - targets.insert(exit_block); - for (bb, _) in exit_snapshots { - targets.insert(*bb); - } - - // live map per block - let mut live_map: BTreeMap> = BTreeMap::new(); - for bb in &targets { - live_map.insert(*bb, BTreeSet::new()); - } - - let mut changed = true; - while changed { - changed = false; - // 逆順で走査(安定順序: BTreeSet) - for bb in targets.iter().rev() { - let mut live: BTreeSet = BTreeSet::new(); - // succ の live を流入(対象集合内のみ) - for succ in mir_query.succs(*bb) { - if targets.contains(&succ) { - if let Some(succ_live) = live_map.get(&succ) { - live.extend(succ_live); - } - } - } - // 命令を逆順スキャン - for inst in mir_query.insts_in_block(*bb).iter().rev() { - // kill writes - for w in mir_query.writes_of(inst) { - live.remove(&w); - } - // add reads - for r in mir_query.reads_of(inst) { - live.insert(r); - } - } - // 更新チェック - let entry = live_map.entry(*bb).or_default(); - if *entry != live { - *entry = live; - changed = true; - } - } - } - - // ValueId→名前の逆引きテーブル(header + exit snapshots) - let mut name_pool: BTreeMap = BTreeMap::new(); - for (name, vid) in header_vals { - name_pool.insert(*vid, name.clone()); - } - for (_bb, snap) in exit_snapshots { - for (name, vid) in snap { - name_pool.insert(*vid, name.clone()); - } - } - - let mut live_names: BTreeSet = BTreeSet::new(); - for live in live_map.values() { - for v in live { - if let Some(name) = name_pool.get(v) { - live_names.insert(name.clone()); - } - } - } - - if trace { - eprintln!( - "[LoopExitLiveness/MirScan] live_at_exit={} vars (use/def scan)", - live_names.len() - ); - } - - live_names - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - struct EmptyQuery; - impl crate::mir::MirQuery for EmptyQuery { - fn insts_in_block(&self, _bb: BasicBlockId) -> &[crate::mir::MirInstruction] { - &[] - } - fn succs(&self, _bb: BasicBlockId) -> Vec { - Vec::new() - } - fn reads_of(&self, _inst: &crate::mir::MirInstruction) -> Vec { - Vec::new() - } - fn writes_of(&self, _inst: &crate::mir::MirInstruction) -> Vec { - Vec::new() - } - } - - #[test] - fn test_compute_live_at_exit_conservative() { - let liveness_box = LoopExitLivenessBox::new(); - let query = EmptyQuery; - - let mut header_vals = BTreeMap::new(); - header_vals.insert("i".to_string(), ValueId(10)); - header_vals.insert("n".to_string(), ValueId(20)); - - let mut snap1 = BTreeMap::new(); - snap1.insert("i".to_string(), ValueId(30)); - snap1.insert("ch".to_string(), ValueId(40)); - - let mut snap2 = BTreeMap::new(); - snap2.insert("i".to_string(), ValueId(50)); - snap2.insert("pname".to_string(), ValueId(60)); - - let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)]; - - let live_at_exit = liveness_box.compute_live_at_exit( - &query, - BasicBlockId(0), - &header_vals, - &exit_snapshots, - ); - - // Phase 1: 空の live_at_exit(MIRスキャン実装待ち) - assert_eq!(live_at_exit.len(), 0); - } - - #[test] - fn test_compute_live_at_exit_empty() { - let liveness_box = LoopExitLivenessBox::new(); - let query = EmptyQuery; - - let header_vals = BTreeMap::new(); - let exit_snapshots = vec![]; - - let live_at_exit = liveness_box.compute_live_at_exit( - &query, - BasicBlockId(0), - &header_vals, - &exit_snapshots, - ); - - assert_eq!(live_at_exit.len(), 0); - } - - #[test] - fn test_compute_live_at_exit_deduplication() { - let liveness_box = LoopExitLivenessBox::new(); - let query = EmptyQuery; - - let mut header_vals = BTreeMap::new(); - header_vals.insert("i".to_string(), ValueId(10)); - - let mut snap1 = BTreeMap::new(); - snap1.insert("i".to_string(), ValueId(20)); // 重複 - - let mut snap2 = BTreeMap::new(); - snap2.insert("i".to_string(), ValueId(30)); // 重複 - - let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)]; - - let live_at_exit = liveness_box.compute_live_at_exit( - &query, - BasicBlockId(0), - &header_vals, - &exit_snapshots, - ); - - // Phase 1: 空の live_at_exit(MIRスキャン実装待ち) - assert_eq!(live_at_exit.len(), 0); - } -} diff --git a/src/mir/phi_core/loop_snapshot_merge.rs b/src/mir/phi_core/loop_snapshot_merge.rs index e5a2ce0b..03703fec 100644 --- a/src/mir/phi_core/loop_snapshot_merge.rs +++ b/src/mir/phi_core/loop_snapshot_merge.rs @@ -1,40 +1,15 @@ /*! - * Phase 36: LoopSnapshotMergeBox - Exit PHI Merge Utility (Pure Static) + * LoopSnapshotMergeBox - Exit PHI merge utility (pinned/carrier + availability) * - * **Responsibility**: Exit PHI input merging with variable classification (Option C) - * - * ## 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 - * - * ## 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) + * - Exit PHI inputs are merged using pinned/carrier hints and availability + * reconstructed from header/exit snapshots (Option C guard)。 + * - Header/exit PHI の仕様や最適化は LoopFormBuilder / LoopScopeShape 側が SSOT。 + * - 将来の縮退・移行計画は docs 側(Phase 37+ メモ)を参照してね。 */ use crate::mir::{BasicBlockId, ValueId}; 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 36: Pure static utility for exit PHI merging (no state) /// /// # Responsibility @@ -54,16 +29,10 @@ impl LoopSnapshotMergeBox { /// # 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 + /// 1. 変数分類(Pinned/Carrier/BodyLocalExit/BodyLocalInternal) + /// 2. スナップショットに基づく availability チェック /// 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 @@ -90,8 +59,8 @@ impl LoopSnapshotMergeBox { /// /// ## Option C ロジック /// - /// 1. LocalScopeInspectorBox で変数定義位置を追跡 - /// 2. LoopVarClassBox で変数を分類 + /// 1. スナップショットから変数定義位置を再構築 + /// 2. pinned/carrier hint を優先し、残りは availability で BodyLocalExit/BodyLocalInternal を判定 /// 3. BodyLocalInternal(全exit predsで定義されていない)変数は skip /// /// ## 例(skip_whitespace バグ修正) @@ -116,18 +85,7 @@ impl LoopSnapshotMergeBox { let debug = std::env::var("NYASH_OPTION_C_DEBUG").is_ok(); - // Phase 69-2: inspector を内部で構築(外部引数から削除) - // exit_snapshots から変数定義位置を再構築 - let mut inspector = LocalScopeInspectorBox::new(); - for (block_id, snapshot) in exit_snapshots { - for var_name in snapshot.keys() { - inspector.record_definition(var_name, *block_id); - } - } - // header_vals も記録 - for var_name in header_vals.keys() { - inspector.record_definition(var_name, header_id); - } + let definitions = build_definitions(header_id, header_vals, exit_snapshots); if debug { eprintln!("[Option C] merge_exit_with_classification called"); eprintln!("[Option C] exit_preds: {:?}", exit_preds); @@ -144,9 +102,6 @@ impl LoopSnapshotMergeBox { // header 値を exit PHI 入力に含めると「非支配ブロックからの値参照」で壊れる。 let header_in_exit_preds = exit_preds.contains(&header_id); - // LoopVarClassBox でフィルタリング - let classifier = LoopVarClassBox::new(); - // すべての変数名を収集(決定的順序のためBTreeSet使用) let mut all_vars: BTreeSet = BTreeSet::new(); all_vars.extend(header_vals.keys().cloned()); @@ -156,34 +111,35 @@ impl LoopSnapshotMergeBox { // 各変数を分類して、exit PHI が必要なもののみ処理(アルファベット順で決定的) for var_name in all_vars { - // Option C: 変数分類(実際のCFG predecessorsを使用) - let class = classifier.classify( + let class = classify_exit_var( &var_name, pinned_vars, carrier_vars, - &inspector, - exit_preds, // ← 実際のCFG predecessorsを使用! + exit_preds, + &definitions, ); + let needs_exit_phi = class_needs_exit_phi(class); if debug { eprintln!( "[Option C] var '{}': {:?} needs_exit_phi={}", var_name, class, - class.needs_exit_phi() + needs_exit_phi ); - let defining_blocks = inspector.get_defining_blocks(&var_name); - eprintln!("[Option C] defining_blocks: {:?}", defining_blocks); + if let Some(defining_blocks) = definitions.get(&var_name) { + eprintln!("[Option C] defining_blocks: {:?}", defining_blocks); + } } // Option C: Additional check - even Carrier/Pinned need definition check! // Carrier/Pinned だからといって、全 exit preds で定義されているとは限らない - let is_in_all_preds = inspector.is_available_in_all(&var_name, exit_preds); + let is_in_all_preds = is_available_in_all(&var_name, exit_preds, &definitions); // exit PHI が不要な場合は skip - if !class.needs_exit_phi() || !is_in_all_preds { + if !needs_exit_phi || !is_in_all_preds { if debug { - if !class.needs_exit_phi() { + if !needs_exit_phi { eprintln!( "[Option C] → SKIP exit PHI for '{}' (class={:?})", var_name, class @@ -240,6 +196,88 @@ impl LoopSnapshotMergeBox { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ExitVarClass { + Pinned, + Carrier, + BodyLocalExit, + BodyLocalInternal, +} + +fn classify_exit_var( + var_name: &str, + pinned_vars: &[String], + carrier_vars: &[String], + exit_preds: &[BasicBlockId], + definitions: &BTreeMap>, +) -> ExitVarClass { + if is_pin_temp(var_name) { + return ExitVarClass::BodyLocalInternal; + } + + if pinned_vars.iter().any(|p| p == var_name) { + return ExitVarClass::Pinned; + } + + if carrier_vars.iter().any(|c| c == var_name) { + return ExitVarClass::Carrier; + } + + if is_available_in_all(var_name, exit_preds, definitions) { + ExitVarClass::BodyLocalExit + } else { + ExitVarClass::BodyLocalInternal + } +} + +fn class_needs_exit_phi(class: ExitVarClass) -> bool { + matches!( + class, + ExitVarClass::Pinned | ExitVarClass::Carrier | ExitVarClass::BodyLocalExit + ) +} + +fn build_definitions( + header_id: BasicBlockId, + header_vals: &BTreeMap, + exit_snapshots: &[(BasicBlockId, BTreeMap)], +) -> BTreeMap> { + let mut definitions: BTreeMap> = BTreeMap::new(); + + for name in header_vals.keys() { + definitions + .entry(name.clone()) + .or_default() + .insert(header_id); + } + + for (bb, snap) in exit_snapshots { + for name in snap.keys() { + definitions.entry(name.clone()).or_default().insert(*bb); + } + } + + definitions +} + +fn is_available_in_all( + var_name: &str, + required_blocks: &[BasicBlockId], + definitions: &BTreeMap>, +) -> bool { + if let Some(defining_blocks) = definitions.get(var_name) { + required_blocks + .iter() + .all(|block| defining_blocks.contains(block)) + } else { + false + } +} + +fn is_pin_temp(var_name: &str) -> bool { + var_name.starts_with("__pin$") && var_name.contains("$@") +} + #[cfg(test)] mod tests { use super::*; @@ -293,7 +331,7 @@ mod tests { let header_vals = BTreeMap::new(); // ch not in header - let mut break1_snap = BTreeMap::new(); + let 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 diff --git a/src/mir/phi_core/loop_var_classifier.rs b/src/mir/phi_core/loop_var_classifier.rs deleted file mode 100644 index 6f148da7..00000000 --- a/src/mir/phi_core/loop_var_classifier.rs +++ /dev/null @@ -1,578 +0,0 @@ -/// LoopVarClassBox - Loop variable classifier -/// -/// # Purpose -/// -/// This box classifies loop variables into 4 categories: -/// - Pinned: Loop-crossing parameters (always need PHI) -/// - Carrier: Loop-modified variables (always need PHI) -/// - BodyLocalExit: Body-local but defined in ALL exit predecessors (need exit PHI) -/// - BodyLocalInternal: Body-local, NOT in all exit predecessors (NO exit PHI) -/// -/// # Responsibility -/// -/// - Classify variables based on their definition scope -/// - Use LocalScopeInspectorBox to check definition locations -/// - Provide classification for PHI generation decision -/// -/// # Design Philosophy (Box Theory) -/// -/// This box has ONE job: classify variables. -/// It doesn't generate PHI nodes or modify IR. -/// It's a pure decision box that other boxes can query. -use super::local_scope_inspector::LocalScopeInspectorBox; -use crate::mir::BasicBlockId; - -/// Variable classification for loop PHI generation -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LoopVarClass { - /// Loop-crossing parameter (always needs header/exit PHI) - /// - /// Example: - /// ``` - /// method process(limit) { // limit is Pinned - /// local i = 0 - /// loop(i < limit) { i = i + 1 } // limit doesn't change - /// } - /// ``` - Pinned, - - /// Loop-modified variable (always needs header/exit PHI) - /// - /// Example: - /// ``` - /// local i = 0 - /// loop(i < 10) { - /// i = i + 1 // i is Carrier (modified in loop) - /// } - /// ``` - Carrier, - - /// Body-local variable defined in ALL exit predecessors (needs exit PHI) - /// - /// Example: - /// ``` - /// loop(condition) { - /// local x = compute() // defined at loop entry - /// if x > 10 { break } // exit path 1: x exists - /// if x < 0 { break } // exit path 2: x exists - /// } - /// // x is BodyLocalExit → needs exit PHI - /// ``` - BodyLocalExit, - - /// Body-local variable NOT in all exit predecessors (NO exit PHI) - /// - /// Example (skip_whitespace bug): - /// ``` - /// loop(1 == 1) { - /// if i >= n { break } // exit path 1: ch doesn't exist - /// local ch = s.substring() // ch defined here - /// if ch == " " { ... } else { break } // exit path 2: ch exists - /// } - /// // ch is BodyLocalInternal → NO exit PHI (would cause pred mismatch!) - /// ``` - BodyLocalInternal, -} - -impl LoopVarClass { - /// Check if this classification requires exit PHI - pub fn needs_exit_phi(self) -> bool { - match self { - LoopVarClass::Pinned => true, - LoopVarClass::Carrier => true, - LoopVarClass::BodyLocalExit => true, - LoopVarClass::BodyLocalInternal => false, // ← Option C: Skip exit PHI! - } - } - - /// Check if this classification requires header PHI - pub fn needs_header_phi(self) -> bool { - match self { - LoopVarClass::Pinned => true, - LoopVarClass::Carrier => true, - LoopVarClass::BodyLocalExit => false, // Body-local: no header PHI - LoopVarClass::BodyLocalInternal => false, - } - } - - /// Get human-readable description - pub fn description(&self) -> &'static str { - match self { - LoopVarClass::Pinned => "Loop-crossing parameter", - LoopVarClass::Carrier => "Loop-modified variable", - LoopVarClass::BodyLocalExit => "Body-local (all exits)", - LoopVarClass::BodyLocalInternal => "Body-local (partial exits)", - } - } -} - -/// Loop variable classifier box -/// -/// # Phase 30: LoopScopeShape 移行中 -/// -/// このBoxは将来 LoopScopeShape に吸収される予定。 -/// 新規コードは `LoopScopeShape::classify()` を直接使うことを推奨。 -/// -/// # Usage (Legacy) -/// -/// ``` -/// let inspector = LocalScopeInspectorBox::new(); -/// // ... record definitions ... -/// -/// let classifier = LoopVarClassBox::new(); -/// let class = classifier.classify( -/// "ch", -/// &["limit", "n"], // pinned_vars -/// &["i"], // carrier_vars -/// &inspector, -/// &exit_preds, -/// ); -/// -/// if class.needs_exit_phi() { -/// // Generate exit PHI -/// } -/// ``` -/// -/// # Usage (Phase 30 Recommended) -/// -/// ``` -/// // LoopScopeShape が利用可能な場合は直接使う -/// let class = scope.classify("ch"); -/// ``` -#[derive(Debug, Clone, Default)] -pub struct LoopVarClassBox; - -impl LoopVarClassBox { - /// Create a new classifier - pub fn new() -> Self { - Self - } - - /// Classify a variable for PHI generation decision - /// - /// # Phase 30 TODO - /// - /// このメソッドは将来 `LoopScopeShape::classify()` に置き換える予定。 - /// 呼び出し側が LoopScopeShape を持っている場合は、そちらを直接使うこと。 - /// - /// # Parameters - /// - /// - `var_name`: Variable to classify - /// - `pinned_vars`: Known loop-crossing parameters - /// - `carrier_vars`: Known loop-modified variables - /// - `inspector`: LocalScopeInspectorBox to check definition locations - /// - `exit_preds`: All exit predecessor blocks - /// - /// # Returns - /// - /// LoopVarClass indicating PHI generation requirements - /// - /// # Example - /// - /// ``` - /// // skip_whitespace scenario: - /// let class = classifier.classify( - /// "ch", - /// &[], // not pinned - /// &[], // not carrier - /// &inspector, // ch only in block 5 - /// &[BasicBlockId(2), BasicBlockId(5)], // exit preds - /// ); - /// - /// assert_eq!(class, LoopVarClass::BodyLocalInternal); - /// assert!(!class.needs_exit_phi()); // ← Skip exit PHI! - /// ``` - pub fn classify( - &self, - var_name: &str, - pinned_vars: &[String], - carrier_vars: &[String], - inspector: &LocalScopeInspectorBox, - exit_preds: &[BasicBlockId], - ) -> LoopVarClass { - // Priority 0: __pin$ temporary variables are ALWAYS BodyLocalInternal - // Reason: These are compiler-generated temporaries for pinning expression values. - // They are defined inside loop bodies and should NEVER get exit PHIs. - // Task先生の発見: ValueId(289)等の未定義値エラーの根本原因! - if var_name.starts_with("__pin$") && var_name.contains("$@") { - return LoopVarClass::BodyLocalInternal; - } - - // Priority 1: Check if it's a pinned variable - if pinned_vars.iter().any(|p| p == var_name) { - return LoopVarClass::Pinned; - } - - // Priority 2: Check if it's a carrier variable - if carrier_vars.iter().any(|c| c == var_name) { - return LoopVarClass::Carrier; - } - - // Priority 3: Check if it's a body-local variable - // Option C logic: Check if defined in ALL exit predecessors - if inspector.is_available_in_all(var_name, exit_preds) { - LoopVarClass::BodyLocalExit - } else { - // ← Option C: Skip exit PHI for partial body-local variables! - LoopVarClass::BodyLocalInternal - } - } - - /// Batch classify multiple variables - /// - /// # Returns - /// - /// Vec of (var_name, classification) tuples - pub fn classify_all( - &self, - var_names: &[String], - pinned_vars: &[String], - carrier_vars: &[String], - inspector: &LocalScopeInspectorBox, - exit_preds: &[BasicBlockId], - ) -> Vec<(String, LoopVarClass)> { - var_names - .iter() - .map(|name| { - let class = self.classify(name, pinned_vars, carrier_vars, inspector, exit_preds); - (name.clone(), class) - }) - .collect() - } - - /// Filter variables that need exit PHI - /// - /// # Returns - /// - /// Vec of variable names that should get exit PHI - pub fn filter_exit_phi_candidates( - &self, - var_names: &[String], - pinned_vars: &[String], - carrier_vars: &[String], - inspector: &LocalScopeInspectorBox, - exit_preds: &[BasicBlockId], - ) -> Vec { - var_names - .iter() - .filter(|name| { - let class = self.classify(name, pinned_vars, carrier_vars, inspector, exit_preds); - class.needs_exit_phi() - }) - .cloned() - .collect() - } - - // ======================================================================== - // Phase 30 F-1.1: LoopScopeShape 委譲メソッド - // ======================================================================== - - /// Phase 30: LoopScopeShape を使って変数を分類 - /// - /// 新規コードではこのメソッドを使うことを推奨。 - /// 将来的には旧 classify() メソッドを削除し、このメソッドが標準になる。 - /// - /// # Example - /// - /// ```ignore - /// let scope = LoopScopeShape::from_existing_boxes(...)?; - /// let class = classifier.classify_with_scope("ch", &scope); - /// ``` - #[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed - pub(crate) fn classify_with_scope( - &self, - var_name: &str, - scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape, - ) -> LoopVarClass { - scope.classify(var_name) - } - - /// Phase 30: LoopScopeShape を使って複数変数を一括分類 - #[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed - pub(crate) fn classify_all_with_scope( - &self, - var_names: &[String], - scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape, - ) -> Vec<(String, LoopVarClass)> { - scope.classify_all(var_names) - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_loop_var_class_needs_exit_phi() { - assert!(LoopVarClass::Pinned.needs_exit_phi()); - assert!(LoopVarClass::Carrier.needs_exit_phi()); - assert!(LoopVarClass::BodyLocalExit.needs_exit_phi()); - assert!(!LoopVarClass::BodyLocalInternal.needs_exit_phi()); // ← Option C! - } - - #[test] - fn test_loop_var_class_needs_header_phi() { - assert!(LoopVarClass::Pinned.needs_header_phi()); - assert!(LoopVarClass::Carrier.needs_header_phi()); - assert!(!LoopVarClass::BodyLocalExit.needs_header_phi()); - assert!(!LoopVarClass::BodyLocalInternal.needs_header_phi()); - } - - #[test] - fn test_classify_pinned_variable() { - let inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - let class = classifier.classify( - "limit", - &["limit".to_string()], // pinned - &[], - &inspector, - &[], - ); - - assert_eq!(class, LoopVarClass::Pinned); - assert!(class.needs_exit_phi()); - assert!(class.needs_header_phi()); - } - - #[test] - fn test_classify_carrier_variable() { - let inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - let class = classifier.classify( - "i", - &[], - &["i".to_string()], // carrier - &inspector, - &[], - ); - - assert_eq!(class, LoopVarClass::Carrier); - assert!(class.needs_exit_phi()); - assert!(class.needs_header_phi()); - } - - #[test] - fn test_classify_body_local_exit() { - let mut inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - // Variable "x" is defined in all exit predecessors - let block_2 = BasicBlockId::new(2); - let block_5 = BasicBlockId::new(5); - - inspector.record_definition("x", block_2); - inspector.record_definition("x", block_5); - - let exit_preds = vec![block_2, block_5]; - - let class = classifier.classify("x", &[], &[], &inspector, &exit_preds); - - assert_eq!(class, LoopVarClass::BodyLocalExit); - assert!(class.needs_exit_phi()); // Should get exit PHI - assert!(!class.needs_header_phi()); - } - - #[test] - fn test_classify_body_local_internal() { - let mut inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - // Variable "ch" is only defined in block 5, not in block 2 - let block_2 = BasicBlockId::new(2); - let block_5 = BasicBlockId::new(5); - - inspector.record_definition("ch", block_5); // Only block 5! - - let exit_preds = vec![block_2, block_5]; - - let class = classifier.classify("ch", &[], &[], &inspector, &exit_preds); - - assert_eq!(class, LoopVarClass::BodyLocalInternal); - assert!(!class.needs_exit_phi()); // ← Option C: Skip exit PHI! - assert!(!class.needs_header_phi()); - } - - /// Test the skip_whitespace PHI bug scenario - #[test] - fn test_skip_whitespace_scenario() { - let mut inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - // Simulate skip_whitespace loop: - // - Variables i, n, s are pinned/carrier (defined everywhere) - // - Variable ch is only defined in block 5 (break path 2) - - let block_2 = BasicBlockId::new(2); // header / break path 1 - let block_5 = BasicBlockId::new(5); // break path 2 - let block_7 = BasicBlockId::new(7); // latch (hypothetical) - - // i, n, s are in all blocks - for var in &["i", "n", "s"] { - inspector.record_definition(var, block_2); - inspector.record_definition(var, block_5); - inspector.record_definition(var, block_7); - } - - // ch is only in block 5 - inspector.record_definition("ch", block_5); - - let exit_preds = vec![block_2, block_5, block_7]; - - // Classify all variables - let pinned = vec!["n".to_string(), "s".to_string()]; - let carrier = vec!["i".to_string()]; - - let i_class = classifier.classify("i", &pinned, &carrier, &inspector, &exit_preds); - let n_class = classifier.classify("n", &pinned, &carrier, &inspector, &exit_preds); - let s_class = classifier.classify("s", &pinned, &carrier, &inspector, &exit_preds); - let ch_class = classifier.classify("ch", &pinned, &carrier, &inspector, &exit_preds); - - // i, n, s should need exit PHI - assert_eq!(i_class, LoopVarClass::Carrier); - assert_eq!(n_class, LoopVarClass::Pinned); - assert_eq!(s_class, LoopVarClass::Pinned); - - assert!(i_class.needs_exit_phi()); - assert!(n_class.needs_exit_phi()); - assert!(s_class.needs_exit_phi()); - - // ch should NOT need exit PHI (Option C: prevents PHI pred mismatch!) - assert_eq!(ch_class, LoopVarClass::BodyLocalInternal); - assert!(!ch_class.needs_exit_phi()); // ← This fixes the bug! - } - - #[test] - fn test_classify_all() { - let mut inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - let block_2 = BasicBlockId::new(2); - let block_5 = BasicBlockId::new(5); - - // Setup: i, n in all blocks; ch only in block 5 - for var in &["i", "n"] { - inspector.record_definition(var, block_2); - inspector.record_definition(var, block_5); - } - inspector.record_definition("ch", block_5); - - let vars = vec!["i".to_string(), "n".to_string(), "ch".to_string()]; - let pinned = vec!["n".to_string()]; - let carrier = vec!["i".to_string()]; - let exit_preds = vec![block_2, block_5]; - - let results = classifier.classify_all(&vars, &pinned, &carrier, &inspector, &exit_preds); - - assert_eq!(results.len(), 3); - assert_eq!(results[0], ("i".to_string(), LoopVarClass::Carrier)); - assert_eq!(results[1], ("n".to_string(), LoopVarClass::Pinned)); - assert_eq!( - results[2], - ("ch".to_string(), LoopVarClass::BodyLocalInternal) - ); - } - - #[test] - fn test_filter_exit_phi_candidates() { - let mut inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - let block_2 = BasicBlockId::new(2); - let block_5 = BasicBlockId::new(5); - - // Setup: i, n in all blocks; ch only in block 5 - for var in &["i", "n"] { - inspector.record_definition(var, block_2); - inspector.record_definition(var, block_5); - } - inspector.record_definition("ch", block_5); - - let vars = vec!["i".to_string(), "n".to_string(), "ch".to_string()]; - let pinned = vec!["n".to_string()]; - let carrier = vec!["i".to_string()]; - let exit_preds = vec![block_2, block_5]; - - let candidates = classifier.filter_exit_phi_candidates( - &vars, - &pinned, - &carrier, - &inspector, - &exit_preds, - ); - - assert_eq!(candidates.len(), 2); - assert!(candidates.contains(&"i".to_string())); - assert!(candidates.contains(&"n".to_string())); - assert!(!candidates.contains(&"ch".to_string())); // ← Option C: ch is filtered out! - } - - /// Test Task先生の発見: __pin$ temporary variables should be BodyLocalInternal - #[test] - fn test_classify_pin_temporary_variables() { - let inspector = LocalScopeInspectorBox::new(); - let classifier = LoopVarClassBox::new(); - - // __pin$ temporary variables should ALWAYS be BodyLocalInternal - // regardless of their definition locations - let pin_vars = vec![ - "__pin$285$@binop_lhs", - "__pin$286$@binop_rhs", - "__pin$437$@binop_lhs", - "__pin$297$@assign", - ]; - - for pin_var in pin_vars { - let class = classifier.classify( - pin_var, - &[], // Not pinned - &[], // Not carrier - &inspector, - &[], // No exit preds - ); - - assert_eq!( - class, - LoopVarClass::BodyLocalInternal, - "Variable '{}' should be BodyLocalInternal", - pin_var - ); - assert!( - !class.needs_exit_phi(), - "Variable '{}' should NOT need exit PHI", - pin_var - ); - assert!( - !class.needs_header_phi(), - "Variable '{}' should NOT need header PHI", - pin_var - ); - } - } - - #[test] - fn test_description() { - assert_eq!( - LoopVarClass::Pinned.description(), - "Loop-crossing parameter" - ); - assert_eq!( - LoopVarClass::Carrier.description(), - "Loop-modified variable" - ); - assert_eq!( - LoopVarClass::BodyLocalExit.description(), - "Body-local (all exits)" - ); - assert_eq!( - LoopVarClass::BodyLocalInternal.description(), - "Body-local (partial exits)" - ); - } -} diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index f881e583..7e61df32 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -13,35 +13,7 @@ pub mod if_phi; // Phase 30 F-2.1: loop_phi 削除(LoopFormBuilder が SSOT) pub mod loop_snapshot_merge; pub mod loopform_builder; - -// Phase 69-4.2: Trio 公開面削減方針 -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// ⚠️ Trio Legacy Boxes (Phase 70 削除予定): -// - LocalScopeInspectorBox (361行) - 変数定義位置追跡(LoopScopeShapeで代替済み) -// - LoopVarClassBox (578行) - 変数分類(LoopScopeShapeで代替済み) -// - LoopExitLivenessBox (414行) - Exit後生存変数分析(LoopScopeShapeで代替済み) -// -// 現在の外部依存(Phase 69-4.1棚卸し済み): -// 1. src/mir/join_ir/lowering/loop_form_intake.rs (~30行) - LoopScopeShape移行待ち -// 2. src/mir/phi_core/loop_snapshot_merge.rs (~60行) - Exit PHI生成で使用中 -// -// Phase 69-4.2 方針: -// - ✅ pub 公開継続(外部依存2箇所が残存) -// - 🎯 目標: phi_core 内部+テストのみが知る状態(現在達成できず) -// - 📋 Phase 70 実装時: json_v0_bridge 移行後に完全削除 -// -// TODO(Phase 70): json_v0_bridge の LoopScopeShape 移行完了後、以下を削除: -// - pub mod local_scope_inspector; (361行) -// - pub mod loop_var_classifier; (578行) -// - pub mod loop_exit_liveness; (414行) -// - loop_snapshot_merge.rs 内の Trio 使用箇所 (~60行) -// - loop_form_intake.rs 内の Trio 使用箇所 (~30行) -// 合計削減見込み: ~1,443行 -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -// Option C PHI bug fix: Box-based design (Phase 70 削除予定) -pub mod local_scope_inspector; -pub mod loop_var_classifier; +// Trio legacy boxes removed in Phase 70: LoopScopeShape now owns classification/liveness. // Phase 26-B: Box-First Refactoring // Phase 30 F-2.1: body_local_phi_builder 削除(LoopScopeShape で代替) @@ -60,12 +32,6 @@ pub mod phi_builder_box; // Phase 35-5: if_body_local_merge 削除(PhiBuilderBoxに吸収済み) // Phase 35-5: phi_invariants 削除(JoinIR Verifierに移譲済み) -// Phase 26-F-4: Loop Exit Liveness Box - exit後で使われる変数決定箱 -// ⚠️ Phase 69-4.2: Trio Legacy Box (Phase 70 削除予定) -// - 現在の外部依存: loop_form_intake.rs が使用中 -// - TODO(Phase 70): LoopScopeShape 移行後に削除 -pub mod loop_exit_liveness; - // Phase 61-7.0: Dead code 削除 // 削除された facade 関数: // - build_if_phis(): 呼び出し元ゼロ、PhiBuilderBox::generate_phis() で代替 diff --git a/src/mir/phi_core/phi_builder_box.rs b/src/mir/phi_core/phi_builder_box.rs index 7166bd3c..06fe9a3e 100644 --- a/src/mir/phi_core/phi_builder_box.rs +++ b/src/mir/phi_core/phi_builder_box.rs @@ -83,7 +83,7 @@ pub struct PhiBuilderBox { /// /// ## 責務分離の原則 /// - **If側(この箱)**: ループコンテキストを「名前セット」だけで受け取る -/// - **Loop側(LoopBuilder)**: LoopVarClassBoxから「キャリア変数名」を抽出 +/// - **Loop側(LoopBuilder)**: LoopScopeShape から「キャリア変数名」を抽出 /// - **橋渡し**: このIfPhiContext経由で最小限の情報伝達 /// /// ## なぜこの設計? @@ -102,7 +102,7 @@ pub struct IfPhiContext { /// - 例: `if i >= n { break }` でthen腕に`i`なし → でもcarrierなのでPHI必要 /// /// # 決定箇所 - /// - LoopBuilder側でLoopVarClassBoxから抽出 + /// - LoopBuilder側でLoopScopeShapeから抽出 /// - Pinned(ループ越しパラメータ) + Carrier(ループ修正変数) pub loop_carrier_names: std::collections::BTreeSet, } @@ -498,7 +498,7 @@ impl PhiBuilderBox { /// │ └─ Carrier変数のPHI /// ├─ Exit PHI: ExitPhiBuilder使用 /// │ ├─ BodyLocalPhiBuilder(要否判定) - /// │ ├─ LoopVarClassBox(変数分類) + /// │ ├─ LoopScopeShape(変数分類) /// │ └─ LocalScopeInspectorBox(定義追跡) /// └─ Seal: PhiInputCollector使用 /// ```