From a6ce464f0f312a2c358af6550b92d045fee56830 Mon Sep 17 00:00:00 2001 From: Moe Charm Date: Wed, 3 Sep 2025 06:07:02 +0900 Subject: [PATCH] Extern registry slot+arity; extern trace; vtable Array/String tests --- src/backend/vm_instructions.rs | 12 +++++++++ src/config/env.rs | 1 + src/runtime/extern_registry.rs | 34 ++++++++++++++++++------ src/tests/vtable_array_string.rs | 45 ++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 src/tests/vtable_array_string.rs diff --git a/src/backend/vm_instructions.rs b/src/backend/vm_instructions.rs index f39f216c..6735f87e 100644 --- a/src/backend/vm_instructions.rs +++ b/src/backend/vm_instructions.rs @@ -630,6 +630,14 @@ impl VM { 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) let host = crate::runtime::get_global_plugin_host(); let host = host.read().map_err(|_| VMError::InvalidInstruction("Plugin host lock poisoned".into()))?; @@ -650,6 +658,10 @@ impl VM { let mut msg = String::new(); 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)); + // 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) { msg.push_str(&format!(" (expected arity {}..{})", spec.min_arity, spec.max_arity)); } else { diff --git a/src/config/env.rs b/src/config/env.rs index 3a021541..91e73a51 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -125,3 +125,4 @@ pub fn abi_strict() -> bool { std::env::var("NYASH_ABI_STRICT").ok().as_deref() // ---- ExternCall strict diagnostics ---- 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") } diff --git a/src/runtime/extern_registry.rs b/src/runtime/extern_registry.rs index f75c8cc8..889ce68f 100644 --- a/src/runtime/extern_registry.rs +++ b/src/runtime/extern_registry.rs @@ -5,20 +5,26 @@ use once_cell::sync::Lazy; #[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, +} static EXTERNS: Lazy> = Lazy::new(|| vec![ // 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 - 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 - 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) - ExternSpec { iface: "env.future", method: "new", min_arity: 1, max_arity: 1 }, - ExternSpec { iface: "env.future", method: "birth", min_arity: 1, max_arity: 1 }, - ExternSpec { iface: "env.future", method: "set", min_arity: 2, max_arity: 2 }, - ExternSpec { iface: "env.future", method: "await", 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, slot: Some(20) }, + 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, slot: Some(22) }, ]); pub fn resolve(iface: &str, method: &str) -> Option { @@ -35,3 +41,15 @@ pub fn all_ifaces() -> Vec<&'static str> { v.sort(); v.dedup(); v } +/// Resolve slot id for an extern call (if assigned) +pub fn resolve_slot(iface: &str, method: &str) -> Option { + 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()) } +} diff --git a/src/tests/vtable_array_string.rs b/src/tests/vtable_array_string.rs new file mode 100644 index 00000000..a59ba6ae --- /dev/null +++ b/src/tests/vtable_array_string.rs @@ -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"); +} +