2025-09-25 01:09:48 +09:00
|
|
|
use super::*;
|
|
|
|
|
use crate::mir::basic_block::BasicBlock;
|
|
|
|
|
use std::mem;
|
|
|
|
|
|
|
|
|
|
impl MirInterpreter {
|
2025-09-26 03:30:59 +09:00
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 01:09:48 +09:00
|
|
|
pub(super) fn exec_function_inner(
|
|
|
|
|
&mut self,
|
|
|
|
|
func: &MirFunction,
|
|
|
|
|
arg_vals: Option<&[VMValue]>,
|
|
|
|
|
) -> Result<VMValue, VMError> {
|
2025-09-28 01:33:58 +09:00
|
|
|
// 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; }
|
2025-09-25 01:09:48 +09:00
|
|
|
let saved_regs = mem::take(&mut self.regs);
|
|
|
|
|
let saved_fn = self.cur_fn.clone();
|
|
|
|
|
self.cur_fn = Some(func.signature.name.clone());
|
|
|
|
|
|
2025-11-01 13:28:56 +09:00
|
|
|
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);
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut cur = func.entry_block;
|
|
|
|
|
let mut last_pred: Option<BasicBlockId> = None;
|
2025-11-02 11:01:03 +09:00
|
|
|
// 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::<u64>().ok())
|
|
|
|
|
.or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::<u64>().ok()))
|
|
|
|
|
.unwrap_or(1_000_000);
|
|
|
|
|
let mut steps: u64 = 0;
|
2025-09-25 01:09:48 +09:00
|
|
|
|
|
|
|
|
loop {
|
2025-11-02 11:01:03 +09:00
|
|
|
steps += 1;
|
|
|
|
|
if steps > max_steps {
|
|
|
|
|
return Err(VMError::InvalidInstruction(format!(
|
|
|
|
|
"vm step budget exceeded (max_steps={})",
|
|
|
|
|
max_steps
|
|
|
|
|
)));
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
let block = func
|
|
|
|
|
.blocks
|
|
|
|
|
.get(&cur)
|
|
|
|
|
.ok_or_else(|| VMError::InvalidBasicBlock(format!("bb {:?} not found", cur)))?;
|
|
|
|
|
|
2025-09-26 03:30:59 +09:00
|
|
|
if Self::trace_enabled() {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[vm-trace] enter bb={:?} pred={:?} fn={}",
|
|
|
|
|
cur,
|
|
|
|
|
last_pred,
|
|
|
|
|
self.cur_fn.as_deref().unwrap_or("")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 01:09:48 +09:00
|
|
|
self.apply_phi_nodes(block, last_pred)?;
|
2025-09-26 03:30:59 +09:00
|
|
|
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);
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
|
|
|
|
|
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<BasicBlockId>,
|
|
|
|
|
) -> Result<(), VMError> {
|
2025-11-03 23:21:48 +09:00
|
|
|
let trace_phi = std::env::var("NYASH_VM_TRACE_PHI").ok().as_deref() == Some("1");
|
|
|
|
|
if trace_phi {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}",
|
|
|
|
|
block.id,
|
|
|
|
|
last_pred,
|
|
|
|
|
block.predecessors
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
for inst in block.phi_instructions() {
|
|
|
|
|
if let MirInstruction::Phi { dst, inputs } = inst {
|
|
|
|
|
let dst_id = *dst;
|
2025-11-03 23:21:48 +09:00
|
|
|
if trace_phi {
|
|
|
|
|
let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect();
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[vm-trace-phi] phi dst={:?} inputs.pred={:?}",
|
|
|
|
|
dst_id, in_preds
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
if let Some(pred) = last_pred {
|
|
|
|
|
if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
|
2025-09-26 14:34:42 +09:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-09-25 01:09:48 +09:00
|
|
|
self.regs.insert(dst_id, v);
|
2025-09-26 03:30:59 +09:00
|
|
|
if Self::trace_enabled() {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[vm-trace] phi dst={:?} take pred={:?} val={:?}",
|
|
|
|
|
dst_id, pred, val
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-02 11:01:03 +09:00
|
|
|
} 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 {
|
2025-11-03 23:21:48 +09:00
|
|
|
if trace_phi {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[vm-trace-phi][strict] mismatch dst={:?} last_pred={:?} inputs={:?} preds_of_bb={:?}",
|
|
|
|
|
dst_id,
|
|
|
|
|
pred,
|
|
|
|
|
inputs,
|
|
|
|
|
block.predecessors
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-02 11:01:03 +09:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
}
|
|
|
|
|
} else if let Some((_, val)) = inputs.first() {
|
2025-09-26 14:34:42 +09:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-09-25 01:09:48 +09:00
|
|
|
self.regs.insert(dst_id, v);
|
2025-09-26 03:30:59 +09:00
|
|
|
if Self::trace_enabled() {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[vm-trace] phi dst={:?} take default val={:?}",
|
|
|
|
|
dst_id, val
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn execute_block_instructions(&mut self, block: &BasicBlock) -> Result<(), VMError> {
|
|
|
|
|
for inst in block.non_phi_instructions() {
|
2025-09-26 03:30:59 +09:00
|
|
|
self.last_block = Some(block.id);
|
|
|
|
|
self.last_inst = Some(inst.clone());
|
|
|
|
|
if Self::trace_enabled() {
|
|
|
|
|
eprintln!("[vm-trace] inst bb={:?} {:?}", block.id, inst);
|
|
|
|
|
}
|
2025-09-25 01:09:48 +09:00
|
|
|
self.execute_instruction(inst)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_terminator(&mut self, block: &BasicBlock) -> Result<BlockOutcome, VMError> {
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
}
|