2025-09-17 07:43:07 +09:00
|
|
|
use crate::backend::vm::ControlFlow;
|
|
|
|
|
use crate::backend::{VMError, VMValue, VM};
|
2025-09-04 03:41:02 +09:00
|
|
|
use crate::box_trait::NyashBox;
|
|
|
|
|
use crate::mir::ValueId;
|
|
|
|
|
|
|
|
|
|
impl VM {
|
|
|
|
|
/// Execute ExternCall instruction
|
2025-09-17 07:43:07 +09:00
|
|
|
pub(crate) fn execute_extern_call(
|
|
|
|
|
&mut self,
|
|
|
|
|
dst: Option<ValueId>,
|
|
|
|
|
iface_name: &str,
|
|
|
|
|
method_name: &str,
|
|
|
|
|
args: &[ValueId],
|
|
|
|
|
) -> Result<ControlFlow, VMError> {
|
2025-09-07 07:28:53 +09:00
|
|
|
// Core-13 pure shims: env.local.{get,set}, env.box.new
|
|
|
|
|
match (iface_name, method_name) {
|
|
|
|
|
("env.local", "get") => {
|
2025-09-17 07:43:07 +09:00
|
|
|
if args.len() != 1 {
|
|
|
|
|
return Err(VMError::InvalidInstruction("env.local.get arity".into()));
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
let ptr = args[0];
|
2025-09-17 07:43:07 +09:00
|
|
|
let v = self
|
|
|
|
|
.get_value(ptr)
|
|
|
|
|
.unwrap_or(crate::backend::vm::VMValue::Void);
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, v);
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.local", "set") => {
|
2025-09-17 07:43:07 +09:00
|
|
|
if args.len() != 2 {
|
|
|
|
|
return Err(VMError::InvalidInstruction("env.local.set arity".into()));
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
let ptr = args[0];
|
|
|
|
|
let val = self.get_value(args[1])?;
|
|
|
|
|
self.set_value(ptr, val);
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, crate::backend::vm::VMValue::Void);
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.box", "new") => {
|
2025-09-17 07:43:07 +09:00
|
|
|
if args.is_empty() {
|
|
|
|
|
return Err(VMError::InvalidInstruction(
|
|
|
|
|
"env.box.new requires type name".into(),
|
|
|
|
|
));
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
// first arg must be Const String type name
|
|
|
|
|
let ty = self.get_value(args[0])?;
|
2025-09-17 07:43:07 +09:00
|
|
|
let ty_name = match ty {
|
|
|
|
|
crate::backend::vm::VMValue::String(s) => s,
|
|
|
|
|
_ => {
|
|
|
|
|
return Err(VMError::InvalidInstruction(
|
|
|
|
|
"env.box.new first arg must be string".into(),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-09-07 07:28:53 +09:00
|
|
|
// remaining args as NyashBox
|
|
|
|
|
let mut ny_args: Vec<Box<dyn NyashBox>> = Vec::new();
|
|
|
|
|
for id in args.iter().skip(1) {
|
|
|
|
|
let v = self.get_value(*id)?;
|
|
|
|
|
ny_args.push(v.to_nyash_box());
|
|
|
|
|
}
|
|
|
|
|
let reg = crate::runtime::box_registry::get_global_registry();
|
|
|
|
|
match reg.create_box(&ty_name, &ny_args) {
|
2025-09-17 07:43:07 +09:00
|
|
|
Ok(b) => {
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, crate::backend::vm::VMValue::from_nyash_box(b));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
return Err(VMError::InvalidInstruction(format!(
|
|
|
|
|
"env.box.new failed for {}: {}",
|
|
|
|
|
ty_name, e
|
|
|
|
|
)));
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
}
|
|
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
// Optional routing to name→slot handlers for stability and diagnostics
|
|
|
|
|
if crate::config::env::extern_route_slots() {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(slot) =
|
|
|
|
|
crate::runtime::extern_registry::resolve_slot(iface_name, method_name)
|
|
|
|
|
{
|
2025-09-04 03:41:02 +09:00
|
|
|
// Decode args to VMValue as needed by handlers below
|
2025-09-17 07:43:07 +09:00
|
|
|
let vm_args: Vec<VMValue> = args
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|a| self.get_value(*a).ok())
|
|
|
|
|
.collect();
|
2025-09-04 03:41:02 +09:00
|
|
|
match (iface_name, method_name, slot) {
|
2025-09-07 07:28:53 +09:00
|
|
|
("env.local", "get", 40) => {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(d) = dst {
|
|
|
|
|
if let Some(a0) = args.get(0) {
|
|
|
|
|
let v = self.get_value(*a0).unwrap_or(VMValue::Void);
|
|
|
|
|
self.set_value(d, v);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.local", "set", 41) => {
|
2025-09-17 07:43:07 +09:00
|
|
|
if args.len() >= 2 {
|
|
|
|
|
let ptr = args[0];
|
|
|
|
|
let val = vm_args.get(1).cloned().unwrap_or(VMValue::Void);
|
|
|
|
|
self.set_value(ptr, val);
|
|
|
|
|
}
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.box", "new", 50) => {
|
2025-09-17 07:43:07 +09:00
|
|
|
if vm_args.is_empty() {
|
|
|
|
|
return Err(VMError::InvalidInstruction(
|
|
|
|
|
"env.box.new requires type".into(),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
let ty = &vm_args[0];
|
|
|
|
|
let ty_name = match ty {
|
|
|
|
|
VMValue::String(s) => s.clone(),
|
|
|
|
|
_ => {
|
|
|
|
|
return Err(VMError::InvalidInstruction(
|
|
|
|
|
"env.box.new first arg must be string".into(),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-09-07 07:28:53 +09:00
|
|
|
let mut ny_args: Vec<Box<dyn NyashBox>> = Vec::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
for v in vm_args.iter().skip(1) {
|
|
|
|
|
ny_args.push(v.to_nyash_box());
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
let reg = crate::runtime::box_registry::get_global_registry();
|
|
|
|
|
match reg.create_box(&ty_name, &ny_args) {
|
2025-09-17 07:43:07 +09:00
|
|
|
Ok(b) => {
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::from_nyash_box(b));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
return Err(VMError::InvalidInstruction(format!(
|
|
|
|
|
"env.box.new failed for {}: {}",
|
|
|
|
|
ty_name, e
|
|
|
|
|
)));
|
|
|
|
|
}
|
2025-09-07 07:28:53 +09:00
|
|
|
}
|
|
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
("env.console", m @ ("log" | "warn" | "error" | "println"), 10) => {
|
|
|
|
|
if let Some(a0) = vm_args.get(0) {
|
2025-09-17 07:43:07 +09:00
|
|
|
match m {
|
|
|
|
|
"warn" => eprintln!("[warn] {}", a0.to_string()),
|
|
|
|
|
"error" => eprintln!("[error] {}", a0.to_string()),
|
|
|
|
|
_ => println!("{}", a0.to_string()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
2025-09-04 03:41:02 +09:00
|
|
|
}
|
|
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.debug", "trace", 11) => {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(a0) = vm_args.get(0) {
|
|
|
|
|
eprintln!("[trace] {}", a0.to_string());
|
|
|
|
|
}
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.runtime", "checkpoint", 12) => {
|
|
|
|
|
if crate::config::env::runtime_checkpoint_trace() {
|
|
|
|
|
let (func, bb, pc) = self.gc_site_info();
|
|
|
|
|
eprintln!("[rt] checkpoint @{} bb={} pc={}", func, bb, pc);
|
|
|
|
|
}
|
|
|
|
|
self.runtime.gc.safepoint();
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(s) = &self.runtime.scheduler {
|
|
|
|
|
s.poll();
|
|
|
|
|
}
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.future", "new", 20) | ("env.future", "birth", 20) => {
|
|
|
|
|
// Create a new Future and optionally set initial value from arg0
|
|
|
|
|
let fut = crate::boxes::future::FutureBox::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(a0) = vm_args.get(0) {
|
|
|
|
|
fut.set_result(a0.to_nyash_box());
|
|
|
|
|
}
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Future(fut));
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.future", "set", 21) => {
|
|
|
|
|
// set(future, value)
|
2025-09-17 07:43:07 +09:00
|
|
|
if vm_args.len() >= 2 {
|
|
|
|
|
if let VMValue::Future(f) = &vm_args[0] {
|
|
|
|
|
f.set_result(vm_args[1].to_nyash_box());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.future", "await", 22) => {
|
|
|
|
|
if let Some(VMValue::Future(fb)) = vm_args.get(0) {
|
|
|
|
|
// Simple blocking await
|
|
|
|
|
let start = std::time::Instant::now();
|
|
|
|
|
let max_ms = crate::config::env::await_max_ms();
|
|
|
|
|
while !fb.ready() {
|
|
|
|
|
std::thread::yield_now();
|
2025-09-17 07:43:07 +09:00
|
|
|
if start.elapsed() >= std::time::Duration::from_millis(max_ms) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
let result = if fb.ready() {
|
|
|
|
|
fb.get()
|
|
|
|
|
} else {
|
|
|
|
|
Box::new(crate::box_trait::StringBox::new("Timeout"))
|
|
|
|
|
};
|
2025-09-04 03:41:02 +09:00
|
|
|
let ok = crate::boxes::result::NyashResultBox::new_ok(result);
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::from_nyash_box(Box::new(ok)));
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
|
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.task", "cancelCurrent", 30) => {
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
|
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.task", "currentToken", 31) => {
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Integer(0));
|
|
|
|
|
}
|
|
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.task", "yieldNow", 32) => {
|
|
|
|
|
std::thread::yield_now();
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.task", "sleepMs", 33) => {
|
2025-09-17 07:43:07 +09:00
|
|
|
let ms = vm_args
|
|
|
|
|
.get(0)
|
|
|
|
|
.map(|v| match v {
|
|
|
|
|
VMValue::Integer(i) => *i,
|
|
|
|
|
_ => 0,
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
if ms > 0 {
|
|
|
|
|
std::thread::sleep(std::time::Duration::from_millis(ms as u64));
|
|
|
|
|
}
|
|
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
_ => { /* fallthrough to host */ }
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-07 07:36:15 +09:00
|
|
|
// Name-route minimal registry without slot
|
|
|
|
|
// env.modules.set(key:any->string, value:any) ; env.modules.get(key) -> any
|
|
|
|
|
match (iface_name, method_name) {
|
|
|
|
|
("env.modules", "set") => {
|
|
|
|
|
// Expect two args
|
2025-09-17 07:43:07 +09:00
|
|
|
let vm_args: Vec<VMValue> = args
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|a| self.get_value(*a).ok())
|
|
|
|
|
.collect();
|
2025-09-07 07:36:15 +09:00
|
|
|
if vm_args.len() >= 2 {
|
|
|
|
|
let key = vm_args[0].to_string();
|
|
|
|
|
let val_box = vm_args[1].to_nyash_box();
|
|
|
|
|
crate::runtime::modules_registry::set(key, val_box);
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-07 07:36:15 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
("env.modules", "get") => {
|
2025-09-17 07:43:07 +09:00
|
|
|
let vm_args: Vec<VMValue> = args
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|a| self.get_value(*a).ok())
|
|
|
|
|
.collect();
|
2025-09-07 07:36:15 +09:00
|
|
|
if let Some(k) = vm_args.get(0) {
|
|
|
|
|
let key = k.to_string();
|
|
|
|
|
if let Some(v) = crate::runtime::modules_registry::get(&key) {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::from_nyash_box(v));
|
|
|
|
|
} else { /* no dst */
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-07 07:36:15 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Name-route minimal registry even when slot routing is disabled
|
|
|
|
|
if iface_name == "env.modules" {
|
|
|
|
|
// Decode args as VMValue for convenience
|
2025-09-17 07:43:07 +09:00
|
|
|
let vm_args: Vec<VMValue> = args
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|a| self.get_value(*a).ok())
|
|
|
|
|
.collect();
|
2025-09-07 07:36:15 +09:00
|
|
|
match method_name {
|
|
|
|
|
"set" => {
|
|
|
|
|
if vm_args.len() >= 2 {
|
|
|
|
|
let key = vm_args[0].to_string();
|
|
|
|
|
let val_box = vm_args[1].to_nyash_box();
|
|
|
|
|
crate::runtime::modules_registry::set(key, val_box);
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-07 07:36:15 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
"get" => {
|
|
|
|
|
if let Some(k) = vm_args.get(0) {
|
|
|
|
|
let key = k.to_string();
|
|
|
|
|
if let Some(v) = crate::runtime::modules_registry::get(&key) {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::from_nyash_box(v));
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(d) = dst {
|
|
|
|
|
self.set_value(d, VMValue::Void);
|
|
|
|
|
}
|
2025-09-07 07:36:15 +09:00
|
|
|
return Ok(ControlFlow::Continue);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Evaluate arguments as NyashBox for loader
|
|
|
|
|
let mut nyash_args: Vec<Box<dyn NyashBox>> = Vec::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
for arg_id in args {
|
|
|
|
|
let arg_value = self.get_value(*arg_id)?;
|
|
|
|
|
nyash_args.push(arg_value.to_nyash_box());
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
|
|
|
|
|
if crate::config::env::extern_trace() {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(slot) =
|
|
|
|
|
crate::runtime::extern_registry::resolve_slot(iface_name, method_name)
|
|
|
|
|
{
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[EXT] call {}.{} slot={} argc={}",
|
|
|
|
|
iface_name,
|
|
|
|
|
method_name,
|
|
|
|
|
slot,
|
|
|
|
|
nyash_args.len()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[EXT] call {}.{} argc={}",
|
|
|
|
|
iface_name,
|
|
|
|
|
method_name,
|
|
|
|
|
nyash_args.len()
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
}
|
|
|
|
|
let host = crate::runtime::get_global_plugin_host();
|
2025-09-17 07:43:07 +09:00
|
|
|
let host = host
|
|
|
|
|
.read()
|
|
|
|
|
.map_err(|_| VMError::InvalidInstruction("Plugin host lock poisoned".into()))?;
|
2025-09-04 03:41:02 +09:00
|
|
|
match host.extern_call(iface_name, method_name, &nyash_args) {
|
2025-09-17 07:43:07 +09:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
Err(_) => {
|
2025-09-17 07:43:07 +09:00
|
|
|
let strict =
|
|
|
|
|
crate::config::env::extern_strict() || crate::config::env::abi_strict();
|
2025-09-04 03:41:02 +09:00
|
|
|
let mut msg = String::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
if strict {
|
|
|
|
|
msg.push_str("ExternCall STRICT: unregistered or unsupported call ");
|
|
|
|
|
} else {
|
|
|
|
|
msg.push_str("ExternCall failed: ");
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
msg.push_str(&format!("{}.{}", iface_name, method_name));
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Err(detail) = crate::runtime::extern_registry::check_arity(
|
|
|
|
|
iface_name,
|
|
|
|
|
method_name,
|
|
|
|
|
nyash_args.len(),
|
|
|
|
|
) {
|
|
|
|
|
msg.push_str(&format!(" ({})", detail));
|
|
|
|
|
}
|
|
|
|
|
if let Some(spec) =
|
|
|
|
|
crate::runtime::extern_registry::resolve(iface_name, method_name)
|
|
|
|
|
{
|
|
|
|
|
msg.push_str(&format!(
|
|
|
|
|
" (expected arity {}..{})",
|
|
|
|
|
spec.min_arity, spec.max_arity
|
|
|
|
|
));
|
2025-09-04 03:41:02 +09:00
|
|
|
} else {
|
|
|
|
|
let known = crate::runtime::extern_registry::known_for_iface(iface_name);
|
2025-09-17 07:43:07 +09:00
|
|
|
if !known.is_empty() {
|
|
|
|
|
msg.push_str(&format!("; known methods: {}", known.join(", ")));
|
|
|
|
|
} else {
|
|
|
|
|
let ifaces = crate::runtime::extern_registry::all_ifaces();
|
|
|
|
|
msg.push_str(&format!("; known interfaces: {}", ifaces.join(", ")));
|
|
|
|
|
}
|
2025-09-04 03:41:02 +09:00
|
|
|
}
|
|
|
|
|
return Err(VMError::InvalidInstruction(msg));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(ControlFlow::Continue)
|
|
|
|
|
}
|
|
|
|
|
}
|