use super::*; use crate::mir::basic_block::BasicBlock; use std::mem; impl MirInterpreter { fn trace_enabled() -> bool { std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") || std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1") } pub(super) fn exec_function_inner( &mut self, func: &MirFunction, arg_vals: Option<&[VMValue]>, ) -> Result { // Phase 1: delegate cross-class reroute / narrow fallbacks to method_router if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { return r; } let saved_regs = mem::take(&mut self.regs); let saved_fn = self.cur_fn.clone(); self.cur_fn = Some(func.signature.name.clone()); match arg_vals { Some(args) => { for (i, pid) in func.params.iter().enumerate() { let v = args.get(i).cloned().unwrap_or(VMValue::Void); self.regs.insert(*pid, v); } } None => { // Seed all parameters with Void so SSA consumers observe defined values. for pid in &func.params { self.regs.insert(*pid, VMValue::Void); } } } let mut cur = func.entry_block; let mut last_pred: Option = None; // Dev/runtime safety valve: cap the number of interpreter steps per function // to prevent infinite loops from hanging the process. let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS") .ok() .and_then(|v| v.parse::().ok()) .or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::().ok())) .unwrap_or(1_000_000); let mut steps: u64 = 0; loop { steps += 1; if steps > max_steps { return Err(VMError::InvalidInstruction(format!( "vm step budget exceeded (max_steps={})", max_steps ))); } let block = func .blocks .get(&cur) .ok_or_else(|| VMError::InvalidBasicBlock(format!("bb {:?} not found", cur)))?; if Self::trace_enabled() { eprintln!( "[vm-trace] enter bb={:?} pred={:?} fn={}", cur, last_pred, self.cur_fn.as_deref().unwrap_or("") ); } self.apply_phi_nodes(block, last_pred)?; if let Err(e) = self.execute_block_instructions(block) { if Self::trace_enabled() { eprintln!( "[vm-trace] error in bb={:?}: {:?}\n last_inst={:?}", cur, e, self.last_inst ); } return Err(e); } match self.handle_terminator(block)? { BlockOutcome::Return(result) => { self.cur_fn = saved_fn; self.regs = saved_regs; return Ok(result); } BlockOutcome::Next { target, predecessor, } => { last_pred = Some(predecessor); cur = target; } } } } fn apply_phi_nodes( &mut self, block: &BasicBlock, last_pred: Option, ) -> Result<(), VMError> { for inst in block.phi_instructions() { if let MirInstruction::Phi { dst, inputs } = inst { let dst_id = *dst; if let Some(pred) = last_pred { if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) { let v = match self.reg_load(*val) { Ok(v) => v, Err(e) => { // Dev safety valve: tolerate undefined phi inputs by substituting Void if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") { if Self::trace_enabled() { eprintln!("[vm-trace] phi tolerate undefined input {:?} -> Void (err={:?})", val, e); } VMValue::Void } else { return Err(e); } } }; self.regs.insert(dst_id, v); if Self::trace_enabled() { eprintln!( "[vm-trace] phi dst={:?} take pred={:?} val={:?}", dst_id, pred, val ); } } else { // No matching predecessor in PHI inputs. Fallback policy: // - Prefer first input when available to avoid use-before-def crashes. // - Emit a trace note when tracing is enabled. // Strict mode: fail-fast when enabled. // Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0). let strict = { let on = |s: &str| { matches!(s, "1"|"true"|"on"|"yes"|"y"|"t"|"enabled"|"enable") }; let off = |s: &str| { matches!(s, "0"|"false"|"off"|"no"|"n"|"f"|"disabled"|"disable") }; if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") { let v = v.to_ascii_lowercase(); if off(&v) { false } else if on(&v) { true } else { true } } else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") { let v = v.to_ascii_lowercase(); if off(&v) { false } else if on(&v) { true } else { true } } else { true } }; if strict { return Err(VMError::InvalidInstruction(format!( "phi pred mismatch at {:?}: no input for predecessor {:?}", dst_id, pred ))); } if let Some((_, val0)) = inputs.first() { let v = match self.reg_load(*val0) { Ok(v) => v, Err(e) => { if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") { if Self::trace_enabled() { eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e); } VMValue::Void } else { return Err(e); } } }; self.regs.insert(dst_id, v); if Self::trace_enabled() { eprintln!( "[vm-trace] phi dst={:?} pred-miss fallback first val={:?}", dst_id, val0 ); } } else { // Empty inputs — assign Void (with optional trace) to avoid undefined dst let v = VMValue::Void; self.regs.insert(dst_id, v); if Self::trace_enabled() { eprintln!("[vm-trace] phi dst={:?} no inputs; assign Void", dst_id); } } } } else if let Some((_, val)) = inputs.first() { let v = match self.reg_load(*val) { Ok(v) => v, Err(e) => { if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") { if Self::trace_enabled() { eprintln!("[vm-trace] phi tolerate undefined default input {:?} -> Void (err={:?})", val, e); } VMValue::Void } else { return Err(e); } } }; self.regs.insert(dst_id, v); if Self::trace_enabled() { eprintln!( "[vm-trace] phi dst={:?} take default val={:?}", dst_id, val ); } } } } Ok(()) } fn execute_block_instructions(&mut self, block: &BasicBlock) -> Result<(), VMError> { for inst in block.non_phi_instructions() { self.last_block = Some(block.id); self.last_inst = Some(inst.clone()); if Self::trace_enabled() { eprintln!("[vm-trace] inst bb={:?} {:?}", block.id, inst); } self.execute_instruction(inst)?; } Ok(()) } fn handle_terminator(&mut self, block: &BasicBlock) -> Result { match &block.terminator { Some(MirInstruction::Return { value }) => { let result = if let Some(v) = value { self.reg_load(*v)? } else { VMValue::Void }; Ok(BlockOutcome::Return(result)) } Some(MirInstruction::Jump { target }) => Ok(BlockOutcome::Next { target: *target, predecessor: block.id, }), Some(MirInstruction::Branch { condition, then_bb, else_bb, }) => { let cond = self.reg_load(*condition)?; let branch = to_bool_vm(&cond).map_err(VMError::TypeError)?; let target = if branch { *then_bb } else { *else_bb }; Ok(BlockOutcome::Next { target, predecessor: block.id, }) } None => Err(VMError::InvalidBasicBlock(format!( "unterminated block {:?}", block.id ))), Some(other) => Err(VMError::InvalidInstruction(format!( "invalid terminator in MIR interp: {:?}", other ))), } } } enum BlockOutcome { Return(VMValue), Next { target: BasicBlockId, predecessor: BasicBlockId, }, }