Docs: Add phase_9_79b_3_vm_vtable_thunks_and_pic.md\n- Plan to formalize TypeMeta+Thunk and upgrade PIC to poly\n- Diagnostics, risks, milestones, exit criteria, and Phase 10 readiness

This commit is contained in:
Moe Charm
2025-08-27 00:03:48 +09:00
parent c0b70c0e4e
commit edf5ccfcb4
10 changed files with 313 additions and 21 deletions

View File

@ -214,6 +214,8 @@ pub struct VM {
pub(super) boxcall_pic_funcname: std::collections::HashMap<String, String>,
/// VTable-like cache: (type, method_id, arity) → direct target (InstanceBox method)
pub(super) boxcall_vtable_funcname: std::collections::HashMap<String, String>,
/// Version map for cache invalidation: label -> version
pub(super) type_versions: std::collections::HashMap<String, u32>,
// Phase 9.78a: Add unified Box handling components
// TODO: Re-enable when interpreter refactoring is complete
// /// Box registry for creating all Box types
@ -277,6 +279,7 @@ impl VM {
boxcall_pic_hits: std::collections::HashMap::new(),
boxcall_pic_funcname: std::collections::HashMap::new(),
boxcall_vtable_funcname: std::collections::HashMap::new(),
type_versions: std::collections::HashMap::new(),
// TODO: Re-enable when interpreter refactoring is complete
// box_registry: Arc::new(UnifiedBoxRegistry::new()),
// #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
@ -305,6 +308,7 @@ impl VM {
boxcall_pic_hits: std::collections::HashMap::new(),
boxcall_pic_funcname: std::collections::HashMap::new(),
boxcall_vtable_funcname: std::collections::HashMap::new(),
type_versions: std::collections::HashMap::new(),
}
}
@ -1027,6 +1031,75 @@ console.log("ok")
assert_eq!(result.to_string_box().value, "void");
}
#[test]
fn test_vm_user_box_vtable_caching_two_calls() {
// Defines a simple user box and calls the same method twice; ensures correctness.
// Builder reserves slots for user methods (from 4), so method_id should be present,
// enabling vtable cache population on first call and (conceptually) direct call on second.
let code = r#"
box Greeter {
init { name }
birth(n) { me.name = n }
greet() { return "Hi, " + me.name }
}
local g
g = new Greeter("Bob")
g.greet()
g.greet()
"#;
// Parse to AST
let ast = crate::parser::NyashParser::parse_from_string(code).expect("parse failed");
// Prepare runtime with user-defined declarations and factory
let runtime = {
let rt = crate::runtime::NyashRuntime::new();
// Collect box declarations into runtime so that user-defined factory works
fn collect_box_decls(ast: &crate::ast::ASTNode, runtime: &crate::runtime::NyashRuntime) {
use crate::core::model::BoxDeclaration as CoreBoxDecl;
fn walk(node: &crate::ast::ASTNode, runtime: &crate::runtime::NyashRuntime) {
match node {
crate::ast::ASTNode::Program { statements, .. } => for st in statements { walk(st, runtime); },
crate::ast::ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
let decl = CoreBoxDecl {
name: name.clone(),
fields: fields.clone(),
public_fields: public_fields.clone(),
private_fields: private_fields.clone(),
methods: methods.clone(),
constructors: constructors.clone(),
init_fields: init_fields.clone(),
weak_fields: weak_fields.clone(),
is_interface: *is_interface,
extends: extends.clone(),
implements: implements.clone(),
type_parameters: type_parameters.clone(),
};
if let Ok(mut map) = runtime.box_declarations.write() { map.insert(name.clone(), decl); }
}
_ => {}
}
}
walk(ast, runtime);
}
collect_box_decls(&ast, &rt);
// Register user-defined factory
let mut shared = crate::interpreter::SharedState::new();
shared.box_declarations = rt.box_declarations.clone();
let udf = std::sync::Arc::new(crate::box_factory::user_defined::UserDefinedBoxFactory::new(shared));
if let Ok(mut reg) = rt.box_registry.lock() { reg.register(udf); }
rt
};
// Compile and execute
let mut compiler = crate::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
assert_eq!(result.to_string_box().value, "Hi, Bob");
}
#[test]
fn test_vm_fastpath_universal_type_and_equals() {
// toString/type/equals/cloneのうち、typeとequalsのfast-pathを検証

View File

@ -17,26 +17,12 @@ use super::vm::ControlFlow;
impl VM {
/// Build a PIC key from receiver and method identity
fn build_pic_key(&self, recv: &VMValue, method: &str, method_id: Option<u16>) -> String {
let rkey = match recv {
VMValue::Integer(_) => "Int",
VMValue::Float(_) => "Float",
VMValue::Bool(_) => "Bool",
VMValue::String(_) => "String",
VMValue::Future(_) => "Future",
VMValue::Void => "Void",
VMValue::BoxRef(b) => {
// Using dynamic type name as fingerprint (will migrate to numeric id later)
return if let Some(mid) = method_id {
format!("BoxRef:{}#{}", b.type_name(), mid)
} else {
format!("BoxRef:{}#{}", b.type_name(), method)
};
}
};
let label = self.cache_label_for_recv(recv);
let ver = self.cache_version_for_label(&label);
if let Some(mid) = method_id {
format!("{}#{}", rkey, mid)
format!("v{}:{}#{}", ver, label, mid)
} else {
format!("{}#{}", rkey, method)
format!("v{}:{}#{}", ver, label, method)
}
}
@ -66,7 +52,35 @@ impl VM {
/// Build vtable cache key for InstanceBox: TypeName#slot/arity
fn build_vtable_key(&self, class_name: &str, method_id: u16, arity: usize) -> String {
format!("VT:{}#{}{}", class_name, method_id, format!("/{}", arity))
// Use same versioning as PIC for BoxRef<Class>
let label = format!("BoxRef:{}", class_name);
let ver = self.cache_version_for_label(&label);
format!("VT@v{}:{}#{}{}", ver, class_name, method_id, format!("/{}", arity))
}
/// Compute cache label for a receiver
fn cache_label_for_recv(&self, recv: &VMValue) -> String {
match recv {
VMValue::Integer(_) => "Int".to_string(),
VMValue::Float(_) => "Float".to_string(),
VMValue::Bool(_) => "Bool".to_string(),
VMValue::String(_) => "String".to_string(),
VMValue::Future(_) => "Future".to_string(),
VMValue::Void => "Void".to_string(),
VMValue::BoxRef(b) => format!("BoxRef:{}", b.type_name()),
}
}
/// Get current version for a cache label (default 0)
fn cache_version_for_label(&self, label: &str) -> u32 {
// Prefer global cache versions so that loaders can invalidate across VMs
crate::runtime::cache_versions::get_version(label)
}
/// Bump version for a label (used to invalidate caches)
#[allow(dead_code)]
pub fn bump_cache_version(&mut self, label: &str) {
crate::runtime::cache_versions::bump_version(label)
}
/// Execute a constant instruction
pub(super) fn execute_const(&mut self, dst: ValueId, value: &ConstValue) -> Result<ControlFlow, VMError> {
@ -610,6 +624,59 @@ impl VM {
Ok(val.to_nyash_box())
})
.collect::<Result<Vec<_>, VMError>>()?;
// PluginBoxV2 fast-path via method_id -> direct invoke_fn (skip name->id resolution)
if let (Some(mid), VMValue::BoxRef(arc_box)) = (method_id, &recv) {
if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
// Encode TLV args (support: int, string, plugin handle)
let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(nyash_args.len() as u16);
let mut enc_failed = false;
for a in &nyash_args {
if let Some(s) = a.as_any().downcast_ref::<crate::box_trait::StringBox>() {
crate::runtime::plugin_ffi_common::encode::string(&mut tlv, &s.value);
} else if let Some(i) = a.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
crate::runtime::plugin_ffi_common::encode::i32(&mut tlv, i.value as i32);
} else if let Some(h) = a.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut tlv, h.inner.type_id, h.inner.instance_id);
} else {
enc_failed = true; break;
}
}
if !enc_failed {
let mut out = vec![0u8; 4096];
let mut out_len: usize = out.len();
let code = unsafe {
(p.inner.invoke_fn)(
p.inner.type_id,
mid as u32,
p.inner.instance_id,
tlv.as_ptr(),
tlv.len(),
out.as_mut_ptr(),
&mut out_len,
)
};
if code == 0 {
// Try decode TLV first entry (string/i32); else return void
let vm_out = if let Some((_tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
// naive: try string, then i32
let s = crate::runtime::plugin_ffi_common::decode::string(payload);
if !s.is_empty() {
VMValue::String(s)
} else if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) {
VMValue::Integer(v as i64)
} else {
VMValue::Void
}
} else {
VMValue::Void
};
if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); }
return Ok(ControlFlow::Continue);
}
}
}
}
if debug_boxcall {
self.debug_log_boxcall(&recv, method, &nyash_args, "START", None);