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:
nyash-codex
2025-11-30 07:03:44 +09:00
parent 74a6f0f93e
commit b4bb6a3dca

View File

@ -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)]));
}
}