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
9.5 KiB
9.5 KiB
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 ベースのアーキテクチャに統一化する。これにより:
- ✅ Hardcoded ValueIds 定数削除(
PATTERN3_K_EXIT_*_ID) - ✅ Exit binding の動的生成
- ✅ Multi-carrier support の完全化
- ✅ Pattern 3/4 の共通化度向上
🔄 変更対象ファイル
ファイル1: src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs
現在:
pub(crate) fn lower_loop_with_if_phi_pattern(
_scope: LoopScopeShape,
join_value_space: &mut JoinValueSpace,
) -> Option<JoinModule>
変更後:
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>
変更内容:
- 戻り値を
Option<JoinModule>→Result<(JoinModule, JoinFragmentMeta), String>に変更 JoinFragmentMetaを構築して返す- ExitMeta を動的に生成
ファイル2: src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs
現在:
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
...
}
変更後:
// Hardcoded 定数削除(削除)
// Lines 300-314: ExitMeta から exit_bindings を動的生成
let exit_bindings = ExitMetaCollector::collect(
self,
&exit_meta,
debug,
);
// Carrier validation(Pattern 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):
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 の最後に以下を追加:
// 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):
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);
}
};
変更後:
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):
// 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
...
}
削除/変更:
// 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):
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![...])
}
簡略化:
// 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)
動作確認:
./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 tests(Phase 195)
確認対象:
test_pattern3_multi_carrier_sum_count- Carrier binding が動的に生成されることを確認
Test 3: Cargo test
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