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

37
CLAUDE_WASM_TASK.md Normal file
View File

@ -0,0 +1,37 @@
Title: WASM Backend v2 (Phase 12) Minimal Handoff
Goal
- Implement a new `src/backend/wasm_v2/` backend that aligns with the unified vtable/slot dispatch model, without touching MIR-level semantics.
Scope
- Allowed:
- `src/backend/wasm_v2/*` (mod.rs, unified_dispatch.rs, vtable_codegen.rs)
- `src/backend/wasm/*` read-only for reference
- `projects/nyash-wasm/*` (demo HTML/JS harness)
- Documentation under `docs/backend/wasm/*.md`
- Avoid (to keep merge easy):
- MIR layer (no plugin-name hardcoding)
- `src/runtime/*` core (HostHandle/host_api/extern are assumed stable)
- JIT/VM files unless behind feature/cfg guards
Design Constraints
- ExternCall and BoxCall remain separate at the MIR/semantics level.
- At lower layers, converge onto a unified host-call shim when needed (same C symbols as VM/JIT).
- Console/print: use `ExternCall(env.console.log)`; do not move to BoxCall.
- vtable/slots must align with `src/runtime/type_registry.rs` (see Array/Map/String slots).
Targets & Flags
- Target: `wasm32-unknown-unknown`
- Feature: `--features wasm-backend`
- Use `#[cfg(feature = "wasm-backend")]` and/or `#[cfg(target_arch = "wasm32")]` to isolate new code.
Acceptance (Minimal)
- Build succeeds for non-wasm targets (no regressions).
- wasm_v2 compiles behind `--features wasm-backend` (even as stubs).
- A demo harness can call `env.console.log` from WASM and produce a message.
- (Nice to have) Array/Map len/size/has/get stubs go through unified dispatch.
Next Steps (Optional)
- Implement unified dispatch bridge to JS host for `env.console` and basic collections.
- Add a minimal test or demo comparing VM vs WASM return values for a simple program.

View File

@ -0,0 +1,24 @@
ExternCall vs BoxCall: 分離設計の理由(要約)
- 目的: VM/JIT間で同一挙動を担保しつつ、最適化や診断を明確にするため、ExternCall と BoxCall を上位で分離、下位で合流する。
- 上位MIR/意味論)
- ExternCall: env.*IO/タスク/デバッグ/チェックポイント等を表現。EffectMaskで最適化境界を明示。
- BoxCall: 型ディスパッチvtable→PIC→汎用。副作用はBox内部に閉じやすい。
- 下位VM/JIT実装/ABI
- 可能な限り共通のHostCall基盤へ合流Cシンボル、HostHandle、TLV
- VM: ExternCall→PluginHostextern_call→必要に応じて host_api へ。
- JIT: 同じCシンボル群を直接リンクすることで一致挙動を確保。
- STRICT厳格モード
- `NYASH_ABI_STRICT=1` または `NYASH_EXTERN_STRICT=1` で未登録/未対応を明確なエラーに。
- vtable側は TypeRegistry に基づき未対応メソッドを検出。
- ExternCall側は Host/Loader が未登録なら明確な診断を返す。
- 最低限ハードコード
- print/console.log 等は ExternCallenv.console側に限定して最小限のハードコード。
- BoxCall 側へのハードコードは避ける(最適化経路やキャッシュと混ざるのを防止)。
この方針により、最適化・キャッシュ・診断の責務範囲が鮮明になり、VM/JIT一致検証も行いやすくなる。

View File

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

View File

