diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index b35a4d78..a2fab89d 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -105,6 +105,8 @@ impl<'a> LoopBuilder<'a> { // Snapshot variables at break point for exit PHI generation let snapshot = self.get_current_variable_map(); let cur_block = self.current_block()?; + eprintln!("[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}", + cur_block, snapshot.keys().collect::>()); self.exit_snapshots.push((cur_block, snapshot)); if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) { @@ -237,8 +239,20 @@ impl<'a> LoopBuilder<'a> { self.exit_snapshots.clear(); // Emit condition check in header + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!("[loopform/condition] BEFORE build_expression: current_block={:?}", self.current_block()?); + } let cond_value = self.parent_builder.build_expression(condition)?; + // Capture the ACTUAL block that emits the branch (might differ from header_id + // if build_expression created new blocks) + let branch_source_block = self.current_block()?; + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!("[loopform/condition] AFTER build_expression: branch_source_block={:?}", branch_source_block); + } self.emit_branch(cond_value, body_id, exit_id)?; + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!("[loopform/condition] AFTER emit_branch: current_block={:?}", self.current_block()?); + } // Lower loop body self.set_current_block(body_id)?; @@ -280,7 +294,7 @@ impl<'a> LoopBuilder<'a> { // Build exit PHIs for break statements let exit_snaps = self.exit_snapshots.clone(); - loopform.build_exit_phis(self, exit_id, &exit_snaps)?; + loopform.build_exit_phis(self, exit_id, branch_source_block, &exit_snaps)?; // Pop loop context crate::mir::builder::loops::pop_loop_context(self.parent_builder); @@ -341,7 +355,7 @@ impl<'a> LoopBuilder<'a> { // 4. ループ変数のPhi nodeを準備 // ここでは、ループ内で変更される可能性のある変数を事前に検出するか、 // または変数アクセス時に遅延生成する(再束縛は条件式構築後に行う) - let incs = self.prepare_loop_variables(header_id, preheader_id, &pre_vars_snapshot)?; + let incs = self.prepare_loop_variables(header_id, preheader_id, &pre_vars_snapshot, &assigned_vars)?; // 5. 条件評価(Phi nodeの結果を使用) // Heuristic pre-pin: if condition is a comparison, evaluate its operands and pin them @@ -566,18 +580,36 @@ impl<'a> LoopBuilder<'a> { // PHI Helpers — prepare/finalize PHIs and block sealing // ============================================================= /// ループ変数の準備(事前検出または遅延生成) + /// + /// ポリシー: + /// - ループキャリア(ループ本体で再代入される変数)と pinned 変数のみを PHI 対象とする。 + /// - ループ不変のローカル(text_len / pattern_len など)は preheader 値をそのまま使い、 + /// 不要な PHI を張らないことで SSA 破綻(同一 ValueId の二重定義)を防ぐ。 fn prepare_loop_variables( &mut self, header_id: BasicBlockId, preheader_id: BasicBlockId, pre_vars_snapshot: &std::collections::HashMap, + assigned_vars: &[String], ) -> Result, String> { use std::sync::atomic::{AtomicUsize, Ordering}; static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst); - // Use the variable map captured at preheader (before switching to header) - let current_vars = pre_vars_snapshot.clone(); + // Use the variable map captured at preheader (before switching to header), + // but filter to: + // - ループキャリア(assigned_vars に含まれる変数) + // - pinned 変数(__pin$*): 受信箱など、ループをまたいで値を運ぶ必要があるもの + let mut current_vars = std::collections::HashMap::new(); + for (name, &val) in pre_vars_snapshot.iter() { + if name.starts_with("__pin$") { + current_vars.insert(name.clone(), val); + continue; + } + if assigned_vars.iter().any(|v| v == name) { + current_vars.insert(name.clone(), val); + } + } // Debug: print current_vars before prepare (guarded by env) let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1"); if dbg { diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index 369b7f77..785c786d 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -222,29 +222,50 @@ impl LoopFormBuilder { /// Similar to header PHIs, but merges: /// - Header fallthrough (normal loop exit) /// - Break snapshots (early exit from loop body) + /// + /// # Parameters + /// - `branch_source_block`: The ACTUAL block that emitted the branch to exit + /// (might differ from header_id if condition evaluation created new blocks) pub fn build_exit_phis( &self, ops: &mut O, exit_id: BasicBlockId, + branch_source_block: BasicBlockId, exit_snapshots: &[(BasicBlockId, HashMap)], ) -> Result<(), String> { ops.set_current_block(exit_id)?; + let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); + if debug { + eprintln!("[DEBUG/exit_phi] ====== Exit PHI Generation ======"); + eprintln!("[DEBUG/exit_phi] exit_id = {:?}, header_id = {:?}, branch_source = {:?}", + exit_id, self.header_id, branch_source_block); + eprintln!("[DEBUG/exit_phi] exit_snapshots.len() = {}", exit_snapshots.len()); + for (i, (bb, snap)) in exit_snapshots.iter().enumerate() { + eprintln!("[DEBUG/exit_phi] snapshot[{}]: block = {:?}, num_vars = {}", + i, bb, snap.len()); + } + eprintln!("[DEBUG/exit_phi] pinned.len() = {}, carriers.len() = {}", + self.pinned.len(), self.carriers.len()); + } + // Collect all variables that need exit PHIs let mut all_vars: HashMap> = HashMap::new(); // Add header fallthrough values (pinned + carriers) + // Use branch_source_block instead of header_id because condition evaluation + // might create new blocks, and the branch is emitted from the LAST block for pinned in &self.pinned { all_vars .entry(pinned.name.clone()) .or_default() - .push((self.header_id, pinned.header_phi)); + .push((branch_source_block, pinned.header_phi)); } for carrier in &self.carriers { all_vars .entry(carrier.name.clone()) .or_default() - .push((self.header_id, carrier.header_phi)); + .push((branch_source_block, carrier.header_phi)); } // Add break snapshot values @@ -262,15 +283,33 @@ impl LoopFormBuilder { // Deduplicate inputs by predecessor block sanitize_phi_inputs(&mut inputs); + if debug { + eprintln!("[DEBUG/exit_phi] Variable '{}': {} inputs before dedup", var_name, inputs.len()); + for (bb, val) in &inputs { + eprintln!("[DEBUG/exit_phi] pred={:?} val={:?}", bb, val); + } + } + match inputs.len() { 0 => {} // No inputs, skip 1 => { // Single predecessor: direct binding + if debug { + eprintln!("[DEBUG/exit_phi] Variable '{}': single predecessor, direct binding to {:?}", + var_name, inputs[0].1); + } ops.update_var(var_name, inputs[0].1); } _ => { // Multiple predecessors: create PHI node let phi_id = ops.new_value(); + if debug { + eprintln!("[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", + phi_id, var_name, inputs.len()); + for (bb, val) in &inputs { + eprintln!("[DEBUG/exit_phi] PHI input: pred={:?} val={:?}", bb, val); + } + } ops.emit_phi(phi_id, inputs)?; ops.update_var(var_name, phi_id); } diff --git a/src/tests/mir_loopform_exit_phi.rs b/src/tests/mir_loopform_exit_phi.rs new file mode 100644 index 00000000..7ce9cfe2 --- /dev/null +++ b/src/tests/mir_loopform_exit_phi.rs @@ -0,0 +1,174 @@ +/*! + * Unit tests for LoopForm v2 exit PHI generation + * + * Tests the build_exit_phis() implementation in loopform_builder.rs + * Focus: predecessor tracking and PHI input generation for break statements + */ + +use crate::parser::NyashParser; +use crate::mir::{MirCompiler, MirVerifier}; + +#[test] +fn test_loopform_exit_phi_single_break() { + // Enable LoopForm PHI v2 and MIR verification + std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); + std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); + std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1"); + + let src = r#" +static box TestExitPhi { + test() { + local i = 0 + loop(i < 10) { + if i == 5 { break } + i = i + 1 + } + return i + } +} +"#; + + println!("=== Test: Single break statement ==="); + + // Parse + let ast = NyashParser::parse_from_string(src) + .expect("parse failed"); + + // Compile + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile failed"); + + // MIR verification + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for err in &errors { + eprintln!("❌ MIR verification error: {}", err); + } + panic!("❌ MIR verification failed with {} errors", errors.len()); + } + println!("✅ MIR verification passed"); +} + +#[test] +fn test_loopform_exit_phi_multiple_breaks() { + std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); + std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); + std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1"); + + let src = r#" +static box TestMultiBreak { + test() { + local i = 0 + loop(i < 10) { + if i == 3 { break } + if i == 5 { break } + i = i + 1 + } + return i + } +} +"#; + + println!("=== Test: Multiple break statements ==="); + + let ast = NyashParser::parse_from_string(src).expect("parse failed"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile failed"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for err in &errors { + eprintln!("❌ MIR verification error: {}", err); + } + panic!("❌ MIR verification failed with {} errors", errors.len()); + } + println!("✅ MIR verification passed"); +} + +#[test] +fn test_loopform_exit_phi_nested_if_break() { + std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); + std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); + std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1"); + + let src = r#" +static box TestNestedBreak { + test() { + local i = 0 + local found = 0 + loop(i < 10) { + if i > 5 { + if i == 7 { + found = 1 + break + } + } + i = i + 1 + } + return found + } +} +"#; + + println!("=== Test: Nested if with break ==="); + + let ast = NyashParser::parse_from_string(src).expect("parse failed"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile failed"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for err in &errors { + eprintln!("❌ MIR verification error: {}", err); + } + panic!("❌ MIR verification failed with {} errors", errors.len()); + } + println!("✅ MIR verification passed"); +} + +#[test] +fn test_loopform_exit_phi_multiple_vars() { + std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); + std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); + std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1"); + + let src = r#" +static box TestMultiVars { + test() { + local i = 0 + local sum = 0 + local product = 1 + loop(i < 10) { + if sum > 20 { break } + sum = sum + i + product = product * 2 + i = i + 1 + } + return sum + } +} +"#; + + println!("=== Test: Multiple variables with break ==="); + + let ast = NyashParser::parse_from_string(src).expect("parse failed"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile failed"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for err in &errors { + eprintln!("❌ MIR verification error: {}", err); + } + panic!("❌ MIR verification failed with {} errors", errors.len()); + } + println!("✅ MIR verification passed"); +}