use super::MirBuilder; use crate::ast::ASTNode; use crate::mir::{BasicBlockId, MirInstruction, ValueId}; use std::collections::HashMap; // Local helper has moved to phi_core::if_phi; keep call sites minimal impl MirBuilder { /// Merge all variables modified in then/else relative to pre_if_snapshot. /// In PHI-off mode inserts edge copies from branch exits to merge. In PHI-on mode emits Phi. /// `skip_var` allows skipping a variable already merged elsewhere (e.g., bound to an expression result). pub(super) fn merge_modified_vars( &mut self, _then_block: super::BasicBlockId, _else_block: super::BasicBlockId, then_exit_block_opt: Option, else_exit_block_opt: Option, pre_if_snapshot: &std::collections::HashMap, then_map_end: &std::collections::HashMap, else_map_end_opt: &Option>, skip_var: Option<&str>, ) -> Result<(), String> { // 📦 Conservative PHI Generation (Box Theory) // Generate PHI for ALL variables present in ANY branch (union), not just modified ones. // This ensures correctness: if a variable is defined in one branch but not the other, // we use the predecessor value as fallback (or skip if undefined everywhere). // // Theory: Conservative ∘ Elimination = Minimal SSA // - Conservative (this): correctness-first, generate all PHIs // - Elimination (future): efficiency optimization, remove unused PHIs use std::collections::HashSet; // Collect all variables from all sources let mut all_vars = HashSet::new(); all_vars.extend(pre_if_snapshot.keys().cloned()); all_vars.extend(then_map_end.keys().cloned()); if let Some(ref else_map) = else_map_end_opt { all_vars.extend(else_map.keys().cloned()); } // Keep track of which vars were changed (for debugging/hints) let changed = crate::mir::phi_core::if_phi::compute_modified_names( pre_if_snapshot, then_map_end, else_map_end_opt, ); let changed_set: HashSet = changed.iter().cloned().collect(); // Debug: Conservative PHI trace let trace_conservative = std::env::var("NYASH_CONSERVATIVE_PHI_TRACE").ok().as_deref() == Some("1"); if trace_conservative { eprintln!("[Conservative PHI] all_vars count: {}", all_vars.len()); eprintln!("[Conservative PHI] pre_if_snapshot: {:?}", pre_if_snapshot.keys().collect::>()); eprintln!("[Conservative PHI] then_map_end: {:?}", then_map_end.keys().collect::>()); if let Some(ref else_map) = else_map_end_opt { eprintln!("[Conservative PHI] else_map_end: {:?}", else_map.keys().collect::>()); } } // Generate PHI for all variables (Conservative) for name in all_vars { if skip_var.map(|s| s == name).unwrap_or(false) { if trace_conservative { eprintln!("[Conservative PHI] Skipping {}: matches skip_var", name); } continue; } // 📦 Conservative PHI: Fallback to predecessor value if not defined in a branch // This handles variables defined in only one branch (e.g., bb16 defines %51, but bb15 doesn't) let pre_val_opt = pre_if_snapshot.get(name.as_str()).copied(); // Get values from each branch, falling back to predecessor value let then_v_opt = then_map_end.get(name.as_str()).copied() .or(pre_val_opt); let else_v_opt = else_map_end_opt .as_ref() .and_then(|m| m.get(name.as_str()).copied()) .or(pre_val_opt); // 📦 Conservative PHI: Handle variables defined in only ONE branch // If variable exists in one branch but not the other (and not in predecessor), // create a fresh "undefined" ValueId for the missing branch. // This ensures all control flow paths have a definition at the merge point. let (then_v, else_v) = match (then_v_opt, else_v_opt) { (Some(tv), Some(ev)) => { if trace_conservative { eprintln!("[Conservative PHI] Generating PHI for {}: then={:?} else={:?}", name, tv, ev); } (tv, ev) }, (Some(tv), None) => { // Variable exists in then branch but not else or predecessor // Emit a 'const void' instruction to represent undefined value let undef = crate::mir::builder::emission::constant::emit_void(self); if trace_conservative { eprintln!("[Conservative PHI] One-branch variable {}: then={:?} else=void({:?})", name, tv, undef); } (tv, undef) }, (None, Some(ev)) => { // Variable exists in else branch but not then or predecessor // Emit a 'const void' instruction to represent undefined value let undef = crate::mir::builder::emission::constant::emit_void(self); if trace_conservative { eprintln!("[Conservative PHI] One-branch variable {}: then=void({:?}) else={:?}", name, undef, ev); } (undef, ev) }, (None, None) => { // Variable doesn't exist anywhere - skip if trace_conservative { eprintln!("[Conservative PHI] Skipping {}: undefined everywhere", name); } continue } }; // フェーズM: 常にPHI命令を使用(no_phi_mode撤廃) // incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_v)); } if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_v)); } match inputs.len() { 0 => {} 1 => { let (_pred, v) = inputs[0]; self.variable_map.insert(name, v); } _ => { if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) { crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs); } let merged = self.insert_phi(inputs)?; self.variable_map.insert(name, merged); } } } // Ensure pinned synthetic slots ("__pin$...") have a block-local definition at the merge, // even if their values did not change across branches. This avoids undefined uses when // subsequent blocks re-use pinned values without modifications. for (pin_name, pre_val) in pre_if_snapshot.iter() { if !pin_name.starts_with("__pin$") { continue; } if skip_var.map(|s| s == pin_name.as_str()).unwrap_or(false) { continue; } if changed_set.contains(pin_name) { continue; } let then_v = then_map_end.get(pin_name.as_str()).copied().unwrap_or(*pre_val); let else_v = else_map_end_opt .as_ref() .and_then(|m| m.get(pin_name.as_str()).copied()) .unwrap_or(*pre_val); let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_v)); } if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_v)); } match inputs.len() { 0 => {} 1 => { let (_pred, v) = inputs[0]; self.variable_map.insert(pin_name.clone(), v); } _ => { if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) { crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs); } let merged = self.next_value_id(); if let (Some(func), Some(cur_bb)) = (self.current_function.as_mut(), self.current_block) { crate::mir::ssot::cf_common::insert_phi_at_head(func, cur_bb, merged, inputs); } else { self.emit_instruction(MirInstruction::Phi { dst: merged, inputs })?; } self.variable_map.insert(pin_name.clone(), merged); } } } Ok(()) } /// Normalize Phi creation for if/else constructs. /// This handles variable reassignment patterns and ensures a single exit value. pub(super) fn normalize_if_else_phi( &mut self, _then_block: BasicBlockId, _else_block: BasicBlockId, then_exit_block_opt: Option, else_exit_block_opt: Option, then_value_raw: ValueId, else_value_raw: ValueId, pre_if_var_map: &HashMap, then_ast_for_analysis: &ASTNode, else_ast_for_analysis: &Option, then_var_map_end: &HashMap, else_var_map_end_opt: &Option>, pre_then_var_value: Option, ) -> Result { // If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else // does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value). let assigned_var_then = crate::mir::phi_core::if_phi::extract_assigned_var(then_ast_for_analysis); let assigned_var_else = else_ast_for_analysis .as_ref() .and_then(|a| crate::mir::phi_core::if_phi::extract_assigned_var(a)); let result_val = self.next_value_id(); // フェーズM: no_phi_mode分岐削除(常にPHI命令を使用) if let Some(var_name) = assigned_var_then.clone() { let else_assigns_same = assigned_var_else .as_ref() .map(|s| s == &var_name) .unwrap_or(false); // Resolve branch-end values for the assigned variable let then_value_for_var = then_var_map_end .get(&var_name) .copied() .unwrap_or(then_value_raw); // Check if else branch actually modified the variable (even if not as last expression) let else_modified_var = else_var_map_end_opt .as_ref() .and_then(|m| m.get(&var_name).copied()); let else_value_for_var = if else_assigns_same { else_var_map_end_opt .as_ref() .and_then(|m| m.get(&var_name).copied()) .unwrap_or(else_value_raw) } else if let Some(else_modified) = else_modified_var { // Else modifies the variable (even if not as the last expression) else_modified } else { // Else doesn't modify the variable: use pre-if value if available pre_then_var_value.unwrap_or(else_value_raw) }; // Build inputs from reachable predecessors only let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_for_var)); } if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_for_var)); } match inputs.len() { 0 => {} 1 => { // Direct bind (no PHI needed) self.variable_map = pre_if_var_map.clone(); self.variable_map.insert(var_name, inputs[0].1); return Ok(inputs[0].1); } _ => { if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) { crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs); } self.insert_phi_with_dst(result_val, inputs)?; } } self.variable_map = pre_if_var_map.clone(); self.variable_map.insert(var_name, result_val); } else { // No variable assignment pattern detected – just emit Phi for expression result let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_raw)); } if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_raw)); } match inputs.len() { 0 => { /* leave result_val as fresh, but unused; synthesize void */ let v = crate::mir::builder::emission::constant::emit_void(self); return Ok(v); } 1 => { return Ok(inputs[0].1); } _ => { if let Some(func) = self.current_function.as_mut() { func.update_cfg(); } if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) { crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs); } self.insert_phi_with_dst(result_val, inputs)?; } } // Merge variable map conservatively to pre-if snapshot (no new bindings) self.variable_map = pre_if_var_map.clone(); } Ok(result_val) } }