2025-09-17 11:45:57 +09:00
|
|
|
|
use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext};
|
2025-09-17 10:58:12 +09:00
|
|
|
|
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> {
|
2025-11-03 23:21:48 +09:00
|
|
|
|
// 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");
|
|
|
|
|
|
}
|
2025-09-17 10:58:12 +09:00
|
|
|
|
let cond_bb = new_block(f);
|
|
|
|
|
|
let body_bb = new_block(f);
|
|
|
|
|
|
let exit_bb = new_block(f);
|
2025-11-01 16:31:48 +09:00
|
|
|
|
|
2025-11-01 16:45:27 +09:00
|
|
|
|
// SSOT(phi_core)への統一: preheader Copy → header Phi seed を集中実装に委譲
|
|
|
|
|
|
// 1) preheader スナップショット
|
2025-11-01 16:31:48 +09:00
|
|
|
|
let base_vars = vars.clone();
|
2025-11-01 16:45:27 +09:00
|
|
|
|
let mut block_var_maps: HashMap<BasicBlockId, HashMap<String, ValueId>> = HashMap::new();
|
|
|
|
|
|
block_var_maps.insert(cur_bb, base_vars.clone());
|
2025-11-01 16:31:48 +09:00
|
|
|
|
|
2025-11-01 16:45:27 +09:00
|
|
|
|
// 2) preheader → header へ Jump(ヘッダで Phi を組む前に制御を渡す)
|
2025-09-17 10:58:12 +09:00
|
|
|
|
if let Some(bb) = f.get_block_mut(cur_bb) {
|
2025-11-04 21:33:09 +09:00
|
|
|
|
if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(f, cur_bb, cond_bb); }
|
2025-09-17 10:58:12 +09:00
|
|
|
|
}
|
2025-11-01 16:31:48 +09:00
|
|
|
|
|
2025-11-01 16:56:26 +09:00
|
|
|
|
// 3) LoopPhiOps アダプタ(準備用: preheader seed 専用)
|
2025-11-01 16:45:27 +09:00
|
|
|
|
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,
|
2025-11-02 15:43:43 +09:00
|
|
|
|
mut inputs: Vec<(BasicBlockId, ValueId)>,
|
2025-11-01 16:45:27 +09:00
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
|
if let Some(bb) = self.f.get_block_mut(block) {
|
2025-11-02 15:43:43 +09:00
|
|
|
|
// 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 });
|
|
|
|
|
|
}
|
2025-11-01 16:45:27 +09:00
|
|
|
|
Ok(())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Err(format!("block {} not found", block.0))
|
2025-09-17 10:58:12 +09:00
|
|
|
|
}
|
2025-11-01 16:45:27 +09:00
|
|
|
|
}
|
|
|
|
|
|
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)]) {
|
2025-11-01 16:37:16 +09:00
|
|
|
|
#[cfg(debug_assertions)]
|
2025-11-01 16:45:27 +09:00
|
|
|
|
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))
|
2025-11-01 16:37:16 +09:00
|
|
|
|
}
|
2025-09-17 10:58:12 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-01 16:45:27 +09:00
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
)?;
|
2025-11-01 16:56:26 +09:00
|
|
|
|
// Header snapshot for exit PHIs
|
|
|
|
|
|
let header_vars_snapshot = ops.vars.clone();
|
2025-11-01 16:45:27 +09:00
|
|
|
|
|
|
|
|
|
|
let (cval, _cend) = super::expr::lower_expr_with_vars(env, ops.f, cond_bb, cond, ops.vars)?;
|
2025-11-04 21:33:09 +09:00
|
|
|
|
crate::mir::ssot::cf_common::set_branch(ops.f, cond_bb, cval, body_bb, exit_bb);
|
2025-11-01 16:45:27 +09:00
|
|
|
|
let mut body_vars = ops.vars.clone();
|
2025-11-01 16:56:26 +09:00
|
|
|
|
// open snapshot frames for nested break/continue
|
|
|
|
|
|
super::push_loop_snapshot_frames();
|
2025-09-17 10:58:12 +09:00
|
|
|
|
loop_stack.push(LoopContext { cond_bb, exit_bb });
|
2025-11-04 20:46:43 +09:00
|
|
|
|
// Detect simple increment hint for this loop body
|
|
|
|
|
|
super::detect_and_push_increment_hint(body);
|
2025-11-01 16:45:27 +09:00
|
|
|
|
let bend_res = lower_stmt_list_with_vars(ops.f, body_bb, body, &mut body_vars, loop_stack, env);
|
2025-09-17 10:58:12 +09:00
|
|
|
|
loop_stack.pop();
|
2025-11-04 20:46:43 +09:00
|
|
|
|
let _ = super::pop_increment_hint();
|
2025-09-17 10:58:12 +09:00
|
|
|
|
let bend = bend_res?;
|
2025-11-01 16:56:26 +09:00
|
|
|
|
// collect snapshots for this loop level
|
|
|
|
|
|
let continue_snaps = super::pop_continue_snapshots();
|
|
|
|
|
|
let exit_snaps = super::pop_exit_snapshots();
|
|
|
|
|
|
// latch(body末尾)スナップショットを別マップに構築
|
|
|
|
|
|
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());
|
2025-11-01 16:45:27 +09:00
|
|
|
|
if let Some(bb) = ops.f.get_block_mut(bend) {
|
2025-11-04 21:33:09 +09:00
|
|
|
|
if !bb.is_terminated() { crate::mir::ssot::cf_common::set_jump(ops.f, bend, cond_bb); }
|
2025-09-17 10:58:12 +09:00
|
|
|
|
}
|
2025-11-19 20:33:24 +09:00
|
|
|
|
// 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());
|
|
|
|
|
|
}
|
2025-09-17 10:58:12 +09:00
|
|
|
|
if backedge_to_cond {
|
2025-11-19 20:33:24 +09:00
|
|
|
|
// 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-01 16:45:27 +09:00
|
|
|
|
// 5) header の不完全PHIを完成(latch + continue スナップショット集約)
|
2025-11-01 16:56:26 +09:00
|
|
|
|
let mut ops_seal = Ops { f: ops.f, vars: ops.vars, block_var_maps: &block_var_maps2 };
|
2025-11-01 16:45:27 +09:00
|
|
|
|
crate::mir::phi_core::loop_phi::seal_incomplete_phis_with(
|
2025-11-01 16:56:26 +09:00
|
|
|
|
&mut ops_seal,
|
2025-11-01 16:45:27 +09:00
|
|
|
|
cond_bb,
|
|
|
|
|
|
bend,
|
|
|
|
|
|
std::mem::take(&mut incomplete),
|
|
|
|
|
|
&continue_snaps,
|
|
|
|
|
|
)?;
|
2025-09-17 10:58:12 +09:00
|
|
|
|
}
|
2025-11-01 16:56:26 +09:00
|
|
|
|
// 6) exit PHIs(breakの合流 + 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,
|
|
|
|
|
|
)?;
|
2025-09-17 10:58:12 +09:00
|
|
|
|
Ok(exit_bb)
|
|
|
|
|
|
}
|