@ -208,6 +208,14 @@ pub struct VM {
pub(super) instr_counter: std::collections::HashMap<&'static str, usize>, pub(super) instr_counter: std::collections::HashMap<&'static str, usize>,
/// Execution start time for optional stats /// Execution start time for optional stats
pub(super) exec_start: Option<Instant>, 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) /// Mono-PIC skeleton: global hit counters keyed by (recv_type, method_id/name)
pub(super) boxcall_pic_hits: std::collections::HashMap<String, u32>, pub(super) boxcall_pic_hits: std::collections::HashMap<String, u32>,
/// Mono-PIC: cached direct targets (currently InstanceBox function name) /// Mono-PIC: cached direct targets (currently InstanceBox function name)
@ -372,6 +380,10 @@ impl VM {
module: None, module: None,
instr_counter: std::collections::HashMap::new(), instr_counter: std::collections::HashMap::new(),
exec_start: None, 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_hits: std::collections::HashMap::new(),
boxcall_pic_funcname: std::collections::HashMap::new(), boxcall_pic_funcname: std::collections::HashMap::new(),
boxcall_poly_pic: std::collections::HashMap::new(), boxcall_poly_pic: std::collections::HashMap::new(),
@ -403,6 +415,10 @@ impl VM {
module: None, module: None,
instr_counter: std::collections::HashMap::new(), instr_counter: std::collections::HashMap::new(),
exec_start: None, 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_hits: std::collections::HashMap::new(),
boxcall_pic_funcname: std::collections::HashMap::new(), boxcall_pic_funcname: std::collections::HashMap::new(),
boxcall_poly_pic: 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 hits_total: u64 = self.boxcall_pic_hits.values().map(|v| *v as u64).sum();
let vt_entries = self.boxcall_vtable_funcname.len(); let vt_entries = self.boxcall_vtable_funcname.len();
eprintln!( eprintln!(
"[VM] PIC/VT summary: poly_sites={} avg_entries={:.2} mono_sites={} 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 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) // Top sites by hits (up to 5)
let mut hits: Vec<(&String, &u32)> = self.boxcall_pic_hits.iter().collect(); let mut hits: Vec<(&String, &u32)> = self.boxcall_pic_hits.iter().collect();

View File

@ -645,7 +645,12 @@ impl VM {
} }
} }
Err(_) => { 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) Ok(ControlFlow::Continue)
@ -883,6 +888,7 @@ impl VM {
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.setField"); crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "BoxCall.setField");
} }
let cloned_box = arc_box.share_box(); 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 out = self.call_box_method(cloned_box, m, nyash_args)?;
let vm_out = VMValue::from_nyash_box(out); let vm_out = VMValue::from_nyash_box(out);
if let Some(dst_id) = dst { self.set_value(dst_id, vm_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 let VMValue::BoxRef(arc_box) = &recv {
if arc_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>().is_some() { 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 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()); let mut vm_args = Vec::with_capacity(1 + args.len());
vm_args.push(recv.clone()); vm_args.push(recv.clone());
for a in args { vm_args.push(self.get_value(*a)?); } for a in args { vm_args.push(self.get_value(*a)?); }
@ -921,6 +928,7 @@ impl VM {
} }
// Fallback to Mono-PIC (legacy) if present // Fallback to Mono-PIC (legacy) if present
if let Some(func_name) = self.boxcall_pic_funcname.get(&pic_key).cloned() { 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 // Build VM args: receiver first, then original args
let mut vm_args = Vec::with_capacity(1 + args.len()); let mut vm_args = Vec::with_capacity(1 + args.len());
vm_args.push(recv.clone()); vm_args.push(recv.clone());
@ -1126,6 +1134,7 @@ impl VM {
// MapBox: size/len/has/get // MapBox: size/len/has/get
if let Some(map) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() { if let Some(map) = b.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if matches!(slot, Some(200|201)) { if matches!(slot, Some(200|201)) {
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
let out = map.size(); let out = map.size();
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue)); return Some(Ok(ControlFlow::Continue));
@ -1141,6 +1150,7 @@ impl VM {
VMValue::Future(ref fut) => Box::new(fut.clone()), VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), 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); let out = map.has(key_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue)); return Some(Ok(ControlFlow::Continue));
@ -1157,6 +1167,7 @@ impl VM {
VMValue::Future(ref fut) => Box::new(fut.clone()), VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), 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); let out = map.get(key_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue)); return Some(Ok(ControlFlow::Continue));
@ -1166,6 +1177,7 @@ impl VM {
// ArrayBox: get/set/len // ArrayBox: get/set/len
if let Some(arr) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() { if let Some(arr) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if matches!(slot, Some(102)) { if matches!(slot, Some(102)) {
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
let out = arr.length(); let out = arr.length();
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue)); 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::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)), 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); let out = arr.get(idx_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue)); return Some(Ok(ControlFlow::Continue));
@ -1202,6 +1215,7 @@ impl VM {
VMValue::Future(ref fut) => Box::new(fut.clone()), VMValue::Future(ref fut) => Box::new(fut.clone()),
VMValue::Void => Box::new(crate::box_trait::VoidBox::new()), 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); let out = arr.set(idx_box, val_box);
if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Some(Ok(ControlFlow::Continue)); return Some(Ok(ControlFlow::Continue));
@ -1211,6 +1225,7 @@ impl VM {
// StringBox: len // StringBox: len
if let Some(sb) = b.as_any().downcast_ref::<crate::box_trait::StringBox>() { if let Some(sb) = b.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if matches!(slot, Some(300)) { 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); 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))); } if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(Box::new(out))); }
return Some(Ok(ControlFlow::Continue)); 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 }
}

View File

