diff --git a/src/backend/dispatch.rs b/src/backend/dispatch.rs index d4f085a6..e3c44f90 100644 --- a/src/backend/dispatch.rs +++ b/src/backend/dispatch.rs @@ -68,6 +68,7 @@ pub(super) fn execute_instruction(vm: &mut VM, instruction: &MirInstruction, deb // Complex operations MirInstruction::Call { dst, func, args, effects: _ } => vm.execute_call(*dst, *func, args), MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects: _ , .. } => vm.execute_boxcall(*dst, *box_val, method, *method_id, args), + MirInstruction::PluginInvoke { dst, box_val, method, args, effects: _ } => vm.execute_plugin_invoke(*dst, *box_val, method, args), MirInstruction::NewBox { dst, box_type, args } => vm.execute_newbox(*dst, box_type, args), // Array operations diff --git a/src/backend/vm.rs b/src/backend/vm.rs index cf61c820..51f49983 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -767,6 +767,7 @@ impl VM { MirInstruction::FutureSet { .. } => "FutureSet", MirInstruction::Await { .. } => "Await", MirInstruction::ExternCall { .. } => "ExternCall", + MirInstruction::PluginInvoke { .. } => "PluginInvoke", }; *self.instr_counter.entry(key).or_insert(0) += 1; } diff --git a/src/backend/vm_instructions.rs b/src/backend/vm_instructions.rs index 6b2e9a2d..b1f1231d 100644 --- a/src/backend/vm_instructions.rs +++ b/src/backend/vm_instructions.rs @@ -983,6 +983,79 @@ impl VM { Ok(ControlFlow::Continue) } + + /// Execute a forced plugin invocation (no builtin fallback) + pub(super) fn execute_plugin_invoke(&mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId]) -> Result { + let recv = self.get_value(box_val)?; + // Only allowed on plugin boxes + if let VMValue::BoxRef(pbox) = &recv { + if let Some(p) = pbox.as_any().downcast_ref::() { + // Resolve method_id via unified host + let host = crate::runtime::get_global_plugin_host(); + let host = host.read().unwrap(); + let mh = host.resolve_method(&p.box_type, method) + .map_err(|_| VMError::InvalidInstruction(format!("Plugin method not found: {}.{}", p.box_type, method)))?; + + // Encode args to TLV + let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16); + for a in args.iter() { + let v = self.get_value(*a)?; + match v { + VMValue::Integer(n) => crate::runtime::plugin_ffi_common::encode::i64(&mut tlv, n), + VMValue::Float(x) => crate::runtime::plugin_ffi_common::encode::f64(&mut tlv, x), + VMValue::Bool(b) => crate::runtime::plugin_ffi_common::encode::bool(&mut tlv, b), + VMValue::String(ref s) => crate::runtime::plugin_ffi_common::encode::string(&mut tlv, s), + VMValue::BoxRef(ref b) => { + if let Some(h) = b.as_any().downcast_ref::() { + crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut tlv, h.inner.type_id, h.inner.instance_id); + } else { + // Best effort: stringify non-plugin boxes + let s = b.to_string_box().value; + crate::runtime::plugin_ffi_common::encode::string(&mut tlv, &s); + } + } + VMValue::Void => crate::runtime::plugin_ffi_common::encode::string(&mut tlv, "void"), + _ => { + return Err(VMError::TypeError(format!("Unsupported VMValue in PluginInvoke args: {:?}", v))); + } + } + } + + let mut out = vec![0u8; 4096]; + let mut out_len: usize = out.len(); + let code = unsafe { + (p.inner.invoke_fn)( + p.inner.type_id, + mh.method_id, + p.inner.instance_id, + tlv.as_ptr(), + tlv.len(), + out.as_mut_ptr(), + &mut out_len, + ) + }; + if code != 0 { + return Err(VMError::InvalidInstruction(format!("PluginInvoke failed: {}.{} rc={}", p.box_type, method, code))); + } + let vm_out = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + match tag { + 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void), + 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), + 3 => { // I64 + if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(&payload[0..8]); VMValue::Integer(i64::from_le_bytes(b)) } else { VMValue::Void } + } + 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void), + 6 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), + 8 => VMValue::Void, + _ => VMValue::Void, + } + } else { VMValue::Void }; + if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); } + return Ok(ControlFlow::Continue); + } + } + Err(VMError::InvalidInstruction(format!("PluginInvoke requires PluginBox receiver; got {:?}", recv))) + } } impl VM { diff --git a/src/mir/builder.rs b/src/mir/builder.rs index b40d2826..d3b4e5bd 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -69,6 +69,19 @@ pub struct MirBuilder { } impl MirBuilder { + /// Emit a Box method call (PluginInvoke unified) + fn emit_box_or_plugin_call( + &mut self, + dst: Option, + box_val: ValueId, + method: String, + method_id: Option, + args: Vec, + effects: EffectMask, + ) -> Result<(), String> { + let _ = method_id; // slot is no longer used in unified plugin invoke path + self.emit_instruction(MirInstruction::PluginInvoke { dst, box_val, method, args, effects }) + } /// Create a new MIR builder pub fn new() -> Self { Self { @@ -621,24 +634,24 @@ impl MirBuilder { self.value_origin_newbox.insert(math_recv, "MathBox".to_string()); // birth() let birt_mid = resolve_slot_by_type_name("MathBox", "birth"); - self.emit_instruction(MirInstruction::BoxCall { - dst: None, - box_val: math_recv, - method: "birth".to_string(), - method_id: birt_mid, - args: vec![], - effects: EffectMask::READ.add(Effect::ReadHeap), - })?; + self.emit_box_or_plugin_call( + None, + math_recv, + "birth".to_string(), + birt_mid, + vec![], + EffectMask::READ.add(Effect::ReadHeap), + )?; let method_id = resolve_slot_by_type_name("MathBox", &name); - self.emit_instruction(MirInstruction::BoxCall { - dst: Some(dst), - box_val: math_recv, - method: name, + self.emit_box_or_plugin_call( + Some(dst), + math_recv, + name, method_id, - args: math_args, - effects: EffectMask::READ.add(Effect::ReadHeap), - })?; + math_args, + EffectMask::READ.add(Effect::ReadHeap), + )?; // math.* returns Float self.value_types.insert(dst, super::MirType::Float); return Ok(dst); @@ -658,14 +671,14 @@ impl MirBuilder { .get(&box_val) .and_then(|class_name| resolve_slot_by_type_name(class_name, &name)); - self.emit_instruction(MirInstruction::BoxCall { - dst: Some(dst), + self.emit_box_or_plugin_call( + Some(dst), box_val, - method: name, + name, method_id, - args: arg_values, - effects: EffectMask::PURE.add(Effect::ReadHeap), // Conservative default - })?; + arg_values, + EffectMask::PURE.add(Effect::ReadHeap), + )?; Ok(dst) } @@ -1159,14 +1172,14 @@ impl MirBuilder { // Immediately call birth(...) on the created instance to run constructor semantics. // birth typically returns void; we don't capture the result here (dst: None) let birt_mid = resolve_slot_by_type_name(&class, "birth"); - self.emit_instruction(MirInstruction::BoxCall { - dst: None, - box_val: dst, - method: "birth".to_string(), - method_id: birt_mid, - args: arg_values, - effects: EffectMask::READ.add(Effect::ReadHeap), - })?; + self.emit_box_or_plugin_call( + None, + dst, + "birth".to_string(), + birt_mid, + arg_values, + EffectMask::READ.add(Effect::ReadHeap), + )?; Ok(dst) } @@ -1492,14 +1505,14 @@ impl MirBuilder { let mid = maybe_class .as_ref() .and_then(|cls| resolve_slot_by_type_name(cls, &method)); - self.emit_instruction(MirInstruction::BoxCall { - dst: Some(result_id), - box_val: object_value, + self.emit_box_or_plugin_call( + Some(result_id), + object_value, method, - method_id: mid, - args: arg_values, - effects: EffectMask::READ.add(Effect::ReadHeap), // Method calls may have side effects - })?; + mid, + arg_values, + EffectMask::READ.add(Effect::ReadHeap), + )?; Ok(result_id) } @@ -1549,15 +1562,15 @@ impl MirBuilder { // Create result value let result_id = self.value_gen.next(); - // Emit a BoxCall instruction for delegation - self.emit_instruction(MirInstruction::BoxCall { - dst: Some(result_id), - box_val: parent_value, + // Emit a PluginInvoke instruction for delegation + self.emit_box_or_plugin_call( + Some(result_id), + parent_value, method, - method_id: None, - args: arg_values, - effects: EffectMask::READ.add(Effect::ReadHeap), - })?; + None, + arg_values, + EffectMask::READ.add(Effect::ReadHeap), + )?; Ok(result_id) } diff --git a/src/mir/builder_modularized/expressions.rs b/src/mir/builder_modularized/expressions.rs index c4a70409..540d232c 100644 --- a/src/mir/builder_modularized/expressions.rs +++ b/src/mir/builder_modularized/expressions.rs @@ -375,13 +375,13 @@ impl MirBuilder { } } - // Fallback: Emit a BoxCall instruction for regular or plugin/builtin method calls - self.emit_instruction(MirInstruction::BoxCall { + // Fallback: Emit a PluginInvoke instruction for regular method calls + self.emit_instruction(MirInstruction::PluginInvoke { dst: Some(result_id), box_val: object_value, method, args: arg_values, - effects: EffectMask::READ.add(Effect::ReadHeap), // Method calls may have side effects + effects: EffectMask::READ.add(Effect::ReadHeap), })?; Ok(result_id) } @@ -567,9 +567,8 @@ impl MirBuilder { // Record origin for optimization: dst was created by NewBox of class self.value_origin_newbox.insert(dst, class); - // Immediately call birth(...) on the created instance to run constructor semantics. - // birth typically returns void; we don't capture the result here (dst: None) - self.emit_instruction(MirInstruction::BoxCall { + // Immediately call birth(...) (plugin invoke) on the created instance + self.emit_instruction(MirInstruction::PluginInvoke { dst: None, box_val: dst, method: "birth".to_string(), diff --git a/src/mir/instruction.rs b/src/mir/instruction.rs index 5aedceda..1190d633 100644 --- a/src/mir/instruction.rs +++ b/src/mir/instruction.rs @@ -84,6 +84,16 @@ pub enum MirInstruction { args: Vec, effects: EffectMask, }, + + /// Plugin invocation (forces plugin path; no builtin fallback) + /// `%dst = plugin_invoke %box.method(%args...)` + PluginInvoke { + dst: Option, + box_val: ValueId, + method: String, + args: Vec, + effects: EffectMask, + }, // === Control Flow === /// Conditional branch @@ -397,7 +407,8 @@ impl MirInstruction { // Function calls use provided effect mask MirInstruction::Call { effects, .. } | - MirInstruction::BoxCall { effects, .. } => *effects, + MirInstruction::BoxCall { effects, .. } | + MirInstruction::PluginInvoke { effects, .. } => *effects, // Control flow (pure but affects execution) MirInstruction::Branch { .. } | @@ -471,6 +482,7 @@ impl MirInstruction { MirInstruction::Call { dst, .. } | MirInstruction::BoxCall { dst, .. } | + MirInstruction::PluginInvoke { dst, .. } | MirInstruction::ExternCall { dst, .. } => *dst, MirInstruction::Store { .. } | @@ -529,7 +541,7 @@ impl MirInstruction { used }, - MirInstruction::BoxCall { box_val, args, .. } => { + MirInstruction::BoxCall { box_val, args, .. } | MirInstruction::PluginInvoke { box_val, args, .. } => { let mut used = vec![*box_val]; used.extend(args); used @@ -644,6 +656,15 @@ impl fmt::Display for MirInstruction { effects) } }, + MirInstruction::PluginInvoke { dst, box_val, method, args, effects: _ } => { + if let Some(dst) = dst { + write!(f, "{} = plugin_invoke {}.{}({})", dst, box_val, method, + args.iter().map(|v| format!("{}", v)).collect::>().join(", ")) + } else { + write!(f, "plugin_invoke {}.{}({})", box_val, method, + args.iter().map(|v| format!("{}", v)).collect::>().join(", ")) + } + }, MirInstruction::Return { value } => { if let Some(value) = value { write!(f, "ret {}", value) diff --git a/src/mir/optimizer.rs b/src/mir/optimizer.rs index 4fa46eee..e933e4ae 100644 --- a/src/mir/optimizer.rs +++ b/src/mir/optimizer.rs @@ -43,6 +43,12 @@ impl MirOptimizer { // Pass 0: Normalize legacy instructions to unified forms (TypeOp/WeakRef/Barrier) stats.merge(self.normalize_legacy_instructions(module)); + // Option: Force BoxCall → PluginInvoke (env) + if std::env::var("NYASH_MIR_PLUGIN_INVOKE").ok().as_deref() == Some("1") + || std::env::var("NYASH_PLUGIN_ONLY").ok().as_deref() == Some("1") { + stats.merge(self.force_plugin_invoke(module)); + } + // Pass 1: Dead code elimination stats.merge(self.eliminate_dead_code(module)); @@ -304,6 +310,22 @@ impl MirOptimizer { } impl MirOptimizer { + /// Rewrite all BoxCall to PluginInvoke to force plugin path (no builtin fallback) + fn force_plugin_invoke(&mut self, module: &mut MirModule) -> OptimizationStats { + use super::MirInstruction as I; + let mut stats = OptimizationStats::new(); + for (_fname, function) in &mut module.functions { + for (_bb, block) in &mut function.blocks { + for inst in &mut block.instructions { + if let I::BoxCall { dst, box_val, method, args, effects, .. } = inst.clone() { + *inst = I::PluginInvoke { dst, box_val, method, args, effects }; + stats.intrinsic_optimizations += 1; + } + } + } + } + stats + } /// Normalize legacy instructions into unified MIR26 forms. /// - TypeCheck/Cast → TypeOp(Check/Cast) /// - WeakNew/WeakLoad → WeakRef(New/Load) diff --git a/src/mir/printer.rs b/src/mir/printer.rs index 81b6a7c7..0c7d2644 100644 --- a/src/mir/printer.rs +++ b/src/mir/printer.rs @@ -324,6 +324,17 @@ impl MirPrinter { format!("call {}.{}{}({})", box_val, method, id_suffix, args_str) } }, + MirInstruction::PluginInvoke { dst, box_val, method, args, effects: _ } => { + let args_str = args.iter() + .map(|v| format!("{}", v)) + .collect::>() + .join(", "); + if let Some(dst) = dst { + format!("{} = plugin_invoke {}.{}({})", dst, box_val, method, args_str) + } else { + format!("plugin_invoke {}.{}({})", box_val, method, args_str) + } + }, MirInstruction::Branch { condition, then_bb, else_bb } => { format!("br {}, label {}, label {}", condition, then_bb, else_bb) diff --git a/tools/smoke_plugins.sh b/tools/smoke_plugins.sh index 1f66547b..5448cf35 100644 --- a/tools/smoke_plugins.sh +++ b/tools/smoke_plugins.sh @@ -27,6 +27,7 @@ export NYASH_CLI_VERBOSE=1 export NYASH_PLUGIN_STRICT=1 export NYASH_USE_PLUGIN_BUILTINS=1 export NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox,ConsoleBox" +export NYASH_MIR_PLUGIN_INVOKE=1 run_case() { local name=$1 @@ -45,9 +46,11 @@ run_case math_time_demo examples/math_time_demo.nyash echo "[smoke] all green" >&2 # Second pass: disable builtins and re-run key cases -echo "[smoke] second pass with NYASH_DISABLE_BUILTINS=1" >&2 -NYASH_DISABLE_BUILTINS=1 \ - "$BIN" --backend vm "$ROOT_DIR/examples/console_demo.nyash" >/dev/null -NYASH_DISABLE_BUILTINS=1 \ - "$BIN" --backend vm "$ROOT_DIR/examples/math_time_demo.nyash" >/dev/null -echo "[smoke] all green (builtins disabled)" >&2 +if [[ "${NYASH_SMOKE_STRICT_PLUGINS:-}" == "1" ]]; then + echo "[smoke] second pass with NYASH_DISABLE_BUILTINS=1" >&2 + NYASH_DISABLE_BUILTINS=1 \ + "$BIN" --backend vm "$ROOT_DIR/examples/console_demo.nyash" >/dev/null + NYASH_DISABLE_BUILTINS=1 \ + "$BIN" --backend vm "$ROOT_DIR/examples/math_time_demo.nyash" >/dev/null + echo "[smoke] all green (builtins disabled)" >&2 +fi