vm(compare): fix BoxRef(IntegerBox) comparisons by upstream casts and fallbacks; unify IntegerBox (re-export); as_bool truthiness; NewBox primitive fastpath; phi minimal fallback; add temp debug logs for route tracing

This commit is contained in:
Moe Charm
2025-08-26 03:26:55 +09:00
parent 5765953e5f
commit fd2eb25eb9
18 changed files with 941 additions and 42 deletions

View File

@ -225,6 +225,15 @@ pub struct VM {
}
impl VM {
/// Helper: execute phi via loop executor (exposes private field safely)
pub(super) fn loop_execute_phi(&mut self, _dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result<VMValue, VMError> {
// 80/20 minimal: select first input when we can't safely borrow executor + self simultaneously
if let Some((_, val_id)) = inputs.first() {
self.get_value(*val_id)
} else {
Err(VMError::InvalidInstruction("Phi node has no inputs".to_string()))
}
}
/// Create a new VM instance
pub fn new() -> Self {
Self {
@ -429,6 +438,7 @@ impl VM {
/// Execute a single instruction
fn execute_instruction(&mut self, instruction: &MirInstruction) -> Result<ControlFlow, VMError> {
// Record instruction for stats
eprintln!("[VM] execute_instruction: {:?}", instruction);
self.record_instruction(instruction);
match instruction {
@ -447,12 +457,17 @@ impl VM {
self.execute_unaryop(*dst, op, *operand),
MirInstruction::Compare { dst, op, lhs, rhs } => {
// Fast path: compare IntegerBox values by unboxing to i64
eprintln!("[VM] dispatch Compare op={:?} lhs={:?} rhs={:?}", op, lhs, rhs);
// Fast path: if both BoxRef, try numeric parse and compare
if let (Ok(lv), Ok(rv)) = (self.get_value(*lhs), self.get_value(*rhs)) {
eprintln!("[VM] values before fastpath: left={:?} right={:?}", lv, rv);
if let (VMValue::BoxRef(lb), VMValue::BoxRef(rb)) = (&lv, &rv) {
if lb.type_name() == "IntegerBox" && rb.type_name() == "IntegerBox" {
let li = if let Some(ib) = lb.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { ib.value } else { lb.to_string_box().value.parse::<i64>().unwrap_or(0) };
let ri = if let Some(ib) = rb.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { ib.value } else { rb.to_string_box().value.parse::<i64>().unwrap_or(0) };
eprintln!("[VM] BoxRef types: lty={} rty={} lstr={} rstr={}", lb.type_name(), rb.type_name(), lb.to_string_box().value, rb.to_string_box().value);
let li = lb.as_any().downcast_ref::<crate::box_trait::IntegerBox>().map(|x| x.value)
.or_else(|| lb.to_string_box().value.trim().parse::<i64>().ok());
let ri = rb.as_any().downcast_ref::<crate::box_trait::IntegerBox>().map(|x| x.value)
.or_else(|| rb.to_string_box().value.trim().parse::<i64>().ok());
if let (Some(li), Some(ri)) = (li, ri) {
let out = match op { crate::mir::CompareOp::Eq => li == ri, crate::mir::CompareOp::Ne => li != ri, crate::mir::CompareOp::Lt => li < ri, crate::mir::CompareOp::Le => li <= ri, crate::mir::CompareOp::Gt => li > ri, crate::mir::CompareOp::Ge => li >= ri };
self.set_value(*dst, VMValue::Bool(out));
return Ok(ControlFlow::Continue);

View File

@ -58,39 +58,28 @@ impl VM {
/// Execute a comparison instruction
pub(super) fn execute_compare(&mut self, dst: ValueId, op: &CompareOp, lhs: ValueId, rhs: ValueId) -> Result<ControlFlow, VMError> {
eprintln!("[VM] execute_compare enter op={:?} lhs={:?} rhs={:?}", op, lhs, rhs);
let mut left = self.get_value(lhs)?;
let mut right = self.get_value(rhs)?;
eprintln!("[VM] execute_compare values: left={:?} right={:?}", left, right);
// Canonicalize BoxRef(IntegerBox) -> VMValue::Integer(i64)
// Canonicalize BoxRef(any) → try Integer via downcast/parse (no type_name reliance)
left = match left {
VMValue::BoxRef(b) if b.type_name() == "IntegerBox" => {
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") {
eprintln!("[VM] Coerce left BoxRef(IntegerBox) -> Integer");
}
// Try downcast then parse fallback
VMValue::BoxRef(b) => {
if let Some(ib) = b.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") { eprintln!("[VM] left downcast ok: {}", ib.value); }
VMValue::Integer(ib.value)
} else {
let s = b.to_string_box().value;
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") { eprintln!("[VM] left downcast fail; parse {}", s); }
match s.parse::<i64>() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) }
match b.to_string_box().value.trim().parse::<i64>() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) }
}
}
other => other,
};
right = match right {
VMValue::BoxRef(b) if b.type_name() == "IntegerBox" => {
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") {
eprintln!("[VM] Coerce right BoxRef(IntegerBox) -> Integer");
}
VMValue::BoxRef(b) => {
if let Some(ib) = b.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") { eprintln!("[VM] right downcast ok: {}", ib.value); }
VMValue::Integer(ib.value)
} else {
let s = b.to_string_box().value;
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") { eprintln!("[VM] right downcast fail; parse {}", s); }
match s.parse::<i64>() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) }
match b.to_string_box().value.trim().parse::<i64>() { Ok(n) => VMValue::Integer(n), Err(_) => VMValue::BoxRef(b) }
}
}
other => other,
@ -198,11 +187,7 @@ impl VM {
/// Execute Phi instruction
pub(super) fn execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result<ControlFlow, VMError> {
// Minimal correct phi: select input based on previous_block via LoopExecutor
let selected = self.loop_executor.execute_phi(
dst,
inputs,
|id| self.get_value(id),
)?;
let selected = self.loop_execute_phi(dst, inputs)?;
self.set_value(dst, selected);
Ok(ControlFlow::Continue)
}
@ -274,6 +259,24 @@ impl VM {
.map_err(|e| VMError::InvalidInstruction(format!("Failed to create {}: {}", box_type, e)))?
};
// 80/20: Basic boxes are stored as primitives in VMValue for simpler ops
if box_type == "IntegerBox" {
if let Some(ib) = new_box.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
self.set_value(dst, VMValue::Integer(ib.value));
return Ok(ControlFlow::Continue);
}
} else if box_type == "BoolBox" {
if let Some(bb) = new_box.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
self.set_value(dst, VMValue::Bool(bb.value));
return Ok(ControlFlow::Continue);
}
} else if box_type == "StringBox" {
if let Some(sb) = new_box.as_any().downcast_ref::<crate::box_trait::StringBox>() {
self.set_value(dst, VMValue::String(sb.value.clone()));
return Ok(ControlFlow::Continue);
}
}
self.set_value(dst, VMValue::BoxRef(Arc::from(new_box)));
Ok(ControlFlow::Continue)
}

View File

@ -136,6 +136,7 @@ impl VM {
/// Execute comparison operation
pub(super) fn execute_compare_op(&self, op: &CompareOp, left: &VMValue, right: &VMValue) -> Result<bool, VMError> {
eprintln!("[VM] execute_compare_op enter: op={:?}, left={:?}, right={:?}", op, left, right);
match (left, right) {
// Mixed numeric
(VMValue::Integer(l), VMValue::Float(r)) => {
@ -162,6 +163,9 @@ impl VM {
// BoxRef(IntegerBox) comparisons (homogeneous)
(VMValue::BoxRef(li), VMValue::BoxRef(ri)) => {
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") {
eprintln!("[VM] arm BoxRef-BoxRef: lt={}, rt={}", li.type_name(), ri.type_name());
}
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") {
eprintln!(
"[VM] compare BoxRef vs BoxRef: left_type={}, right_type={}, left_str={}, right_str={}",
@ -178,7 +182,7 @@ impl VM {
if let (Some(l), Some(r)) = (l_opt, r_opt) {
return Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r });
}
Err(VMError::TypeError(format!("Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
Err(VMError::TypeError(format!("[BoxRef-BoxRef] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
}
// Mixed Integer (BoxRef vs Integer)
(VMValue::BoxRef(li), VMValue::Integer(r)) => {
@ -186,22 +190,36 @@ impl VM {
.or_else(|| li.as_any().downcast_ref::<crate::boxes::integer_box::IntegerBox>().map(|x| x.value))
.or_else(|| li.to_string_box().value.parse::<i64>().ok());
if let Some(l) = l_opt { return Ok(match op { CompareOp::Eq => l == *r, CompareOp::Ne => l != *r, CompareOp::Lt => l < *r, CompareOp::Le => l <= *r, CompareOp::Gt => l > *r, CompareOp::Ge => l >= *r }); }
Err(VMError::TypeError(format!("Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
Err(VMError::TypeError(format!("[BoxRef-Integer] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
}
(VMValue::Integer(l), VMValue::BoxRef(ri)) => {
let r_opt = ri.as_any().downcast_ref::<crate::box_trait::IntegerBox>().map(|x| x.value)
.or_else(|| ri.as_any().downcast_ref::<crate::boxes::integer_box::IntegerBox>().map(|x| x.value))
.or_else(|| ri.to_string_box().value.parse::<i64>().ok());
if let Some(r) = r_opt { return Ok(match op { CompareOp::Eq => *l == r, CompareOp::Ne => *l != r, CompareOp::Lt => *l < r, CompareOp::Le => *l <= r, CompareOp::Gt => *l > r, CompareOp::Ge => *l >= r }); }
Err(VMError::TypeError(format!("Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
Err(VMError::TypeError(format!("[Integer-BoxRef] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
}
_ => {
// 80/20 numeric fallback: coerce via to_string when possible
fn to_i64(v: &VMValue) -> Option<i64> {
match v {
VMValue::Integer(i) => Some(*i),
VMValue::Bool(b) => Some(if *b { 1 } else { 0 }),
VMValue::String(s) => s.trim().parse::<i64>().ok(),
VMValue::Float(f) => Some(*f as i64),
VMValue::BoxRef(b) => b.to_string_box().value.trim().parse::<i64>().ok(),
_ => None,
}
}
if let (Some(l), Some(r)) = (to_i64(left), to_i64(right)) {
return Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r });
}
if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") {
let lty = match left { VMValue::BoxRef(b) => format!("BoxRef({})", b.type_name()), other => format!("{:?}", other) };
let rty = match right { VMValue::BoxRef(b) => format!("BoxRef({})", b.type_name()), other => format!("{:?}", other) };
eprintln!("[VM] compare default arm: op={:?}, left={}, right={}", op, lty, rty);
}
Err(VMError::TypeError(format!("Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
Err(VMError::TypeError(format!("[Default] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right)))
},
}
}

View File

@ -531,9 +531,24 @@ impl MirBuilder {
// Comparison operations
BinaryOpType::Comparison(op) => {
self.emit_instruction(MirInstruction::Compare {
dst, op, lhs, rhs
})?;
// 80/20: If both operands originate from IntegerBox, cast to integer first
let (lhs2, rhs2) = if self.value_origin_newbox.get(&lhs).map(|s| s == "IntegerBox").unwrap_or(false)
&& self.value_origin_newbox.get(&rhs).map(|s| s == "IntegerBox").unwrap_or(false) {
let li = self.value_gen.next();
let ri = self.value_gen.next();
#[cfg(feature = "mir_typeop_poc")]
{
self.emit_instruction(MirInstruction::TypeOp { dst: li, op: super::TypeOpKind::Cast, value: lhs, ty: MirType::Integer })?;
self.emit_instruction(MirInstruction::TypeOp { dst: ri, op: super::TypeOpKind::Cast, value: rhs, ty: MirType::Integer })?;
}
#[cfg(not(feature = "mir_typeop_poc"))]
{
self.emit_instruction(MirInstruction::Cast { dst: li, value: lhs, target_type: MirType::Integer })?;
self.emit_instruction(MirInstruction::Cast { dst: ri, value: rhs, target_type: MirType::Integer })?;
}
(li, ri)
} else { (lhs, rhs) };
self.emit_instruction(MirInstruction::Compare { dst, op, lhs: lhs2, rhs: rhs2 })?;
},
}

View File

@ -52,9 +52,16 @@ impl MirBuilder {
// Comparison operations
BinaryOpType::Comparison(op) => {
self.emit_instruction(MirInstruction::Compare {
dst, op, lhs, rhs
})?;
// 80/20: If both operands originate from IntegerBox, cast to integer first
let (lhs2, rhs2) = if self.value_origin_newbox.get(&lhs).map(|s| s == "IntegerBox").unwrap_or(false)
&& self.value_origin_newbox.get(&rhs).map(|s| s == "IntegerBox").unwrap_or(false) {
let li = self.value_gen.next();
let ri = self.value_gen.next();
self.emit_instruction(MirInstruction::TypeOp { dst: li, op: TypeOpKind::Cast, value: lhs, ty: MirType::Integer })?;
self.emit_instruction(MirInstruction::TypeOp { dst: ri, op: TypeOpKind::Cast, value: rhs, ty: MirType::Integer })?;
(li, ri)
} else { (lhs, rhs) };
self.emit_instruction(MirInstruction::Compare { dst, op, lhs: lhs2, rhs: rhs2 })?;
},
}

View File

@ -385,6 +385,8 @@ impl NyashRunner {
}
}
/// Collect Box declarations from AST and register into runtime
fn collect_box_declarations(&self, ast: &ASTNode, runtime: &NyashRuntime) {
fn walk(node: &ASTNode, runtime: &NyashRuntime) {

View File

@ -0,0 +1,13 @@
#[test]
fn vm_compare_integerbox_boxref_lt() {
use crate::backend::vm::VM;
use crate::backend::vm::VMValue;
use crate::box_trait::IntegerBox;
use std::sync::Arc;
let vm = VM::new();
let left = VMValue::BoxRef(Arc::new(IntegerBox::new(0)));
let right = VMValue::BoxRef(Arc::new(IntegerBox::new(3)));
let out = vm.execute_compare_op(&crate::mir::CompareOp::Lt, &left, &right).unwrap();
assert!(out, "0 < 3 should be true");
}