refactor(joinir): Pattern 3 ExitMeta化 - Hardcoded ValueIds削除

Refactoring 5.1: Pattern 3 を Pattern 4 と同じ ExitMeta ベースアーキテクチャに統一化

Changes:
1. loop_with_if_phi_minimal.rs
   - 署名: Option<JoinModule> → Result<(JoinModule, JoinFragmentMeta), String>
   - ExitMeta 動的生成ロジック追加(sum, count)
   - インポート追加: carrier_info::{ExitMeta, JoinFragmentMeta}

2. pattern3_with_if_phi.rs
   - Hardcoded 定数削除(PATTERN3_K_EXIT_*_ID 2個削除)
   - Manual exit binding 42行 → ExitMetaCollector 4行に置き換え
   - インポート追加: ExitMetaCollector

3. loop_patterns/with_if_phi.rs
   - Result型変更に対応(.ok()? で変換)

Benefits:
- Pattern 3/4 アーキテクチャ統一化 
- 19行純削減(+55 -74行、3ファイル合計) 
- Hardcoded ValueIds 完全撤廃 
- Phase 213 AST-based generalization の基盤強化 

Tests: All tests passing, loop_if_phi.hako outputs "sum=9" correctly
This commit is contained in:
nyash-codex
2025-12-10 00:29:25 +09:00
parent d7805e5974
commit 8394018694
7 changed files with 865 additions and 86 deletions

View File

@ -741,22 +741,44 @@
- **成果**: Pattern routing は完全に成功、AST-based lowerer 汎用化は Phase 213 へ - **成果**: Pattern routing は完全に成功、AST-based lowerer 汎用化は Phase 213 へ
- **ドキュメント**: phase212-5-implementation-complete.md - **ドキュメント**: phase212-5-implementation-complete.md
- [ ] **Phase 213: Pattern 3 Lowerer 汎用化if-sum minimal** 🚧 **次のフェーズ** - [x] **Phase 213-2: PatternPipelineContext + CarrierUpdateInfo 拡張** ✅ (2025-12-09)
- Task 213-2-2: `PatternPipelineContext` に Pattern 3 用フィールド追加
- `loop_condition: Option<ASTNode>` - ループ条件 AST 保存
- `loop_body: Option<Vec<ASTNode>>` - ループ本体 AST 保存
- `loop_update_summary: Option<LoopUpdateSummary>` - キャリア更新情報
- Task 213-2-3: `CarrierUpdateInfo` に then/else expression 追加
- `then_expr: Option<ASTNode>` - then 分岐更新式
- `else_expr: Option<ASTNode>` - else 分岐更新式
- **成果**: Phase 213 AST-based generalization の基盤完成
- 🚧 **Refactoring 5.1: Pattern 3 Hardcoded ValueIds → ExitMeta化** (2025-12-09)
- **目的**: Pattern 3 lowerer を Pattern 4 と同じ ExitMeta ベースアーキテクチャに統一化
- **変更対象**:
1. `loop_with_if_phi_minimal.rs`
- 署名: `Option<JoinModule>` → `Result<(JoinModule, JoinFragmentMeta), String>`
- ExitMeta 動的生成ロジック追加k_exit parameters → exit_values HashMap
2. `pattern3_with_if_phi.rs`
- Hardcoded 定数削除(`PATTERN3_K_EXIT_SUM_FINAL_ID`, `PATTERN3_K_EXIT_COUNT_FINAL_ID`
- Manual exit binding → `ExitMetaCollector::collect()` に置き換え
- **期待効果**: 42 行削減pattern3_with_if_phi.rs の 22%
- **実装進捗**: Task エージェント実装中
- **テスト計画**: `loop_if_phi.hako` / `phase212_if_sum_min.hako` で検証
- [ ] **Phase 213: Pattern 3 Lowerer 汎用化if-sum minimal** 🚧 **次フェーズ**
- **目的**: Pattern 3 lowerer を AST-based に汎用化し、`phase212_if_sum_min.hako` で RC=2 達成 - **目的**: Pattern 3 lowerer を AST-based に汎用化し、`phase212_if_sum_min.hako` で RC=2 達成
- **スコープ**: - **前置**: Phase 213-2 + Refactoring 5.1 で基盤・アーキテクチャ統一完了
1. 設計ドキュメント作成 - **スコープPhase 214 予定)**:
- `phase213-pattern3-if-sum-generalization.md` 1. Pattern3IfAnalyzer 実装
- 入力: LoopPatternContext + LoopUpdateSummary + IfPhiContext - Loop body から if statement 抽出
- 出力: JoinModule + ExitMeta複数キャリア対応 - If condition → BoolExprLowerer で JoinIR lowering
2. P3 lowerer から定数ハードコード削除 - Then/else branches → キャリア更新式抽出
- `loop_with_if_phi_minimal.rs` の固定条件/更新(`i<=5`, `i%2==1`, `sum+i`)を削除 2. P3 lowerer AST-based 汎用化
- LoopUpdateSummary / BoolExprLowerer / CarrierInfo から動的に AST を読み込み - `loop_condition` から loop limit を動的抽出
- `loop_body::If` から if condition を動的抽出
- `loop_update_summary` から then/else updates を動的抽出
3. `phase212_if_sum_min.hako` 再実行 3. `phase212_if_sum_min.hako` 再実行
- 期待: Pattern 3 routing → JoinIR/MIR/VM → RC=2 - 期待: Pattern 3 routing → JoinIR/MIR/VM → RC=2
- **Fail-Fast 戦略**: Verifier panic や制約エラーがあればログを Phase 213 doc に記録 - **ドキュメント**: phase213-pattern3-if-sum-generalization.md設計完了
- **ドキュメント更新**:
- phase212-5-implementation-complete.md に Phase 213 への継続を明記
- CURRENT_TASK.md 本項目
--- ---

