feat(joinir): Phase 48-4 LoopScopeShape Trio質問API統合
## 目的 Trio(LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspectorBox)が 答えている「質問」を LoopScopeShape に移し、JoinIR lowering が LoopScopeShape だけを見るようにする。 ## Phase 48-4 実装内容 ### 新規フィールド追加 - **variable_definitions**: `BTreeMap<String, BTreeSet<BasicBlockId>>` - 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<String> ``` - **目的**: 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 <noreply@anthropic.com>
This commit is contained in:
@ -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<String>,
|
||||
|
||||
/// 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<String, BTreeSet<BasicBlockId>>,
|
||||
}
|
||||
|
||||
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<String> {
|
||||
&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)]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user