Phase 12: extern registry + diagnostics, JIT host-bridge PoC by-slot, vtable Map.set + VT/PIC tracing, and tests

This commit is contained in:
Moe Charm
2025-09-03 05:56:57 +09:00
parent 0722b410a1
commit 294b45b9f4
8 changed files with 234 additions and 4 deletions

View File

@ -646,11 +646,22 @@ impl VM {
}
Err(_) => {
let strict = crate::config::env::extern_strict() || crate::config::env::abi_strict();
if strict {
return Err(VMError::InvalidInstruction(format!("ExternCall STRICT: unregistered or unsupported call {}.{}", iface_name, method_name)));
// Build suggestion list
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));
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 {
return Err(VMError::InvalidInstruction(format!("ExternCall failed: {}.{}", iface_name, method_name)));
let known = crate::runtime::extern_registry::known_for_iface(iface_name);
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(", ")));
}
}
return Err(VMError::InvalidInstruction(msg));
}
}
Ok(ControlFlow::Continue)
@ -918,6 +929,7 @@ impl VM {
if let VMValue::BoxRef(arc_box) = &recv {
if arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>().is_some() {
if let Some(func_name) = self.try_poly_pic(&pic_key, &recv) {
if crate::config::env::vm_pic_trace() { eprintln!("[PIC] poly hit {}", pic_key); }
self.boxcall_hits_poly_pic = self.boxcall_hits_poly_pic.saturating_add(1);
let mut vm_args = Vec::with_capacity(1 + args.len());
vm_args.push(recv.clone());
@ -928,6 +940,7 @@ impl VM {
}
// Fallback to Mono-PIC (legacy) if present
if let Some(func_name) = self.boxcall_pic_funcname.get(&pic_key).cloned() {
if crate::config::env::vm_pic_trace() { eprintln!("[PIC] mono hit {}", pic_key); }
self.boxcall_hits_mono_pic = self.boxcall_hits_mono_pic.saturating_add(1);
// Build VM args: receiver first, then original args
let mut vm_args = Vec::with_capacity(1 + args.len());
@ -1131,10 +1144,11 @@ impl VM {
if let Some(_tb) = crate::runtime::type_registry::resolve_typebox_by_name(ty_name) {
// name+arity→slot 解決
let slot = crate::runtime::type_registry::resolve_slot_by_name(ty_name, _method, _args.len());
// MapBox: size/len/has/get
// MapBox: size/len/has/get/set
if let Some(map) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if matches!(slot, Some(200|201)) {
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.size/len slot={:?}", slot); }
let out = map.size();
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue));
@ -1151,6 +1165,8 @@ impl VM {
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.has"); }
let out = map.has(key_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue));
@ -1168,16 +1184,48 @@ impl VM {
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.get"); }
let out = map.get(key_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue));
}
}
if matches!(slot, Some(204)) {
if let (Ok(a0), Ok(a1)) = (self.get_value(_args[0]), self.get_value(_args[1])) {
let key_box: Box<dyn NyashBox> = match a0 {
VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)),
VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)),
VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)),
VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)),
VMValue::BoxRef(ref bx) => bx.share_box(),
VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
let val_box: Box<dyn NyashBox> = match a1 {
VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)),
VMValue::String(ref s) => Box::new(crate::box_trait::StringBox::new(s)),
VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)),
VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)),
VMValue::BoxRef(ref bx) => bx.share_box(),
VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
// Barrier: mutation
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Map.set");
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] MapBox.set"); }
let out = map.set(key_box, val_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue));
}
}
}
// ArrayBox: get/set/len
if let Some(arr) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if matches!(slot, Some(102)) {
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] ArrayBox.len"); }
let out = arr.length();
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue));
@ -1192,6 +1240,8 @@ impl VM {
VMValue::BoxRef(_) | VMValue::Future(_) | VMValue::Void => Box::new(crate::box_trait::IntegerBox::new(0)),
};
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] ArrayBox.get"); }
let out = arr.get(idx_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue));
@ -1216,6 +1266,10 @@ impl VM {
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
// Barrier: mutation
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Array.set");
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] ArrayBox.set"); }
let out = arr.set(idx_box, val_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue));
@ -1226,6 +1280,7 @@ impl VM {
if let Some(sb) = b.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if matches!(slot, Some(300)) {
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
if crate::config::env::vm_vt_trace() { eprintln!("[VT] StringBox.len"); }
let out = crate::box_trait::IntegerBox::new(sb.value.len() as i64);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(Box::new(out))); }
return Some(Ok(ControlFlow::Continue));

View File

