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:
37
CLAUDE_WASM_TASK.md
Normal file
37
CLAUDE_WASM_TASK.md
Normal 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.
|
||||||
|
|
||||||
24
docs/development/design/extern-vs-boxcall.md
Normal file
24
docs/development/design/extern-vs-boxcall.md
Normal 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→PluginHost(extern_call)→必要に応じて host_api へ。
|
||||||
|
- JIT: 同じCシンボル群を直接リンクすることで一致挙動を確保。
|
||||||
|
|
||||||
|
- STRICT(厳格モード)
|
||||||
|
- `NYASH_ABI_STRICT=1` または `NYASH_EXTERN_STRICT=1` で未登録/未対応を明確なエラーに。
|
||||||
|
- vtable側は TypeRegistry に基づき未対応メソッドを検出。
|
||||||
|
- ExternCall側は Host/Loader が未登録なら明確な診断を返す。
|
||||||
|
|
||||||
|
- 最低限ハードコード
|
||||||
|
- print/console.log 等は ExternCall(env.console)側に限定して最小限のハードコード。
|
||||||
|
- BoxCall 側へのハードコードは避ける(最適化経路やキャッシュと混ざるのを防止)。
|
||||||
|
|
||||||
|
この方針により、最適化・キャッシュ・診断の責務範囲が鮮明になり、VM/JIT一致検証も行いやすくなる。
|
||||||
|
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
17
src/backend/wasm_v2/mod.rs
Normal file
17
src/backend/wasm_v2/mod.rs
Normal 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())
|
||||||
|
}
|
||||||
|
|
||||||
25
src/backend/wasm_v2/unified_dispatch.rs
Normal file
25
src/backend/wasm_v2/unified_dispatch.rs
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
18
src/backend/wasm_v2/vtable_codegen.rs
Normal file
18
src/backend/wasm_v2/vtable_codegen.rs
Normal 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 }
|
||||||
|
}
|
||||||
|
|
||||||
@ -91,6 +91,45 @@ fn main() {
|
|||||||
} else {
|
} else {
|
||||||
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!");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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") }
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user