Extern registry slot+arity; extern trace; vtable Array/String tests
This commit is contained in:
@ -630,6 +630,14 @@ impl VM {
|
|||||||
nyash_args.push(arg_value.to_nyash_box());
|
nyash_args.push(arg_value.to_nyash_box());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional trace
|
||||||
|
if crate::config::env::extern_trace() {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
// Route through unified plugin host (delegates to v2, handles env.* stubs)
|
// Route through unified plugin host (delegates to v2, handles env.* stubs)
|
||||||
let host = crate::runtime::get_global_plugin_host();
|
let host = crate::runtime::get_global_plugin_host();
|
||||||
let host = host.read().map_err(|_| VMError::InvalidInstruction("Plugin host lock poisoned".into()))?;
|
let host = host.read().map_err(|_| VMError::InvalidInstruction("Plugin host lock poisoned".into()))?;
|
||||||
@ -650,6 +658,10 @@ impl VM {
|
|||||||
let mut msg = String::new();
|
let mut msg = String::new();
|
||||||
if strict { msg.push_str("ExternCall STRICT: unregistered or unsupported call "); } else { msg.push_str("ExternCall failed: "); }
|
if strict { msg.push_str("ExternCall STRICT: unregistered or unsupported call "); } else { msg.push_str("ExternCall failed: "); }
|
||||||
msg.push_str(&format!("{}.{}", iface_name, method_name));
|
msg.push_str(&format!("{}.{}", iface_name, method_name));
|
||||||
|
// Arity check when known
|
||||||
|
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) {
|
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));
|
msg.push_str(&format!(" (expected arity {}..{})", spec.min_arity, spec.max_arity));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -125,3 +125,4 @@ pub fn abi_strict() -> bool { std::env::var("NYASH_ABI_STRICT").ok().as_deref()
|
|||||||
|
|
||||||
// ---- ExternCall strict diagnostics ----
|
// ---- ExternCall strict diagnostics ----
|
||||||
pub fn extern_strict() -> bool { std::env::var("NYASH_EXTERN_STRICT").ok().as_deref() == Some("1") }
|
pub fn extern_strict() -> bool { std::env::var("NYASH_EXTERN_STRICT").ok().as_deref() == Some("1") }
|
||||||
|
pub fn extern_trace() -> bool { std::env::var("NYASH_EXTERN_TRACE").ok().as_deref() == Some("1") }
|
||||||
|
|||||||
@ -5,20 +5,26 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ExternSpec { pub iface: &'static str, pub method: &'static str, pub min_arity: u8, pub max_arity: u8 }
|
pub struct ExternSpec {
|
||||||
|
pub iface: &'static str,
|
||||||
|
pub method: &'static str,
|
||||||
|
pub min_arity: u8,
|
||||||
|
pub max_arity: u8,
|
||||||
|
pub slot: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
static EXTERNS: Lazy<Vec<ExternSpec>> = Lazy::new(|| vec![
|
static EXTERNS: Lazy<Vec<ExternSpec>> = Lazy::new(|| vec![
|
||||||
// console
|
// console
|
||||||
ExternSpec { iface: "env.console", method: "log", min_arity: 1, max_arity: 1 },
|
ExternSpec { iface: "env.console", method: "log", min_arity: 1, max_arity: 1, slot: Some(10) },
|
||||||
// debug
|
// debug
|
||||||
ExternSpec { iface: "env.debug", method: "trace", min_arity: 1, max_arity: 255 },
|
ExternSpec { iface: "env.debug", method: "trace", min_arity: 1, max_arity: 255, slot: Some(11) },
|
||||||
// runtime
|
// runtime
|
||||||
ExternSpec { iface: "env.runtime", method: "checkpoint", min_arity: 0, max_arity: 0 },
|
ExternSpec { iface: "env.runtime", method: "checkpoint", min_arity: 0, max_arity: 0, slot: Some(12) },
|
||||||
// future (scaffold)
|
// future (scaffold)
|
||||||
ExternSpec { iface: "env.future", method: "new", min_arity: 1, max_arity: 1 },
|
ExternSpec { iface: "env.future", method: "new", min_arity: 1, max_arity: 1, slot: Some(20) },
|
||||||
ExternSpec { iface: "env.future", method: "birth", min_arity: 1, max_arity: 1 },
|
ExternSpec { iface: "env.future", method: "birth", min_arity: 1, max_arity: 1, slot: Some(20) },
|
||||||
ExternSpec { iface: "env.future", method: "set", min_arity: 2, max_arity: 2 },
|
ExternSpec { iface: "env.future", method: "set", min_arity: 2, max_arity: 2, slot: Some(21) },
|
||||||
ExternSpec { iface: "env.future", method: "await", min_arity: 1, max_arity: 1 },
|
ExternSpec { iface: "env.future", method: "await", min_arity: 1, max_arity: 1, slot: Some(22) },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
pub fn resolve(iface: &str, method: &str) -> Option<ExternSpec> {
|
pub fn resolve(iface: &str, method: &str) -> Option<ExternSpec> {
|
||||||
@ -35,3 +41,15 @@ pub fn all_ifaces() -> Vec<&'static str> {
|
|||||||
v.sort(); v.dedup(); v
|
v.sort(); v.dedup(); v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve slot id for an extern call (if assigned)
|
||||||
|
pub fn resolve_slot(iface: &str, method: &str) -> Option<u16> {
|
||||||
|
resolve(iface, method).and_then(|s| s.slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check arity against registry; returns Ok or an explanatory error string
|
||||||
|
pub fn check_arity(iface: &str, method: &str, argc: usize) -> Result<(), String> {
|
||||||
|
if let Some(s) = resolve(iface, method) {
|
||||||
|
if argc as u8 >= s.min_arity && argc as u8 <= s.max_arity { Ok(()) }
|
||||||
|
else { Err(format!("arity {} out of range {}..{}", argc, s.min_arity, s.max_arity)) }
|
||||||
|
} else { Err("unknown extern".to_string()) }
|
||||||
|
}
|
||||||
|
|||||||
45
src/tests/vtable_array_string.rs
Normal file
45
src/tests/vtable_array_string.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#[test]
|
||||||
|
fn vtable_array_and_string_len_get_set() {
|
||||||
|
use crate::backend::vm::VM;
|
||||||
|
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirInstruction, EffectMask, BasicBlockId, ConstValue, MirType};
|
||||||
|
std::env::set_var("NYASH_ABI_VTABLE", "1");
|
||||||
|
|
||||||
|
// Array: set(0, "x"); len(); get(0)
|
||||||
|
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::String, effects: EffectMask::PURE };
|
||||||
|
let mut f = MirFunction::new(sig, BasicBlockId::new(0));
|
||||||
|
let bb = f.entry_block;
|
||||||
|
let arr = f.next_value_id();
|
||||||
|
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] });
|
||||||
|
let idx0 = f.next_value_id();
|
||||||
|
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: idx0, value: ConstValue::Integer(0) });
|
||||||
|
let sval = f.next_value_id();
|
||||||
|
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: sval, value: ConstValue::String("x".into()) });
|
||||||
|
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: None, box_val: arr, method: "set".into(), args: vec![idx0, sval], method_id: None, effects: EffectMask::PURE });
|
||||||
|
let lenv = f.next_value_id();
|
||||||
|
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(lenv), box_val: arr, method: "len".into(), args: vec![], method_id: None, effects: EffectMask::PURE });
|
||||||
|
// sanity: len should be 1 (not asserted here, just exercise path)
|
||||||
|
let got = f.next_value_id();
|
||||||
|
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(got), box_val: arr, method: "get".into(), args: vec![idx0], method_id: None, effects: EffectMask::PURE });
|
||||||
|
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(got) });
|
||||||
|
let mut m = MirModule::new("tarr".into()); m.add_function(f);
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let out = vm.execute_module(&m).expect("vm exec");
|
||||||
|
assert_eq!(out.to_string_box().value, "x");
|
||||||
|
|
||||||
|
// String: len()
|
||||||
|
let sig2 = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE };
|
||||||
|
let mut f2 = MirFunction::new(sig2, BasicBlockId::new(0));
|
||||||
|
let bb2 = f2.entry_block;
|
||||||
|
let s = f2.next_value_id();
|
||||||
|
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::Const { dst: s, value: ConstValue::String("abc".into()) });
|
||||||
|
let sb = f2.next_value_id();
|
||||||
|
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::NewBox { dst: sb, box_type: "StringBox".into(), args: vec![s] });
|
||||||
|
let ln = f2.next_value_id();
|
||||||
|
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(ln), box_val: sb, method: "len".into(), args: vec![], method_id: None, effects: EffectMask::PURE });
|
||||||
|
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::Return { value: Some(ln) });
|
||||||
|
let mut m2 = MirModule::new("tstr".into()); m2.add_function(f2);
|
||||||
|
let mut vm2 = VM::new();
|
||||||
|
let out2 = vm2.execute_module(&m2).expect("vm exec");
|
||||||
|
assert_eq!(out2.to_string_box().value, "3");
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user