fix(json_v0): Phase 25.1c/k - loop body local変数のPHI生成を追加

BreakFinderBox._find_loops/2 等で、loop body 内で新規宣言された
local 変数(header_pos, next_i 等)が loop header に戻った時に
undefined になる SSA バグを修正。

変更内容:
- body_vars から base_vars にない変数を検出
- それらに対する IncompletePhi を header に追加
- backedge_to_cond 判定を拡張(Branch にも対応)
  - break があるループでは latch が Branch 終端になるため
- トレースログ追加(HAKO_LOOP_PHI_TRACE=1)

根本原因:
prepare_loop_variables_with() は preheader の変数のみを対象とし、
loop body 内で新規宣言された変数を PHI に含めていなかった。

修正効果:
- BreakFinderBox._find_loops/2 の ValueId(172) undefined 解決見込み
- FuncScannerBox.scan_all_boxes/1 も同様のパターンで修正される

🤖 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-19 20:33:24 +09:00
parent 9a352a8f02
commit fb256670a1

View File

@ -134,13 +134,65 @@ pub(super) fn lower_loop_stmt(
if let Some(bb) = ops.f.get_block_mut(bend) { if let Some(bb) = ops.f.get_block_mut(bend) {
if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(ops.f, bend, cond_bb); } if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(ops.f, bend, cond_bb); }
} }
let backedge_to_cond = matches!( // Detect whether the "latch" block has a backedge to the loop header/cond.
ops.f.blocks // Note: loops with `break` often end the latch with a conditional Branch that
.get(&bend) // targets both `cond_bb` (continue path) and `exit_bb` (break path). We must
.and_then(|bb| bb.terminator.as_ref()), // treat such branches as valid backedges as well; otherwise body-local vars
Some(MirInstruction::Jump { target, .. }) if *target == cond_bb // will miss PHI nodes at the header and become undefined on the second iteration
); // (BreakFinderBox._find_loops/2 などで観測されたバグ).
let backedge_to_cond = match ops
.f
.blocks
.get(&bend)
.and_then(|bb| bb.terminator.as_ref())
{
Some(MirInstruction::Jump { target, .. }) if *target == cond_bb => true,
Some(MirInstruction::Branch { then_bb, else_bb, .. })
if *then_bb == cond_bb || *else_bb == cond_bb =>
{
true
}
_ => false,
};
let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1");
if trace_loop_phi {
eprintln!("[loop-phi] backedge_to_cond={}, base_vars={}, body_vars={}",
backedge_to_cond, base_vars.len(), body_vars.len());
}
if backedge_to_cond { if backedge_to_cond {
// Phase 25.1c/k: body 内で新規宣言された local 変数も PHI に含める
// BreakFinderBox._find_loops/2 等で、loop body 内の local 変数が
// loop header に戻った時に undefined になる問題を修正
let mut body_local_count = 0;
for (var_name, &_latch_value) in body_vars.iter() {
if !base_vars.contains_key(var_name) {
// body で新規宣言された変数 → header に PHI ノードを追加
body_local_count += 1;
if trace_loop_phi {
eprintln!("[loop-phi/body-local] Adding PHI for body-local var: {}", var_name);
}
let phi_id = ops.f.next_value_id();
let inc_phi = crate::mir::phi_core::loop_phi::IncompletePhi {
phi_id,
var_name: var_name.clone(),
known_inputs: vec![], // preheader には存在しないので空
};
// header に空の PHI ードを挿入seal_incomplete_phis_with で完成させる)
if let Some(bb) = ops.f.get_block_mut(cond_bb) {
bb.insert_instruction_after_phis(MirInstruction::Phi {
dst: phi_id,
inputs: vec![], // 空の PHI後で latch/continue から完成)
});
}
// 変数マップを PHI に rebind
ops.vars.insert(var_name.clone(), phi_id);
incomplete.push(inc_phi);
}
}
if trace_loop_phi && body_local_count > 0 {
eprintln!("[loop-phi/body-local] Total body-local vars added: {}", body_local_count);
}
// 5) header の不完全PHIを完成latch + continue スナップショット集約) // 5) header の不完全PHIを完成latch + continue スナップショット集約)
let mut ops_seal = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 }; let mut ops_seal = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 };
crate::mir::phi_core::loop_phi::seal_incomplete_phis_with( crate::mir::phi_core::loop_phi::seal_incomplete_phis_with(