@ -92,5 +92,44 @@ fn main() {
println!(" get(slot=100) failed with code {}", code2); println!(" get(slot=100) failed with code {}", code2);
} }
// MapBox slots test: set/get/has/size
println!("\nReverse host-call (by-slot) MapBox test:");
let map = nyash_rust::boxes::map_box::MapBox::new();
let map_h = nyash_rust::runtime::host_handles::to_handle_box(Box::new(map));
// set("k","v") → slot=204
let mut tlv_set = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(2);
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_set, "k");
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_set, "v");
let mut out_s = vec![0u8; 256];
let mut out_s_len: usize = out_s.len();
let code_s = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 204, tlv_set.as_ptr(), tlv_set.len(), out_s.as_mut_ptr(), &mut out_s_len) };
println!(" set(slot=204) -> code={}, out_len={}", code_s, out_s_len);
// get("k") → slot=203
let mut tlv_get = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(1);
nyash_rust::runtime::plugin_ffi_common::encode::string(&mut tlv_get, "k");
let mut out_g = vec![0u8; 256];
let mut out_g_len: usize = out_g.len();
let code_g = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 203, tlv_get.as_ptr(), tlv_get.len(), out_g.as_mut_ptr(), &mut out_g_len) };
if code_g == 0 {
if let Some((tag, _sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out_g[..out_g_len]) {
if tag == 6 || tag == 7 {
let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload);
println!(" get(slot=203) -> '{}'", s);
} else {
println!(" get(slot=203) -> tag={}, size={}", tag, _sz);
}
}
}
// has("k") → slot=202
let mut out_hb = vec![0u8; 16];
let mut out_hb_len: usize = out_hb.len();
let code_hb = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 202, tlv_get.as_ptr(), tlv_get.len(), out_hb.as_mut_ptr(), &mut out_hb_len) };
println!(" has(slot=202) -> code={}, out_len={}", code_hb, out_hb_len);
// size() → slot=200
let mut out_sz = vec![0u8; 32];
let mut out_sz_len: usize = out_sz.len();
let code_sz = unsafe { nyash_rust::runtime::host_api::nyrt_host_call_slot(map_h, 200, std::ptr::null(), 0, out_sz.as_mut_ptr(), &mut out_sz_len) };
println!(" size(slot=200) -> code={}, out_len={}", code_sz, out_sz_len);
println!("\nTest completed!"); println!("\nTest completed!");
} }

View File