@ -99,6 +99,8 @@ pub fn gc_trace() -> bool { std::env::var("NYASH_GC_TRACE").ok().as_deref() == S
pub fn gc_barrier_trace() -> bool { std::env::var("NYASH_GC_BARRIER_TRACE").ok().as_deref() == Some("1") }
pub fn runtime_checkpoint_trace() -> bool { std::env::var("NYASH_RUNTIME_CHECKPOINT_TRACE").ok().as_deref() == Some("1") }
pub fn vm_pic_stats() -> bool { std::env::var("NYASH_VM_PIC_STATS").ok().as_deref() == Some("1") }
pub fn vm_vt_trace() -> bool { std::env::var("NYASH_VM_VT_TRACE").ok().as_deref() == Some("1") }
pub fn vm_pic_trace() -> bool { std::env::var("NYASH_VM_PIC_TRACE").ok().as_deref() == Some("1") }
pub fn gc_barrier_strict() -> bool { std::env::var("NYASH_GC_BARRIER_STRICT").ok().as_deref() == Some("1") }
/// Return 0 (off) to 3 (max) for `NYASH_GC_TRACE`.

View File

@ -206,6 +206,7 @@ impl JitEngine {
/// Register built-in externs (collections)
fn register_default_externs(&mut self) {
use crate::jit::r#extern::collections as c;
use crate::jit::r#extern::host_bridge as hb;
self.register_extern(c::SYM_ARRAY_LEN, Arc::new(|args| c::array_len(args)));
self.register_extern(c::SYM_ARRAY_GET, Arc::new(|args| c::array_get(args)));
self.register_extern(c::SYM_ARRAY_SET, Arc::new(|args| c::array_set(args)));
@ -213,6 +214,16 @@ impl JitEngine {
self.register_extern(c::SYM_MAP_GET, Arc::new(|args| c::map_get(args)));
self.register_extern(c::SYM_MAP_SET, Arc::new(|args| c::map_set(args)));
self.register_extern(c::SYM_MAP_SIZE, Arc::new(|args| c::map_size(args)));
// Host-bridge variants (by-slot via C symbol). Guarded by env opt-in for now.
if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") {
self.register_extern(hb::SYM_HOST_ARRAY_LEN, Arc::new(|args| hb::array_len(args)));
self.register_extern(hb::SYM_HOST_ARRAY_GET, Arc::new(|args| hb::array_get(args)));
self.register_extern(hb::SYM_HOST_ARRAY_SET, Arc::new(|args| hb::array_set(args)));
self.register_extern(hb::SYM_HOST_MAP_SIZE, Arc::new(|args| hb::map_size(args)));
self.register_extern(hb::SYM_HOST_MAP_GET, Arc::new(|args| hb::map_get(args)));
self.register_extern(hb::SYM_HOST_MAP_SET, Arc::new(|args| hb::map_set(args)));
self.register_extern(hb::SYM_HOST_MAP_HAS, Arc::new(|args| hb::map_has(args)));
}
}
pub fn register_extern(&mut self, name: &str, f: Arc<dyn Fn(&[crate::backend::vm::VMValue]) -> crate::backend::vm::VMValue + Send + Sync>) {

83
src/jit/extern/host_bridge.rs vendored Normal file
View File

@ -0,0 +1,83 @@
//! JIT externs bridging to NyRT host API (C symbols) via by-slot encoding.
//!
//! 目的: VM/JIT一致のため、JITからも host_api::nyrt_host_call_slot を使うPoC。
use crate::backend::vm::VMValue;
fn tlv_encode_values(args: &[VMValue]) -> Vec<u8> {
use crate::runtime::plugin_ffi_common::encode as enc;
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16);
for a in args {
match a {
VMValue::Integer(i) => enc::i64(&mut buf, *i),
VMValue::Float(f) => enc::f64(&mut buf, *f),
VMValue::Bool(b) => enc::bool(&mut buf, *b),
VMValue::String(s) => enc::string(&mut buf, s),
VMValue::BoxRef(_) | VMValue::Future(_) | VMValue::Void => enc::string(&mut buf, ""),
}
}
buf
}
fn call_slot(handle: u64, slot: u64, argv: &[VMValue]) -> VMValue {
let tlv = tlv_encode_values(argv);
let mut out = vec![0u8; 256];
let mut out_len: usize = out.len();
let code = unsafe { crate::runtime::host_api::nyrt_host_call_slot(handle, slot, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) };
if code != 0 { return VMValue::Void; }
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
match tag {
6|7 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)),
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 => crate::runtime::plugin_ffi_common::decode::u64(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void),
5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void),
9 => {
if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) {
if let Some(arc) = crate::runtime::host_handles::get(h) { return VMValue::BoxRef(arc); }
}
VMValue::Void
}
_ => VMValue::Void,
}
} else { VMValue::Void }
}
fn to_handle(recv: &VMValue) -> Option<u64> {
match recv {
VMValue::BoxRef(arc) => Some(crate::runtime::host_handles::to_handle_arc(arc.clone())),
_ => None,
}
}
// Public bridge helpers (symbol strings align with collections for PoC)
pub const SYM_HOST_ARRAY_GET: &str = "nyash.host.array.get"; // (ArrayBox, i64)
pub const SYM_HOST_ARRAY_SET: &str = "nyash.host.array.set"; // (ArrayBox, i64, val)
pub const SYM_HOST_ARRAY_LEN: &str = "nyash.host.array.len"; // (ArrayBox)
pub const SYM_HOST_MAP_GET: &str = "nyash.host.map.get"; // (MapBox, key)
pub const SYM_HOST_MAP_SET: &str = "nyash.host.map.set"; // (MapBox, key, val)
pub const SYM_HOST_MAP_SIZE: &str = "nyash.host.map.size"; // (MapBox)
pub const SYM_HOST_MAP_HAS: &str = "nyash.host.map.has"; // (MapBox, key)
pub fn array_get(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 100, &args[1..]) } else { VMValue::Void }
}
pub fn array_set(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 101, &args[1..]) } else { VMValue::Void }
}
pub fn array_len(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 102, &[]) } else { VMValue::Integer(0) }
}
pub fn map_get(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 203, &args[1..]) } else { VMValue::Void }
}
pub fn map_set(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 204, &args[1..]) } else { VMValue::Void }
}
pub fn map_size(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 200, &[]) } else { VMValue::Integer(0) }
}
pub fn map_has(args: &[VMValue]) -> VMValue {
if let Some(h) = to_handle(args.get(0).unwrap_or(&VMValue::Void)) { call_slot(h, 202, &args[1..]) } else { VMValue::Bool(false) }
}

