2025-08-13 05:59:10 +00:00
|
|
|
/*!
|
|
|
|
|
* VM Backend - Execute MIR instructions in a virtual machine
|
2025-08-26 01:07:23 +09:00
|
|
|
*
|
|
|
|
|
* Purpose: Core VM (execute loop, storage, control-flow, integration glue)
|
|
|
|
|
* Responsibilities: fetch/dispatch instructions, manage values/blocks, stats hooks
|
|
|
|
|
* Key APIs: VM::execute_module, execute_instruction, get_value/set_value
|
|
|
|
|
* Typical Callers: runner (VM backend), instruction handlers (vm_instructions)
|
2025-08-13 05:59:10 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
use crate::mir::{MirModule, MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId};
|
|
|
|
|
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox};
|
|
|
|
|
use std::collections::HashMap;
|
2025-08-20 17:58:51 +09:00
|
|
|
use std::sync::Arc;
|
2025-08-20 18:57:10 +09:00
|
|
|
use crate::runtime::NyashRuntime;
|
|
|
|
|
use crate::scope_tracker::ScopeTracker;
|
|
|
|
|
// MirModule is already imported via crate::mir at top
|
|
|
|
|
use crate::instance_v2::InstanceBox;
|
2025-08-18 23:36:40 +09:00
|
|
|
use super::vm_phi::LoopExecutor;
|
2025-08-23 03:40:17 +09:00
|
|
|
use std::time::Instant;
|
2025-08-26 04:45:19 +09:00
|
|
|
use super::frame::ExecutionFrame;
|
|
|
|
|
use super::control_flow;
|
2025-08-13 05:59:10 +00:00
|
|
|
|
2025-08-20 17:58:51 +09:00
|
|
|
// Phase 9.78a: Import necessary components for unified Box handling
|
|
|
|
|
// TODO: Re-enable when interpreter refactoring is complete
|
|
|
|
|
// use crate::box_factory::UnifiedBoxRegistry;
|
|
|
|
|
// use crate::instance_v2::InstanceBox;
|
|
|
|
|
// use crate::interpreter::BoxDeclaration;
|
|
|
|
|
// use crate::scope_tracker::ScopeTracker;
|
|
|
|
|
// #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
|
|
|
|
// use crate::runtime::plugin_loader_v2::PluginLoaderV2;
|
|
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
/// VM execution error
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum VMError {
|
|
|
|
|
InvalidValue(String),
|
|
|
|
|
InvalidInstruction(String),
|
|
|
|
|
InvalidBasicBlock(String),
|
|
|
|
|
DivisionByZero,
|
|
|
|
|
StackUnderflow,
|
|
|
|
|
TypeError(String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for VMError {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
VMError::InvalidValue(msg) => write!(f, "Invalid value: {}", msg),
|
|
|
|
|
VMError::InvalidInstruction(msg) => write!(f, "Invalid instruction: {}", msg),
|
|
|
|
|
VMError::InvalidBasicBlock(msg) => write!(f, "Invalid basic block: {}", msg),
|
|
|
|
|
VMError::DivisionByZero => write!(f, "Division by zero"),
|
|
|
|
|
VMError::StackUnderflow => write!(f, "Stack underflow"),
|
|
|
|
|
VMError::TypeError(msg) => write!(f, "Type error: {}", msg),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::error::Error for VMError {}
|
|
|
|
|
|
|
|
|
|
/// VM value representation
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum VMValue {
|
|
|
|
|
Integer(i64),
|
|
|
|
|
Float(f64),
|
|
|
|
|
Bool(bool),
|
|
|
|
|
String(String),
|
2025-08-13 11:46:01 +00:00
|
|
|
Future(crate::boxes::future::FutureBox),
|
2025-08-13 05:59:10 +00:00
|
|
|
Void,
|
2025-08-20 17:58:51 +09:00
|
|
|
// Phase 9.78a: Add BoxRef for complex Box types
|
|
|
|
|
BoxRef(Arc<dyn NyashBox>),
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-20 04:45:26 +09:00
|
|
|
// Manual PartialEq implementation to avoid requiring PartialEq on FutureBox
|
|
|
|
|
impl PartialEq for VMValue {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
match (self, other) {
|
|
|
|
|
(VMValue::Integer(a), VMValue::Integer(b)) => a == b,
|
|
|
|
|
(VMValue::Float(a), VMValue::Float(b)) => a == b,
|
|
|
|
|
(VMValue::Bool(a), VMValue::Bool(b)) => a == b,
|
|
|
|
|
(VMValue::String(a), VMValue::String(b)) => a == b,
|
|
|
|
|
(VMValue::Void, VMValue::Void) => true,
|
|
|
|
|
// Future equality semantics are not defined; treat distinct futures as not equal
|
|
|
|
|
(VMValue::Future(_), VMValue::Future(_)) => false,
|
2025-08-20 17:58:51 +09:00
|
|
|
// BoxRef equality by reference
|
|
|
|
|
(VMValue::BoxRef(_), VMValue::BoxRef(_)) => false,
|
2025-08-20 04:45:26 +09:00
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
impl VMValue {
|
|
|
|
|
/// Convert to NyashBox for output
|
|
|
|
|
pub fn to_nyash_box(&self) -> Box<dyn NyashBox> {
|
|
|
|
|
match self {
|
|
|
|
|
VMValue::Integer(i) => Box::new(IntegerBox::new(*i)),
|
2025-08-28 22:31:51 +09:00
|
|
|
VMValue::Float(f) => Box::new(crate::boxes::FloatBox::new(*f)),
|
2025-08-13 05:59:10 +00:00
|
|
|
VMValue::Bool(b) => Box::new(BoolBox::new(*b)),
|
|
|
|
|
VMValue::String(s) => Box::new(StringBox::new(s)),
|
2025-08-13 11:46:01 +00:00
|
|
|
VMValue::Future(f) => Box::new(f.clone()),
|
2025-08-13 05:59:10 +00:00
|
|
|
VMValue::Void => Box::new(VoidBox::new()),
|
2025-08-21 01:18:25 +09:00
|
|
|
// BoxRef returns a shared handle (do NOT birth a new instance)
|
|
|
|
|
VMValue::BoxRef(arc_box) => arc_box.share_box(),
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-25 15:50:49 +09:00
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
/// Get string representation for printing
|
|
|
|
|
pub fn to_string(&self) -> String {
|
|
|
|
|
match self {
|
|
|
|
|
VMValue::Integer(i) => i.to_string(),
|
|
|
|
|
VMValue::Float(f) => f.to_string(),
|
|
|
|
|
VMValue::Bool(b) => b.to_string(),
|
|
|
|
|
VMValue::String(s) => s.clone(),
|
2025-08-13 11:46:01 +00:00
|
|
|
VMValue::Future(f) => f.to_string_box().value,
|
2025-08-13 05:59:10 +00:00
|
|
|
VMValue::Void => "void".to_string(),
|
2025-08-20 17:58:51 +09:00
|
|
|
VMValue::BoxRef(arc_box) => arc_box.to_string_box().value,
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to convert to integer
|
|
|
|
|
pub fn as_integer(&self) -> Result<i64, VMError> {
|
|
|
|
|
match self {
|
|
|
|
|
VMValue::Integer(i) => Ok(*i),
|
|
|
|
|
_ => Err(VMError::TypeError(format!("Expected integer, got {:?}", self))),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Attempt to convert to bool
|
|
|
|
|
pub fn as_bool(&self) -> Result<bool, VMError> {
|
|
|
|
|
match self {
|
|
|
|
|
VMValue::Bool(b) => Ok(*b),
|
|
|
|
|
VMValue::Integer(i) => Ok(*i != 0),
|
2025-08-23 18:12:33 +09:00
|
|
|
// Pragmatic coercions for dynamic boxes
|
|
|
|
|
VMValue::BoxRef(b) => {
|
|
|
|
|
// BoolBox → bool
|
|
|
|
|
if let Some(bb) = b.as_any().downcast_ref::<BoolBox>() {
|
|
|
|
|
return Ok(bb.value);
|
|
|
|
|
}
|
2025-08-26 02:39:52 +09:00
|
|
|
// IntegerBox → truthy if non-zero (legacy and new)
|
|
|
|
|
if let Some(ib) = b.as_any().downcast_ref::<IntegerBox>() { return Ok(ib.value != 0); }
|
|
|
|
|
if let Some(ib) = b.as_any().downcast_ref::<crate::boxes::integer_box::IntegerBox>() { return Ok(ib.value != 0); }
|
2025-08-23 18:12:33 +09:00
|
|
|
// VoidBox → false (nullish false)
|
|
|
|
|
if b.as_any().downcast_ref::<VoidBox>().is_some() {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
Err(VMError::TypeError(format!("Expected bool, got BoxRef({})", b.type_name())))
|
|
|
|
|
}
|
2025-08-26 02:39:52 +09:00
|
|
|
// Treat plain Void as false for logical contexts
|
|
|
|
|
VMValue::Void => Ok(false),
|
2025-08-13 05:59:10 +00:00
|
|
|
_ => Err(VMError::TypeError(format!("Expected bool, got {:?}", self))),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-13 11:46:01 +00:00
|
|
|
|
|
|
|
|
/// Convert from NyashBox to VMValue
|
|
|
|
|
pub fn from_nyash_box(nyash_box: Box<dyn crate::box_trait::NyashBox>) -> VMValue {
|
2025-08-20 17:58:51 +09:00
|
|
|
// Try to downcast to known types for optimization
|
2025-08-13 11:46:01 +00:00
|
|
|
if let Some(int_box) = nyash_box.as_any().downcast_ref::<IntegerBox>() {
|
|
|
|
|
VMValue::Integer(int_box.value)
|
|
|
|
|
} else if let Some(bool_box) = nyash_box.as_any().downcast_ref::<BoolBox>() {
|
|
|
|
|
VMValue::Bool(bool_box.value)
|
|
|
|
|
} else if let Some(string_box) = nyash_box.as_any().downcast_ref::<StringBox>() {
|
|
|
|
|
VMValue::String(string_box.value.clone())
|
|
|
|
|
} else if let Some(future_box) = nyash_box.as_any().downcast_ref::<crate::boxes::future::FutureBox>() {
|
|
|
|
|
VMValue::Future(future_box.clone())
|
|
|
|
|
} else {
|
2025-08-20 17:58:51 +09:00
|
|
|
// Phase 9.78a: For all other Box types (user-defined, plugin), store as BoxRef
|
|
|
|
|
VMValue::BoxRef(Arc::from(nyash_box))
|
2025-08-13 11:46:01 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&ConstValue> for VMValue {
|
|
|
|
|
fn from(const_val: &ConstValue) -> Self {
|
|
|
|
|
match const_val {
|
|
|
|
|
ConstValue::Integer(i) => VMValue::Integer(*i),
|
|
|
|
|
ConstValue::Float(f) => VMValue::Float(*f),
|
|
|
|
|
ConstValue::Bool(b) => VMValue::Bool(*b),
|
|
|
|
|
ConstValue::String(s) => VMValue::String(s.clone()),
|
|
|
|
|
ConstValue::Null => VMValue::Void, // Simplified
|
|
|
|
|
ConstValue::Void => VMValue::Void,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Virtual Machine state
|
|
|
|
|
pub struct VM {
|
2025-08-18 07:44:34 +00:00
|
|
|
/// Value storage (uses ValueId as direct index into Vec for O(1) access)
|
|
|
|
|
values: Vec<Option<VMValue>>,
|
2025-08-13 05:59:10 +00:00
|
|
|
/// Current function being executed
|
|
|
|
|
current_function: Option<String>,
|
2025-08-26 04:45:19 +09:00
|
|
|
/// Frame state (current block, pc, last result)
|
|
|
|
|
frame: ExecutionFrame,
|
2025-08-18 23:36:40 +09:00
|
|
|
/// Previous basic block (for phi node resolution)
|
|
|
|
|
previous_block: Option<BasicBlockId>,
|
2025-08-13 10:20:37 +00:00
|
|
|
/// Simple field storage for objects (maps reference -> field -> value)
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) object_fields: HashMap<ValueId, HashMap<String, VMValue>>,
|
2025-08-21 03:08:13 +09:00
|
|
|
/// Class name mapping for objects (for visibility checks)
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) object_class: HashMap<ValueId, String>,
|
2025-08-21 03:42:20 +09:00
|
|
|
/// Marks ValueIds that represent internal (me/this) references within the current function
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) object_internal: std::collections::HashSet<ValueId>,
|
2025-08-18 23:36:40 +09:00
|
|
|
/// Loop executor for handling phi nodes and loop-specific logic
|
|
|
|
|
loop_executor: LoopExecutor,
|
2025-08-20 18:57:10 +09:00
|
|
|
/// Shared runtime for box creation and declarations
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) runtime: NyashRuntime,
|
2025-08-20 18:57:10 +09:00
|
|
|
/// Scope tracker for calling fini on scope exit
|
|
|
|
|
scope_tracker: ScopeTracker,
|
|
|
|
|
/// Active MIR module during execution (for function calls)
|
|
|
|
|
module: Option<MirModule>,
|
2025-08-23 03:40:17 +09:00
|
|
|
/// Instruction execution counters (by MIR opcode)
|
2025-08-26 01:42:18 +09:00
|
|
|
pub(super) instr_counter: std::collections::HashMap<&'static str, usize>,
|
2025-08-23 03:40:17 +09:00
|
|
|
/// Execution start time for optional stats
|
2025-08-26 01:42:18 +09:00
|
|
|
pub(super) exec_start: Option<Instant>,
|
2025-09-03 05:41:33 +09:00
|
|
|
/// Stats: number of BoxCall hits via VTable path
|
|
|
|
|
pub(super) boxcall_hits_vtable: u64,
|
|
|
|
|
/// Stats: number of BoxCall hits via Poly-PIC path
|
|
|
|
|
pub(super) boxcall_hits_poly_pic: u64,
|
|
|
|
|
/// Stats: number of BoxCall hits via Mono-PIC path
|
|
|
|
|
pub(super) boxcall_hits_mono_pic: u64,
|
|
|
|
|
/// Stats: number of BoxCall hits via generic fallback path
|
|
|
|
|
pub(super) boxcall_hits_generic: u64,
|
2025-08-26 20:59:37 +09:00
|
|
|
/// Mono-PIC skeleton: global hit counters keyed by (recv_type, method_id/name)
|
|
|
|
|
pub(super) boxcall_pic_hits: std::collections::HashMap<String, u32>,
|
2025-08-26 22:11:17 +09:00
|
|
|
/// Mono-PIC: cached direct targets (currently InstanceBox function name)
|
|
|
|
|
pub(super) boxcall_pic_funcname: std::collections::HashMap<String, String>,
|
2025-08-27 01:03:55 +09:00
|
|
|
/// Poly-PIC: per call-site up to 4 entries of (label, version, func_name)
|
|
|
|
|
pub(super) boxcall_poly_pic: std::collections::HashMap<String, Vec<(String, u32, String)>>,
|
2025-08-26 22:47:06 +09:00
|
|
|
/// VTable-like cache: (type, method_id, arity) → direct target (InstanceBox method)
|
|
|
|
|
pub(super) boxcall_vtable_funcname: std::collections::HashMap<String, String>,
|
2025-08-27 00:03:48 +09:00
|
|
|
/// Version map for cache invalidation: label -> version
|
|
|
|
|
pub(super) type_versions: std::collections::HashMap<String, u32>,
|
2025-08-27 03:16:57 +09:00
|
|
|
/// Optional JIT manager (Phase 10_a skeleton)
|
|
|
|
|
pub(super) jit_manager: Option<crate::jit::manager::JitManager>,
|
2025-08-20 17:58:51 +09:00
|
|
|
// Phase 9.78a: Add unified Box handling components
|
|
|
|
|
// TODO: Re-enable when interpreter refactoring is complete
|
|
|
|
|
// /// Box registry for creating all Box types
|
|
|
|
|
// box_registry: Arc<UnifiedBoxRegistry>,
|
|
|
|
|
// /// Plugin loader for external Box types
|
|
|
|
|
// #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
|
|
|
|
// plugin_loader: Option<Arc<PluginLoaderV2>>,
|
|
|
|
|
// Scope tracker for lifecycle management
|
|
|
|
|
// scope_tracker: ScopeTracker,
|
|
|
|
|
// /// Box declarations from the AST
|
|
|
|
|
// box_declarations: Arc<RwLock<HashMap<String, BoxDeclaration>>>,
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl VM {
|
2025-09-03 05:04:56 +09:00
|
|
|
pub fn runtime_ref(&self) -> &NyashRuntime { &self.runtime }
|
2025-08-27 17:06:46 +09:00
|
|
|
/// Enter a GC root region and return a guard that leaves on drop
|
|
|
|
|
pub(super) fn enter_root_region(&mut self) {
|
|
|
|
|
self.scope_tracker.enter_root_region();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pin a slice of VMValue as roots in the current region
|
|
|
|
|
pub(super) fn pin_roots<'a>(&mut self, values: impl IntoIterator<Item = &'a VMValue>) {
|
|
|
|
|
for v in values {
|
|
|
|
|
self.scope_tracker.pin_root(v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Leave current GC root region
|
|
|
|
|
pub(super) fn leave_root_region(&mut self) { self.scope_tracker.leave_root_region(); }
|
|
|
|
|
|
|
|
|
|
/// Site info for GC logs: (func, bb, pc)
|
|
|
|
|
pub(super) fn gc_site_info(&self) -> (String, i64, i64) {
|
|
|
|
|
let func = self.current_function.as_deref().unwrap_or("<none>").to_string();
|
|
|
|
|
let bb = self.frame.current_block.map(|b| b.0 as i64).unwrap_or(-1);
|
|
|
|
|
let pc = self.frame.pc as i64;
|
|
|
|
|
(func, bb, pc)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Print a simple breakdown of root VMValue kinds and top BoxRef types
|
|
|
|
|
fn gc_print_roots_breakdown(&self) {
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
let roots = self.scope_tracker.roots_snapshot();
|
|
|
|
|
let mut kinds: HashMap<&'static str, u64> = HashMap::new();
|
|
|
|
|
let mut box_types: HashMap<String, u64> = HashMap::new();
|
|
|
|
|
for v in &roots {
|
|
|
|
|
match v {
|
|
|
|
|
VMValue::Integer(_) => *kinds.entry("Integer").or_insert(0) += 1,
|
|
|
|
|
VMValue::Float(_) => *kinds.entry("Float").or_insert(0) += 1,
|
|
|
|
|
VMValue::Bool(_) => *kinds.entry("Bool").or_insert(0) += 1,
|
|
|
|
|
VMValue::String(_) => *kinds.entry("String").or_insert(0) += 1,
|
|
|
|
|
VMValue::Future(_) => *kinds.entry("Future").or_insert(0) += 1,
|
|
|
|
|
VMValue::Void => *kinds.entry("Void").or_insert(0) += 1,
|
|
|
|
|
VMValue::BoxRef(b) => {
|
|
|
|
|
let tn = b.type_name().to_string();
|
|
|
|
|
*box_types.entry(tn).or_insert(0) += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
eprintln!("[GC] roots_breakdown: kinds={:?}", kinds);
|
|
|
|
|
let mut top: Vec<(String, u64)> = box_types.into_iter().collect();
|
|
|
|
|
top.sort_by(|a, b| b.1.cmp(&a.1));
|
|
|
|
|
top.truncate(5);
|
|
|
|
|
eprintln!("[GC] roots_boxref_top5: {:?}", top);
|
|
|
|
|
}
|
|
|
|
|
fn gc_print_reachability_depth2(&self) {
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
let roots = self.scope_tracker.roots_snapshot();
|
|
|
|
|
let mut child_types: HashMap<String, u64> = HashMap::new();
|
|
|
|
|
let mut child_count = 0u64;
|
|
|
|
|
for v in &roots {
|
|
|
|
|
if let VMValue::BoxRef(b) = v {
|
|
|
|
|
if let Some(arr) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
|
|
|
if let Ok(items) = arr.items.read() {
|
|
|
|
|
for item in items.iter() {
|
|
|
|
|
let tn = item.type_name().to_string();
|
|
|
|
|
*child_types.entry(tn).or_insert(0) += 1;
|
|
|
|
|
child_count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(map) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
|
|
|
let vals = map.values();
|
|
|
|
|
if let Some(arr2) = vals.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
|
|
|
if let Ok(items) = arr2.items.read() {
|
|
|
|
|
for item in items.iter() {
|
|
|
|
|
let tn = item.type_name().to_string();
|
|
|
|
|
*child_types.entry(tn).or_insert(0) += 1;
|
|
|
|
|
child_count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let mut top: Vec<(String, u64)> = child_types.into_iter().collect();
|
|
|
|
|
top.sort_by(|a, b| b.1.cmp(&a.1));
|
|
|
|
|
top.truncate(5);
|
|
|
|
|
eprintln!("[GC] depth2_children: total={} top5={:?}", child_count, top);
|
|
|
|
|
}
|
2025-08-27 03:16:57 +09:00
|
|
|
fn jit_threshold_from_env() -> u32 {
|
|
|
|
|
std::env::var("NYASH_JIT_THRESHOLD")
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|s| s.parse::<u32>().ok())
|
|
|
|
|
.filter(|&v| v > 0)
|
|
|
|
|
.unwrap_or(64)
|
|
|
|
|
}
|
2025-08-26 05:49:23 +09:00
|
|
|
/// Helper: execute phi via LoopExecutor with previous_block-based selection (Step2 skeleton)
|
|
|
|
|
pub(super) fn loop_execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result<VMValue, VMError> {
|
2025-08-26 04:34:14 +09:00
|
|
|
if inputs.is_empty() {
|
|
|
|
|
return Err(VMError::InvalidInstruction("Phi node has no inputs".to_string()));
|
|
|
|
|
}
|
|
|
|
|
let debug_phi = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1")
|
|
|
|
|
|| std::env::var("NYASH_VM_DEBUG_PHI").ok().as_deref() == Some("1");
|
2025-08-26 05:49:23 +09:00
|
|
|
if debug_phi { eprintln!("[VM] phi-select (delegated) prev={:?} inputs={:?}", self.previous_block, inputs); }
|
|
|
|
|
// Borrow just the values storage immutably to avoid borrowing entire &self in closure
|
|
|
|
|
let values_ref = &self.values;
|
|
|
|
|
let res = self.loop_executor.execute_phi(dst, inputs, |val_id| {
|
|
|
|
|
let index = val_id.to_usize();
|
|
|
|
|
if index < values_ref.len() {
|
|
|
|
|
if let Some(ref value) = values_ref[index] {
|
|
|
|
|
Ok(value.clone())
|
|
|
|
|
} else {
|
|
|
|
|
Err(VMError::InvalidValue(format!("Value {} not set", val_id)))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err(VMError::InvalidValue(format!("Value {} out of bounds", val_id)))
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if debug_phi {
|
|
|
|
|
match &res {
|
|
|
|
|
Ok(v) => eprintln!("[VM] phi-result -> {:?}", v),
|
|
|
|
|
Err(e) => eprintln!("[VM] phi-error -> {:?}", e),
|
2025-08-26 04:34:14 +09:00
|
|
|
}
|
2025-08-26 03:26:55 +09:00
|
|
|
}
|
2025-08-26 05:49:23 +09:00
|
|
|
res
|
2025-08-26 03:26:55 +09:00
|
|
|
}
|
2025-08-13 05:59:10 +00:00
|
|
|
/// Create a new VM instance
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
2025-08-18 07:44:34 +00:00
|
|
|
values: Vec::new(),
|
2025-08-13 05:59:10 +00:00
|
|
|
current_function: None,
|
2025-08-26 04:45:19 +09:00
|
|
|
frame: ExecutionFrame::new(),
|
2025-08-18 23:36:40 +09:00
|
|
|
previous_block: None,
|
2025-08-13 10:20:37 +00:00
|
|
|
object_fields: HashMap::new(),
|
2025-08-21 03:08:13 +09:00
|
|
|
object_class: HashMap::new(),
|
2025-08-21 03:42:20 +09:00
|
|
|
object_internal: std::collections::HashSet::new(),
|
2025-08-18 23:36:40 +09:00
|
|
|
loop_executor: LoopExecutor::new(),
|
2025-08-20 18:57:10 +09:00
|
|
|
runtime: NyashRuntime::new(),
|
|
|
|
|
scope_tracker: ScopeTracker::new(),
|
|
|
|
|
module: None,
|
2025-08-23 03:40:17 +09:00
|
|
|
instr_counter: std::collections::HashMap::new(),
|
|
|
|
|
exec_start: None,
|
2025-09-03 05:41:33 +09:00
|
|
|
boxcall_hits_vtable: 0,
|
|
|
|
|
boxcall_hits_poly_pic: 0,
|
|
|
|
|
boxcall_hits_mono_pic: 0,
|
|
|
|
|
boxcall_hits_generic: 0,
|
2025-08-26 20:59:37 +09:00
|
|
|
boxcall_pic_hits: std::collections::HashMap::new(),
|
2025-08-26 22:11:17 +09:00
|
|
|
boxcall_pic_funcname: std::collections::HashMap::new(),
|
2025-08-27 01:03:55 +09:00
|
|
|
boxcall_poly_pic: std::collections::HashMap::new(),
|
2025-08-26 22:47:06 +09:00
|
|
|
boxcall_vtable_funcname: std::collections::HashMap::new(),
|
2025-08-27 00:03:48 +09:00
|
|
|
type_versions: std::collections::HashMap::new(),
|
2025-08-27 03:16:57 +09:00
|
|
|
jit_manager: Some(crate::jit::manager::JitManager::new(Self::jit_threshold_from_env())),
|
2025-08-20 17:58:51 +09:00
|
|
|
// TODO: Re-enable when interpreter refactoring is complete
|
|
|
|
|
// box_registry: Arc::new(UnifiedBoxRegistry::new()),
|
|
|
|
|
// #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
|
|
|
|
// plugin_loader: None,
|
|
|
|
|
// scope_tracker: ScopeTracker::new(),
|
|
|
|
|
// box_declarations: Arc::new(RwLock::new(HashMap::new())),
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-20 18:57:10 +09:00
|
|
|
|
|
|
|
|
/// Create a VM with an external runtime (dependency injection)
|
|
|
|
|
pub fn with_runtime(runtime: NyashRuntime) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
values: Vec::new(),
|
|
|
|
|
current_function: None,
|
2025-08-26 04:45:19 +09:00
|
|
|
frame: ExecutionFrame::new(),
|
2025-08-20 18:57:10 +09:00
|
|
|
previous_block: None,
|
|
|
|
|
object_fields: HashMap::new(),
|
2025-08-21 03:08:13 +09:00
|
|
|
object_class: HashMap::new(),
|
2025-08-21 03:42:20 +09:00
|
|
|
object_internal: std::collections::HashSet::new(),
|
2025-08-20 18:57:10 +09:00
|
|
|
loop_executor: LoopExecutor::new(),
|
|
|
|
|
runtime,
|
|
|
|
|
scope_tracker: ScopeTracker::new(),
|
|
|
|
|
module: None,
|
2025-08-23 03:40:17 +09:00
|
|
|
instr_counter: std::collections::HashMap::new(),
|
|
|
|
|
exec_start: None,
|
2025-09-03 05:41:33 +09:00
|
|
|
boxcall_hits_vtable: 0,
|
|
|
|
|
boxcall_hits_poly_pic: 0,
|
|
|
|
|
boxcall_hits_mono_pic: 0,
|
|
|
|
|
boxcall_hits_generic: 0,
|
2025-08-26 20:59:37 +09:00
|
|
|
boxcall_pic_hits: std::collections::HashMap::new(),
|
2025-08-26 22:11:17 +09:00
|
|
|
boxcall_pic_funcname: std::collections::HashMap::new(),
|
2025-08-27 01:03:55 +09:00
|
|
|
boxcall_poly_pic: std::collections::HashMap::new(),
|
2025-08-26 22:47:06 +09:00
|
|
|
boxcall_vtable_funcname: std::collections::HashMap::new(),
|
2025-08-27 00:03:48 +09:00
|
|
|
type_versions: std::collections::HashMap::new(),
|
2025-08-27 03:16:57 +09:00
|
|
|
jit_manager: Some(crate::jit::manager::JitManager::new(Self::jit_threshold_from_env())),
|
2025-08-20 18:57:10 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-13 05:59:10 +00:00
|
|
|
|
2025-08-20 17:58:51 +09:00
|
|
|
// TODO: Re-enable when interpreter refactoring is complete
|
|
|
|
|
/*
|
|
|
|
|
/// Create a new VM instance with Box registry and declarations
|
|
|
|
|
pub fn new_with_registry(
|
|
|
|
|
box_registry: Arc<UnifiedBoxRegistry>,
|
|
|
|
|
box_declarations: Arc<RwLock<HashMap<String, BoxDeclaration>>>
|
|
|
|
|
) -> Self {
|
|
|
|
|
// Implementation pending interpreter refactoring
|
|
|
|
|
unimplemented!()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Phase 9.78a: Create VM with plugin support
|
|
|
|
|
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
|
|
|
|
pub fn new_with_plugins(
|
|
|
|
|
box_registry: Arc<UnifiedBoxRegistry>,
|
|
|
|
|
plugin_loader: Arc<PluginLoaderV2>,
|
|
|
|
|
box_declarations: Arc<RwLock<HashMap<String, BoxDeclaration>>>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
// Implementation pending interpreter refactoring
|
|
|
|
|
unimplemented!()
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
/// Execute a MIR module
|
|
|
|
|
pub fn execute_module(&mut self, module: &MirModule) -> Result<Box<dyn NyashBox>, VMError> {
|
2025-08-20 18:57:10 +09:00
|
|
|
// Store module for nested calls
|
|
|
|
|
self.module = Some(module.clone());
|
2025-08-27 01:03:55 +09:00
|
|
|
// Optional: dump registry for debugging
|
|
|
|
|
if std::env::var("NYASH_REG_DUMP").ok().as_deref() == Some("1") {
|
|
|
|
|
crate::runtime::type_meta::dump_registry();
|
|
|
|
|
}
|
2025-08-23 03:40:17 +09:00
|
|
|
// Reset stats
|
|
|
|
|
self.instr_counter.clear();
|
|
|
|
|
self.exec_start = Some(Instant::now());
|
2025-08-13 05:59:10 +00:00
|
|
|
// Find main function
|
|
|
|
|
let main_function = module.get_function("main")
|
|
|
|
|
.ok_or_else(|| VMError::InvalidInstruction("No main function found".to_string()))?;
|
|
|
|
|
|
|
|
|
|
// Execute main function
|
|
|
|
|
let result = self.execute_function(main_function)?;
|
|
|
|
|
|
2025-08-23 03:40:17 +09:00
|
|
|
// Optional: print VM stats
|
|
|
|
|
self.maybe_print_stats();
|
2025-08-28 09:26:58 +09:00
|
|
|
// Optional: print concise JIT unified stats
|
|
|
|
|
self.maybe_print_jit_unified_stats();
|
2025-08-23 03:40:17 +09:00
|
|
|
|
2025-08-27 01:03:55 +09:00
|
|
|
// Optional: print cache stats summary
|
2025-09-03 05:04:56 +09:00
|
|
|
if crate::config::env::vm_pic_stats() {
|
2025-08-27 01:03:55 +09:00
|
|
|
self.print_cache_stats_summary();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-28 09:26:58 +09:00
|
|
|
// Optional: print JIT detailed summary (top functions)
|
2025-08-27 03:16:57 +09:00
|
|
|
if let Some(jm) = &self.jit_manager { jm.print_summary(); }
|
|
|
|
|
|
2025-08-27 17:06:46 +09:00
|
|
|
// Optional: GC diagnostics if enabled
|
2025-09-03 05:04:56 +09:00
|
|
|
{
|
|
|
|
|
let lvl = crate::config::env::gc_trace_level();
|
|
|
|
|
if lvl > 0 {
|
2025-08-27 17:06:46 +09:00
|
|
|
if let Some((sp, rd, wr)) = self.runtime.gc.snapshot_counters() {
|
|
|
|
|
eprintln!("[GC] counters: safepoints={} read_barriers={} write_barriers={}", sp, rd, wr);
|
|
|
|
|
}
|
|
|
|
|
let roots_total = self.scope_tracker.root_count_total();
|
|
|
|
|
let root_regions = self.scope_tracker.root_regions();
|
|
|
|
|
let field_slots: usize = self.object_fields.values().map(|m| m.len()).sum();
|
|
|
|
|
eprintln!("[GC] mock_mark: roots_total={} regions={} object_field_slots={}", roots_total, root_regions, field_slots);
|
2025-09-03 05:04:56 +09:00
|
|
|
if lvl >= 2 { self.gc_print_roots_breakdown(); }
|
|
|
|
|
if lvl >= 3 { self.gc_print_reachability_depth2(); }
|
2025-08-27 17:06:46 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
// Convert result to NyashBox
|
|
|
|
|
Ok(result.to_nyash_box())
|
|
|
|
|
}
|
2025-08-20 18:57:10 +09:00
|
|
|
|
2025-08-27 01:03:55 +09:00
|
|
|
fn print_cache_stats_summary(&self) {
|
|
|
|
|
let sites_poly = self.boxcall_poly_pic.len();
|
|
|
|
|
let entries_poly: usize = self.boxcall_poly_pic.values().map(|v| v.len()).sum();
|
|
|
|
|
let avg_entries = if sites_poly > 0 { (entries_poly as f64) / (sites_poly as f64) } else { 0.0 };
|
|
|
|
|
let sites_mono = self.boxcall_pic_funcname.len();
|
|
|
|
|
let hits_total: u64 = self.boxcall_pic_hits.values().map(|v| *v as u64).sum();
|
|
|
|
|
let vt_entries = self.boxcall_vtable_funcname.len();
|
|
|
|
|
eprintln!(
|
2025-09-03 05:41:33 +09:00
|
|
|
"[VM] PIC/VT summary: poly_sites={} avg_entries={:.2} mono_sites={} hits_total={} vt_entries={} | hits: vt={} poly={} mono={} generic={}",
|
|
|
|
|
sites_poly, avg_entries, sites_mono, hits_total, vt_entries,
|
|
|
|
|
self.boxcall_hits_vtable, self.boxcall_hits_poly_pic, self.boxcall_hits_mono_pic, self.boxcall_hits_generic
|
2025-08-27 01:03:55 +09:00
|
|
|
);
|
|
|
|
|
// Top sites by hits (up to 5)
|
|
|
|
|
let mut hits: Vec<(&String, &u32)> = self.boxcall_pic_hits.iter().collect();
|
|
|
|
|
hits.sort_by(|a, b| b.1.cmp(a.1));
|
|
|
|
|
for (i, (k, v)) in hits.into_iter().take(5).enumerate() {
|
|
|
|
|
eprintln!(" #{} {} hits={}", i+1, k, v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 18:57:10 +09:00
|
|
|
/// Call a MIR function by name with VMValue arguments
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) fn call_function_by_name(&mut self, func_name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
|
2025-08-27 17:06:46 +09:00
|
|
|
// Root region: ensure args stay rooted during nested call
|
|
|
|
|
self.enter_root_region();
|
|
|
|
|
self.pin_roots(args.iter());
|
2025-08-20 18:57:10 +09:00
|
|
|
let module_ref = self.module.as_ref().ok_or_else(|| VMError::InvalidInstruction("No active module".to_string()))?;
|
|
|
|
|
let function_ref = module_ref.get_function(func_name)
|
|
|
|
|
.ok_or_else(|| VMError::InvalidInstruction(format!("Function '{}' not found", func_name)))?;
|
|
|
|
|
// Clone function to avoid borrowing conflicts during execution
|
|
|
|
|
let function = function_ref.clone();
|
|
|
|
|
|
|
|
|
|
// Save current frame
|
|
|
|
|
let saved_values = std::mem::take(&mut self.values);
|
|
|
|
|
let saved_current_function = self.current_function.clone();
|
2025-08-26 04:45:19 +09:00
|
|
|
let saved_current_block = self.frame.current_block;
|
2025-08-20 18:57:10 +09:00
|
|
|
let saved_previous_block = self.previous_block;
|
2025-08-26 04:45:19 +09:00
|
|
|
let saved_pc = self.frame.pc;
|
|
|
|
|
let saved_last_result = self.frame.last_result;
|
2025-08-20 18:57:10 +09:00
|
|
|
|
|
|
|
|
// Bind parameters
|
|
|
|
|
for (i, param_id) in function.params.iter().enumerate() {
|
|
|
|
|
if let Some(arg) = args.get(i) {
|
|
|
|
|
self.set_value(*param_id, arg.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-21 03:42:20 +09:00
|
|
|
// Heuristic: map `me` (first param) to class name parsed from function name (e.g., User.method/N)
|
|
|
|
|
if let Some(first) = function.params.get(0) {
|
|
|
|
|
if let Some((class_part, _rest)) = func_name.split_once('.') {
|
|
|
|
|
// Record class for internal field visibility checks
|
|
|
|
|
self.object_class.insert(*first, class_part.to_string());
|
|
|
|
|
// Mark internal reference
|
|
|
|
|
self.object_internal.insert(*first);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 18:57:10 +09:00
|
|
|
// Execute the function
|
|
|
|
|
let result = self.execute_function(&function);
|
|
|
|
|
|
|
|
|
|
// Restore frame
|
|
|
|
|
self.values = saved_values;
|
|
|
|
|
self.current_function = saved_current_function;
|
2025-08-26 04:45:19 +09:00
|
|
|
self.frame.current_block = saved_current_block;
|
2025-08-20 18:57:10 +09:00
|
|
|
self.previous_block = saved_previous_block;
|
2025-08-26 04:45:19 +09:00
|
|
|
self.frame.pc = saved_pc;
|
|
|
|
|
self.frame.last_result = saved_last_result;
|
2025-08-27 17:06:46 +09:00
|
|
|
// Leave GC root region
|
|
|
|
|
self.scope_tracker.leave_root_region();
|
2025-08-20 18:57:10 +09:00
|
|
|
result
|
|
|
|
|
}
|
2025-08-13 05:59:10 +00:00
|
|
|
|
|
|
|
|
/// Execute a single function
|
|
|
|
|
fn execute_function(&mut self, function: &MirFunction) -> Result<VMValue, VMError> {
|
|
|
|
|
self.current_function = Some(function.signature.name.clone());
|
2025-08-27 03:16:57 +09:00
|
|
|
// Phase 10_a: JIT profiling (function entry)
|
|
|
|
|
if let Some(jm) = &mut self.jit_manager {
|
2025-08-29 02:05:39 +09:00
|
|
|
// Allow threshold to react to env updates (e.g., DebugConfigBox.apply at runtime)
|
|
|
|
|
if let Ok(s) = std::env::var("NYASH_JIT_THRESHOLD") {
|
|
|
|
|
if let Ok(t) = s.parse::<u32>() { if t > 0 { jm.set_threshold(t); } }
|
|
|
|
|
}
|
2025-08-27 03:16:57 +09:00
|
|
|
jm.record_entry(&function.signature.name);
|
|
|
|
|
// Try compile if hot (no-op for now, returns fake handle)
|
|
|
|
|
let _ = jm.maybe_compile(&function.signature.name, function);
|
2025-08-28 09:26:58 +09:00
|
|
|
// Record per-function lower stats captured during last JIT lower (if any)
|
|
|
|
|
// Note: The current engine encapsulates its LowerCore; expose via last_stats on a new instance as needed.
|
2025-08-27 03:16:57 +09:00
|
|
|
if jm.is_compiled(&function.signature.name) && std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") {
|
|
|
|
|
if let Some(h) = jm.handle_of(&function.signature.name) {
|
|
|
|
|
eprintln!("[JIT] dispatch would go to handle={} for {} (stub)", h, function.signature.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-13 05:59:10 +00:00
|
|
|
|
2025-08-18 23:36:40 +09:00
|
|
|
// Initialize loop executor for this function
|
|
|
|
|
self.loop_executor.initialize();
|
|
|
|
|
|
2025-08-20 18:57:10 +09:00
|
|
|
// Enter a new scope for this function
|
|
|
|
|
self.scope_tracker.push_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::push_task_scope();
|
2025-08-20 17:58:51 +09:00
|
|
|
|
2025-08-27 03:16:57 +09:00
|
|
|
// Phase 10_c: try a JIT dispatch when enabled; fallback to VM on trap/miss
|
|
|
|
|
// Prepare arguments from current frame params before borrowing jit_manager mutably
|
|
|
|
|
let args_vec: Vec<VMValue> = function
|
|
|
|
|
.params
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|pid| self.get_value(*pid).ok())
|
|
|
|
|
.collect();
|
2025-08-27 17:06:46 +09:00
|
|
|
if std::env::var("NYASH_JIT_EXEC").ok().as_deref() == Some("1") {
|
2025-08-28 09:26:58 +09:00
|
|
|
let jit_only = std::env::var("NYASH_JIT_ONLY").ok().as_deref() == Some("1");
|
2025-08-27 17:06:46 +09:00
|
|
|
// Root regionize args for JIT call
|
|
|
|
|
self.enter_root_region();
|
|
|
|
|
self.pin_roots(args_vec.iter());
|
2025-09-03 09:12:39 +09:00
|
|
|
if let Some(compiled) = self.jit_manager.as_ref().map(|jm| jm.is_compiled(&function.signature.name)) {
|
|
|
|
|
if compiled {
|
|
|
|
|
crate::runtime::host_api::set_current_vm(self as *mut _);
|
|
|
|
|
let jit_val = if let Some(jm_mut) = self.jit_manager.as_mut() { jm_mut.execute_compiled(&function.signature.name, &function.signature.return_type, &args_vec) } else { None };
|
|
|
|
|
crate::runtime::host_api::clear_current_vm();
|
|
|
|
|
if let Some(val) = jit_val {
|
2025-08-27 03:16:57 +09:00
|
|
|
// Exit scope before returning
|
2025-08-27 17:06:46 +09:00
|
|
|
self.leave_root_region();
|
2025-08-27 03:16:57 +09:00
|
|
|
self.scope_tracker.pop_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::pop_task_scope();
|
2025-08-27 03:16:57 +09:00
|
|
|
return Ok(val);
|
2025-08-27 17:06:46 +09:00
|
|
|
} else if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") ||
|
|
|
|
|
std::env::var("NYASH_JIT_TRAP_LOG").ok().as_deref() == Some("1") {
|
|
|
|
|
eprintln!("[JIT] fallback: VM path taken for {}", function.signature.name);
|
2025-08-28 09:26:58 +09:00
|
|
|
if jit_only {
|
|
|
|
|
self.leave_root_region();
|
|
|
|
|
self.scope_tracker.pop_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::pop_task_scope();
|
2025-08-28 09:26:58 +09:00
|
|
|
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT trap occurred for {}", function.signature.name)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if jit_only {
|
|
|
|
|
// Try to compile now and execute; if not possible, error out
|
2025-09-03 09:12:39 +09:00
|
|
|
if let Some(jm_mut) = self.jit_manager.as_mut() { let _ = jm_mut.maybe_compile(&function.signature.name, function); }
|
|
|
|
|
if self.jit_manager.as_ref().map(|jm| jm.is_compiled(&function.signature.name)).unwrap_or(false) {
|
|
|
|
|
crate::runtime::host_api::set_current_vm(self as *mut _);
|
|
|
|
|
let jit_val = if let Some(jm_mut) = self.jit_manager.as_mut() { jm_mut.execute_compiled(&function.signature.name, &function.signature.return_type, &args_vec) } else { None };
|
|
|
|
|
crate::runtime::host_api::clear_current_vm();
|
|
|
|
|
if let Some(val) = jit_val {
|
2025-08-28 09:26:58 +09:00
|
|
|
self.leave_root_region();
|
|
|
|
|
self.scope_tracker.pop_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::pop_task_scope();
|
2025-08-28 09:26:58 +09:00
|
|
|
return Ok(val);
|
|
|
|
|
} else {
|
|
|
|
|
self.leave_root_region();
|
|
|
|
|
self.scope_tracker.pop_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::pop_task_scope();
|
2025-08-28 09:26:58 +09:00
|
|
|
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT execution failed for {}", function.signature.name)));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.leave_root_region();
|
|
|
|
|
self.scope_tracker.pop_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::pop_task_scope();
|
2025-08-28 09:26:58 +09:00
|
|
|
return Err(VMError::InvalidInstruction(format!("JIT-only enabled but function not compiled: {}", function.signature.name)));
|
2025-08-27 03:16:57 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-27 17:06:46 +09:00
|
|
|
}
|
|
|
|
|
// Leave root region if not compiled or after fallback
|
|
|
|
|
self.leave_root_region();
|
|
|
|
|
} else {
|
|
|
|
|
if let Some(jm_mut) = &mut self.jit_manager {
|
2025-08-27 03:16:57 +09:00
|
|
|
let argc = function.params.len();
|
2025-08-27 17:06:46 +09:00
|
|
|
let _would = jm_mut.maybe_dispatch(&function.signature.name, argc);
|
2025-08-27 03:16:57 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
// Start at entry block
|
|
|
|
|
let mut current_block = function.entry_block;
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
let block = function.get_block(current_block)
|
|
|
|
|
.ok_or_else(|| VMError::InvalidBasicBlock(format!("Block {} not found", current_block)))?;
|
|
|
|
|
|
2025-08-26 04:45:19 +09:00
|
|
|
self.frame.current_block = Some(current_block);
|
2025-08-26 05:49:23 +09:00
|
|
|
self.frame.pc = 0;
|
2025-08-13 05:59:10 +00:00
|
|
|
|
|
|
|
|
let mut next_block = None;
|
|
|
|
|
let mut should_return = None;
|
|
|
|
|
|
2025-08-13 12:18:47 +00:00
|
|
|
// Execute instructions in this block (including terminator)
|
|
|
|
|
let all_instructions: Vec<_> = block.all_instructions().collect();
|
|
|
|
|
for (index, instruction) in all_instructions.iter().enumerate() {
|
2025-08-26 05:49:23 +09:00
|
|
|
self.frame.pc = index;
|
2025-08-13 05:59:10 +00:00
|
|
|
|
|
|
|
|
match self.execute_instruction(instruction)? {
|
|
|
|
|
ControlFlow::Continue => continue,
|
|
|
|
|
ControlFlow::Jump(target) => {
|
|
|
|
|
next_block = Some(target);
|
|
|
|
|
break;
|
|
|
|
|
},
|
|
|
|
|
ControlFlow::Return(value) => {
|
|
|
|
|
should_return = Some(value);
|
|
|
|
|
break;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle control flow
|
|
|
|
|
if let Some(return_value) = should_return {
|
2025-08-20 18:57:10 +09:00
|
|
|
// Exit scope before returning
|
|
|
|
|
self.scope_tracker.pop_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::pop_task_scope();
|
2025-08-13 05:59:10 +00:00
|
|
|
return Ok(return_value);
|
|
|
|
|
} else if let Some(target) = next_block {
|
2025-08-26 04:45:19 +09:00
|
|
|
// Update previous block before jumping and record transition via control_flow helper
|
|
|
|
|
control_flow::record_transition(&mut self.previous_block, &mut self.loop_executor, current_block, target).ok();
|
2025-08-13 05:59:10 +00:00
|
|
|
current_block = target;
|
|
|
|
|
} else {
|
|
|
|
|
// Block ended without terminator - this shouldn't happen in well-formed MIR
|
|
|
|
|
// but let's handle it gracefully by returning void
|
2025-08-20 18:57:10 +09:00
|
|
|
// Exit scope before returning
|
|
|
|
|
self.scope_tracker.pop_scope();
|
2025-09-02 03:41:51 +09:00
|
|
|
crate::runtime::global_hooks::pop_task_scope();
|
2025-08-13 05:59:10 +00:00
|
|
|
return Ok(VMValue::Void);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Execute a single instruction
|
|
|
|
|
fn execute_instruction(&mut self, instruction: &MirInstruction) -> Result<ControlFlow, VMError> {
|
2025-08-23 03:40:17 +09:00
|
|
|
// Record instruction for stats
|
2025-08-26 04:34:14 +09:00
|
|
|
let debug_global = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1");
|
|
|
|
|
let debug_exec = debug_global || std::env::var("NYASH_VM_DEBUG_EXEC").ok().as_deref() == Some("1");
|
|
|
|
|
if debug_exec { eprintln!("[VM] execute_instruction: {:?}", instruction); }
|
2025-08-23 03:40:17 +09:00
|
|
|
self.record_instruction(instruction);
|
2025-08-26 04:50:04 +09:00
|
|
|
super::dispatch::execute_instruction(self, instruction, debug_global)
|
2025-08-25 15:50:49 +09:00
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
2025-08-26 04:50:04 +09:00
|
|
|
|
2025-08-25 15:50:49 +09:00
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
|
|
|
|
|
/// Get a value from storage
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) fn get_value(&self, value_id: ValueId) -> Result<VMValue, VMError> {
|
2025-08-18 07:44:34 +00:00
|
|
|
let index = value_id.to_usize();
|
|
|
|
|
if index < self.values.len() {
|
|
|
|
|
if let Some(ref value) = self.values[index] {
|
|
|
|
|
Ok(value.clone())
|
|
|
|
|
} else {
|
|
|
|
|
Err(VMError::InvalidValue(format!("Value {} not set", value_id)))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err(VMError::InvalidValue(format!("Value {} out of bounds", value_id)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set a value in the VM storage
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) fn set_value(&mut self, value_id: ValueId, value: VMValue) {
|
2025-08-18 07:44:34 +00:00
|
|
|
let index = value_id.to_usize();
|
|
|
|
|
// Resize Vec if necessary
|
|
|
|
|
if index >= self.values.len() {
|
|
|
|
|
self.values.resize(index + 1, None);
|
|
|
|
|
}
|
|
|
|
|
self.values[index] = Some(value);
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-23 03:40:17 +09:00
|
|
|
|
|
|
|
|
/// Record an instruction execution for statistics
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) fn record_instruction(&mut self, instruction: &MirInstruction) {
|
2025-08-23 03:40:17 +09:00
|
|
|
let key: &'static str = match instruction {
|
|
|
|
|
MirInstruction::Const { .. } => "Const",
|
|
|
|
|
MirInstruction::BinOp { .. } => "BinOp",
|
|
|
|
|
MirInstruction::UnaryOp { .. } => "UnaryOp",
|
|
|
|
|
MirInstruction::Compare { .. } => "Compare",
|
|
|
|
|
MirInstruction::Load { .. } => "Load",
|
|
|
|
|
MirInstruction::Store { .. } => "Store",
|
|
|
|
|
MirInstruction::Call { .. } => "Call",
|
|
|
|
|
MirInstruction::BoxCall { .. } => "BoxCall",
|
|
|
|
|
MirInstruction::Branch { .. } => "Branch",
|
|
|
|
|
MirInstruction::Jump { .. } => "Jump",
|
|
|
|
|
MirInstruction::Return { .. } => "Return",
|
|
|
|
|
MirInstruction::Phi { .. } => "Phi",
|
|
|
|
|
MirInstruction::NewBox { .. } => "NewBox",
|
|
|
|
|
MirInstruction::TypeCheck { .. } => "TypeCheck",
|
|
|
|
|
MirInstruction::Cast { .. } => "Cast",
|
2025-08-23 19:27:02 +09:00
|
|
|
MirInstruction::TypeOp { .. } => "TypeOp",
|
2025-08-23 03:40:17 +09:00
|
|
|
MirInstruction::ArrayGet { .. } => "ArrayGet",
|
|
|
|
|
MirInstruction::ArraySet { .. } => "ArraySet",
|
|
|
|
|
MirInstruction::Copy { .. } => "Copy",
|
|
|
|
|
MirInstruction::Debug { .. } => "Debug",
|
|
|
|
|
MirInstruction::Print { .. } => "Print",
|
|
|
|
|
MirInstruction::Nop => "Nop",
|
|
|
|
|
MirInstruction::Throw { .. } => "Throw",
|
|
|
|
|
MirInstruction::Catch { .. } => "Catch",
|
|
|
|
|
MirInstruction::Safepoint => "Safepoint",
|
|
|
|
|
MirInstruction::RefNew { .. } => "RefNew",
|
|
|
|
|
MirInstruction::RefGet { .. } => "RefGet",
|
|
|
|
|
MirInstruction::RefSet { .. } => "RefSet",
|
|
|
|
|
MirInstruction::WeakNew { .. } => "WeakNew",
|
|
|
|
|
MirInstruction::WeakLoad { .. } => "WeakLoad",
|
|
|
|
|
MirInstruction::BarrierRead { .. } => "BarrierRead",
|
|
|
|
|
MirInstruction::BarrierWrite { .. } => "BarrierWrite",
|
2025-08-23 19:27:02 +09:00
|
|
|
MirInstruction::WeakRef { .. } => "WeakRef",
|
|
|
|
|
MirInstruction::Barrier { .. } => "Barrier",
|
2025-08-23 03:40:17 +09:00
|
|
|
MirInstruction::FutureNew { .. } => "FutureNew",
|
|
|
|
|
MirInstruction::FutureSet { .. } => "FutureSet",
|
|
|
|
|
MirInstruction::Await { .. } => "Await",
|
|
|
|
|
MirInstruction::ExternCall { .. } => "ExternCall",
|
2025-08-30 02:04:00 +09:00
|
|
|
MirInstruction::PluginInvoke { .. } => "PluginInvoke",
|
2025-08-23 03:40:17 +09:00
|
|
|
};
|
|
|
|
|
*self.instr_counter.entry(key).or_insert(0) += 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:39:08 +00:00
|
|
|
|
2025-08-20 17:58:51 +09:00
|
|
|
/// Phase 9.78a: Unified method dispatch for all Box types
|
|
|
|
|
fn call_unified_method(&self, box_value: Box<dyn NyashBox>, method: &str, args: Vec<Box<dyn NyashBox>>) -> Result<Box<dyn NyashBox>, VMError> {
|
|
|
|
|
// For now, we use the simplified method dispatch
|
|
|
|
|
// In a full implementation, this would check for InstanceBox and dispatch appropriately
|
2025-08-26 00:48:09 +09:00
|
|
|
self.call_box_method_impl(box_value, method, args)
|
2025-08-20 17:58:51 +09:00
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:39:08 +00:00
|
|
|
/// Call a method on a Box - simplified version of interpreter method dispatch
|
2025-08-26 20:30:07 +09:00
|
|
|
pub(super) fn call_box_method(&self, box_value: Box<dyn NyashBox>, method: &str, mut _args: Vec<Box<dyn NyashBox>>) -> Result<Box<dyn NyashBox>, VMError> {
|
2025-08-14 03:39:08 +00:00
|
|
|
// For now, implement basic methods for common box types
|
|
|
|
|
// This is a simplified version - real implementation would need full method dispatch
|
2025-08-23 05:27:18 +09:00
|
|
|
|
2025-08-26 20:30:07 +09:00
|
|
|
// 🌟 Universal methods pre-dispatch (non-invasive)
|
|
|
|
|
match method {
|
|
|
|
|
"toString" => {
|
|
|
|
|
if !_args.is_empty() {
|
|
|
|
|
return Ok(Box::new(StringBox::new(format!("Error: toString() expects 0 arguments, got {}", _args.len()))));
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(StringBox::new(box_value.to_string_box().value)));
|
|
|
|
|
}
|
|
|
|
|
"type" => {
|
|
|
|
|
if !_args.is_empty() {
|
|
|
|
|
return Ok(Box::new(StringBox::new(format!("Error: type() expects 0 arguments, got {}", _args.len()))));
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(StringBox::new(box_value.type_name())));
|
|
|
|
|
}
|
|
|
|
|
"equals" => {
|
|
|
|
|
if _args.len() != 1 {
|
|
|
|
|
return Ok(Box::new(StringBox::new(format!("Error: equals() expects 1 argument, got {}", _args.len()))));
|
|
|
|
|
}
|
|
|
|
|
let rhs = _args.remove(0);
|
|
|
|
|
let eq = box_value.equals(&*rhs);
|
|
|
|
|
return Ok(Box::new(eq));
|
|
|
|
|
}
|
|
|
|
|
"clone" => {
|
|
|
|
|
if !_args.is_empty() {
|
|
|
|
|
return Ok(Box::new(StringBox::new(format!("Error: clone() expects 0 arguments, got {}", _args.len()))));
|
|
|
|
|
}
|
|
|
|
|
return Ok(box_value.clone_box());
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 18:12:33 +09:00
|
|
|
// ResultBox (NyashResultBox - new)
|
2025-08-23 05:27:18 +09:00
|
|
|
if let Some(result_box) = box_value.as_any().downcast_ref::<crate::boxes::result::NyashResultBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
// Rust側の公開APIメソッド名に合わせたバリアントを許容
|
|
|
|
|
"is_ok" | "isOk" => {
|
|
|
|
|
return Ok(result_box.is_ok());
|
|
|
|
|
}
|
|
|
|
|
"get_value" | "getValue" => {
|
|
|
|
|
return Ok(result_box.get_value());
|
|
|
|
|
}
|
|
|
|
|
"get_error" | "getError" => {
|
|
|
|
|
return Ok(result_box.get_error());
|
|
|
|
|
}
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 05:49:23 +09:00
|
|
|
// Legacy box_trait::ResultBox is no longer handled here (migration complete)
|
2025-08-23 18:12:33 +09:00
|
|
|
|
2025-08-31 06:22:48 +09:00
|
|
|
// InstanceBox field access unification: getField/setField
|
|
|
|
|
if let Some(instance) = box_value.as_any().downcast_ref::<InstanceBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"getField" => {
|
|
|
|
|
if _args.len() != 1 { return Ok(Box::new(StringBox::new("getField(name) requires 1 arg"))); }
|
|
|
|
|
let name = _args[0].to_string_box().value;
|
|
|
|
|
if let Some(shared) = instance.get_field(&name) {
|
|
|
|
|
return Ok(shared.clone_box());
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(VoidBox::new()));
|
|
|
|
|
}
|
|
|
|
|
"setField" => {
|
|
|
|
|
if _args.len() != 2 { return Ok(Box::new(StringBox::new("setField(name, value) requires 2 args"))); }
|
|
|
|
|
let name = _args[0].to_string_box().value;
|
|
|
|
|
let val_arc: crate::box_trait::SharedNyashBox = std::sync::Arc::from(_args[1].clone_or_share());
|
|
|
|
|
let _ = instance.set_field(&name, val_arc);
|
|
|
|
|
return Ok(Box::new(VoidBox::new()));
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-28 09:26:58 +09:00
|
|
|
// JitStatsBox methods (process-local JIT counters)
|
|
|
|
|
if let Some(_jsb) = box_value.as_any().downcast_ref::<crate::boxes::jit_stats_box::JitStatsBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"toJson" | "toJSON" => {
|
|
|
|
|
return Ok(crate::boxes::jit_stats_box::JitStatsBox::new().to_json());
|
|
|
|
|
}
|
|
|
|
|
// Return detailed per-function stats as JSON array
|
|
|
|
|
// Each item: { name, phi_total, phi_b1, ret_bool_hint, hits, compiled, handle }
|
|
|
|
|
"perFunction" | "per_function" => {
|
|
|
|
|
if let Some(jm) = &self.jit_manager {
|
|
|
|
|
let v = jm.per_function_stats();
|
|
|
|
|
let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, phi_t, phi_b1, rb, hits, compiled, handle)| {
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"name": name,
|
|
|
|
|
"phi_total": phi_t,
|
|
|
|
|
"phi_b1": phi_b1,
|
|
|
|
|
"ret_bool_hint": rb,
|
|
|
|
|
"hits": hits,
|
|
|
|
|
"compiled": compiled,
|
|
|
|
|
"handle": handle
|
|
|
|
|
})
|
|
|
|
|
}).collect();
|
|
|
|
|
let s = serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string());
|
|
|
|
|
return Ok(Box::new(crate::box_trait::StringBox::new(s)));
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(crate::box_trait::StringBox::new("[]")));
|
|
|
|
|
}
|
|
|
|
|
"top5" => {
|
|
|
|
|
if let Some(jm) = &self.jit_manager {
|
|
|
|
|
let v = jm.top_hits(5);
|
|
|
|
|
let arr: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| {
|
|
|
|
|
serde_json::json!({
|
|
|
|
|
"name": name,
|
|
|
|
|
"hits": hits,
|
|
|
|
|
"compiled": compiled,
|
|
|
|
|
"handle": handle
|
|
|
|
|
})
|
|
|
|
|
}).collect();
|
|
|
|
|
let s = serde_json::to_string(&arr).unwrap_or_else(|_| "[]".to_string());
|
|
|
|
|
return Ok(Box::new(crate::box_trait::StringBox::new(s)));
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(crate::box_trait::StringBox::new("[]")));
|
|
|
|
|
}
|
|
|
|
|
"summary" => {
|
|
|
|
|
let cfg = crate::jit::config::current();
|
|
|
|
|
let caps = crate::jit::config::probe_capabilities();
|
|
|
|
|
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
|
|
|
|
|
let b1_norm = crate::jit::rt::b1_norm_get();
|
|
|
|
|
let ret_b1 = crate::jit::rt::ret_bool_hint_get();
|
|
|
|
|
let mut payload = serde_json::json!({
|
|
|
|
|
"abi_mode": abi_mode,
|
|
|
|
|
"abi_b1_enabled": cfg.native_bool_abi,
|
|
|
|
|
"abi_b1_supported": caps.supports_b1_sig,
|
|
|
|
|
"b1_norm_count": b1_norm,
|
|
|
|
|
"ret_bool_hint_count": ret_b1,
|
|
|
|
|
"top5": []
|
|
|
|
|
});
|
|
|
|
|
if let Some(jm) = &self.jit_manager {
|
|
|
|
|
let v = jm.top_hits(5);
|
|
|
|
|
let top5: Vec<serde_json::Value> = v.into_iter().map(|(name, hits, compiled, handle)| serde_json::json!({
|
|
|
|
|
"name": name, "hits": hits, "compiled": compiled, "handle": handle
|
|
|
|
|
})).collect();
|
|
|
|
|
if let Some(obj) = payload.as_object_mut() { obj.insert("top5".to_string(), serde_json::Value::Array(top5)); }
|
|
|
|
|
}
|
|
|
|
|
let s = serde_json::to_string_pretty(&payload).unwrap_or_else(|_| "{}".to_string());
|
|
|
|
|
return Ok(Box::new(crate::box_trait::StringBox::new(s)));
|
|
|
|
|
}
|
|
|
|
|
_ => return Ok(Box::new(crate::box_trait::VoidBox::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:39:08 +00:00
|
|
|
// StringBox methods
|
|
|
|
|
if let Some(string_box) = box_value.as_any().downcast_ref::<StringBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"length" | "len" => {
|
|
|
|
|
return Ok(Box::new(IntegerBox::new(string_box.value.len() as i64)));
|
|
|
|
|
},
|
|
|
|
|
"toString" => {
|
|
|
|
|
return Ok(Box::new(StringBox::new(string_box.value.clone())));
|
|
|
|
|
},
|
2025-08-14 03:46:06 +00:00
|
|
|
"substring" => {
|
|
|
|
|
// substring(start, end) - simplified implementation
|
|
|
|
|
if _args.len() >= 2 {
|
|
|
|
|
if let (Some(start_box), Some(end_box)) = (_args.get(0), _args.get(1)) {
|
|
|
|
|
if let (Some(start_int), Some(end_int)) = (
|
|
|
|
|
start_box.as_any().downcast_ref::<IntegerBox>(),
|
|
|
|
|
end_box.as_any().downcast_ref::<IntegerBox>()
|
|
|
|
|
) {
|
|
|
|
|
let start = start_int.value.max(0) as usize;
|
|
|
|
|
let end = end_int.value.max(0) as usize;
|
|
|
|
|
let len = string_box.value.len();
|
|
|
|
|
|
|
|
|
|
if start <= len {
|
|
|
|
|
let end_idx = end.min(len);
|
|
|
|
|
if start <= end_idx {
|
|
|
|
|
let substr = &string_box.value[start..end_idx];
|
|
|
|
|
return Ok(Box::new(StringBox::new(substr)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(StringBox::new(""))); // Return empty string on error
|
|
|
|
|
},
|
|
|
|
|
"concat" => {
|
|
|
|
|
// concat(other) - concatenate with another string
|
|
|
|
|
if let Some(other_box) = _args.get(0) {
|
|
|
|
|
let other_str = other_box.to_string_box().value;
|
|
|
|
|
let result = string_box.value.clone() + &other_str;
|
|
|
|
|
return Ok(Box::new(StringBox::new(result)));
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(StringBox::new(string_box.value.clone())));
|
|
|
|
|
},
|
2025-08-14 03:39:08 +00:00
|
|
|
_ => return Ok(Box::new(VoidBox::new())), // Unsupported method
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 18:37:38 +09:00
|
|
|
// ArrayBox methods (minimal set)
|
|
|
|
|
if let Some(array_box) = box_value.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"push" => {
|
|
|
|
|
if let Some(v) = _args.get(0) { return Ok(array_box.push(v.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: push(value) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"pop" => { return Ok(array_box.pop()); },
|
|
|
|
|
"length" | "len" => { return Ok(array_box.length()); },
|
|
|
|
|
"get" => {
|
|
|
|
|
if let Some(i) = _args.get(0) { return Ok(array_box.get(i.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: get(index) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"set" => {
|
|
|
|
|
if _args.len() >= 2 { return Ok(array_box.set(_args[0].clone_or_share(), _args[1].clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: set(index, value) requires 2 args")));
|
|
|
|
|
},
|
|
|
|
|
"remove" => {
|
|
|
|
|
if let Some(i) = _args.get(0) { return Ok(array_box.remove(i.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: remove(index) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"contains" => {
|
|
|
|
|
if let Some(v) = _args.get(0) { return Ok(array_box.contains(v.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: contains(value) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"indexOf" => {
|
|
|
|
|
if let Some(v) = _args.get(0) { return Ok(array_box.indexOf(v.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: indexOf(value) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"clear" => { return Ok(array_box.clear()); },
|
|
|
|
|
"join" => {
|
|
|
|
|
if let Some(sep) = _args.get(0) { return Ok(array_box.join(sep.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: join(sep) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"sort" => { return Ok(array_box.sort()); },
|
|
|
|
|
"reverse" => { return Ok(array_box.reverse()); },
|
|
|
|
|
"slice" => {
|
|
|
|
|
if _args.len() >= 2 { return Ok(array_box.slice(_args[0].clone_or_share(), _args[1].clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: slice(start, end) requires 2 args")));
|
|
|
|
|
},
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MapBox methods (minimal set)
|
|
|
|
|
if let Some(map_box) = box_value.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"set" => {
|
|
|
|
|
if _args.len() >= 2 { return Ok(map_box.set(_args[0].clone_or_share(), _args[1].clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: set(key, value) requires 2 args")));
|
|
|
|
|
},
|
|
|
|
|
"get" => {
|
|
|
|
|
if let Some(k) = _args.get(0) { return Ok(map_box.get(k.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: get(key) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"has" => {
|
|
|
|
|
if let Some(k) = _args.get(0) { return Ok(map_box.has(k.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: has(key) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"delete" | "remove" => {
|
|
|
|
|
if let Some(k) = _args.get(0) { return Ok(map_box.delete(k.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: delete(key) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"keys" => { return Ok(map_box.keys()); },
|
|
|
|
|
"values" => { return Ok(map_box.values()); },
|
|
|
|
|
"size" => { return Ok(map_box.size()); },
|
|
|
|
|
"clear" => { return Ok(map_box.clear()); },
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-28 22:31:51 +09:00
|
|
|
// MathBox methods (minimal set)
|
|
|
|
|
if let Some(math_box) = box_value.as_any().downcast_ref::<crate::boxes::math_box::MathBox>() {
|
|
|
|
|
// Coerce numeric-like StringBox to FloatBox for function-style lowering path
|
|
|
|
|
let mut coerce_num = |b: &Box<dyn NyashBox>| -> Box<dyn NyashBox> {
|
|
|
|
|
if let Some(sb) = b.as_any().downcast_ref::<StringBox>() {
|
|
|
|
|
let s = sb.value.trim();
|
|
|
|
|
if let Ok(f) = s.parse::<f64>() { return Box::new(crate::boxes::FloatBox::new(f)); }
|
|
|
|
|
if let Ok(i) = s.parse::<i64>() { return Box::new(IntegerBox::new(i)); }
|
|
|
|
|
}
|
|
|
|
|
b.clone_or_share()
|
|
|
|
|
};
|
|
|
|
|
match method {
|
|
|
|
|
"min" => {
|
|
|
|
|
if _args.len() >= 2 {
|
|
|
|
|
let a = coerce_num(&_args[0]);
|
|
|
|
|
let b = coerce_num(&_args[1]);
|
|
|
|
|
return Ok(math_box.min(a, b));
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: min(a,b) requires 2 args")));
|
|
|
|
|
}
|
|
|
|
|
"max" => {
|
|
|
|
|
if _args.len() >= 2 {
|
|
|
|
|
let a = coerce_num(&_args[0]);
|
|
|
|
|
let b = coerce_num(&_args[1]);
|
|
|
|
|
return Ok(math_box.max(a, b));
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: max(a,b) requires 2 args")));
|
|
|
|
|
}
|
|
|
|
|
"abs" => {
|
|
|
|
|
if let Some(v) = _args.get(0) { return Ok(math_box.abs(coerce_num(v))); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: abs(x) requires 1 arg")));
|
|
|
|
|
}
|
|
|
|
|
"sin" => {
|
|
|
|
|
if let Some(v) = _args.get(0) { return Ok(math_box.sin(coerce_num(v))); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: sin(x) requires 1 arg")));
|
|
|
|
|
}
|
|
|
|
|
"cos" => {
|
|
|
|
|
if let Some(v) = _args.get(0) { return Ok(math_box.cos(coerce_num(v))); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: cos(x) requires 1 arg")));
|
|
|
|
|
}
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 18:37:38 +09:00
|
|
|
// SocketBox methods (minimal set + timeout variants)
|
|
|
|
|
if let Some(sock) = box_value.as_any().downcast_ref::<crate::boxes::socket_box::SocketBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"bind" => {
|
|
|
|
|
if _args.len() >= 2 { return Ok(sock.bind(_args[0].clone_or_share(), _args[1].clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: bind(address, port) requires 2 args")));
|
|
|
|
|
},
|
|
|
|
|
"listen" => {
|
|
|
|
|
if let Some(b) = _args.get(0) { return Ok(sock.listen(b.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: listen(backlog) requires 1 arg")));
|
|
|
|
|
},
|
|
|
|
|
"accept" => { return Ok(sock.accept()); },
|
|
|
|
|
"acceptTimeout" | "accept_timeout" => {
|
|
|
|
|
if let Some(ms) = _args.get(0) { return Ok(sock.accept_timeout(ms.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(crate::box_trait::VoidBox::new()));
|
|
|
|
|
},
|
|
|
|
|
"connect" => {
|
|
|
|
|
if _args.len() >= 2 { return Ok(sock.connect(_args[0].clone_or_share(), _args[1].clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("Error: connect(address, port) requires 2 args")));
|
|
|
|
|
},
|
|
|
|
|
"read" => { return Ok(sock.read()); },
|
|
|
|
|
"recvTimeout" | "recv_timeout" => {
|
|
|
|
|
if let Some(ms) = _args.get(0) { return Ok(sock.recv_timeout(ms.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(StringBox::new("")));
|
|
|
|
|
},
|
|
|
|
|
"write" => {
|
|
|
|
|
if let Some(d) = _args.get(0) { return Ok(sock.write(d.clone_or_share())); }
|
|
|
|
|
return Ok(Box::new(crate::box_trait::BoolBox::new(false)));
|
|
|
|
|
},
|
|
|
|
|
"close" => { return Ok(sock.close()); },
|
|
|
|
|
"isServer" | "is_server" => { return Ok(sock.is_server()); },
|
|
|
|
|
"isConnected" | "is_connected" => { return Ok(sock.is_connected()); },
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:39:08 +00:00
|
|
|
// IntegerBox methods
|
|
|
|
|
if let Some(integer_box) = box_value.as_any().downcast_ref::<IntegerBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"toString" => {
|
|
|
|
|
return Ok(Box::new(StringBox::new(integer_box.value.to_string())));
|
|
|
|
|
},
|
|
|
|
|
"abs" => {
|
|
|
|
|
return Ok(Box::new(IntegerBox::new(integer_box.value.abs())));
|
|
|
|
|
},
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())), // Unsupported method
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BoolBox methods
|
|
|
|
|
if let Some(bool_box) = box_value.as_any().downcast_ref::<BoolBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"toString" => {
|
|
|
|
|
return Ok(Box::new(StringBox::new(bool_box.value.to_string())));
|
|
|
|
|
},
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())), // Unsupported method
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:46:06 +00:00
|
|
|
// ArrayBox methods - needed for kilo editor
|
|
|
|
|
if let Some(array_box) = box_value.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
|
|
|
match method {
|
|
|
|
|
"length" | "len" => {
|
2025-08-14 23:59:11 +00:00
|
|
|
let items = array_box.items.read().unwrap();
|
2025-08-14 03:46:06 +00:00
|
|
|
return Ok(Box::new(IntegerBox::new(items.len() as i64)));
|
|
|
|
|
},
|
|
|
|
|
"get" => {
|
|
|
|
|
// get(index) - get element at index
|
|
|
|
|
if let Some(index_box) = _args.get(0) {
|
|
|
|
|
if let Some(index_int) = index_box.as_any().downcast_ref::<IntegerBox>() {
|
2025-08-14 23:59:11 +00:00
|
|
|
let items = array_box.items.read().unwrap();
|
2025-08-14 03:46:06 +00:00
|
|
|
let index = index_int.value as usize;
|
|
|
|
|
if index < items.len() {
|
|
|
|
|
return Ok(items[index].clone_box());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Ok(Box::new(VoidBox::new())); // Return void for out of bounds
|
|
|
|
|
},
|
|
|
|
|
"set" => {
|
|
|
|
|
// set(index, value) - simplified implementation
|
|
|
|
|
// Note: This is a read-only operation in the VM for now
|
|
|
|
|
// In a real implementation, we'd need mutable access
|
|
|
|
|
return Ok(Box::new(VoidBox::new()));
|
|
|
|
|
},
|
|
|
|
|
"push" => {
|
|
|
|
|
// push(value) - simplified implementation
|
|
|
|
|
// Note: This is a read-only operation in the VM for now
|
|
|
|
|
return Ok(Box::new(VoidBox::new()));
|
|
|
|
|
},
|
|
|
|
|
"insert" => {
|
|
|
|
|
// insert(index, value) - simplified implementation
|
|
|
|
|
// Note: This is a read-only operation in the VM for now
|
|
|
|
|
return Ok(Box::new(VoidBox::new()));
|
|
|
|
|
},
|
|
|
|
|
_ => return Ok(Box::new(VoidBox::new())), // Unsupported method
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 18:12:33 +09:00
|
|
|
// PluginBoxV2 support
|
|
|
|
|
if let Some(plugin_box) = box_value.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
|
|
|
|
|
// For toString on plugins, return a descriptive string
|
|
|
|
|
if method == "toString" {
|
|
|
|
|
return Ok(Box::new(StringBox::new(format!("{}(id={})", plugin_box.box_type, plugin_box.inner.instance_id))));
|
|
|
|
|
}
|
2025-08-29 10:22:44 +09:00
|
|
|
|
|
|
|
|
// Name-based fallback: delegate to unified PluginHost to invoke by (box_type, method)
|
|
|
|
|
// This preserves existing semantics but actually performs the plugin call instead of no-op.
|
|
|
|
|
let host = crate::runtime::get_global_plugin_host();
|
|
|
|
|
if let Ok(h) = host.read() {
|
|
|
|
|
// Prepare args as NyashBox list; they are already Box<dyn NyashBox>
|
|
|
|
|
let nyash_args: Vec<Box<dyn NyashBox>> = _args.into_iter().map(|b| b).collect();
|
|
|
|
|
match h.invoke_instance_method(&plugin_box.box_type, method, plugin_box.inner.instance_id, &nyash_args) {
|
|
|
|
|
Ok(Some(ret)) => return Ok(ret),
|
|
|
|
|
Ok(None) => return Ok(Box::new(VoidBox::new())),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
eprintln!("[VM] Plugin invoke error: {}.{} -> {}", plugin_box.box_type, method, e.message());
|
|
|
|
|
return Ok(Box::new(VoidBox::new()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-23 18:12:33 +09:00
|
|
|
return Ok(Box::new(VoidBox::new()));
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 03:39:08 +00:00
|
|
|
// Default: return void for any unrecognized box type or method
|
|
|
|
|
Ok(Box::new(VoidBox::new()))
|
|
|
|
|
}
|
2025-08-13 05:59:10 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-27 17:06:46 +09:00
|
|
|
/// RAII guard for GC root regions
|
|
|
|
|
// Root region guard removed in favor of explicit enter/leave to avoid borrow conflicts
|
|
|
|
|
|
2025-08-13 05:59:10 +00:00
|
|
|
/// Control flow result from instruction execution
|
2025-08-25 15:50:49 +09:00
|
|
|
pub(super) enum ControlFlow {
|
2025-08-13 05:59:10 +00:00
|
|
|
Continue,
|
|
|
|
|
Jump(BasicBlockId),
|
|
|
|
|
Return(VMValue),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for VM {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirType, EffectMask, BasicBlock};
|
2025-08-20 23:07:16 +09:00
|
|
|
use crate::parser::NyashParser;
|
2025-08-20 19:34:09 +09:00
|
|
|
use crate::runtime::NyashRuntime;
|
|
|
|
|
use crate::core::model::BoxDeclaration as CoreBoxDecl;
|
|
|
|
|
use crate::interpreter::SharedState;
|
|
|
|
|
use crate::box_factory::user_defined::UserDefinedBoxFactory;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use std::collections::HashMap;
|
2025-08-13 05:59:10 +00:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_basic_vm_execution() {
|
|
|
|
|
let mut vm = VM::new();
|
|
|
|
|
|
|
|
|
|
// Test constant loading
|
|
|
|
|
let const_instr = MirInstruction::Const {
|
|
|
|
|
dst: ValueId(1),
|
|
|
|
|
value: ConstValue::Integer(42),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = vm.execute_instruction(&const_instr);
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
|
|
let value = vm.get_value(ValueId(1)).unwrap();
|
|
|
|
|
assert_eq!(value.as_integer().unwrap(), 42);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_binary_operations() {
|
|
|
|
|
let mut vm = VM::new();
|
|
|
|
|
|
|
|
|
|
// Load constants
|
2025-08-18 07:44:34 +00:00
|
|
|
vm.set_value(ValueId(1), VMValue::Integer(10));
|
|
|
|
|
vm.set_value(ValueId(2), VMValue::Integer(32));
|
2025-08-13 05:59:10 +00:00
|
|
|
|
|
|
|
|
// Test addition
|
|
|
|
|
let add_instr = MirInstruction::BinOp {
|
|
|
|
|
dst: ValueId(3),
|
|
|
|
|
op: BinaryOp::Add,
|
|
|
|
|
lhs: ValueId(1),
|
|
|
|
|
rhs: ValueId(2),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = vm.execute_instruction(&add_instr);
|
|
|
|
|
assert!(result.is_ok());
|
|
|
|
|
|
|
|
|
|
let value = vm.get_value(ValueId(3)).unwrap();
|
|
|
|
|
assert_eq!(value.as_integer().unwrap(), 42);
|
|
|
|
|
}
|
2025-08-20 19:34:09 +09:00
|
|
|
|
|
|
|
|
fn collect_box_declarations(ast: &crate::ast::ASTNode, runtime: &NyashRuntime) {
|
|
|
|
|
fn walk(node: &crate::ast::ASTNode, runtime: &NyashRuntime) {
|
|
|
|
|
match node {
|
|
|
|
|
crate::ast::ASTNode::Program { statements, .. } => {
|
|
|
|
|
for st in statements { walk(st, runtime); }
|
|
|
|
|
}
|
2025-08-21 03:08:13 +09:00
|
|
|
crate::ast::ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
|
2025-08-20 19:34:09 +09:00
|
|
|
let decl = CoreBoxDecl {
|
|
|
|
|
name: name.clone(),
|
|
|
|
|
fields: fields.clone(),
|
2025-08-21 03:08:13 +09:00
|
|
|
public_fields: public_fields.clone(),
|
|
|
|
|
private_fields: private_fields.clone(),
|
2025-08-20 19:34:09 +09:00
|
|
|
methods: methods.clone(),
|
|
|
|
|
constructors: constructors.clone(),
|
|
|
|
|
init_fields: init_fields.clone(),
|
|
|
|
|
weak_fields: weak_fields.clone(),
|
|
|
|
|
is_interface: *is_interface,
|
|
|
|
|
extends: extends.clone(),
|
|
|
|
|
implements: implements.clone(),
|
|
|
|
|
type_parameters: type_parameters.clone(),
|
|
|
|
|
};
|
|
|
|
|
if let Ok(mut map) = runtime.box_declarations.write() {
|
|
|
|
|
map.insert(name.clone(), decl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
walk(ast, runtime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_vm_user_box_birth_and_method() {
|
|
|
|
|
let code = r#"
|
|
|
|
|
box Person {
|
|
|
|
|
init { name }
|
|
|
|
|
birth(n) {
|
|
|
|
|
me.name = n
|
|
|
|
|
}
|
|
|
|
|
greet() {
|
|
|
|
|
return "Hello, " + me.name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Person("Alice").greet()
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
// Parse to AST
|
|
|
|
|
let ast = NyashParser::parse_from_string(code).expect("parse failed");
|
|
|
|
|
|
|
|
|
|
// Prepare runtime with user-defined declarations and factory
|
|
|
|
|
let runtime = {
|
|
|
|
|
let rt = NyashRuntime::new();
|
|
|
|
|
collect_box_declarations(&ast, &rt);
|
|
|
|
|
let mut shared = SharedState::new();
|
|
|
|
|
shared.box_declarations = rt.box_declarations.clone();
|
|
|
|
|
let udf = Arc::new(UserDefinedBoxFactory::new(shared));
|
|
|
|
|
if let Ok(mut reg) = rt.box_registry.lock() { reg.register(udf); }
|
|
|
|
|
rt
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Compile to MIR
|
|
|
|
|
let mut compiler = crate::mir::MirCompiler::new();
|
|
|
|
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
|
|
|
|
|
|
|
|
|
// Debug: Print MIR
|
|
|
|
|
println!("=== MIR Output ===");
|
|
|
|
|
let mut printer = crate::mir::MirPrinter::verbose();
|
|
|
|
|
println!("{}", printer.print_module(&compile_result.module));
|
|
|
|
|
println!("==================");
|
|
|
|
|
|
|
|
|
|
// Execute with VM
|
|
|
|
|
let mut vm = VM::with_runtime(runtime);
|
|
|
|
|
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
|
|
|
|
assert_eq!(result.to_string_box().value, "Hello, Alice");
|
|
|
|
|
}
|
2025-08-20 20:01:55 +09:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_vm_user_box_var_then_method() {
|
|
|
|
|
let code = r#"
|
|
|
|
|
box Counter {
|
|
|
|
|
init { x }
|
|
|
|
|
birth(n) { me.x = n }
|
|
|
|
|
inc() { me.x = me.x + 1 }
|
|
|
|
|
get() { return me.x }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local c
|
|
|
|
|
c = new Counter(10)
|
|
|
|
|
c.inc()
|
|
|
|
|
c.get()
|
|
|
|
|
"#;
|
|
|
|
|
let ast = NyashParser::parse_from_string(code).expect("parse failed");
|
|
|
|
|
let runtime = {
|
|
|
|
|
let rt = NyashRuntime::new();
|
|
|
|
|
collect_box_declarations(&ast, &rt);
|
|
|
|
|
let mut shared = SharedState::new();
|
|
|
|
|
shared.box_declarations = rt.box_declarations.clone();
|
|
|
|
|
let udf = Arc::new(UserDefinedBoxFactory::new(shared));
|
|
|
|
|
if let Ok(mut reg) = rt.box_registry.lock() { reg.register(udf); }
|
|
|
|
|
rt
|
|
|
|
|
};
|
|
|
|
|
let mut compiler = crate::mir::MirCompiler::new();
|
|
|
|
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
|
|
|
|
let mut vm = VM::with_runtime(runtime);
|
|
|
|
|
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
|
|
|
|
assert_eq!(result.to_string_box().value, "11");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_vm_extern_console_log() {
|
|
|
|
|
let code = r#"
|
|
|
|
|
console.log("ok")
|
|
|
|
|
"#;
|
|
|
|
|
let ast = NyashParser::parse_from_string(code).expect("parse failed");
|
|
|
|
|
let runtime = NyashRuntime::new();
|
|
|
|
|
let mut compiler = crate::mir::MirCompiler::new();
|
|
|
|
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
|
|
|
|
let mut vm = VM::with_runtime(runtime);
|
|
|
|
|
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
|
|
|
|
assert_eq!(result.to_string_box().value, "void");
|
|
|
|
|
}
|
2025-08-26 21:12:32 +09:00
|
|
|
|
2025-08-27 00:03:48 +09:00
|
|
|
#[test]
|
|
|
|
|
fn test_vm_user_box_vtable_caching_two_calls() {
|
|
|
|
|
// Defines a simple user box and calls the same method twice; ensures correctness.
|
|
|
|
|
// Builder reserves slots for user methods (from 4), so method_id should be present,
|
|
|
|
|
// enabling vtable cache population on first call and (conceptually) direct call on second.
|
|
|
|
|
let code = r#"
|
|
|
|
|
box Greeter {
|
|
|
|
|
init { name }
|
|
|
|
|
birth(n) { me.name = n }
|
|
|
|
|
greet() { return "Hi, " + me.name }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local g
|
|
|
|
|
g = new Greeter("Bob")
|
|
|
|
|
g.greet()
|
|
|
|
|
g.greet()
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
// Parse to AST
|
|
|
|
|
let ast = crate::parser::NyashParser::parse_from_string(code).expect("parse failed");
|
|
|
|
|
|
|
|
|
|
// Prepare runtime with user-defined declarations and factory
|
|
|
|
|
let runtime = {
|
|
|
|
|
let rt = crate::runtime::NyashRuntime::new();
|
|
|
|
|
// Collect box declarations into runtime so that user-defined factory works
|
|
|
|
|
fn collect_box_decls(ast: &crate::ast::ASTNode, runtime: &crate::runtime::NyashRuntime) {
|
|
|
|
|
use crate::core::model::BoxDeclaration as CoreBoxDecl;
|
|
|
|
|
fn walk(node: &crate::ast::ASTNode, runtime: &crate::runtime::NyashRuntime) {
|
|
|
|
|
match node {
|
|
|
|
|
crate::ast::ASTNode::Program { statements, .. } => for st in statements { walk(st, runtime); },
|
|
|
|
|
crate::ast::ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
|
|
|
|
|
let decl = CoreBoxDecl {
|
|
|
|
|
name: name.clone(),
|
|
|
|
|
fields: fields.clone(),
|
|
|
|
|
public_fields: public_fields.clone(),
|
|
|
|
|
private_fields: private_fields.clone(),
|
|
|
|
|
methods: methods.clone(),
|
|
|
|
|
constructors: constructors.clone(),
|
|
|
|
|
init_fields: init_fields.clone(),
|
|
|
|
|
weak_fields: weak_fields.clone(),
|
|
|
|
|
is_interface: *is_interface,
|
|
|
|
|
extends: extends.clone(),
|
|
|
|
|
implements: implements.clone(),
|
|
|
|
|
type_parameters: type_parameters.clone(),
|
|
|
|
|
};
|
|
|
|
|
if let Ok(mut map) = runtime.box_declarations.write() { map.insert(name.clone(), decl); }
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
walk(ast, runtime);
|
|
|
|
|
}
|
|
|
|
|
collect_box_decls(&ast, &rt);
|
|
|
|
|
// Register user-defined factory
|
|
|
|
|
let mut shared = crate::interpreter::SharedState::new();
|
|
|
|
|
shared.box_declarations = rt.box_declarations.clone();
|
|
|
|
|
let udf = std::sync::Arc::new(crate::box_factory::user_defined::UserDefinedBoxFactory::new(shared));
|
|
|
|
|
if let Ok(mut reg) = rt.box_registry.lock() { reg.register(udf); }
|
|
|
|
|
rt
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Compile and execute
|
|
|
|
|
let mut compiler = crate::mir::MirCompiler::new();
|
|
|
|
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
|
|
|
|
let mut vm = VM::with_runtime(runtime);
|
|
|
|
|
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
|
|
|
|
assert_eq!(result.to_string_box().value, "Hi, Bob");
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 21:12:32 +09:00
|
|
|
#[test]
|
|
|
|
|
fn test_vm_fastpath_universal_type_and_equals() {
|
|
|
|
|
// toString/type/equals/cloneのうち、typeとequalsのfast-pathを検証
|
|
|
|
|
// 1) type: (new ArrayBox()).type() == "ArrayBox"
|
|
|
|
|
let code_type = r#"
|
|
|
|
|
return (new ArrayBox()).type()
|
|
|
|
|
"#;
|
|
|
|
|
let ast = NyashParser::parse_from_string(code_type).expect("parse failed");
|
|
|
|
|
let runtime = NyashRuntime::new();
|
|
|
|
|
let mut compiler = crate::mir::MirCompiler::new();
|
|
|
|
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
|
|
|
|
let mut vm = VM::with_runtime(runtime);
|
|
|
|
|
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
|
|
|
|
assert_eq!(result.to_string_box().value, "ArrayBox");
|
|
|
|
|
|
|
|
|
|
// 2) equals: (new IntegerBox(5)).equals(5) == true
|
|
|
|
|
let code_eq = r#"
|
|
|
|
|
return (new IntegerBox(5)).equals(5)
|
|
|
|
|
"#;
|
|
|
|
|
let ast = NyashParser::parse_from_string(code_eq).expect("parse failed");
|
|
|
|
|
let runtime = NyashRuntime::new();
|
|
|
|
|
let mut compiler = crate::mir::MirCompiler::new();
|
|
|
|
|
let compile_result = compiler.compile(ast).expect("mir compile failed");
|
|
|
|
|
let mut vm = VM::with_runtime(runtime);
|
|
|
|
|
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
|
|
|
|
|
assert_eq!(result.to_string_box().value, "true");
|
|
|
|
|
}
|
2025-08-20 04:45:26 +09:00
|
|
|
}
|