@ -120,3 +120,6 @@ pub fn rewrite_future() -> bool { std::env::var("NYASH_REWRITE_FUTURE").ok().as_
// ---- Phase 12: Nyash ABI (vtable) toggles ---- // ---- Phase 12: Nyash ABI (vtable) toggles ----
pub fn abi_vtable() -> bool { std::env::var("NYASH_ABI_VTABLE").ok().as_deref() == Some("1") } pub fn abi_vtable() -> bool { std::env::var("NYASH_ABI_VTABLE").ok().as_deref() == Some("1") }
pub fn abi_strict() -> bool { std::env::var("NYASH_ABI_STRICT").ok().as_deref() == Some("1") } pub fn abi_strict() -> bool { std::env::var("NYASH_ABI_STRICT").ok().as_deref() == Some("1") }
// ---- ExternCall strict diagnostics ----
pub fn extern_strict() -> bool { std::env::var("NYASH_EXTERN_STRICT").ok().as_deref() == Some("1") }

View File

@ -197,6 +197,12 @@ fn plugin_box_from_handle(_type_id: u32, _instance_id: u32) -> Option<std::sync:
// 100: ArrayBox.get(index: i64) -> any // 100: ArrayBox.get(index: i64) -> any
// 101: ArrayBox.set(index: i64, value: any) -> any // 101: ArrayBox.set(index: i64, value: any) -> any
// 102: ArrayBox.len() -> i64 // 102: ArrayBox.len() -> i64
// 200: MapBox.size() -> i64
// 201: MapBox.len() -> i64
// 202: MapBox.has(key:any) -> bool
// 203: MapBox.get(key:any) -> any
// 204: MapBox.set(key:any, value:any) -> any
// 300: StringBox.len() -> i64
#[no_mangle] #[no_mangle]
pub extern "C" fn nyrt_host_call_slot(handle: u64, selector_id: u64, pub extern "C" fn nyrt_host_call_slot(handle: u64, selector_id: u64,
args_ptr: *const u8, args_len: usize, args_ptr: *const u8, args_len: usize,
@ -296,6 +302,86 @@ pub extern "C" fn nyrt_host_call_slot(handle: u64, selector_id: u64,
} }
} }
} }
200 | 201 | 202 | 203 | 204 => {
if let Some(map) = recv_arc.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
match selector_id {
200 | 201 => {
let out = map.size();
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
let buf = tlv_encode_one(&vmv);
return encode_out(out_ptr, out_len, &buf);
}
202 => { // has(key)
if argv.len() >= 1 {
let key_box: Box<dyn NyashBox> = match argv[0].clone() {
crate::backend::vm::VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)),
crate::backend::vm::VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)),
crate::backend::vm::VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)),
crate::backend::vm::VMValue::String(s) => Box::new(crate::box_trait::StringBox::new(s)),
crate::backend::vm::VMValue::BoxRef(b) => b.share_box(),
crate::backend::vm::VMValue::Future(fu) => Box::new(fu),
crate::backend::vm::VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
let out = map.has(key_box);
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
let buf = tlv_encode_one(&vmv);
return encode_out(out_ptr, out_len, &buf);
}
}
203 => { // get(key)
if argv.len() >= 1 {
let key_box: Box<dyn NyashBox> = match argv[0].clone() {
crate::backend::vm::VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)),
crate::backend::vm::VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)),
crate::backend::vm::VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)),
crate::backend::vm::VMValue::String(s) => Box::new(crate::box_trait::StringBox::new(s)),
crate::backend::vm::VMValue::BoxRef(b) => b.share_box(),
crate::backend::vm::VMValue::Future(fu) => Box::new(fu),
crate::backend::vm::VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
let out = map.get(key_box);
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
let buf = tlv_encode_one(&vmv);
return encode_out(out_ptr, out_len, &buf);
}
}
204 => { // set(key, value)
if argv.len() >= 2 {
let key_box: Box<dyn NyashBox> = match argv[0].clone() {
crate::backend::vm::VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)),
crate::backend::vm::VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)),
crate::backend::vm::VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)),
crate::backend::vm::VMValue::String(s) => Box::new(crate::box_trait::StringBox::new(s)),
crate::backend::vm::VMValue::BoxRef(b) => b.share_box(),
crate::backend::vm::VMValue::Future(fu) => Box::new(fu),
crate::backend::vm::VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
let val_box: Box<dyn NyashBox> = match argv[1].clone() {
crate::backend::vm::VMValue::Integer(i) => Box::new(crate::box_trait::IntegerBox::new(i)),
crate::backend::vm::VMValue::Float(f) => Box::new(crate::boxes::math_box::FloatBox::new(f)),
crate::backend::vm::VMValue::Bool(b) => Box::new(crate::box_trait::BoolBox::new(b)),
crate::backend::vm::VMValue::String(s) => Box::new(crate::box_trait::StringBox::new(s)),
crate::backend::vm::VMValue::BoxRef(b) => b.share_box(),
crate::backend::vm::VMValue::Future(fu) => Box::new(fu),
crate::backend::vm::VMValue::Void => Box::new(crate::box_trait::VoidBox::new()),
};
let out = map.set(key_box, val_box);
let vmv = crate::backend::vm::VMValue::from_nyash_box(out);
let buf = tlv_encode_one(&vmv);
return encode_out(out_ptr, out_len, &buf);
}
}
_ => {}
}
}
}
300 => {
if let Some(sb) = recv_arc.as_any().downcast_ref::<crate::box_trait::StringBox>() {
let out = crate::backend::vm::VMValue::Integer(sb.value.len() as i64);
let buf = tlv_encode_one(&out);
return encode_out(out_ptr, out_len, &buf);
}
}
_ => {} _ => {}
} }
-10 -10

View File

@ -40,5 +40,37 @@ mod tests {
assert_eq!(vm_s, jit_s, "VM and JIT results should match"); assert_eq!(vm_s, jit_s, "VM and JIT results should match");
} }
}
#[cfg(feature = "cranelift-jit")]
#[test]
fn identical_vm_and_jit_console_log_side_effect_free() {
// Build: const 1; extern_call env.console.log(1); return 1
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirInstruction, EffectMask};
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: crate::mir::MirType::Integer, effects: EffectMask::PURE };
let mut func = MirFunction::new(sig, crate::mir::BasicBlockId::new(0));
let bb = func.entry_block;
let v0 = func.next_value_id();
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v0, value: crate::mir::ConstValue::Integer(1) });
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: vec![v0],
effects: EffectMask::IO,
});
func.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(v0) });
let mut module = MirModule::new("identical_console".into());
module.add_function(func);
// Run VM
let mut vm = VM::new();
let vm_out = vm.execute_module(&module).expect("VM exec");
let vm_s = vm_out.to_string_box().value;
// Run JIT (Cranelift minimal) — ExternCallはスキップされる想定
let jit_out = crate::backend::cranelift_compile_and_execute(&module, "identical_console").expect("JIT exec");
let jit_s = jit_out.to_string_box().value;
assert_eq!(vm_s, jit_s, "VM and JIT results should match despite console.log side effects");
}
}