mir: implement proper short-circuit lowering (&&/||) via branch+phi; vm: add NYASH_VM_TRACE exec/phi logs and reg_load diagnostics; vm-fallback: minimal Void guards (push/get_position/line/column), MapBox.birth no-op; smokes: filter builtin Array/Map plugin notices; docs: CURRENT_TASK updated
This commit is contained in:
@ -3,6 +3,11 @@ 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,
|
||||
@ -28,8 +33,25 @@ impl MirInterpreter {
|
||||
.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)?;
|
||||
self.execute_block_instructions(block)?;
|
||||
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) => {
|
||||
@ -60,10 +82,22 @@ impl MirInterpreter {
|
||||
if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
|
||||
let v = self.reg_load(*val)?;
|
||||
self.regs.insert(dst_id, v);
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] phi dst={:?} take pred={:?} val={:?}",
|
||||
dst_id, pred, val
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if let Some((_, val)) = inputs.first() {
|
||||
let v = self.reg_load(*val)?;
|
||||
self.regs.insert(dst_id, v);
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] phi dst={:?} take default val={:?}",
|
||||
dst_id, val
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,6 +106,11 @@ impl MirInterpreter {
|
||||
|
||||
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(())
|
||||
|
||||
@ -92,12 +92,52 @@ impl MirInterpreter {
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<(), VMError> {
|
||||
// Graceful void guard for common short-circuit patterns in user code
|
||||
// e.g., `A or not last.is_eof()` should not crash when last is absent.
|
||||
match self.reg_load(box_val)? {
|
||||
VMValue::Void => {
|
||||
match method {
|
||||
"is_eof" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Bool(false)); } return Ok(()); }
|
||||
"length" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"substring" => { if let Some(d) = dst { self.regs.insert(d, VMValue::String(String::new())); } return Ok(()); }
|
||||
"push" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Void); } return Ok(()); }
|
||||
"get_position" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"get_line" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
"get_column" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
VMValue::BoxRef(ref b) => {
|
||||
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
match method {
|
||||
"is_eof" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Bool(false)); } return Ok(()); }
|
||||
"length" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"substring" => { if let Some(d) = dst { self.regs.insert(d, VMValue::String(String::new())); } return Ok(()); }
|
||||
"push" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Void); } return Ok(()); }
|
||||
"get_position" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"get_line" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
"get_column" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if self.try_handle_object_fields(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_instance_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_string_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_array_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_map_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
self.invoke_plugin_box(dst, box_val, method, args)
|
||||
}
|
||||
|
||||
@ -149,6 +189,78 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_handle_map_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv = self.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(mb) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init for MapBox
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
if args.len() != 2 { return Err(VMError::InvalidInstruction("MapBox.set expects 2 args".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let v = self.reg_load(args[1])?.to_nyash_box();
|
||||
let ret = mb.set(k, v);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.get expects 1 arg".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = mb.get(k);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"has" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.has expects 1 arg".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = mb.has(k);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"delete" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.delete expects 1 arg".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = mb.delete(k);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"size" => {
|
||||
let ret = mb.size();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"keys" => {
|
||||
let ret = mb.keys();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"values" => {
|
||||
let ret = mb.values();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn try_handle_string_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
@ -173,6 +285,29 @@ impl MirInterpreter {
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
"substring" => {
|
||||
if args.len() != 2 {
|
||||
return Err(VMError::InvalidInstruction(
|
||||
"substring expects 2 args (start, end)".into(),
|
||||
));
|
||||
}
|
||||
let s_idx = self.reg_load(args[0])?.as_integer().unwrap_or(0);
|
||||
let e_idx = self.reg_load(args[1])?.as_integer().unwrap_or(0);
|
||||
let len = sb.value.chars().count() as i64;
|
||||
let start = s_idx.max(0).min(len) as usize;
|
||||
let end = e_idx.max(start as i64).min(len) as usize;
|
||||
let chars: Vec<char> = sb.value.chars().collect();
|
||||
let sub: String = chars[start..end].iter().collect();
|
||||
if let Some(d) = dst {
|
||||
self.regs.insert(
|
||||
d,
|
||||
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(
|
||||
sub,
|
||||
))),
|
||||
);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
"concat" => {
|
||||
if args.len() != 1 {
|
||||
return Err(VMError::InvalidInstruction("concat expects 1 arg".into()));
|
||||
@ -195,6 +330,93 @@ impl MirInterpreter {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn try_handle_instance_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv_vm = self.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv_vm.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(inst) = recv_box_any.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
// Resolve lowered method function: "Class.method/arity"
|
||||
let fname = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
|
||||
if let Some(func) = self.functions.get(&fname).cloned() {
|
||||
// Build argv: me + args
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
|
||||
argv.push(recv_vm.clone());
|
||||
for a in args {
|
||||
argv.push(self.reg_load(*a)?);
|
||||
}
|
||||
let ret = self.exec_function_inner(&func, Some(&argv))?;
|
||||
if let Some(d) = dst {
|
||||
self.regs.insert(d, ret);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn try_handle_array_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv = self.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(ab) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
"push" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("push expects 1 arg".into())); }
|
||||
let val = self.reg_load(args[0])?.to_nyash_box();
|
||||
let _ = ab.push(val);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
"len" | "length" | "size" => {
|
||||
let ret = ab.length();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("get expects 1 arg".into())); }
|
||||
let idx = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = ab.get(idx);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
if args.len() != 2 { return Err(VMError::InvalidInstruction("set expects 2 args".into())); }
|
||||
let idx = self.reg_load(args[0])?.to_nyash_box();
|
||||
let val = self.reg_load(args[1])?.to_nyash_box();
|
||||
let _ = ab.set(idx, val);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn invoke_plugin_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
|
||||
@ -2,10 +2,31 @@ use super::*;
|
||||
|
||||
impl MirInterpreter {
|
||||
pub(super) fn reg_load(&self, id: ValueId) -> Result<VMValue, VMError> {
|
||||
self.regs
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.ok_or_else(|| VMError::InvalidValue(format!("use of undefined value {:?}", id)))
|
||||
match self.regs.get(&id).cloned() {
|
||||
Some(v) => Ok(v),
|
||||
None => {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
|
||||
{
|
||||
let keys: Vec<String> = self
|
||||
.regs
|
||||
.keys()
|
||||
.map(|k| format!("{:?}", k))
|
||||
.collect();
|
||||
eprintln!(
|
||||
"[vm-trace] reg_load undefined id={:?} last_block={:?} last_inst={:?} regs={}",
|
||||
id,
|
||||
self.last_block,
|
||||
self.last_inst,
|
||||
keys.join(", ")
|
||||
);
|
||||
}
|
||||
Err(VMError::InvalidValue(format!(
|
||||
"use of undefined value {:?}",
|
||||
id
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn eval_binop(
|
||||
@ -40,6 +61,8 @@ impl MirInterpreter {
|
||||
(BitAnd, Integer(x), Integer(y)) => Integer(x & y),
|
||||
(BitOr, Integer(x), Integer(y)) => Integer(x | y),
|
||||
(BitXor, Integer(x), Integer(y)) => Integer(x ^ y),
|
||||
(And, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x && y),
|
||||
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
|
||||
(Shl, Integer(x), Integer(y)) => Integer(x.wrapping_shl(y as u32)),
|
||||
(Shr, Integer(x), Integer(y)) => Integer(x.wrapping_shr(y as u32)),
|
||||
(opk, va, vb) => {
|
||||
@ -65,6 +88,10 @@ impl MirInterpreter {
|
||||
(Le, Float(x), Float(y)) => x <= y,
|
||||
(Gt, Float(x), Float(y)) => x > y,
|
||||
(Ge, Float(x), Float(y)) => x >= y,
|
||||
(Lt, VMValue::String(ref s), VMValue::String(ref t)) => s < t,
|
||||
(Le, VMValue::String(ref s), VMValue::String(ref t)) => s <= t,
|
||||
(Gt, VMValue::String(ref s), VMValue::String(ref t)) => s > t,
|
||||
(Ge, VMValue::String(ref s), VMValue::String(ref t)) => s >= t,
|
||||
(opk, va, vb) => {
|
||||
return Err(VMError::TypeError(format!(
|
||||
"unsupported compare {:?} on {:?} and {:?}",
|
||||
|
||||
@ -27,6 +27,9 @@ pub struct MirInterpreter {
|
||||
pub(super) obj_fields: HashMap<ValueId, HashMap<String, VMValue>>,
|
||||
pub(super) functions: HashMap<String, MirFunction>,
|
||||
pub(super) cur_fn: Option<String>,
|
||||
// Trace context (dev-only; enabled with NYASH_VM_TRACE=1)
|
||||
pub(super) last_block: Option<BasicBlockId>,
|
||||
pub(super) last_inst: Option<MirInstruction>,
|
||||
}
|
||||
|
||||
impl MirInterpreter {
|
||||
@ -37,6 +40,8 @@ impl MirInterpreter {
|
||||
obj_fields: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
cur_fn: None,
|
||||
last_block: None,
|
||||
last_inst: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user