Phase 12: add wasm_v2 scaffold (unified vtable slots), expand host by-slot (Map/String), STRICT extern diagnostics, identical-exec console.log test, and CLAUDE_WASM_TASK handoff

This commit is contained in:
Moe Charm
2025-09-03 05:41:33 +09:00
parent 53d88157aa
commit 0722b410a1
12 changed files with 319 additions and 4 deletions

View File

@ -20,6 +20,8 @@ pub mod mir_interpreter; // Lightweight MIR interpreter
pub mod wasm;
#[cfg(feature = "wasm-backend")]
pub mod aot;
#[cfg(feature = "wasm-backend")]
pub mod wasm_v2;
#[cfg(feature = "llvm")]
pub mod llvm;

View File

@ -208,6 +208,14 @@ pub struct VM {
pub(super) instr_counter: std::collections::HashMap<&'static str, usize>,
/// Execution start time for optional stats
pub(super) exec_start: Option<Instant>,
/// Stats: number of BoxCall hits via VTable path
pub(super) boxcall_hits_vtable: u64,
/// Stats: number of BoxCall hits via Poly-PIC path
pub(super) boxcall_hits_poly_pic: u64,
/// Stats: number of BoxCall hits via Mono-PIC path
pub(super) boxcall_hits_mono_pic: u64,
/// Stats: number of BoxCall hits via generic fallback path
pub(super) boxcall_hits_generic: u64,
/// Mono-PIC skeleton: global hit counters keyed by (recv_type, method_id/name)
pub(super) boxcall_pic_hits: std::collections::HashMap<String, u32>,
/// Mono-PIC: cached direct targets (currently InstanceBox function name)
@ -372,6 +380,10 @@ impl VM {
module: None,
instr_counter: std::collections::HashMap::new(),
exec_start: None,
boxcall_hits_vtable: 0,
boxcall_hits_poly_pic: 0,
boxcall_hits_mono_pic: 0,
boxcall_hits_generic: 0,
boxcall_pic_hits: std::collections::HashMap::new(),
boxcall_pic_funcname: std::collections::HashMap::new(),
boxcall_poly_pic: std::collections::HashMap::new(),
@ -403,6 +415,10 @@ impl VM {
module: None,
instr_counter: std::collections::HashMap::new(),
exec_start: None,
boxcall_hits_vtable: 0,
boxcall_hits_poly_pic: 0,
boxcall_hits_mono_pic: 0,
boxcall_hits_generic: 0,
boxcall_pic_hits: std::collections::HashMap::new(),
boxcall_pic_funcname: std::collections::HashMap::new(),
boxcall_poly_pic: std::collections::HashMap::new(),
@ -494,8 +510,9 @@ impl VM {
let hits_total: u64 = self.boxcall_pic_hits.values().map(|v| *v as u64).sum();
let vt_entries = self.boxcall_vtable_funcname.len();
eprintln!(
"[VM] PIC/VT summary: poly_sites={} avg_entries={:.2} mono_sites={} hits_total={} vt_entries={}",
sites_poly, avg_entries, sites_mono, hits_total, vt_entries
"[VM] PIC/VT summary: poly_sites={} avg_entries={:.2} mono_sites={} hits_total={} vt_entries={} | hits: vt={} poly={} mono={} generic={}",
sites_poly, avg_entries, sites_mono, hits_total, vt_entries,
self.boxcall_hits_vtable, self.boxcall_hits_poly_pic, self.boxcall_hits_mono_pic, self.boxcall_hits_generic
);
// Top sites by hits (up to 5)
let mut hits: Vec<(&String, &u32)> = self.boxcall_pic_hits.iter().collect();

View File

@ -645,7 +645,12 @@ impl VM {
}
}
Err(_) => {
return Err(VMError::InvalidInstruction(format!("ExternCall failed: {}.{}", iface_name, method_name)));
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)));
} else {
return Err(VMError::InvalidInstruction(format!("ExternCall failed: {}.{}", iface_name, method_name)));
}
}
}
Ok(ControlFlow::Continue)
@ -883,6 +888,7 @@ impl VM {
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.setField");
}
let cloned_box = arc_box.share_box();
self.boxcall_hits_generic = self.boxcall_hits_generic.saturating_add(1);
let out = self.call_box_method(cloned_box, m, nyash_args)?;
let vm_out = VMValue::from_nyash_box(out);
if let Some(dst_id) = dst { self.set_value(dst_id, vm_out); }
@ -912,6 +918,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) {
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());
for a in args { vm_args.push(self.get_value(*a)?); }
@ -921,6 +928,7 @@ impl VM {
}
// Fallback to Mono-PIC (legacy) if present
if let Some(func_name) = self.boxcall_pic_funcname.get(&pic_key).cloned() {
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());
vm_args.push(recv.clone());
@ -1126,6 +1134,7 @@ impl VM {
// MapBox: size/len/has/get
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);
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));
@ -1141,6 +1150,7 @@ impl VM {
VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
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));
@ -1157,6 +1167,7 @@ impl VM {
VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
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));
@ -1166,6 +1177,7 @@ impl VM {
// 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);
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));
@ -1179,6 +1191,7 @@ impl VM {
VMValue::Float(f) => Box::new(crate::box_trait::IntegerBox::new(f as i64)),
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);
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));
@ -1202,6 +1215,7 @@ impl VM {
VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
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));
@ -1211,6 +1225,7 @@ impl VM {
// StringBox: len
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);
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

