Phase 12: VM/JIT identical execution tests + host API slot routing

ChatGPT5による統一実行パス実装:
- VM/JIT同一実行テスト追加(Array/Map/String/Instance)
- host_api slot経由呼び出し(NYASH_JIT_HOST_BRIDGE=1)
- extern_registry拡張(console系メソッドslot登録)
- CI: vm-jit-identical.yml(STRICT/非STRICT両系テスト)
- InstanceBox getField/setField slot 1,2統一

技術的改善:
- JIT: ops_ext委譲による統一メソッド解決
- VM: vtable/PIC/名前ベースフォールバック階層
- host_bridge: TLV encode/decode BoxRef対応
- C ABI: nyrt_host_api.h外部公開ヘッダー

テスト追加:
- identical_exec_collections: Array/Map操作一致
- identical_exec_instance: ユーザー定義Box一致
- identical_exec_string: StringBox操作一致
- host_reverse_slot: 逆引きslot解決テスト

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-09-03 09:12:39 +09:00
parent 51e8e7582a
commit 773256380d
25 changed files with 988 additions and 37 deletions

View File

@ -18,10 +18,17 @@ static EXTERNS: Lazy<Vec<ExternSpec>> = Lazy::new(|| vec![
ExternSpec { iface: "env.console", method: "log", min_arity: 1, max_arity: 255, slot: Some(10) },
ExternSpec { iface: "env.console", method: "warn", min_arity: 1, max_arity: 255, slot: Some(10) },
ExternSpec { iface: "env.console", method: "error", min_arity: 1, max_arity: 255, slot: Some(10) },
ExternSpec { iface: "env.console", method: "info", min_arity: 1, max_arity: 255, slot: Some(10) },
ExternSpec { iface: "env.console", method: "debug", min_arity: 1, max_arity: 255, slot: Some(10) },
// debug
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, slot: Some(12) },
// task
ExternSpec { iface: "env.task", method: "cancelCurrent", min_arity: 0, max_arity: 0, slot: Some(30) },
ExternSpec { iface: "env.task", method: "currentToken", min_arity: 0, max_arity: 0, slot: Some(31) },
ExternSpec { iface: "env.task", method: "yieldNow", min_arity: 0, max_arity: 0, slot: Some(32) },
ExternSpec { iface: "env.task", method: "sleepMs", min_arity: 1, max_arity: 1, slot: Some(33) },
// future (scaffold)
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) },

View File

@ -81,7 +81,7 @@ fn encode_out(out_ptr: *mut u8, out_len: *mut usize, buf: &[u8]) -> i32 {
}
}
#[no_mangle]
#[cfg_attr(all(not(test), feature = "c-abi-export"), no_mangle)]
pub extern "C" fn nyrt_host_call_name(handle: u64, method_ptr: *const u8, method_len: usize,
args_ptr: *const u8, args_len: usize,
out_ptr: *mut u8, out_len: *mut usize) -> i32 {
@ -194,6 +194,8 @@ fn plugin_box_from_handle(_type_id: u32, _instance_id: u32) -> Option<std::sync:
// Minimal slot mapping (subject to consolidation with TypeRegistry):
// 1: InstanceBox.getField(name: string) -> any
// 2: InstanceBox.setField(name: string, value: any-primitive) -> bool
// 3: InstanceBox.has(name: string) -> bool
// 4: InstanceBox.size() -> i64
// 100: ArrayBox.get(index: i64) -> any
// 101: ArrayBox.set(index: i64, value: any) -> any
// 102: ArrayBox.len() -> i64
@ -203,7 +205,7 @@ fn plugin_box_from_handle(_type_id: u32, _instance_id: u32) -> Option<std::sync:
// 203: MapBox.get(key:any) -> any
// 204: MapBox.set(key:any, value:any) -> any
// 300: StringBox.len() -> i64
#[no_mangle]
#[cfg_attr(all(not(test), feature = "c-abi-export"), no_mangle)]
pub extern "C" fn nyrt_host_call_slot(handle: u64, selector_id: u64,
args_ptr: *const u8, args_len: usize,
out_ptr: *mut u8, out_len: *mut usize) -> i32 {
@ -223,7 +225,7 @@ pub extern "C" fn nyrt_host_call_slot(handle: u64, selector_id: u64,
}
match selector_id {
1 | 2 => {
1 | 2 | 3 | 4 => {
if let Some(inst) = recv_arc.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if selector_id == 1 {
// getField(name)
@ -243,7 +245,7 @@ pub extern "C" fn nyrt_host_call_slot(handle: u64, selector_id: u64,
let buf = tlv_encode_one(&out);
return encode_out(out_ptr, out_len, &buf);
}
} else {
} else if selector_id == 2 {
// setField(name, value)
if argv.len() >= 2 {
let field = match &argv[0] { crate::backend::vm::VMValue::String(s) => s.clone(), v => v.to_string() };
@ -260,6 +262,19 @@ pub extern "C" fn nyrt_host_call_slot(handle: u64, selector_id: u64,
let buf = tlv_encode_one(&crate::backend::vm::VMValue::Bool(true));
return encode_out(out_ptr, out_len, &buf);
}
} else if selector_id == 3 {
// has(name)
if argv.len() >= 1 {
let field = match &argv[0] { crate::backend::vm::VMValue::String(s) => s.clone(), v => v.to_string() };
let has = inst.get_field_unified(&field).is_some();
let buf = tlv_encode_one(&crate::backend::vm::VMValue::Bool(has));
return encode_out(out_ptr, out_len, &buf);
}
} else if selector_id == 4 {
// size()
let sz = inst.fields_ng.lock().map(|m| m.len() as i64).unwrap_or(0);
let buf = tlv_encode_one(&crate::backend::vm::VMValue::Integer(sz));
return encode_out(out_ptr, out_len, &buf);
}
}
}

View File

@ -34,12 +34,27 @@ const STRING_METHODS: &[MethodEntry] = &[
];
static STRINGBOX_TB: TypeBox = TypeBox::new_with("StringBox", STRING_METHODS);
// --- InstanceBox ---
// Representative methods exposed via unified slots for field access and diagnostics.
// 1: getField(name)
// 2: setField(name, value)
// 3: has(name)
// 4: size()
const INSTANCE_METHODS: &[MethodEntry] = &[
MethodEntry { name: "getField", arity: 1, slot: 1 },
MethodEntry { name: "setField", arity: 2, slot: 2 },
MethodEntry { name: "has", arity: 1, slot: 3 },
MethodEntry { name: "size", arity: 0, slot: 4 },
];
static INSTANCEBOX_TB: TypeBox = TypeBox::new_with("InstanceBox", INSTANCE_METHODS);
/// 型名から TypeBox を解決(雛形)。現在は常に None。
pub fn resolve_typebox_by_name(type_name: &str) -> Option<&'static TypeBox> {
match type_name {
"MapBox" => Some(&MAPBOX_TB),
"ArrayBox" => Some(&ARRAYBOX_TB),
"StringBox" => Some(&STRINGBOX_TB),
"InstanceBox" => Some(&INSTANCEBOX_TB),
_ => None,
}
}
@ -53,3 +68,12 @@ pub fn resolve_slot_by_name(type_name: &str, method: &str, arity: usize) -> Opti
}
None
}
/// Return list of known methods for a type (names only) for diagnostics.
pub fn known_methods_for(type_name: &str) -> Option<Vec<&'static str>> {
let tb = resolve_typebox_by_name(type_name)?;
let mut v: Vec<&'static str> = tb.methods.iter().map(|m| m.name).collect();
v.sort();
v.dedup();
Some(v)
}