Extern registry slot+arity; extern trace; vtable Array/String tests

This commit is contained in:
Moe Charm
2025-09-03 06:07:02 +09:00
parent e8f61d878f
commit a6ce464f0f
4 changed files with 84 additions and 8 deletions

View File

@ -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 {

View File

@ -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") }

View File

@ -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()) }
}

View 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");
}