fix: ループexit PHI生成を追加し、break後の変数値伝播を修正

問題:
- ループexit時のPHI命令が完全に欠落していた
- break後の変数値が初期値に戻ってしまうバグ
- gemini_test_case.nyashで期待値2→実際0が出力

解決:
- LoopBuilderにexit_snapshots追加でbreak時点の変数を収集
- do_break()でスナップショット収集処理を追加
- create_exit_phis()メソッドを新規実装し、exit PHI生成

効果:
- gemini_test_caseが正しく2を出力
- 0回実行、複数break、continue混在すべてのケースで正常動作
- collect_printsのnullエラー解消

テスト済み:
- gemini_test_case.nyash:  期待値2
- test_loop_zero.nyash:  期待値42
- test_multi_break.nyash:  期待値20
- test_continue_break.nyash:  期待値3

MIR確認:
bb3: %15 = phi [%4, bb1], [%9, bb9]
exit PHIが正しく生成されている

Thanks: ChatGPT Pro for root cause analysis

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-23 09:48:29 +09:00
parent fc4c866151
commit c9e4a1a6e6
2 changed files with 132 additions and 26 deletions

View File

@ -44,6 +44,9 @@ pub struct LoopBuilder<'a> {
/// continue文からの変数スナップショット
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
/// break文からの変数スナップショットexit PHI生成用
exit_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
// フェーズM: no_phi_modeフィールド削除常にPHI使用
}
@ -75,6 +78,11 @@ impl<'a> LoopBuilder<'a> {
/// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block.
fn do_break(&mut self) -> Result<ValueId, String> {
// Snapshot variables at break point for exit PHI generation
let snapshot = self.get_current_variable_map();
let cur_block = self.current_block()?;
self.exit_snapshots.push((cur_block, snapshot));
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) {
self.jump_with_pred(exit_bb)?;
}
@ -108,6 +116,7 @@ impl<'a> LoopBuilder<'a> {
block_var_maps: HashMap::new(),
loop_header: None,
continue_snapshots: Vec::new(),
exit_snapshots: Vec::new(), // exit PHI用のスナップショット
// フェーズM: no_phi_modeフィールド削除
}
}
@ -235,8 +244,12 @@ impl<'a> LoopBuilder<'a> {
// 9. Headerブロックをシール全predecessors確定
self.seal_block(header_id, latch_id)?;
// 10. ループ後の処理
// 10. ループ後の処理 - Exit PHI生成
self.set_current_block(after_loop_id)?;
// Exit PHIの生成 - break時点での変数値を統一
self.create_exit_phis(header_id, after_loop_id)?;
// Pop loop context
crate::mir::builder::loops::pop_loop_context(self.parent_builder);
@ -317,6 +330,54 @@ impl<'a> LoopBuilder<'a> {
Ok(())
}
/// Exitブロックで変数のPHIを生成breakポイントでの値を統一
fn create_exit_phis(&mut self, header_id: BasicBlockId, exit_id: BasicBlockId) -> Result<(), String> {
// 全変数名を収集exit_snapshots内のすべての変数
let mut all_vars = std::collections::HashSet::new();
// Header直行ケース0回実行の変数を収集
let header_vars = self.get_current_variable_map();
for var_name in header_vars.keys() {
all_vars.insert(var_name.clone());
}
// break時点の変数を収集
for (_, snapshot) in &self.exit_snapshots {
for var_name in snapshot.keys() {
all_vars.insert(var_name.clone());
}
}
// 各変数に対してExit PHIを生成
for var_name in all_vars {
let mut phi_inputs = Vec::new();
// Header直行ケース0回実行の入力
if let Some(header_value) = header_vars.get(&var_name) {
phi_inputs.push((header_id, *header_value));
}
// 各breakポイントからの入力
for (block_id, snapshot) in &self.exit_snapshots {
if let Some(value) = snapshot.get(&var_name) {
phi_inputs.push((*block_id, *value));
}
}
// PHI入力が2つ以上なら、PHIードを生成
if phi_inputs.len() > 1 {
let phi_dst = self.new_value();
self.emit_phi_at_block_start(exit_id, phi_dst, phi_inputs)?;
self.update_variable(var_name, phi_dst);
} else if phi_inputs.len() == 1 {
// 単一入力なら直接使用(最適化)
self.update_variable(var_name, phi_inputs[0].1);
}
}
Ok(())
}
// --- ヘルパーメソッド(親ビルダーへの委譲) ---
fn current_block(&self) -> Result<BasicBlockId, String> {