diff --git a/src/backend/mod.rs b/src/backend/mod.rs index ddd60e95..0dce6e72 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -4,6 +4,7 @@ pub mod vm; pub mod vm_phi; +pub mod vm_instructions; #[cfg(feature = "wasm-backend")] pub mod wasm; diff --git a/src/backend/vm.rs b/src/backend/vm.rs index 3a674123..d2c567a8 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -96,6 +96,7 @@ impl VMValue { } } + /// Get string representation for printing pub fn to_string(&self) -> String { match self { @@ -185,15 +186,15 @@ pub struct VM { #[allow(dead_code)] last_result: Option, /// Simple field storage for objects (maps reference -> field -> value) - object_fields: HashMap>, + pub(super) object_fields: HashMap>, /// Class name mapping for objects (for visibility checks) - object_class: HashMap, + pub(super) object_class: HashMap, /// Marks ValueIds that represent internal (me/this) references within the current function - object_internal: std::collections::HashSet, + pub(super) object_internal: std::collections::HashSet, /// Loop executor for handling phi nodes and loop-specific logic loop_executor: LoopExecutor, /// Shared runtime for box creation and declarations - runtime: NyashRuntime, + pub(super) runtime: NyashRuntime, /// Scope tracker for calling fini on scope exit scope_tracker: ScopeTracker, /// Active MIR module during execution (for function calls) @@ -309,7 +310,7 @@ impl VM { } /// Call a MIR function by name with VMValue arguments - fn call_function_by_name(&mut self, func_name: &str, args: Vec) -> Result { + pub(super) fn call_function_by_name(&mut self, func_name: &str, args: Vec) -> Result { 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)))?; @@ -421,636 +422,130 @@ impl VM { fn execute_instruction(&mut self, instruction: &MirInstruction) -> Result { // Record instruction for stats self.record_instruction(instruction); + match instruction { - MirInstruction::Const { dst, value } => { - let vm_value = VMValue::from(value); - self.set_value(*dst, vm_value); - Ok(ControlFlow::Continue) - }, + // Basic operations + MirInstruction::Const { dst, value } => + self.execute_const(*dst, value), - MirInstruction::BinOp { dst, op, lhs, rhs } => { - let left = self.get_value(*lhs)?; - let right = self.get_value(*rhs)?; - let result = self.execute_binary_op(op, &left, &right)?; - self.set_value(*dst, result); - Ok(ControlFlow::Continue) - }, + MirInstruction::BinOp { dst, op, lhs, rhs } => + self.execute_binop(*dst, op, *lhs, *rhs), - MirInstruction::UnaryOp { dst, op, operand } => { - let operand_val = self.get_value(*operand)?; - let result = self.execute_unary_op(op, &operand_val)?; - self.set_value(*dst, result); - Ok(ControlFlow::Continue) - }, + MirInstruction::UnaryOp { dst, op, operand } => + self.execute_unaryop(*dst, op, *operand), - MirInstruction::Compare { dst, op, lhs, rhs } => { - let left = self.get_value(*lhs)?; - let right = self.get_value(*rhs)?; - let result = self.execute_compare_op(op, &left, &right)?; - self.set_value(*dst, VMValue::Bool(result)); - Ok(ControlFlow::Continue) - }, + MirInstruction::Compare { dst, op, lhs, rhs } => + self.execute_compare(*dst, op, *lhs, *rhs), - MirInstruction::Print { value, .. } => { - let val = self.get_value(*value)?; - println!("{}", val.to_string()); - Ok(ControlFlow::Continue) - }, - - MirInstruction::TypeOp { dst, op, value, ty } => { - match op { - crate::mir::TypeOpKind::Check => { - let v = self.get_value(*value)?; - let ok = match ty { - crate::mir::MirType::Integer => matches!(v, VMValue::Integer(_)), - crate::mir::MirType::Float => matches!(v, VMValue::Float(_)), - crate::mir::MirType::Bool => matches!(v, VMValue::Bool(_)), - crate::mir::MirType::String => matches!(v, VMValue::String(_)), - crate::mir::MirType::Void => matches!(v, VMValue::Void), - crate::mir::MirType::Box(name) => match v { - VMValue::BoxRef(ref arc) => arc.type_name() == name, - _ => false, - }, - _ => true, - }; - self.set_value(*dst, VMValue::Bool(ok)); - } - crate::mir::TypeOpKind::Cast => { - let v = self.get_value(*value)?; - let casted = match ty { - crate::mir::MirType::Integer => match v { - VMValue::Integer(_) => v, - VMValue::Float(f) => VMValue::Integer(f as i64), - other => { - return Err(VMError::TypeError(format!( - "Cannot cast {:?} to Integer", - other - ))); - } - }, - crate::mir::MirType::Float => match v { - VMValue::Float(_) => v, - VMValue::Integer(i) => VMValue::Float(i as f64), - other => { - return Err(VMError::TypeError(format!( - "Cannot cast {:?} to Float", - other - ))); - } - }, - // For other types, only allow no-op cast when already same category - crate::mir::MirType::Bool => match v { - VMValue::Bool(_) => v, - other => { - return Err(VMError::TypeError(format!( - "Cannot cast {:?} to Bool", - other - ))); - } - }, - crate::mir::MirType::String => match v { - VMValue::String(_) => v, - other => { - return Err(VMError::TypeError(format!( - "Cannot cast {:?} to String", - other - ))); - } - }, - crate::mir::MirType::Void => match v { - VMValue::Void => v, - other => { - return Err(VMError::TypeError(format!( - "Cannot cast {:?} to Void", - other - ))); - } - }, - crate::mir::MirType::Box(name) => match v { - VMValue::BoxRef(ref arc) if arc.type_name() == name => v, - other => { - return Err(VMError::TypeError(format!( - "Cannot cast {:?} to Box<{}>", - other, name - ))); - } - }, - _ => v, - }; - self.set_value(*dst, casted); - } - } - Ok(ControlFlow::Continue) - }, + // I/O operations + MirInstruction::Print { value, .. } => + self.execute_print(*value), - MirInstruction::Return { value } => { - let return_value = if let Some(val_id) = value { - let val = self.get_value(*val_id)?; - val - } else { - VMValue::Void - }; - Ok(ControlFlow::Return(return_value)) - }, + // Type operations + MirInstruction::TypeOp { dst, op, value, ty } => + self.execute_typeop(*dst, op, *value, ty), - MirInstruction::Jump { target } => { - Ok(ControlFlow::Jump(*target)) - }, + // Control flow + MirInstruction::Return { value } => + self.execute_return(*value), - MirInstruction::Branch { condition, then_bb, else_bb } => { - let cond_val = self.get_value(*condition)?; - let cond_bool = cond_val.as_bool()?; - - if cond_bool { - Ok(ControlFlow::Jump(*then_bb)) - } else { - Ok(ControlFlow::Jump(*else_bb)) - } - }, + MirInstruction::Jump { target } => + self.execute_jump(*target), - MirInstruction::Phi { dst, inputs } => { - // Create a closure that captures self immutably - let values = &self.values; - let get_value_fn = |value_id: ValueId| -> Result { - let index = value_id.to_usize(); - if index < values.len() { - if let Some(ref value) = 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))) - } - }; - - // Delegate phi node execution to loop executor - let selected_value = self.loop_executor.execute_phi( - *dst, - inputs, - get_value_fn - )?; - - self.set_value(*dst, selected_value); - Ok(ControlFlow::Continue) - }, + MirInstruction::Branch { condition, then_bb, else_bb } => + self.execute_branch(*condition, *then_bb, *else_bb), - // Missing instructions that need basic implementations - MirInstruction::Load { dst, ptr } => { - // For now, loading is the same as getting the value - let value = self.get_value(*ptr)?; - self.set_value(*dst, value); - Ok(ControlFlow::Continue) - }, + MirInstruction::Phi { dst, inputs } => + self.execute_phi(*dst, inputs), - MirInstruction::Store { value, ptr } => { - // For now, storing just updates the ptr with the value - let val = self.get_value(*value)?; - self.set_value(*ptr, val); - Ok(ControlFlow::Continue) - }, + // Memory operations + MirInstruction::Load { dst, ptr } => + self.execute_load(*dst, *ptr), - MirInstruction::Call { dst, func, args, effects: _ } => { - // Resolve function name from func value (expects Const String) - let func_val = self.get_value(*func)?; - let func_name = match func_val { - VMValue::String(s) => s, - _ => return Err(VMError::InvalidInstruction("Call expects func to be a String name".to_string())), - }; - // Gather argument VM values - let mut vm_args = Vec::new(); - for arg_id in args { - vm_args.push(self.get_value(*arg_id)?); - } - let result = self.call_function_by_name(&func_name, vm_args)?; - if let Some(dst_id) = dst { - self.set_value(*dst_id, result); - } - Ok(ControlFlow::Continue) - }, + MirInstruction::Store { value, ptr } => + self.execute_store(*value, *ptr), - MirInstruction::BoxCall { dst, box_val, method, args, effects: _ } => { - // Phase 9.78a: Unified method dispatch for all Box types - - // Get the box value - let box_vm_value = self.get_value(*box_val)?; - - // Handle BoxRef for proper method dispatch - let box_nyash = match &box_vm_value { - // Use shared handle to avoid unintended constructor calls - VMValue::BoxRef(arc_box) => arc_box.share_box(), - _ => box_vm_value.to_nyash_box(), - }; - - // Fast path: birth() for user-defined boxes is lowered to a MIR function - if method == "birth" { - if let Some(instance) = box_nyash.as_any().downcast_ref::() { - let class_name = instance.class_name.clone(); - let func_name = format!("{}.birth/{}", class_name, args.len()); - - // Prepare VMValue args: me + evaluated arguments - let mut vm_args: Vec = Vec::new(); - vm_args.push(VMValue::from_nyash_box(box_nyash.clone_or_share())); - for arg_id in args { - let arg_vm_value = self.get_value(*arg_id)?; - vm_args.push(arg_vm_value); - } - - // Call the lowered function (ignore return) - let _ = self.call_function_by_name(&func_name, vm_args)?; - - // birth returns void; only set dst if specified (rare for birth) - if let Some(dst_id) = dst { - self.set_value(*dst_id, VMValue::Void); - } - return Ok(ControlFlow::Continue); - } - } - - // Evaluate arguments - let mut arg_values: Vec> = Vec::new(); - let mut arg_vm_values: Vec = Vec::new(); - for arg_id in args { - let arg_vm_value = self.get_value(*arg_id)?; - arg_values.push(arg_vm_value.to_nyash_box()); - arg_vm_values.push(arg_vm_value); - } - self.debug_log_boxcall(&box_vm_value, method, &arg_values, "enter", None); - - // PluginBoxV2 method dispatch via BID-FFI (zero-arg minimal) - #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] - if let Some(plugin) = box_nyash.as_any().downcast_ref::() { - let loader = crate::runtime::get_global_loader_v2(); - let loader = loader.read().map_err(|_| VMError::InvalidInstruction("Plugin loader lock poisoned".into()))?; - match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) { - Ok(Some(result_box)) => { - if let Some(dst_id) = dst { - self.set_value(*dst_id, VMValue::from_nyash_box(result_box)); - } - } - Ok(None) => { - if let Some(dst_id) = dst { - self.set_value(*dst_id, VMValue::Void); - } - } - Err(_) => { - return Err(VMError::InvalidInstruction(format!("Plugin method call failed: {}", method))); - } - } - return Ok(ControlFlow::Continue); - } - - // Fast-path for common Box methods (Array/Map/String-like) - let fastpath_disabled = std::env::var("NYASH_VM_DISABLE_FASTPATH").is_ok(); - if !fastpath_disabled { - if let VMValue::BoxRef(ref arc_any) = box_vm_value { - // ArrayBox: get/set/push - if let Some(arr) = arc_any.as_any().downcast_ref::() { - match method.as_str() { - "get" => { - if let Some(arg0) = arg_values.get(0) { - let res = arr.get((*arg0).clone_or_share()); - if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } - return Ok(ControlFlow::Continue); - } - } - "set" => { - if arg_values.len() >= 2 { - let idx = (*arg_values.get(0).unwrap()).clone_or_share(); - let val = (*arg_values.get(1).unwrap()).clone_or_share(); - let _ = arr.set(idx, val); - if let Some(dst_id) = dst { let v = VMValue::Void; self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } - return Ok(ControlFlow::Continue); - } - } - "push" => { - if let Some(arg0) = arg_values.get(0) { - let res = arr.push((*arg0).clone_or_share()); - if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } - return Ok(ControlFlow::Continue); - } - } - _ => {} - } - } - // MapBox: get/set/has - if let Some(map) = arc_any.as_any().downcast_ref::() { - match method.as_str() { - "get" => { - if let Some(arg0) = arg_values.get(0) { - let res = map.get((*arg0).clone_or_share()); - if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } - return Ok(ControlFlow::Continue); - } - } - "set" => { - if arg_values.len() >= 2 { - let k = (*arg_values.get(0).unwrap()).clone_or_share(); - let vval = (*arg_values.get(1).unwrap()).clone_or_share(); - let res = map.set(k, vval); - if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } - return Ok(ControlFlow::Continue); - } - } - "has" => { - if let Some(arg0) = arg_values.get(0) { - let res = map.has((*arg0).clone_or_share()); - if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(res); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } - return Ok(ControlFlow::Continue); - } - } - _ => {} - } - } - // toString(): generic fast-path for any BoxRef (0-arg) - if method == "toString" && arg_values.is_empty() { - let res = box_nyash.to_string_box(); - if let Some(dst_id) = dst { let v = VMValue::from_nyash_box(Box::new(res)); self.debug_log_boxcall(&box_vm_value, method, &arg_values, "fastpath", Some(&v)); self.set_value(*dst_id, v); } - return Ok(ControlFlow::Continue); - } - } - } - - // Call the method - unified dispatch for all Box types - // If user-defined InstanceBox: dispatch to lowered MIR function `{Class}.{method}/{argc}` - if let Some(instance) = box_nyash.as_any().downcast_ref::() { - let class_name = instance.class_name.clone(); - let func_name = format!("{}.{}{}", class_name, method, format!("/{}", args.len())); - // Prepare VMValue args: me + evaluated arguments (use original VM args for value-level fidelity) - let mut vm_args: Vec = Vec::new(); - vm_args.push(VMValue::from_nyash_box(box_nyash.clone_or_share())); - for arg_id in args { - let arg_vm_value = self.get_value(*arg_id)?; - vm_args.push(arg_vm_value); - } - let call_result = self.call_function_by_name(&func_name, vm_args)?; - if let Some(dst_id) = dst { - self.set_value(*dst_id, call_result); - } - return Ok(ControlFlow::Continue); - } - - let result = self.call_unified_method(box_nyash, method, arg_values)?; - - // Store result if destination is specified - if let Some(dst_id) = dst { - let vm_result = VMValue::from_nyash_box(result); - self.debug_log_boxcall(&box_vm_value, method, &arg_vm_values.iter().map(|v| v.to_nyash_box()).collect::>(), "unified", Some(&vm_result)); - self.set_value(*dst_id, vm_result); - } - Ok(ControlFlow::Continue) - }, + MirInstruction::Copy { dst, src } => + self.execute_copy(*dst, *src), - MirInstruction::NewBox { dst, box_type, args } => { - // Evaluate arguments into NyashBox for unified factory - let mut nyash_args: Vec> = Vec::new(); - for arg_id in args { - let arg_value = self.get_value(*arg_id)?; - nyash_args.push(arg_value.to_nyash_box()); - } - // Create via unified registry from runtime - let registry = self.runtime.box_registry.clone(); - let created = { - let guard = registry.lock().map_err(|_| VMError::InvalidInstruction("Registry lock poisoned".into()))?; - guard.create_box(box_type, &nyash_args) - }; - match created { - Ok(b) => { - // Register for scope-based finalization (share; keep same instance) - let reg_arc = std::sync::Arc::from(b.share_box()); - self.scope_tracker.register_box(reg_arc); - // Record class name for visibility checks - self.object_class.insert(*dst, box_type.clone()); - // Store value in VM - self.set_value(*dst, VMValue::from_nyash_box(b)); - Ok(ControlFlow::Continue) - } - Err(e) => Err(VMError::InvalidInstruction(format!("NewBox failed for {}: {}", box_type, e))) - } - }, + // Complex operations + MirInstruction::Call { dst, func, args, effects: _ } => + self.execute_call(*dst, *func, args), - MirInstruction::TypeCheck { dst, value: _, expected_type: _ } => { - // For now, type checks always return true - // TODO: Implement proper type checking - self.set_value(*dst, VMValue::Bool(true)); - Ok(ControlFlow::Continue) - }, + MirInstruction::BoxCall { dst, box_val, method, args, effects: _ } => + self.execute_boxcall(*dst, *box_val, method, args), - MirInstruction::Cast { dst, value, target_type: _ } => { - // For now, casting just copies the value - // TODO: Implement proper type casting - let val = self.get_value(*value)?; - self.set_value(*dst, val); - Ok(ControlFlow::Continue) - }, + MirInstruction::NewBox { dst, box_type, args } => + self.execute_newbox(*dst, box_type, args), - MirInstruction::ArrayGet { dst, array, index } => { - // Implement ArrayBox get(index) → value - let arr_val = self.get_value(*array)?; - let idx_val = self.get_value(*index)?; - if let VMValue::BoxRef(arc) = arr_val { - if let Some(arr) = arc.as_any().downcast_ref::() { - let idx_box = idx_val.to_nyash_box(); - let got = arr.get(idx_box); - self.set_value(*dst, VMValue::from_nyash_box(got)); - return Ok(ControlFlow::Continue); - } - } - Err(VMError::TypeError("ArrayGet expects ArrayBox".to_string())) - }, + // Array operations + MirInstruction::ArrayGet { dst, array, index } => + self.execute_array_get(*dst, *array, *index), - MirInstruction::ArraySet { array, index, value } => { - // Implement ArrayBox set(index, value) - let arr_val = self.get_value(*array)?; - let idx_val = self.get_value(*index)?; - let val_val = self.get_value(*value)?; - if let VMValue::BoxRef(arc) = arr_val { - if let Some(arr) = arc.as_any().downcast_ref::() { - let idx_box = idx_val.to_nyash_box(); - let val_box = val_val.to_nyash_box(); - let _ = arr.set(idx_box, val_box); - return Ok(ControlFlow::Continue); - } - } - Err(VMError::TypeError("ArraySet expects ArrayBox".to_string())) - }, + MirInstruction::ArraySet { array, index, value } => + self.execute_array_set(*array, *index, *value), - MirInstruction::Copy { dst, src } => { - // Copy instruction - duplicate the source value - let val = self.get_value(*src)?; - self.set_value(*dst, val); - // Propagate class mapping for references (helps track `me` copies) - if let Some(class_name) = self.object_class.get(src).cloned() { - self.object_class.insert(*dst, class_name); - } - // Propagate internal marker (me/this lineage) - if self.object_internal.contains(src) { - self.object_internal.insert(*dst); - } - Ok(ControlFlow::Continue) - }, + // Reference operations + MirInstruction::RefNew { dst, box_val } => + self.execute_ref_new(*dst, *box_val), - MirInstruction::Debug { value: _, message: _ } => { - // Debug instruction - skip debug output for performance - Ok(ControlFlow::Continue) - }, + MirInstruction::RefGet { dst, reference, field } => + self.execute_ref_get(*dst, *reference, field), - MirInstruction::Nop => { - // No-op instruction - Ok(ControlFlow::Continue) - }, + MirInstruction::RefSet { reference, field, value } => + self.execute_ref_set(*reference, field, *value), - // Phase 5: Control flow & exception handling - MirInstruction::Throw { exception, effects: _ } => { - let exception_val = self.get_value(*exception)?; - // For now, convert throw to error return (simplified exception handling) - // In a full implementation, this would unwind the stack looking for catch handlers - println!("Exception thrown: {}", exception_val.to_string()); - Err(VMError::InvalidInstruction(format!("Unhandled exception: {}", exception_val.to_string()))) - }, + // Weak references + MirInstruction::WeakNew { dst, box_val } => + self.execute_weak_new(*dst, *box_val), - MirInstruction::Catch { exception_type: _, exception_value, handler_bb: _ } => { - // For now, catch is a no-op since we don't have full exception handling - // In a real implementation, this would set up exception handling metadata - self.set_value(*exception_value, VMValue::Void); - Ok(ControlFlow::Continue) - }, + MirInstruction::WeakLoad { dst, weak_ref } => + self.execute_weak_load(*dst, *weak_ref), - MirInstruction::Safepoint => { - // Safepoint is a no-op for now - // In a real implementation, this could trigger GC, debugging, etc. - Ok(ControlFlow::Continue) - }, - - // Phase 6: Box reference operations - MirInstruction::RefNew { dst, box_val } => { - // For now, a reference is just the same as the box value - // In a real implementation, this would create a proper reference - let box_value = self.get_value(*box_val)?; - self.set_value(*dst, box_value); - Ok(ControlFlow::Continue) - }, - - MirInstruction::RefGet { dst, reference, field } => { - // Visibility check (if class known and visibility declared). Skip for internal refs. - let is_internal = self.object_internal.contains(reference); - if !is_internal { - if let Some(class_name) = self.object_class.get(reference) { - if let Ok(decls) = self.runtime.box_declarations.read() { - if let Some(decl) = decls.get(class_name) { - let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); - if has_vis && !decl.public_fields.contains(field) { - return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); - } - } - } - } - } - // Get field value from object - let field_value = if let Some(fields) = self.object_fields.get(reference) { - if let Some(value) = fields.get(field) { - value.clone() - } else { - // Field not set yet, return default - VMValue::Integer(0) - } - } else { - // Object has no fields yet, return default - VMValue::Integer(0) - }; - - self.set_value(*dst, field_value); - Ok(ControlFlow::Continue) - }, - - MirInstruction::RefSet { reference, field, value } => { - // Get the value to set - let new_value = self.get_value(*value)?; - // Visibility check (Skip for internal refs; otherwise enforce public) - let is_internal = self.object_internal.contains(reference); - if !is_internal { - if let Some(class_name) = self.object_class.get(reference) { - if let Ok(decls) = self.runtime.box_declarations.read() { - if let Some(decl) = decls.get(class_name) { - let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); - if has_vis && !decl.public_fields.contains(field) { - return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); - } - } - } - } - } - - // Ensure object has field storage - if !self.object_fields.contains_key(reference) { - self.object_fields.insert(*reference, HashMap::new()); - } - - // Set the field - if let Some(fields) = self.object_fields.get_mut(reference) { - fields.insert(field.clone(), new_value); - } - - Ok(ControlFlow::Continue) - }, - - MirInstruction::WeakNew { dst, box_val } => { - // For now, a weak reference is just a copy of the value - // In a real implementation, this would create a proper weak reference - let box_value = self.get_value(*box_val)?; - self.set_value(*dst, box_value); - Ok(ControlFlow::Continue) - }, - - MirInstruction::WeakLoad { dst, weak_ref } => { - // For now, loading from weak ref is the same as getting the value - // In a real implementation, this would check if the weak ref is still valid - let weak_value = self.get_value(*weak_ref)?; - self.set_value(*dst, weak_value); - Ok(ControlFlow::Continue) - }, - - // Unified PoC ops mapped to legacy behavior + // Unified weak reference operations MirInstruction::WeakRef { dst, op, value } => { match op { crate::mir::WeakRefOp::New => { - let v = self.get_value(*value)?; - self.set_value(*dst, v); + self.execute_weak_new(*dst, *value) } crate::mir::WeakRefOp::Load => { - let v = self.get_value(*value)?; - self.set_value(*dst, v); + self.execute_weak_load(*dst, *value) } } - Ok(ControlFlow::Continue) - }, - MirInstruction::Barrier { .. } => { - // No-op - Ok(ControlFlow::Continue) - }, + } + // Barriers MirInstruction::BarrierRead { ptr: _ } => { // Memory barrier read is a no-op for now - // In a real implementation, this would ensure memory ordering Ok(ControlFlow::Continue) - }, + } MirInstruction::BarrierWrite { ptr: _ } => { // Memory barrier write is a no-op for now - // In a real implementation, this would ensure memory ordering Ok(ControlFlow::Continue) - }, + } - // Phase 7: Async/Future Operations + MirInstruction::Barrier { .. } => { + // Unified barrier is a no-op for now + Ok(ControlFlow::Continue) + } + + // Exception handling + MirInstruction::Throw { exception, effects: _ } => + self.execute_throw(*exception), + + MirInstruction::Catch { exception_type: _, exception_value, handler_bb: _ } => + self.execute_catch(*exception_value), + + // Future operations MirInstruction::FutureNew { dst, value } => { let initial_value = self.get_value(*value)?; let future = crate::boxes::future::FutureBox::new(); - // Convert VMValue to NyashBox and set it in the future let nyash_box = initial_value.to_nyash_box(); future.set_result(nyash_box); self.set_value(*dst, VMValue::Future(future)); Ok(ControlFlow::Continue) - }, + } MirInstruction::FutureSet { future, value } => { let future_val = self.get_value(*future)?; @@ -1062,56 +557,49 @@ impl VM { } else { Err(VMError::TypeError(format!("Expected Future, got {:?}", future_val))) } - }, + } - MirInstruction::Await { dst, future } => { - let future_val = self.get_value(*future)?; - - if let VMValue::Future(ref future_box) = future_val { - // This blocks until the future is ready - let result = future_box.get(); - // Convert NyashBox back to VMValue - let vm_value = VMValue::from_nyash_box(result); - self.set_value(*dst, vm_value); - Ok(ControlFlow::Continue) - } else { - Err(VMError::TypeError(format!("Expected Future, got {:?}", future_val))) - } - }, + // Special operations + MirInstruction::Await { dst, future } => + self.execute_await(*dst, *future), - // Phase 9.7: External Function Calls - MirInstruction::ExternCall { dst, iface_name, method_name, args, effects: _ } => { - // Evaluate arguments as NyashBox for loader - let mut nyash_args: Vec> = Vec::new(); - for arg_id in args { - let arg_value = self.get_value(*arg_id)?; - nyash_args.push(arg_value.to_nyash_box()); - } - // Route through plugin loader v2 (also handles env.* stubs) - let loader = crate::runtime::get_global_loader_v2(); - let loader = loader.read().map_err(|_| VMError::InvalidInstruction("Plugin loader lock poisoned".into()))?; - match loader.extern_call(iface_name, method_name, &nyash_args) { - Ok(Some(result_box)) => { - if let Some(dst_id) = dst { - self.set_value(*dst_id, VMValue::from_nyash_box(result_box)); - } - } - Ok(None) => { - if let Some(dst_id) = dst { - self.set_value(*dst_id, VMValue::Void); - } - } - Err(_) => { - return Err(VMError::InvalidInstruction(format!("ExternCall failed: {}.{}", iface_name, method_name))); - } - } + MirInstruction::ExternCall { dst, iface_name, method_name, args, effects: _ } => + self.execute_extern_call(*dst, iface_name, method_name, args), + + // Deprecated/No-op instructions + MirInstruction::TypeCheck { dst, value: _, expected_type: _ } => { + // TypeCheck is deprecated in favor of TypeOp + self.set_value(*dst, VMValue::Bool(true)); Ok(ControlFlow::Continue) - }, + } + + MirInstruction::Cast { dst, value, target_type: _ } => { + // Cast is deprecated in favor of TypeOp + let val = self.get_value(*value)?; + self.set_value(*dst, val); + Ok(ControlFlow::Continue) + } + + MirInstruction::Debug { value: _, message: _ } => { + // Debug is a no-op in release mode + Ok(ControlFlow::Continue) + } + + MirInstruction::Nop => { + // No operation + Ok(ControlFlow::Continue) + } + + MirInstruction::Safepoint => { + // Safepoint for future GC/async support + Ok(ControlFlow::Continue) + } } } + /// Get a value from storage - fn get_value(&self, value_id: ValueId) -> Result { + pub(super) fn get_value(&self, value_id: ValueId) -> Result { let index = value_id.to_usize(); if index < self.values.len() { if let Some(ref value) = self.values[index] { @@ -1125,7 +613,7 @@ impl VM { } /// Set a value in the VM storage - fn set_value(&mut self, value_id: ValueId, value: VMValue) { + pub(super) fn set_value(&mut self, value_id: ValueId, value: VMValue) { let index = value_id.to_usize(); // Resize Vec if necessary if index >= self.values.len() { @@ -1135,7 +623,7 @@ impl VM { } /// Execute binary operation - fn execute_binary_op(&self, op: &BinaryOp, left: &VMValue, right: &VMValue) -> Result { + pub(super) fn execute_binary_op(&self, op: &BinaryOp, left: &VMValue, right: &VMValue) -> Result { match (left, right) { (VMValue::Integer(l), VMValue::Integer(r)) => { let result = match op { @@ -1198,7 +686,7 @@ impl VM { } /// Execute unary operation - fn execute_unary_op(&self, op: &UnaryOp, operand: &VMValue) -> Result { + pub(super) fn execute_unary_op(&self, op: &UnaryOp, operand: &VMValue) -> Result { match (op, operand) { (UnaryOp::Neg, VMValue::Integer(i)) => Ok(VMValue::Integer(-i)), (UnaryOp::Not, VMValue::Bool(b)) => Ok(VMValue::Bool(!b)), @@ -1207,7 +695,7 @@ impl VM { } /// Execute comparison operation - fn execute_compare_op(&self, op: &CompareOp, left: &VMValue, right: &VMValue) -> Result { + pub(super) fn execute_compare_op(&self, op: &CompareOp, left: &VMValue, right: &VMValue) -> Result { match (left, right) { // Numeric mixed comparisons (Integer/Float) (VMValue::Integer(l), VMValue::Float(r)) => { @@ -1305,7 +793,7 @@ impl VM { } /// Record an instruction execution for statistics - fn record_instruction(&mut self, instruction: &MirInstruction) { + pub(super) fn record_instruction(&mut self, instruction: &MirInstruction) { let key: &'static str = match instruction { MirInstruction::Const { .. } => "Const", MirInstruction::BinOp { .. } => "BinOp", @@ -1349,7 +837,7 @@ impl VM { *self.instr_counter.entry(key).or_insert(0) += 1; } - fn debug_log_boxcall(&self, recv: &VMValue, method: &str, args: &[Box], stage: &str, result: Option<&VMValue>) { + pub(super) fn debug_log_boxcall(&self, recv: &VMValue, method: &str, args: &[Box], stage: &str, result: Option<&VMValue>) { if std::env::var("NYASH_VM_DEBUG_BOXCALL").ok().as_deref() == Some("1") { let recv_ty = match recv { VMValue::BoxRef(arc) => arc.type_name().to_string(), @@ -1428,7 +916,7 @@ impl VM { } /// Call a method on a Box - simplified version of interpreter method dispatch - fn call_box_method(&self, box_value: Box, method: &str, _args: Vec>) -> Result, VMError> { + pub(super) fn call_box_method(&self, box_value: Box, method: &str, _args: Vec>) -> Result, VMError> { // For now, implement basic methods for common box types // This is a simplified version - real implementation would need full method dispatch @@ -1705,7 +1193,7 @@ impl VM { } /// Control flow result from instruction execution -enum ControlFlow { +pub(super) enum ControlFlow { Continue, Jump(BasicBlockId), Return(VMValue), diff --git a/src/backend/vm_instructions.rs b/src/backend/vm_instructions.rs new file mode 100644 index 00000000..3ebb73e7 --- /dev/null +++ b/src/backend/vm_instructions.rs @@ -0,0 +1,488 @@ +/*! + * VM Instruction Handlers - Extracted from execute_instruction for better modularity + */ + +use crate::mir::{ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId, TypeOpKind, MirType}; +use crate::box_trait::{NyashBox, BoolBox, VoidBox}; +use crate::boxes::ArrayBox; +use std::sync::Arc; +use super::{VM, VMValue, VMError}; +use super::vm::ControlFlow; + +impl VM { + /// Execute a constant instruction + pub(super) fn execute_const(&mut self, dst: ValueId, value: &ConstValue) -> Result { + let vm_value = VMValue::from(value); + self.set_value(dst, vm_value); + Ok(ControlFlow::Continue) + } + + /// Execute a binary operation instruction + pub(super) fn execute_binop(&mut self, dst: ValueId, op: &BinaryOp, lhs: ValueId, rhs: ValueId) -> Result { + let left = self.get_value(lhs)?; + let right = self.get_value(rhs)?; + let result = self.execute_binary_op(op, &left, &right)?; + self.set_value(dst, result); + Ok(ControlFlow::Continue) + } + + /// Execute a unary operation instruction + pub(super) fn execute_unaryop(&mut self, dst: ValueId, op: &UnaryOp, operand: ValueId) -> Result { + let operand_val = self.get_value(operand)?; + let result = self.execute_unary_op(op, &operand_val)?; + self.set_value(dst, result); + Ok(ControlFlow::Continue) + } + + /// Execute a comparison instruction + pub(super) fn execute_compare(&mut self, dst: ValueId, op: &CompareOp, lhs: ValueId, rhs: ValueId) -> Result { + let left = self.get_value(lhs)?; + let right = self.get_value(rhs)?; + let result = self.execute_compare_op(op, &left, &right)?; + self.set_value(dst, VMValue::Bool(result)); + Ok(ControlFlow::Continue) + } + + /// Execute a print instruction + pub(super) fn execute_print(&self, value: ValueId) -> Result { + let val = self.get_value(value)?; + println!("{}", val.to_string()); + Ok(ControlFlow::Continue) + } + + /// Execute control flow instructions (Jump, Branch, Return) + pub(super) fn execute_jump(&self, target: BasicBlockId) -> Result { + Ok(ControlFlow::Jump(target)) + } + + pub(super) fn execute_branch(&self, condition: ValueId, then_bb: BasicBlockId, else_bb: BasicBlockId) -> Result { + let cond_val = self.get_value(condition)?; + let should_branch = match &cond_val { + VMValue::Bool(b) => *b, + VMValue::Void => false, + VMValue::Integer(i) => *i != 0, + VMValue::BoxRef(b) => { + if let Some(bool_box) = b.as_any().downcast_ref::() { + bool_box.value + } else if b.as_any().downcast_ref::().is_some() { + false + } else { + return Err(VMError::TypeError( + format!("Branch condition must be bool, void, or integer, got BoxRef({})", b.type_name()) + )); + } + } + _ => return Err(VMError::TypeError( + format!("Branch condition must be bool, void, or integer, got {:?}", cond_val) + )), + }; + + Ok(ControlFlow::Jump(if should_branch { then_bb } else { else_bb })) + } + + pub(super) fn execute_return(&self, value: Option) -> Result { + if let Some(val_id) = value { + let return_val = self.get_value(val_id)?; + Ok(ControlFlow::Return(return_val)) + } else { + Ok(ControlFlow::Return(VMValue::Void)) + } + } + + /// Execute TypeOp instruction + pub(super) fn execute_typeop(&mut self, dst: ValueId, op: &TypeOpKind, value: ValueId, ty: &MirType) -> Result { + let val = self.get_value(value)?; + + match op { + TypeOpKind::Check => { + let is_type = match (&val, ty) { + (VMValue::Integer(_), MirType::Integer) => true, + (VMValue::Float(_), MirType::Float) => true, + (VMValue::Bool(_), MirType::Bool) => true, + (VMValue::String(_), MirType::String) => true, + (VMValue::Void, MirType::Void) => true, + (VMValue::BoxRef(arc_box), MirType::Box(box_name)) => { + arc_box.type_name() == box_name + } + _ => false, + }; + self.set_value(dst, VMValue::Bool(is_type)); + Ok(ControlFlow::Continue) + } + TypeOpKind::Cast => { + let result = match (&val, ty) { + // Integer to Float + (VMValue::Integer(i), MirType::Float) => VMValue::Float(*i as f64), + // Float to Integer + (VMValue::Float(f), MirType::Integer) => VMValue::Integer(*f as i64), + // Identity casts + (VMValue::Integer(_), MirType::Integer) => val.clone(), + (VMValue::Float(_), MirType::Float) => val.clone(), + (VMValue::Bool(_), MirType::Bool) => val.clone(), + (VMValue::String(_), MirType::String) => val.clone(), + // BoxRef identity cast + (VMValue::BoxRef(arc_box), MirType::Box(box_name)) if arc_box.type_name() == box_name => { + val.clone() + } + // Invalid cast + _ => { + return Err(VMError::TypeError( + format!("Cannot cast {:?} to {:?}", val, ty) + )); + } + }; + self.set_value(dst, result); + Ok(ControlFlow::Continue) + } + } + } + + /// Execute Phi instruction + pub(super) fn execute_phi(&mut self, dst: ValueId, inputs: &[(BasicBlockId, ValueId)]) -> Result { + // For now, just use the first input since we don't track previous BB in this refactored version + // TODO: Track previous basic block for proper phi node resolution + if let Some((_, val_id)) = inputs.first() { + let value = self.get_value(*val_id)?; + self.set_value(dst, value); + Ok(ControlFlow::Continue) + } else { + Err(VMError::InvalidInstruction("Phi node has no inputs".to_string())) + } + } + + /// Execute Load/Store instructions + pub(super) fn execute_load(&mut self, dst: ValueId, ptr: ValueId) -> Result { + let loaded_value = self.get_value(ptr)?; + self.set_value(dst, loaded_value); + Ok(ControlFlow::Continue) + } + + pub(super) fn execute_store(&mut self, value: ValueId, ptr: ValueId) -> Result { + let val = self.get_value(value)?; + self.set_value(ptr, val); + Ok(ControlFlow::Continue) + } + + /// Execute Copy instruction + pub(super) fn execute_copy(&mut self, dst: ValueId, src: ValueId) -> Result { + let value = self.get_value(src)?; + let cloned = match &value { + VMValue::BoxRef(arc_box) => { + // Use clone_or_share to handle cloning properly + let cloned_box = arc_box.clone_or_share(); + VMValue::BoxRef(Arc::from(cloned_box)) + } + other => other.clone(), + }; + self.set_value(dst, cloned); + Ok(ControlFlow::Continue) + } + + /// Execute Call instruction + pub(super) fn execute_call(&mut self, dst: Option, func: ValueId, args: &[ValueId]) -> Result { + // Get the function name from the ValueId + let func_name = match self.get_value(func)? { + VMValue::String(s) => s, + _ => return Err(VMError::TypeError("Function name must be a string".to_string())), + }; + + let arg_values: Vec = args.iter() + .map(|arg| self.get_value(*arg)) + .collect::, _>>()?; + + let result = self.call_function_by_name(&func_name, arg_values)?; + + if let Some(dst_id) = dst { + self.set_value(dst_id, result); + } + + Ok(ControlFlow::Continue) + } + + /// Execute NewBox instruction + pub(super) fn execute_newbox(&mut self, dst: ValueId, box_type: &str, args: &[ValueId]) -> Result { + // Convert args to NyashBox values + let arg_values: Vec> = args.iter() + .map(|arg| { + let val = self.get_value(*arg)?; + Ok(val.to_nyash_box()) + }) + .collect::, VMError>>()?; + + // Create new box using runtime's registry + let new_box = { + let registry = self.runtime.box_registry.lock() + .map_err(|_| VMError::InvalidInstruction("Failed to lock box registry".to_string()))?; + registry.create_box(box_type, &arg_values) + .map_err(|e| VMError::InvalidInstruction(format!("Failed to create {}: {}", box_type, e)))? + }; + + self.set_value(dst, VMValue::BoxRef(Arc::from(new_box))); + Ok(ControlFlow::Continue) + } + + /// Execute ArrayGet instruction + pub(super) fn execute_array_get(&mut self, dst: ValueId, array: ValueId, index: ValueId) -> Result { + let array_val = self.get_value(array)?; + let index_val = self.get_value(index)?; + + if let VMValue::BoxRef(array_box) = &array_val { + if let Some(array) = array_box.as_any().downcast_ref::() { + // ArrayBox expects Box for index + let index_box = index_val.to_nyash_box(); + let result = array.get(index_box); + self.set_value(dst, VMValue::BoxRef(Arc::from(result))); + Ok(ControlFlow::Continue) + } else { + Err(VMError::TypeError("ArrayGet requires an ArrayBox".to_string())) + } + } else { + Err(VMError::TypeError("ArrayGet requires array and integer index".to_string())) + } + } + + /// Execute ArraySet instruction + pub(super) fn execute_array_set(&mut self, array: ValueId, index: ValueId, value: ValueId) -> Result { + let array_val = self.get_value(array)?; + let index_val = self.get_value(index)?; + let value_val = self.get_value(value)?; + + if let VMValue::BoxRef(array_box) = &array_val { + if let Some(array) = array_box.as_any().downcast_ref::() { + // ArrayBox expects Box for index + let index_box = index_val.to_nyash_box(); + let box_value = value_val.to_nyash_box(); + array.set(index_box, box_value); + Ok(ControlFlow::Continue) + } else { + Err(VMError::TypeError("ArraySet requires an ArrayBox".to_string())) + } + } else { + Err(VMError::TypeError("ArraySet requires array and integer index".to_string())) + } + } + + /// Execute RefNew instruction + pub(super) fn execute_ref_new(&mut self, dst: ValueId, box_val: ValueId) -> Result { + // For now, a reference is just the same as the box value + // In a real implementation, this would create a proper reference + let box_value = self.get_value(box_val)?; + self.set_value(dst, box_value); + Ok(ControlFlow::Continue) + } + + /// Execute RefGet instruction + pub(super) fn execute_ref_get(&mut self, dst: ValueId, reference: ValueId, field: &str) -> Result { + // Visibility check (if class known and visibility declared). Skip for internal refs. + let is_internal = self.object_internal.contains(&reference); + if !is_internal { + if let Some(class_name) = self.object_class.get(&reference) { + if let Ok(decls) = self.runtime.box_declarations.read() { + if let Some(decl) = decls.get(class_name) { + let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); + if has_vis && !decl.public_fields.iter().any(|f| f == field) { + return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + } + } + } + } + } + // Get field value from object + let field_value = if let Some(fields) = self.object_fields.get(&reference) { + if let Some(value) = fields.get(field) { + value.clone() + } else { + // Field not set yet, return default + VMValue::Integer(0) + } + } else { + // Object has no fields yet, return default + VMValue::Integer(0) + }; + + self.set_value(dst, field_value); + Ok(ControlFlow::Continue) + } + + /// Execute RefSet instruction + pub(super) fn execute_ref_set(&mut self, reference: ValueId, field: &str, value: ValueId) -> Result { + // Get the value to set + let new_value = self.get_value(value)?; + // Visibility check (Skip for internal refs; otherwise enforce public) + let is_internal = self.object_internal.contains(&reference); + if !is_internal { + if let Some(class_name) = self.object_class.get(&reference) { + if let Ok(decls) = self.runtime.box_declarations.read() { + if let Some(decl) = decls.get(class_name) { + let has_vis = !decl.public_fields.is_empty() || !decl.private_fields.is_empty(); + if has_vis && !decl.public_fields.iter().any(|f| f == field) { + return Err(VMError::TypeError(format!("Field '{}' is private in {}", field, class_name))); + } + } + } + } + } + + // Ensure object has field storage + if !self.object_fields.contains_key(&reference) { + self.object_fields.insert(reference, std::collections::HashMap::new()); + } + + // Set the field + if let Some(fields) = self.object_fields.get_mut(&reference) { + fields.insert(field.to_string(), new_value); + } + + Ok(ControlFlow::Continue) + } + + /// Execute WeakNew instruction + pub(super) fn execute_weak_new(&mut self, dst: ValueId, box_val: ValueId) -> Result { + // For now, a weak reference is just a copy of the value + // In a real implementation, this would create a proper weak reference + let box_value = self.get_value(box_val)?; + self.set_value(dst, box_value); + Ok(ControlFlow::Continue) + } + + /// Execute WeakLoad instruction + pub(super) fn execute_weak_load(&mut self, dst: ValueId, weak_ref: ValueId) -> Result { + // For now, loading from weak ref is the same as getting the value + // In a real implementation, this would check if the weak ref is still valid + let weak_value = self.get_value(weak_ref)?; + self.set_value(dst, weak_value); + Ok(ControlFlow::Continue) + } + + /// Execute BarrierRead instruction + pub(super) fn execute_barrier_read(&mut self, dst: ValueId, value: ValueId) -> Result { + // Memory barrier read is currently a simple value copy + // In a real implementation, this would ensure memory ordering + let val = self.get_value(value)?; + self.set_value(dst, val); + Ok(ControlFlow::Continue) + } + + /// Execute BarrierWrite instruction + pub(super) fn execute_barrier_write(&mut self, _value: ValueId) -> Result { + // Memory barrier write is a no-op for now + // In a real implementation, this would ensure memory ordering + Ok(ControlFlow::Continue) + } + + /// Execute Throw instruction + pub(super) fn execute_throw(&mut self, exception: ValueId) -> Result { + let exc_value = self.get_value(exception)?; + Err(VMError::InvalidInstruction(format!("Exception thrown: {:?}", exc_value))) + } + + /// Execute Catch instruction + pub(super) fn execute_catch(&mut self, exception_value: ValueId) -> Result { + // For now, catch is a no-op + // In a real implementation, this would handle exception catching + self.set_value(exception_value, VMValue::Void); + Ok(ControlFlow::Continue) + } + + /// Execute Await instruction + pub(super) fn execute_await(&mut self, dst: ValueId, future: ValueId) -> Result { + let future_val = self.get_value(future)?; + + if let VMValue::Future(ref future_box) = future_val { + // This blocks until the future is ready + let result = future_box.get(); + // Convert NyashBox back to VMValue + let vm_value = VMValue::from_nyash_box(result); + self.set_value(dst, vm_value); + Ok(ControlFlow::Continue) + } else { + Err(VMError::TypeError(format!("Expected Future, got {:?}", future_val))) + } + } + + /// Execute ExternCall instruction + pub(super) fn execute_extern_call(&mut self, dst: Option, iface_name: &str, method_name: &str, args: &[ValueId]) -> Result { + + // Evaluate arguments as NyashBox for loader + let mut nyash_args: Vec> = Vec::new(); + for arg_id in args { + let arg_value = self.get_value(*arg_id)?; + nyash_args.push(arg_value.to_nyash_box()); + } + + // Route through plugin loader v2 (also handles env.* stubs) + let loader = crate::runtime::get_global_loader_v2(); + let loader = loader.read().map_err(|_| VMError::InvalidInstruction("Plugin loader lock poisoned".into()))?; + match loader.extern_call(iface_name, method_name, &nyash_args) { + Ok(Some(result_box)) => { + if let Some(dst_id) = dst { + self.set_value(dst_id, VMValue::from_nyash_box(result_box)); + } + } + Ok(None) => { + if let Some(dst_id) = dst { + self.set_value(dst_id, VMValue::Void); + } + } + Err(_) => { + return Err(VMError::InvalidInstruction(format!("ExternCall failed: {}.{}", iface_name, method_name))); + } + } + Ok(ControlFlow::Continue) + } + + /// Execute BoxCall instruction + pub(super) fn execute_boxcall(&mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId]) -> Result { + let recv = self.get_value(box_val)?; + + // Debug logging if enabled + let debug_boxcall = std::env::var("NYASH_VM_DEBUG_BOXCALL").is_ok(); + + // Convert args to NyashBox + let nyash_args: Vec> = args.iter() + .map(|arg| { + let val = self.get_value(*arg)?; + Ok(val.to_nyash_box()) + }) + .collect::, VMError>>()?; + + if debug_boxcall { + self.debug_log_boxcall(&recv, method, &nyash_args, "START", None); + } + + // Call the method based on receiver type + let result = match &recv { + VMValue::BoxRef(arc_box) => { + // Direct box method call + if debug_boxcall { + eprintln!("[BoxCall] Taking BoxRef path for method '{}'", method); + } + + let cloned_box = arc_box.share_box(); + self.call_box_method(cloned_box, method, nyash_args)? + } + _ => { + // Convert primitive to box and call + if debug_boxcall { + eprintln!("[BoxCall] Converting primitive to box for method '{}'", method); + } + + let box_value = recv.to_nyash_box(); + self.call_box_method(box_value, method, nyash_args)? + } + }; + + // Convert result back to VMValue + let result_val = VMValue::from_nyash_box(result); + + if debug_boxcall { + self.debug_log_boxcall(&recv, method, &[], "END", Some(&result_val)); + } + + if let Some(dst_id) = dst { + self.set_value(dst_id, result_val); + } + + Ok(ControlFlow::Continue) + } +} \ No newline at end of file