From fb256670a1cb583a33d3167bc3ebb64397311e05 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Wed, 19 Nov 2025 20:33:24 +0900 Subject: [PATCH] =?UTF-8?q?fix(json=5Fv0):=20Phase=2025.1c/k=20-=20loop=20?= =?UTF-8?q?body=20local=E5=A4=89=E6=95=B0=E3=81=AEPHI=E7=94=9F=E6=88=90?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/runner/json_v0_bridge/lowering/loop_.rs | 64 +++++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index 020b657c..cf7c964e 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -134,13 +134,65 @@ pub(super) fn lower_loop_stmt( 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); } } - let backedge_to_cond = matches!( - ops.f.blocks - .get(&bend) - .and_then(|bb| bb.terminator.as_ref()), - Some(MirInstruction::Jump { target, .. }) if *target == cond_bb - ); + // Detect whether the "latch" block has a backedge to the loop header/cond. + // Note: loops with `break` often end the latch with a conditional Branch that + // targets both `cond_bb` (continue path) and `exit_bb` (break path). We must + // treat such branches as valid backedges as well; otherwise body-local vars + // 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 { + // 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 スナップショット集約) 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(