@ -0,0 +1,17 @@
//! WASM Backend v2 (Phase 12 scaffolding)
//!
//! 目的:
//! - vtable/スロット解決に基づく統一ディスパッチ経路の雛形
//! - 既存ビルドに影響を与えない最小構成feature/target gate
#![cfg(feature = "wasm-backend")]
pub mod unified_dispatch;
pub mod vtable_codegen;
/// エントリポイントの雛形
pub fn compile_and_execute_v2(_module: &crate::mir::MirModule, _temp_name: &str) -> Result<Box<dyn crate::box_trait::NyashBox>, String> {
// まだ未実装: vtable_codegenで生成したスロット表を unified_dispatch 経由で実行
Err("wasm_v2: not implemented (scaffold)".to_string())
}

View File

@ -0,0 +1,25 @@
//! Unified dispatch (WASM v2)
//!
//! - TypeRegistryのスロット表と一致させた呼び出し分岐の雛形
//! - ここではあくまで「どのスロットに行くか」の判定のみ提供
#![cfg(feature = "wasm-backend")]
use crate::box_trait::NyashBox;
/// 受信ボックス/メソッド名/アリティからスロットを解決し、識別子を返す。
pub fn resolve_slot(recv: &dyn NyashBox, method: &str, arity: usize) -> Option<u16> {
let ty = recv.type_name();
crate::runtime::type_registry::resolve_slot_by_name(ty, method, arity)
}
/// 実際の呼び出し分岐は、将来的にここから生成済みのstubsに委譲する予定。
pub fn dispatch_by_slot(
_slot: u16,
_recv: &dyn NyashBox,
_args: &[Box<dyn NyashBox>],
) -> Option<Box<dyn NyashBox>> {
// 未実装: wasm_v2ではJS/hostへのブリッジや、Wasm内の簡易実装に委譲
None
}

View File

@ -0,0 +1,18 @@
//! VTable codegen stubs (WASM v2)
//!
//! - 将来的にTypeRegistryから静的テーブル/インライン分岐を生成
//! - 現段階はプレースホルダ
#![cfg(feature = "wasm-backend")]
/// 生成結果のメタ情報(雛形)
pub struct GeneratedVTableInfo {
pub types: usize,
pub methods: usize,
}
pub fn generate_tables() -> GeneratedVTableInfo {
// 未実装: TypeRegistry::resolve_typebox_by_name()/methods を走査して集計
GeneratedVTableInfo { types: 0, methods: 0 }
}