View File

@ -5,6 +5,7 @@
//! these externs once call emission is added.
pub mod collections;
pub mod host_bridge;
pub mod handles;
pub mod birth;
pub mod runtime;

View File

@ -0,0 +1,37 @@
//! Extern interface registry (env.*) for diagnostics and optional slotting
//!
//! 目的: ExternCallの未登録/未対応時に候補提示やSTRICT診断を改善する。
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 }
static EXTERNS: Lazy<Vec<ExternSpec>> = Lazy::new(|| vec![
// console
ExternSpec { iface: "env.console", method: "log", min_arity: 1, max_arity: 1 },
// debug
ExternSpec { iface: "env.debug", method: "trace", min_arity: 1, max_arity: 255 },
// runtime
ExternSpec { iface: "env.runtime", method: "checkpoint", min_arity: 0, max_arity: 0 },
// 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 },
]);
pub fn resolve(iface: &str, method: &str) -> Option<ExternSpec> {
EXTERNS.iter().copied().find(|e| e.iface == iface && e.method == method)
}
pub fn known_for_iface(iface: &str) -> Vec<&'static str> {
let mut v: Vec<&'static str> = EXTERNS.iter().filter(|e| e.iface == iface).map(|e| e.method).collect();
v.sort(); v.dedup(); v
}
pub fn all_ifaces() -> Vec<&'static str> {
let mut v: Vec<&'static str> = EXTERNS.iter().map(|e| e.iface).collect();
v.sort(); v.dedup(); v
}

View File

@ -21,6 +21,7 @@ pub mod type_box_abi; // Phase 12: Nyash ABI (vtable) 雛形
pub mod type_registry; // Phase 12: TypeId→TypeBox 解決(雛形)
pub mod host_handles; // C ABI(TLV) 向け HostHandle レジストリ(ユーザー/内蔵Box受け渡し
pub mod host_api; // C ABI: plugins -> host 逆呼び出しAPITLSでVMに橋渡し
pub mod extern_registry; // ExternCall (env.*) 登録・診断用レジストリ
#[cfg(test)]
mod tests;

View File

@ -0,0 +1,40 @@
#[test]
fn vtable_map_set_and_strict_unknown() {
use crate::backend::vm::VM;
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirInstruction, EffectMask, BasicBlockId, ConstValue, MirType};
std::env::set_var("NYASH_ABI_VTABLE", "1");
// Build: new MapBox; call set("k","v"); size(); return size
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE };
let mut f = MirFunction::new(sig, BasicBlockId::new(0));
let bb = f.entry_block;
let mapv = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] });
let k = f.next_value_id(); let v = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: k, value: ConstValue::String("k".into()) });
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v, value: ConstValue::String("v".into()) });
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: None, box_val: mapv, method: "set".into(), args: vec![k, v], method_id: None, effects: EffectMask::PURE });
let sz = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(sz), box_val: mapv, method: "size".into(), args: vec![], method_id: None, effects: EffectMask::PURE });
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(sz) });
let mut m = MirModule::new("t".into()); m.add_function(f);
let mut vm = VM::new();
let out = vm.execute_module(&m).expect("vm exec");
let s = out.to_string_box().value; assert_eq!(s, "1");
// STRICT unknown method on MapBox should error
std::env::set_var("NYASH_ABI_STRICT", "1");
let sig2 = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Void, effects: EffectMask::PURE };
let mut f2 = MirFunction::new(sig2, BasicBlockId::new(0));
let bb2 = f2.entry_block;
let m2 = f2.next_value_id();
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::NewBox { dst: m2, box_type: "MapBox".into(), args: vec![] });
// Call unknown method
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::BoxCall { dst: None, box_val: m2, method: "unknown".into(), args: vec![], method_id: None, effects: EffectMask::PURE });
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::Return { value: None });
let mut mm = MirModule::new("t2".into()); mm.add_function(f2);
let mut vm2 = VM::new();
let res = vm2.execute_module(&mm);
assert!(res.is_err(), "STRICT should error on unknown vtable method");
}