From b4bb6a3dca8c884255e2ade5be2e97a5e463b4df Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Sun, 30 Nov 2025 07:03:44 +0900 Subject: [PATCH] =?UTF-8?q?feat(joinir):=20Phase=2048-4=20LoopScopeShape?= =?UTF-8?q?=20Trio=E8=B3=AA=E5=95=8FAPI=E7=B5=B1=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 目的 Trio(LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspectorBox)が 答えている「質問」を LoopScopeShape に移し、JoinIR lowering が LoopScopeShape だけを見るようにする。 ## Phase 48-4 実装内容 ### 新規フィールド追加 - **variable_definitions**: `BTreeMap>` - LocalScopeInspectorBox の var_definitions 情報を統合 - Phase 48-4: 空の BTreeMap で初期化(API のみ提供) - Phase 48-5+: from_existing_boxes_legacy で情報抽出予定 ### 新規 API 追加 #### 1. get_exit_live() - exit_live 系 API ```rust pub fn get_exit_live(&self) -> &BTreeSet ``` - **目的**: LoopExitLivenessBox::compute_live_at_exit() の代替 - **設計**: 「質問だけの薄い箱」原則に基づく - callsite は `scope.get_exit_live()` という質問形式でアクセス - フィールド直接参照より API 安定性が高い #### 2. is_available_in_all() - available 系 API ```rust pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool ``` - **目的**: LocalScopeInspectorBox::is_available_in_all() の代替 - **Phase 48-4 実装**: variable_definitions が空のため常に false を返す - **Phase 48-5+ 計画**: LocalScopeInspectorBox から情報抽出して統合 ### from_existing_boxes_legacy 修正 - `variable_definitions` を空で初期化(L503-505) - return 文に variable_definitions フィールド追加(L519-520) ### テストコード #### 新規テスト(3本) 1. **test_get_exit_live**: get_exit_live() API の動作確認 2. **test_is_available_in_all_phase48_4**: Phase 48-4(空の variable_definitions)での動作確認 3. **test_is_available_in_all_phase48_5_future**: Phase 48-5+ での統合状態をシミュレート #### 既存テスト修正 - 3箇所の手動構築箇所に `variable_definitions: BTreeMap::new()` 追加 - test_from_scope_validation_header_eq_exit (L967) - test_classify_method (L1118) - test_classify_phi_consistency (L1155) ## テスト結果 ``` running 13 tests test test_get_exit_live ... ok test test_is_available_in_all_phase48_4 ... ok test test_is_available_in_all_phase48_5_future ... ok [... 10 other tests ...] test result: ok. 13 passed; 0 failed ``` ## 箱理論の実践 ### 「質問だけの薄い箱」原則 - ✅ Trio の型を知らない形で API を提供 - ✅ callsite が質問形式でアクセス(`scope.get_exit_live()`) - ✅ 内部実装は段階的に構築(Phase 48-4: API, Phase 48-5+: 実装) ### 段階的移行戦略 - **Phase 48-4**: API のみ提供(variable_definitions 空) - **Phase 48-5**: JoinIR lowering から Trio を外す - **Phase 48-6**: Trio を from_existing_boxes_legacy だけに押し込む ## Phase 48-3 棚卸し結果との整合性 Phase 48-3 で洗い出した Trio 使用箇所(P1: 22箇所)のうち、 10箇所の legacy 経路を Phase 48-5+ で段階削除可能に。 ## 次のステップ(Phase 48-5) - JoinIR lowering から Trio を外す - LoopScopeShape のメソッド呼び出しに差し替え - variable_definitions に LocalScopeInspectorBox の情報を統合 🎉 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/mir/join_ir/lowering/loop_scope_shape.rs | 204 +++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/src/mir/join_ir/lowering/loop_scope_shape.rs b/src/mir/join_ir/lowering/loop_scope_shape.rs index 2acbe931..f4302adb 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape.rs @@ -201,6 +201,25 @@ pub(crate) struct LoopScopeShape { /// Progress carrier for loop termination check (future Verifier use) /// Typically the loop index variable (i, pos, etc.) pub progress_carrier: Option, + + /// Phase 48-4: 変数定義ブロックのマッピング + /// + /// LocalScopeInspectorBox の var_definitions 情報を統合。 + /// + /// # Phase 48-4 Note + /// + /// 現在は空の BTreeMap で初期化(API のみ提供)。 + /// Phase 48-5+ で from_existing_boxes_legacy から LocalScopeInspectorBox の情報を抽出して統合予定。 + /// + /// # Structure + /// + /// - Key: 変数名 + /// - Value: その変数が定義されているブロック ID の集合 + /// + /// # Usage + /// + /// `is_available_in_all()` メソッドで、変数が全指定ブロックで利用可能かを判定。 + pub(crate) variable_definitions: BTreeMap>, } impl LoopScopeShape { @@ -481,6 +500,10 @@ impl LoopScopeShape { // Determine progress_carrier (heuristic: first carrier, typically 'i') let progress_carrier = carriers.iter().next().cloned(); + // Phase 48-4: variable_definitions を空で初期化(API のみ提供) + // Phase 48-5+ で LocalScopeInspectorBox から情報を抽出して統合予定 + let variable_definitions = BTreeMap::new(); + Some(Self { // Block IDs from LoopForm header, @@ -493,6 +516,8 @@ impl LoopScopeShape { body_locals, exit_live, progress_carrier, + // Phase 48-4: Trio 質問 API サポート + variable_definitions, }) } @@ -544,6 +569,84 @@ impl LoopScopeShape { .map(|name| (name.clone(), self.classify(name))) .collect() } + + // ======================================================================== + // Phase 48-4: Trio 質問 API の統合 + // ======================================================================== + + /// Phase 48-4: ループ終了時に live な変数集合を返す + /// + /// LoopExitLivenessBox::compute_live_at_exit() の代替 API。 + /// + /// # Returns + /// + /// ループ終了後に使用される変数の集合(pinned + carriers + BodyLocalExit) + /// + /// # Phase 48-4 設計 + /// + /// この API は「質問だけの薄い箱」原則に基づく: + /// - callsite は `scope.get_exit_live()` という質問形式でアクセス + /// - フィールド直接参照より API 安定性が高い + /// - 将来 exit_live の実装変更に強い + /// + /// # Example + /// + /// ```ignore + /// let exit_live = scope.get_exit_live(); + /// for var in exit_live { + /// // exit PHI 生成 + /// } + /// ``` + pub fn get_exit_live(&self) -> &BTreeSet { + &self.exit_live + } + + /// Phase 48-4: 変数が全指定ブロックで利用可能か判定 + /// + /// LocalScopeInspectorBox::is_available_in_all() の代替 API。 + /// + /// # Arguments + /// + /// - `var_name`: 判定する変数名 + /// - `required_blocks`: 全てで利用可能であるべきブロック集合 + /// + /// # Returns + /// + /// - `true`: 変数が全指定ブロックで利用可能(全ブロックで定義済み) + /// - `false`: 一部ブロックで未定義、または変数が存在しない + /// + /// # Phase 48-4 実装状況 + /// + /// 現在は `variable_definitions` が空のため常に false を返す(API のみ提供)。 + /// Phase 48-5+ で from_existing_boxes_legacy から LocalScopeInspectorBox の情報を抽出して統合予定。 + /// + /// # Phase 48-5+ 実装計画 + /// + /// ```ignore + /// // LocalScopeInspectorBox から情報抽出 + /// for var in all_vars { + /// let def_blocks = inspector.get_defining_blocks(var); + /// variable_definitions.insert(var.clone(), def_blocks); + /// } + /// ``` + /// + /// # Example + /// + /// ```ignore + /// // BodyLocalExit vs BodyLocalInternal の判定に使用 + /// if scope.is_available_in_all(&var_name, &[body, exit]) { + /// LoopVarClass::BodyLocalExit + /// } else { + /// LoopVarClass::BodyLocalInternal + /// } + /// ``` + 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)) + } else { + false + } + } } // ============================================================================ @@ -861,6 +964,7 @@ mod tests { body_locals: std::collections::BTreeSet::new(), exit_live: vec!["i".to_string()].into_iter().collect(), progress_carrier: Some("i".to_string()), + variable_definitions: BTreeMap::new(), // Phase 48-4 }; // from_scope は None を返すべき @@ -1011,6 +1115,7 @@ mod tests { .into_iter() .collect(), progress_carrier: Some("i".to_string()), + variable_definitions: BTreeMap::new(), // Phase 48-4 }; // Pinned classification @@ -1047,6 +1152,7 @@ mod tests { .into_iter() .collect(), progress_carrier: Some("i".to_string()), + variable_definitions: BTreeMap::new(), // Phase 48-4 }; // classify() と needs_header_phi() / needs_exit_phi() が一致することを確認 @@ -1066,4 +1172,102 @@ mod tests { ); } } + + // ======================================================================== + // Phase 48-4: Trio 質問 API のテスト + // ======================================================================== + + /// Phase 48-4: get_exit_live() API テスト + #[test] + fn test_get_exit_live() { + let scope = LoopScopeShape { + header: BasicBlockId::new(2), + body: BasicBlockId::new(3), + latch: BasicBlockId::new(4), + exit: BasicBlockId::new(100), + pinned: vec!["s".to_string()].into_iter().collect(), + carriers: vec!["i".to_string()].into_iter().collect(), + body_locals: BTreeSet::new(), + exit_live: vec!["s".to_string(), "i".to_string()].into_iter().collect(), + progress_carrier: Some("i".to_string()), + variable_definitions: BTreeMap::new(), + }; + + let exit_live = scope.get_exit_live(); + assert_eq!(exit_live.len(), 2); + assert!(exit_live.contains("s")); + assert!(exit_live.contains("i")); + } + + /// Phase 48-4: is_available_in_all() API テスト(空の variable_definitions) + #[test] + fn test_is_available_in_all_phase48_4() { + // Phase 48-4: variable_definitions が空のため常に false + let scope = LoopScopeShape { + header: BasicBlockId::new(2), + body: BasicBlockId::new(3), + latch: BasicBlockId::new(4), + exit: BasicBlockId::new(100), + pinned: vec!["s".to_string()].into_iter().collect(), + carriers: vec!["i".to_string()].into_iter().collect(), + body_locals: BTreeSet::new(), + exit_live: vec!["s".to_string(), "i".to_string()].into_iter().collect(), + progress_carrier: Some("i".to_string()), + variable_definitions: BTreeMap::new(), // Phase 48-4: 空で初期化 + }; + + // variable_definitions が空のため、すべて false を返す + assert!(!scope.is_available_in_all("x", &[BasicBlockId::new(3)])); + assert!(!scope.is_available_in_all("i", &[BasicBlockId::new(3)])); + assert!(!scope.is_available_in_all("unknown", &[BasicBlockId::new(3)])); + } + + /// Phase 48-5+ 想定: is_available_in_all() with variable_definitions + #[test] + fn test_is_available_in_all_phase48_5_future() { + // Phase 48-5+ で variable_definitions が統合された状態をシミュレート + let mut variable_definitions = BTreeMap::new(); + variable_definitions.insert( + "x".to_string(), + vec![BasicBlockId::new(3), BasicBlockId::new(4)] + .into_iter() + .collect(), + ); + variable_definitions.insert( + "i".to_string(), + vec![BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)] + .into_iter() + .collect(), + ); + + let scope = LoopScopeShape { + header: BasicBlockId::new(2), + body: BasicBlockId::new(3), + latch: BasicBlockId::new(4), + exit: BasicBlockId::new(100), + pinned: vec!["s".to_string()].into_iter().collect(), + carriers: vec!["i".to_string()].into_iter().collect(), + body_locals: vec!["x".to_string()].into_iter().collect(), + exit_live: vec!["s".to_string(), "i".to_string(), "x".to_string()] + .into_iter() + .collect(), + progress_carrier: Some("i".to_string()), + variable_definitions, // Phase 48-5+ で統合された状態 + }; + + // x は block 3, 4 で定義 → block 3, 4 を要求すれば true + assert!(scope.is_available_in_all("x", &[BasicBlockId::new(3), BasicBlockId::new(4)])); + + // x は block 2 で未定義 → block 2, 3 を要求すれば false + assert!(!scope.is_available_in_all("x", &[BasicBlockId::new(2), BasicBlockId::new(3)])); + + // i は block 2, 3, 4 で定義 → すべて要求しても true + assert!(scope.is_available_in_all( + "i", + &[BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)] + )); + + // unknown は variable_definitions にない → false + assert!(!scope.is_available_in_all("unknown", &[BasicBlockId::new(3)])); + } }