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, loop_stack: &mut Vec, env: &BridgeEnv, ) -> Result { // 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); // SSOT(phi_core)への統一: preheader Copy → header Phi seed を集中実装に委譲 // 1) preheader スナップショット let base_vars = vars.clone(); let mut block_var_maps: HashMap> = 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, block_var_maps: &'a HashMap>, } 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 = 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 { 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(); // latch(body末尾)スナップショットを別マップに構築 let mut block_var_maps2: HashMap> = 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 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, )?; Ok(exit_bb) }