Files
hakorune/src/runner/json_v0_bridge/lowering/loop_.rs
nyash-codex fb256670a1 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>
2025-11-19 20:33:24 +09:00

217 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext};
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
use std::collections::HashMap;
use super::super::ast::StmtV0;
use super::super::ast::ExprV0;
pub(super) fn lower_loop_stmt(
f: &mut MirFunction,
cur_bb: BasicBlockId,
cond: &ExprV0,
body: &[StmtV0],
vars: &mut HashMap<String, ValueId>,
loop_stack: &mut Vec<LoopContext>,
env: &BridgeEnv,
) -> Result<BasicBlockId, String> {
// Unification toggle (default ON). For now legacy path is removed; when OFF, warn and proceed unified.
let unify_on = std::env::var("NYASH_MIR_UNIFY_LOOPFORM")
.ok()
.map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1"|"true"|"on"))
.unwrap_or(true);
if !unify_on {
crate::cli_v!("[loopform] NYASH_MIR_UNIFY_LOOPFORM=0 requested, but legacy path is unavailable; using unified phi_core path");
}
let cond_bb = new_block(f);
let body_bb = new_block(f);
let exit_bb = new_block(f);
// SSOTphi_coreへの統一: preheader Copy → header Phi seed を集中実装に委譲
// 1) preheader スナップショット
let base_vars = vars.clone();
let mut block_var_maps: HashMap<BasicBlockId, HashMap<String, ValueId>> = HashMap::new();
block_var_maps.insert(cur_bb, base_vars.clone());
// 2) preheader → header へ Jumpヘッダで Phi を組む前に制御を渡す)
if let Some(bb) = f.get_block_mut(cur_bb) {
if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, cur_bb, cond_bb); }
}
// 3) LoopPhiOps アダプタ(準備用: preheader seed 専用)
struct Ops<'a> {
f: &'a mut MirFunction,
vars: &'a mut HashMap<String, ValueId>,
block_var_maps: &'a HashMap<BasicBlockId, HashMap<String, ValueId>>,
}
impl crate::mir::phi_core::loop_phi::LoopPhiOps for Ops<'_> {
fn new_value(&mut self) -> ValueId { self.f.next_value_id() }
fn emit_phi_at_block_start(
&mut self,
block: BasicBlockId,
dst: ValueId,
mut inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
if let Some(bb) = self.f.get_block_mut(block) {
// If a PHI for the same dst already exists at block start, merge inputs instead of inserting
let mut found = false;
// Count PHIs at head and iterate by index to allow mutation
let phi_count = bb.phi_instructions().count();
for i in 0..phi_count {
if let Some(MirInstruction::Phi { dst: existing_dst, inputs: existing_inputs }) = bb.instructions.get_mut(i) {
if *existing_dst == dst {
// Merge: add only missing predecessors (build a set first to avoid borrow conflicts)
let mut pred_set: std::collections::HashSet<BasicBlockId> = existing_inputs.iter().map(|(bbid, _)| *bbid).collect();
for (pred, val) in inputs.drain(..) {
if !pred_set.contains(&pred) {
existing_inputs.push((pred, val));
pred_set.insert(pred);
}
}
found = true;
break;
}
}
}
if !found {
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs });
}
Ok(())
} else {
Err(format!("block {} not found", block.0))
}
}
fn update_var(&mut self, name: String, value: ValueId) { self.vars.insert(name, value); }
fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option<ValueId> {
self.block_var_maps.get(&block).and_then(|m| m.get(name).copied())
}
fn debug_verify_phi_inputs(&mut self, merge_bb: BasicBlockId, inputs: &[(BasicBlockId, ValueId)]) {
#[cfg(debug_assertions)]
if let Some(func) = Some(&*self.f) {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, merge_bb, inputs);
}
}
fn emit_copy_at_preheader(
&mut self,
preheader_id: BasicBlockId,
dst: ValueId,
src: ValueId,
) -> Result<(), String> {
if let Some(bb) = self.f.get_block_mut(preheader_id) {
bb.add_instruction(MirInstruction::Copy { dst, src });
Ok(())
} else {
Err(format!("preheader block {} not found", preheader_id.0))
}
}
}
let mut ops = Ops { f, vars, block_var_maps: &block_var_maps };
// 4) header の不完全PHIを宣言preheaderのコピー値でseedし、変数マップをPHIへ再束縛
let mut incomplete = crate::mir::phi_core::loop_phi::prepare_loop_variables_with(
&mut ops, cond_bb, cur_bb, &base_vars,
)?;
// Header snapshot for exit PHIs
let header_vars_snapshot = ops.vars.clone();
let (cval, _cend) = super::expr::lower_expr_with_vars(env, ops.f, cond_bb, cond, ops.vars)?;
crate::mir::ssot::cf_common::set_branch(ops.f, cond_bb, cval, body_bb, exit_bb);
let mut body_vars = ops.vars.clone();
// open snapshot frames for nested break/continue
super::push_loop_snapshot_frames();
loop_stack.push(LoopContext { cond_bb, exit_bb });
// Detect simple increment hint for this loop body
super::detect_and_push_increment_hint(body);
let bend_res = lower_stmt_list_with_vars(ops.f, body_bb, body, &mut body_vars, loop_stack, env);
loop_stack.pop();
let _ = super::pop_increment_hint();
let bend = bend_res?;
// collect snapshots for this loop level
let continue_snaps = super::pop_continue_snapshots();
let exit_snaps = super::pop_exit_snapshots();
// latchbody末尾スナップショットを別マップに構築
let mut block_var_maps2: HashMap<BasicBlockId, HashMap<String, ValueId>> = HashMap::new();
block_var_maps2.insert(cur_bb, base_vars.clone());
block_var_maps2.insert(bend, body_vars.clone());
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); }
}
// 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(
&mut ops_seal,
cond_bb,
bend,
std::mem::take(&mut incomplete),
&continue_snaps,
)?;
}
// 6) exit PHIsbreakの合流 + headerからのフォールスルー
let mut ops_exit = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 };
crate::mir::phi_core::loop_phi::build_exit_phis_with(
&mut ops_exit,
cond_bb,
exit_bb,
&header_vars_snapshot,
&exit_snaps,
)?;
Ok(exit_bb)
}