View File

@ -0,0 +1,217 @@
# Phase 213: Progress Checkpoint 1
**Date**: 2025-12-09
**Status**: ✅ Foundation Complete, Ready for Lowerer Refactoring
**Commit**: d7805e59
---
## 🎯 Completed Work
### ✅ Task 213-2-2: PatternPipelineContext Extension
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs`
**Changes**:
1. Added new fields for Pattern 3:
- `loop_condition: Option<ASTNode>` - Loop condition AST
- `loop_body: Option<Vec<ASTNode>>` - Loop body AST
- `loop_update_summary: Option<LoopUpdateSummary>` - Update expressions
2. Updated `build_pattern_context()` for Pattern3:
- Stores `condition.clone()` in `loop_condition`
- Stores `body.to_vec()` in `loop_body`
- Placeholder `None` for `loop_update_summary` (TODO: Task 213-2-4)
3. Updated test cases with new fields
### ✅ Task 213-2-3: CarrierUpdateInfo Extension
**File**: `src/mir/join_ir/lowering/loop_update_summary.rs`
**Changes**:
1. Extended `CarrierUpdateInfo` struct:
- `then_expr: Option<ASTNode>` - Then branch update expression
- `else_expr: Option<ASTNode>` - Else branch update expression
2. Updated `analyze_loop_updates()`:
- Default `None` for `then_expr`/`else_expr`
- Comment: "Will be populated by Pattern 3 analyzer"
---
## 📊 Current State Analysis
### MIR Analysis for `phase212_if_sum_min.hako`
**Expected vs Actual**:
| Element | Expected | Actual (Hardcoded) |
|---------|----------|-------------------|
| Loop limit | `i < 3` | `i <= 5` |
| Loop cond | `icmp Lt %8, %18` | `icmp Le %8, %18` |
| If cond | `i > 0` | `i % 2 == 1` |
| If cond code | `icmp Gt %8, %zero` | `%8 Mod %2; icmp Eq ... %1` |
| Then update | `sum + 1` | `sum + i` |
| Then code | `%9 Add %const_1` | `%9 Add %8` |
**Root Cause**: `loop_with_if_phi_minimal.rs` lines 217-385 are completely hardcoded for `loop_if_phi.hako` test pattern.
---
## 🔄 Next Steps (3 Approaches)
### Approach A: Full AST-Based Generalization (Phase 213 Original Plan)
**Tasks**:
1. Create `Pattern3IfAnalyzer` module
2. Extract if statement from loop body
3. Parse then/else branches for carrier updates
4. Populate `LoopUpdateSummary` with AST expressions
5. Replace hardcoded conditions in lowerer
6. Replace hardcoded updates in lowerer
7. Return `ExitMeta` instead of hardcoded ValueIds
**Pros**: Complete generalization, supports all if-sum patterns
**Cons**: Large scope, ~500-800 lines of new code
### Approach B: Minimal Incremental (80/20 Rule)
**Tasks**:
1. Just replace the 3 hardcoded constants:
- Loop limit: 5 → extract from AST condition
- If condition: `i % 2 == 1` → extract from AST if statement
- Update value: `i` → extract from AST assignment
2. Keep existing structure, minimal changes
**Pros**: Small scope, fast to implement, gets `phase212_if_sum_min.hako` working
**Cons**: Still not fully generic, will need refactoring later
### Approach C: Hybrid - BoolExprLowerer First
**Tasks**:
1. Focus on condition lowering only (loop + if)
2. Keep update expressions hardcoded for now
3. Use existing `condition_to_joinir` infrastructure
4. Defer update generalization to Phase 214
**Pros**: Leverages existing infrastructure, cleaner architecture
**Cons**: `phase212_if_sum_min.hako` still won't work fully
---
## 💭 Analysis & Recommendation
### Key Insight from Phase 212.5
Phase 212.5 discovered that:
1. Pattern routing works correctly (✅ Pattern 3 detected)
2. MIR PHI generation works (✅ `%31 = phi` created)
3. **Only the lowerer's hardcoded values are wrong**
This means the JoinIR → MIR pipeline is solid. We just need to feed it the right JoinIR.
### Recommendation: Approach B (Minimal Incremental)
**Rationale**:
1. **User's 80/20 philosophy**: "完璧より進捗" (progress over perfection)
2. **Fail-Fast principle**: Get `phase212_if_sum_min.hako` working first
3. **Box Theory**: Make minimal, reversible change
4. **Evidence-based**: Phase 212.5 proved the pipeline works
**Implementation Plan**:
1. Extract loop condition from `ctx.loop_condition` AST
2. Extract if condition from `ctx.loop_body` if statement
3. Extract update expression from if-then assignment
4. Replace 3 hardcoded sections in `loop_with_if_phi_minimal.rs`
5. Test with `phase212_if_sum_min.hako` → RC=2 ✅
6. Keep existing tests working (backward compatibility)
**Estimated effort**: 2-3 hours (vs 8-10 hours for Approach A)
---
## 🔍 Technical Details for Approach B
### What Needs to Change
**File**: `src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs`
**Section 1: Loop Condition (lines 217-233)**
```rust
// Before (hardcoded):
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_5,
value: ConstValue::Integer(5), // ← Hardcoded!
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_le,
op: CompareOp::Le, // ← Hardcoded!
lhs: i_param,
rhs: const_5,
}));
// After (AST-based):
// Extract from ctx.loop_condition: "i < 3"
// Lower to JoinIR using existing infrastructure
```
**Section 2: If Condition (lines 254-288)**
```rust
// Before (hardcoded):
// const 2, mod, const 1, eq → (i % 2 == 1)
// After (AST-based):
// Extract from loop_body if statement: "i > 0"
// Lower to JoinIR: const 0, cmp Gt
```
**Section 3: Update Expression (lines 290-298)**
```rust
// Before (hardcoded):
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: sum_then,
op: BinOpKind::Add,
lhs: sum_param,
rhs: i_param, // ← Hardcoded! Should be const 1
}));
// After (AST-based):
// Extract from then-branch assignment: "sum = sum + 1"
// Lower to JoinIR: sum_param Add const_1
```
### Helper Functions Needed
```rust
// Extract loop condition details
fn extract_loop_condition(condition: &ASTNode)
-> Result<(CompareOp, i64), String>
// Extract if statement from loop body
fn extract_if_statement(body: &[ASTNode])
-> Result<&ASTNode, String>
// Extract if condition details
fn extract_if_condition(if_node: &ASTNode)
-> Result<(CompareOp, i64), String>
// Extract update value from assignment
fn extract_update_value(assignment: &ASTNode)
-> Result<i64, String>
```
---
## 📋 Decision Point
**Question for user**: Which approach should we take?
A. Full AST-based generalization (Approach A)
B. Minimal incremental replacement (Approach B) ← **Recommended**
C. Hybrid BoolExprLowerer-first (Approach C)
D. Different approach?
**Current blockers**: None - foundation is complete
**Current branch**: main (`d7805e59`)
**Build status**: ✅ Passing

View File

@ -0,0 +1,199 @@
# Phase 213: セッション進捗サマリー
**Date**: 2025-12-09 (Continuation Session)
**Status**: 🚧 進行中
**Current Commit**: d7805e59 (Phase 213-2 & Refactoring計画)
---
## 📊 セッション成果
### ✅ Phase 213-2: データ構造拡張完了
**完了内容:**
1. `PatternPipelineContext` 拡張
- `loop_condition: Option<ASTNode>` - ループ条件AST保存
- `loop_body: Option<Vec<ASTNode>>` - ループ本体AST保存
- `loop_update_summary: Option<LoopUpdateSummary>` - キャリア更新情報
2. `CarrierUpdateInfo` 拡張
- `then_expr: Option<ASTNode>` - then分岐の更新式
- `else_expr: Option<ASTNode>` - else分岐の更新式
3. `build_pattern_context()` 更新
- Pattern 3 向けにループ条件・本体を保存
**コード削減**: 0行新規追加
**テスト**: ✅ Build成功、既存テスト合格
---
### 🔍 リファクタリング機会調査完了
**調査範囲:** JoinIR Lowering層 + Pattern Builder層
**発見:**
- **共通化度**: 45% (1,500行の重複コード)
- **レガシー度**: 25% (hardcode + PoC コメント)
- **削減可能**: 500-700行 (14-20%)
**リファクタリング提案(優先度順):**
| # | 項目 | 優先度 | 所要時間 | 削減行数 |
|---|------|--------|---------|---------|
| 5.1 | Pattern 3 Hardcode削除 + ExitMeta化 | HIGH | 3-4h | 22行 |
| 5.2 | Dummy Count Backward Compat削除 | HIGH | 2-3h | 20行 |
| 5.3 | Loop Template Extraction | MEDIUM | 4-6h | 150行 |
| 5.4 | ValueId Allocation標準化 | MEDIUM | 3-4h | 200行 |
| 5.5 | LowererTrait化 | LOW | 8-10h | 大幅改修 |
| 5.6 | PatternPipeline Cleanup | LOW | 2-3h | 細かい整理 |
---
### 🚀 実装中: Refactoring 5.1
**目標:** Pattern 3 を Pattern 4 と同じ ExitMeta ベースアーキテクチャに統一化
**変更対象:**
1. `loop_with_if_phi_minimal.rs`
- 署名: `Option<JoinModule>``Result<(JoinModule, JoinFragmentMeta), String>`
- ExitMeta 動的生成ロジック追加
2. `pattern3_with_if_phi.rs`
- Hardcoded 定数削除(`PATTERN3_K_EXIT_*_ID`
- Manual exit binding → ExitMetaCollector に置き換え
**期待効果:**
- ✅ Hardcoded ValueIds 完全削除
- ✅ Pattern 3/4 アーキテクチャ統一化
- ✅ 42行削減pattern3_with_if_phi.rs の22%
- ✅ Phase 213 AST-based generalization の基盤強化
---
## 🏗️ アーキテクチャの進化
### Before (Phase 195)
```
Pattern 3 Lowerer (Test-Only PoC)
├─ Hardcoded loop condition: i <= 5
├─ Hardcoded if condition: i % 2 == 1
├─ Hardcoded updates: sum+i, count+1
└─ Hardcoded exit ValueIds: ValueId(24), ValueId(25)
Pattern 3 Builder
├─ Manual exit binding construction
├─ Dummy count backward compat hack
└─ if has_count { ... } else { ... } 複雑な分岐
```
### After Refactoring 5.1 (Phase 213)
```
Pattern 3 Lowerer (ExitMeta化)
├─ JoinModule + JoinFragmentMeta を返す
├─ ExitMeta 動的生成: {"sum": ValueId(...), "count": ValueId(...)}
└─ Result型エラーハンドリング
Pattern 3 Builder
├─ ExitMetaCollector で動的 exit binding 生成
├─ Hardcoded 定数削除
└─ Carrier validationPattern 4と同じ
```
---
## 📝 次のステップ(推奨順)
### Step 1: Refactoring 5.1 完了待機 (進行中)
- Task エージェント実装中
- Build & Test 確認待ち
### Step 2: Refactoring 5.1 統合 & コミット
- 実装結果確認
- 既存テスト合格確認
- コミット & ドキュメント更新
### Step 3: Refactoring 5.2 実装(選択的)
- Dummy count backward compat 削除
- Single-carrier テスト廃止 or 更新
- Multi-carrier の完全化
### Step 4: Phase 213 本体進行Phase 214に延期予定
- Pattern3IfAnalyzer 実装
- AST-based condition lowering
- AST-based update expression lowering
- ExitMeta による exit binding 統一化
---
## 🎯 Phase 213 最終目標 (Phase 213 + 214)
**短期Phase 213):**
- ✅ PatternPipelineContext 拡張DONE
- ✅ CarrierUpdateInfo 拡張DONE
- 🚧 Refactoring 5.1-5.2(実装中)
**中期Phase 214:**
- Pattern3IfAnalyzer 実装
- AST-based generalization
- `phase212_if_sum_min.hako` → RC=2 達成
**長期Phase 220+:**
- Refactoring 5.3-5.5(アーキテクチャ完成化)
---
## 📚 作成ドキュメント
1. **phase213-progress-checkpoint-1.md**
- 基盤完成時点での進捗報告
- 3つのアプローチ提案
2. **refactoring-5-1-pattern3-exitmeta.md**
- 詳細な実装計画5ステップ
- Before/After コード比較
- テスト戦略 & リスク管理
3. **phase213-session-summary.md** (このファイル)
- セッション全体の進捗まとめ
---
## 🔗 関連リソース
- **Master Plan**: docs/private/roadmap2/phases/00_MASTER_ROADMAP.md
- **Phase 213 Design Doc**: phase213-pattern3-if-sum-generalization.md
- **Phase 212.5 Report**: phase212-5-implementation-complete.md
- **Refactoring 5.1 Plan**: refactoring-5-1-pattern3-exitmeta.md
---
## ✨ Session Highlights
### 🎓 学習ポイント
1. **Box Theory の実践**
- Pattern 3 を修正可能・差し替え可能な箱として設計
- ExitMeta による境界の明確化
2. **アーキテクチャ統一化**
- Pattern 3/4 が同じアーキテクチャになることで保守性向上
- レガシーコードhardcodeを完全排除
3. **段階的改善80/20ルール**
- Phase 213-2: データ構造基盤DONE
- Phase 213: Refactoring 整理整頓(実装中)
- Phase 214: AST-based generalization計画
### 🚀 次のセッションへの引き継ぎ
- **Refactoring 5.1 の実装完了**
- **Phase 213 本体AST-based loweringへの準備完了**
- **包括的な計画ドキュメント整備完了**
---
**Status**: Phase 213 の基盤構築完了 ✅
**Next**: Refactoring 5.1 の結果確認 & 統合

View File

@ -0,0 +1,360 @@
# Refactoring 5.1: Pattern 3 Hardcoded ValueIds → ExitMeta化
**Date**: 2025-12-09
**Status**: 🚧 In Progress
**Estimated Time**: 3-4 hours
**Priority**: HIGH
---
## 📋 目標
Pattern 3 lowerer`loop_with_if_phi_minimal.rs`)を Pattern 4 と同じ ExitMeta ベースのアーキテクチャに統一化する。これにより:
1. ✅ Hardcoded ValueIds 定数削除(`PATTERN3_K_EXIT_*_ID`
2. ✅ Exit binding の動的生成
3. ✅ Multi-carrier support の完全化
4. ✅ Pattern 3/4 の共通化度向上
---
## 🔄 変更対象ファイル
### ファイル1: `src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs`
**現在:**
```rust
pub(crate) fn lower_loop_with_if_phi_pattern(
_scope: LoopScopeShape,
join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule>
```
**変更後:**
```rust
use crate::mir::join_ir::lowering::join_fragment_meta::JoinFragmentMeta;
pub(crate) fn lower_loop_with_if_phi_pattern(
_scope: LoopScopeShape,
join_value_space: &mut JoinValueSpace,
) -> Result<(JoinModule, JoinFragmentMeta), String>
```
**変更内容:**
1. 戻り値を `Option<JoinModule>``Result<(JoinModule, JoinFragmentMeta), String>` に変更
2. `JoinFragmentMeta` を構築して返す
3. ExitMeta を動的に生成
### ファイル2: `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
**現在:**
```rust
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24); // Hardcoded!
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25); // Hardcoded!
// Lines 118-164: has_count 条件分岐で exit_bindings を手動構築
let exit_bindings = if has_count {
vec![
LoopExitBinding {
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ← Hardcoded!
...
},
...
]
} else {
// Dummy count hack
...
}
```
**変更後:**
```rust
// Hardcoded 定数削除(削除)
// Lines 300-314: ExitMeta から exit_bindings を動的生成
let exit_bindings = ExitMetaCollector::collect(
self,
&exit_meta,
debug,
);
// Carrier validationPattern 4と同じ
for carrier in &carrier_info.carriers {
if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) {
return Err(format!(
"[cf_loop/pattern3] Carrier '{}' not found in exit bindings",
carrier.name
));
}
}
```
---
## 🔧 実装ステップ
### Step 1: `loop_with_if_phi_minimal.rs` の k_exit 関数を分析
**現在の k_exit 実装lines 401-415:**
```rust
let mut k_exit_func = JoinFunction::new(
k_exit_id,
"k_exit".to_string(),
vec![sum_final, count_final], // Phase 195: Multi-carrier
);
k_exit_func.body.push(JoinInst::Ret {
value: Some(sum_final),
});
```
**分析:**
- `k_exit` の parameters: `[sum_final, count_final]` が exit PHI
- k_exit は `sum_final` を return最初の carrier
- ExitMeta として記録すべき情報:
- `sum``sum_final` (ValueId(24))
- `count``count_final` (ValueId(25))
### Step 2: ExitMeta 構築ロジック追加
lowerer の最後に以下を追加:
```rust
// Phase 213: Build ExitMeta for dynamic exit binding generation
use crate::mir::join_ir::lowering::join_fragment_meta::JoinFragmentMeta;
use crate::mir::join_ir::lowering::exit_meta::ExitMeta;
use std::collections::HashMap;
let mut exit_values = HashMap::new();
// Map carrier names to their k_exit parameter ValueIds
exit_values.insert("sum".to_string(), sum_final);
if has_count {
exit_values.insert("count".to_string(), count_final);
}
let exit_meta = ExitMeta {
exit_values,
exit_func_id: k_exit_id,
};
let fragment_meta = JoinFragmentMeta {
exit_meta,
// その他フィールド
};
Ok((join_module, fragment_meta))
```
**注:** `has_count` フラグは必要。multi-carrier に対応するため。
### Step 3: `pattern3_with_if_phi.rs` での lowerer 呼び出し変更
**現在lines 109-116:**
```rust
let join_module = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
Some(module) => module,
None => {
trace::trace().debug("pattern3", "Pattern 3 lowerer returned None");
return Ok(None);
}
};
```
**変更後:**
```rust
let (join_module, exit_meta) = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
Ok(result) => result,
Err(e) => {
trace::trace().debug("pattern3", &format!("Pattern 3 lowerer failed: {}", e));
return Err(format!("[cf_loop/pattern3] Lowering failed: {}", e));
}
};
trace::trace().debug(
"pattern3",
&format!("ExitMeta: {} exit values", exit_meta.exit_values.len())
);
for (carrier_name, join_value) in &exit_meta.exit_values {
trace::trace().debug(
"pattern3",
&format!(" {} → ValueId({})", carrier_name, join_value.0)
);
}
```
### Step 4: Exit binding 動的生成Hardcoded 定数削除)
**現在lines 8-30, 118-164:**
```rust
// Hardcoded constants
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24);
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25);
// Manual exit_bindings construction with has_count branching
let exit_bindings = if has_count {
vec![
LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ← Hardcoded!
host_slot: sum_var_id,
},
LoopExitBinding {
carrier_name: "count".to_string(),
join_exit_value: PATTERN3_K_EXIT_COUNT_FINAL_ID, // ← Hardcoded!
host_slot: count_var_id,
}
]
} else {
// Single-carrier hack
...
}
```
**削除/変更:**
```rust
// Hardcoded constants削除
// const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24);
// const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25);
// Dynamic exit binding generation追加
use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
let exit_bindings = ExitMetaCollector::collect(
self,
&exit_meta,
debug,
);
// Carrier validation
for carrier in &carrier_info.carriers {
if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) {
return Err(format!(
"[cf_loop/pattern3] Carrier '{}' not found in exit bindings",
carrier.name
));
}
}
```
### Step 5: Dummy Count Backward Compat 簡略化
**現在lines 145-164:**
```rust
if has_count {
// Multi-carrier case
(vec![...], vec![...], vec![...])
} else {
// Single-carrier case with Dummy void
let dummy_count_id = constant::emit_void(self);
(vec![...], vec![..., dummy_count_id], vec![...])
}
```
**簡略化:**
```rust
// Phase 213: Always use multi-carrier structure
// Single-carrier tests will use dummy void internally
let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)];
let mut host_inputs = vec![ctx.loop_var_id, sum_var_id];
if has_count {
host_inputs.push(carrier_count_var_id);
} else {
// Use void dummy for backward compat
host_inputs.push(constant::emit_void(self));
}
```
---
## 📊 Before/After Code Change Summary
### `loop_with_if_phi_minimal.rs`
| 項目 | Before | After | 削減 |
|------|--------|-------|------|
| 関数署名 | `Option<JoinModule>` | `Result<(JoinModule, JoinFragmentMeta)>` | - |
| k_exit 構築 | あり | あり(変更なし) | 0行 |
| ExitMeta 構築 | なし | 新規追加 | +20行 |
| **計** | 428行 | 448行 | +20行 |
### `pattern3_with_if_phi.rs`
| 項目 | Before | After | 削減 |
|------|--------|-------|------|
| Hardcoded 定数 | 2個lines 8-30 | 削除 | -2行 |
| Manual exit binding | ありlines 118-164 | ExitMetaCollector化 | -40行 |
| Dummy count hack | あり | 簡略化 | -5行 |
| Lowerer呼び出し | None/Some | Ok/Err | -5行 |
| ExitMeta debug | なし | 新規追加 | +10行 |
| **計** | 191行 | 149行 | **-42行22%削減)** |
---
## ✅ テスト戦略
### Test 1: `loop_if_phi.hako` (既存 test)
**動作確認:**
```bash
./target/release/hakorune --dump-mir apps/tests/loop_if_phi.hako 2>&1 | grep -A 5 "k_exit"
```
**期待:** k_exit が sum/count を正しく処理(変わらず)
### Test 2: Multi-carrier testsPhase 195
**確認対象:**
- `test_pattern3_multi_carrier_sum_count`
- Carrier binding が動的に生成されることを確認
### Test 3: Cargo test
```bash
cargo test --release pattern3 2>&1 | tail -20
```
---
## 🚨 リスク & ミティゲーション
### リスク 1: ExitMeta 構築が複雑
**ミティゲーション:**
- Pattern 4 のコードをコピーペースト基準にする
- 最小限の変更に留める
### リスク 2: ExitMetaCollector の動作確認
**ミティゲーション:**
- Pattern 4 で既に使用済み(実績あり)
- Carrier validation で エラー検出
### リスク 3: Dummy count backward compat 破損
**ミティゲーション:**
- has_count フラグで分岐を保つ
- 古い単一キャリア テストは動作のまま
---
## 📝 Checklist
- [ ] Step 1: k_exit 実装分析完了
- [ ] Step 2: ExitMeta 構築ロジック追加
- [ ] Step 3: lowerer 呼び出し変更
- [ ] Step 4: Hardcoded 定数削除 + ExitMetaCollector 導入
- [ ] Step 5: Dummy count 簡略化
- [ ] Step 6: Build 成功確認
- [ ] Step 7: Test 実行確認
- [ ] Step 8: Commit & Document 更新
- [ ] Refactoring 5.1 COMPLETE ✅
---
## 📚 参考資料
- Pattern 4 実装: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs` (lines 300-380)
- ExitMetaCollector: `src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs`
- JoinFragmentMeta: `src/mir/join_ir/lowering/join_fragment_meta.rs`

View File

@ -3,31 +3,11 @@
use crate::ast::ASTNode; use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder; use crate::mir::builder::MirBuilder;
use crate::mir::ValueId; use crate::mir::ValueId;
use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
use super::super::trace; use super::super::trace;
/// Phase 179-A / Phase 195: Expected ValueIds for k_exit parameters in Pattern 3 // Phase 213: Hardcoded ValueIds removed - now using ExitMeta-based exit binding generation
/// These correspond to the exit PHI inputs in the JoinIR lowering for loop_with_if_phi_minimal // See: ExitMetaCollector usage below (lines 115-135)
///
/// # TODO (Phase 179 Task 2): Convert to ExitMeta-based exit binding generation
///
/// **Current State**: Hardcoded ValueIds - fragile and non-reusable
///
/// **Why it's hardcoded**:
/// - Pattern 3's lowerer (`lower_loop_with_if_phi_pattern`) returns `Option<JoinModule>`
/// - Unlike Pattern 4 which returns `(JoinModule, JoinFragmentMeta)`
/// - No ExitMeta available to dynamically look up exit PHI ValueIds
///
/// **Migration Path** (when Pattern 3 lowerer is updated):
/// 1. Change `lower_loop_with_if_phi_pattern` to return `(JoinModule, JoinFragmentMeta)`
/// 2. Remove these constants
/// 3. Use ExitMeta loop (like Pattern 4 lines 350-378) to generate exit_bindings dynamically
/// 4. See: pattern4_with_continue.rs lines 350-378 for reference implementation
///
/// **Impact**: Low priority - Pattern 3 is test-only and works correctly with hardcoded values
///
/// Phase 195: Multi-carrier support - now includes both sum_final and count_final
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24); // Phase 195: Updated from ValueId(18)
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25); // Phase 195: New count carrier
/// Phase 194: Detection function for Pattern 3 /// Phase 194: Detection function for Pattern 3
/// ///
@ -106,62 +86,53 @@ impl MirBuilder {
let mut join_value_space = JoinValueSpace::new(); let mut join_value_space = JoinValueSpace::new();
// Call Pattern 3 lowerer with preprocessed scope // Call Pattern 3 lowerer with preprocessed scope
let join_module = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) { let (join_module, fragment_meta) = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
Some(module) => module, Ok(result) => result,
None => { Err(e) => {
// Phase 195: Use unified trace trace::trace().debug("pattern3", &format!("Pattern 3 lowerer failed: {}", e));
trace::trace().debug("pattern3", "Pattern 3 lowerer returned None"); return Err(format!("[cf_loop/pattern3] Lowering failed: {}", e));
return Ok(None);
} }
}; };
let exit_meta = &fragment_meta.exit_meta;
trace::trace().debug(
"pattern3",
&format!("ExitMeta: {} exit values", exit_meta.exit_values.len())
);
for (carrier_name, join_value) in &exit_meta.exit_values {
trace::trace().debug(
"pattern3",
&format!(" {} → ValueId({})", carrier_name, join_value.0)
);
}
// Phase 195: Create boundary from context (multi-carrier support with backward compatibility) // Phase 195: Create boundary from context (multi-carrier support with backward compatibility)
// Phase 201: Use JoinInlineBoundaryBuilder for clean construction // Phase 201: Use JoinInlineBoundaryBuilder for clean construction
// Canonical Builder pattern - see docs/development/current/main/joinir-boundary-builder-pattern.md // Canonical Builder pattern - see docs/development/current/main/joinir-boundary-builder-pattern.md
self.trace_varmap("pattern3_before_merge"); self.trace_varmap("pattern3_before_merge");
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
// Phase 195: Build inputs and exit_bindings dynamically based on available carriers // Phase 213: Use ExitMetaCollector for dynamic exit binding generation
let (join_inputs, host_inputs, exit_bindings) = if has_count { // Note: ExitMetaCollector internally validates that all exit carriers in ExitMeta
// Multi-carrier: i, sum, count // have corresponding variable_map entries. No additional validation needed here.
let count_var_id = count_carrier_opt.unwrap().host_id; let exit_bindings = ExitMetaCollector::collect(
( self,
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters exit_meta,
vec![ctx.loop_var_id, sum_var_id, count_var_id], // Host's loop variables debug,
vec![ );
LoopExitBinding {
carrier_name: "sum".to_string(), // Build join_inputs and host_inputs (retain existing logic)
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24) let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)];
host_slot: sum_var_id, let mut host_inputs = vec![ctx.loop_var_id, sum_var_id];
},
LoopExitBinding { if has_count {
carrier_name: "count".to_string(), host_inputs.push(count_carrier_opt.unwrap().host_id);
join_exit_value: PATTERN3_K_EXIT_COUNT_FINAL_ID, // ValueId(25)
host_slot: count_var_id,
}
]
)
} else { } else {
// Single-carrier (backward compatibility): i, sum only
// Phase 195: JoinIR lowerer now always generates 3 parameters (i, sum, count)
// For backward compat, we create a dummy count variable that will be discarded
use crate::mir::builder::emission::constant; use crate::mir::builder::emission::constant;
let dummy_count_id = constant::emit_void(self); // Use void as dummy value let dummy_count_id = constant::emit_void(self);
host_inputs.push(dummy_count_id);
(
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters (i, sum, count)
vec![ctx.loop_var_id, sum_var_id, dummy_count_id], // Host's loop variables (count is dummy)
vec![
LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24)
host_slot: sum_var_id,
} }
// Don't bind count in single-carrier mode - it's just discarded
]
)
};
let boundary = JoinInlineBoundaryBuilder::new() let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs) .with_inputs(join_inputs, host_inputs)

View File

@ -110,7 +110,8 @@ pub fn lower_loop_with_conditional_phi_to_joinir(
let mut join_value_space = JoinValueSpace::new(); let mut join_value_space = JoinValueSpace::new();
// Generate JoinIR module // Generate JoinIR module
let _join_module = lower_loop_with_if_phi_pattern(placeholder_scope, &mut join_value_space)?; // Phase 213: Updated to handle Result<(JoinModule, JoinFragmentMeta), String>
let _result = lower_loop_with_if_phi_pattern(placeholder_scope, &mut join_value_space).ok()?;
// Phase 188-Impl-3: Pattern 3 is now integrated via the router // Phase 188-Impl-3: Pattern 3 is now integrated via the router
// This function delegates to loop_with_if_phi_minimal which generates JoinModule // This function delegates to loop_with_if_phi_minimal which generates JoinModule

View File

@ -67,6 +67,7 @@
//! //!
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later. //! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
use crate::mir::join_ir::lowering::carrier_info::{ExitMeta, JoinFragmentMeta};
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::{ use crate::mir::join_ir::{
@ -101,8 +102,8 @@ use crate::mir::join_ir::{
/// ///
/// # Returns /// # Returns
/// ///
/// * `Some(JoinModule)` - Successfully lowered to JoinIR /// * `Ok((JoinModule, JoinFragmentMeta))` - Successfully lowered to JoinIR with exit metadata
/// * `None` - Pattern not matched (fallback to other lowerers) /// * `Err(String)` - Pattern lowering failed with error message
/// ///
/// # Boundary Contract /// # Boundary Contract
/// ///
@ -113,7 +114,7 @@ use crate::mir::join_ir::{
pub(crate) fn lower_loop_with_if_phi_pattern( pub(crate) fn lower_loop_with_if_phi_pattern(
_scope: LoopScopeShape, _scope: LoopScopeShape,
join_value_space: &mut JoinValueSpace, join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule> { ) -> Result<(JoinModule, JoinFragmentMeta), String> {
// Phase 202-B: Use JoinValueSpace for unified ValueId allocation // Phase 202-B: Use JoinValueSpace for unified ValueId allocation
// - Local region (1000+) ensures no collision with Param region (100-999) // - Local region (1000+) ensures no collision with Param region (100-999)
let mut alloc_value = || join_value_space.alloc_local(); let mut alloc_value = || join_value_space.alloc_local();
@ -417,6 +418,14 @@ pub(crate) fn lower_loop_with_if_phi_pattern(
// Set entry point // Set entry point
join_module.entry = Some(main_id); join_module.entry = Some(main_id);
// Phase 213: Build ExitMeta for dynamic exit binding generation
let mut exit_values = vec![];
exit_values.push(("sum".to_string(), sum_final));
exit_values.push(("count".to_string(), count_final)); // Phase 195: always include count
let exit_meta = ExitMeta::multiple(exit_values);
let fragment_meta = JoinFragmentMeta::carrier_only(exit_meta);
eprintln!("[joinir/pattern3] Generated JoinIR for Loop with If-Else PHI (Phase 195: multi-carrier)"); eprintln!("[joinir/pattern3] Generated JoinIR for Loop with If-Else PHI (Phase 195: multi-carrier)");
eprintln!("[joinir/pattern3] Functions: main, loop_step, k_exit"); eprintln!("[joinir/pattern3] Functions: main, loop_step, k_exit");
eprintln!("[joinir/pattern3] Carriers: i (counter), sum (accumulator), count (counter) [Phase 195]"); eprintln!("[joinir/pattern3] Carriers: i (counter), sum (accumulator), count (counter) [Phase 195]");
@ -424,5 +433,5 @@ pub(crate) fn lower_loop_with_if_phi_pattern(
eprintln!("[joinir/pattern3] sum_new = (i % 2 == 1) ? sum+i : sum+0"); eprintln!("[joinir/pattern3] sum_new = (i % 2 == 1) ? sum+i : sum+0");
eprintln!("[joinir/pattern3] count_new = (i % 2 == 1) ? count+1 : count+0 [Phase 195]"); eprintln!("[joinir/pattern3] count_new = (i % 2 == 1) ? count+1 : count+0 [Phase 195]");
Some(join_module) Ok((join_module, fragment_meta))
} }