📚 Phase 12: Nyashスクリプトプラグインシステム設計と埋め込みVM構想

## 主な成果
- Nyashスクリプトでプラグイン作成可能という革命的発見
- C ABI制約の分析と埋め込みVMによる解決策
- MIR/VM/JIT層での箱引数サポートの詳細分析

## ドキュメント作成
- Phase 12基本構想(README.md)
- Gemini/Codex先生の技術分析
- C ABIとの整合性問題と解決策
- 埋め込みVM実装ロードマップ
- 箱引数サポートの技術詳細

## 重要な洞察
- 制約は「リンク時にC ABI必要」のみ
- 埋め込みVMでMIRバイトコード実行により解決可能
- Nyashスクリプト→C ABIプラグイン変換が実現可能

Everything is Box → Everything is Plugin → Everything is Possible!
This commit is contained in:
Moe Charm
2025-08-30 22:52:16 +09:00
parent 7a0f9bd432
commit c13d9c045e
82 changed files with 5842 additions and 138 deletions

View File

@ -251,22 +251,38 @@ extern "C" fn nyash_plugin_invoke3_i64(type_id: i64, method_id: i64, argc: i64,
crate::jit::observe::trace_push(format!("i64.end rc={} out_len={} pre_ok={} post_ok={}", rc, out_len, pre_ok, post_ok));
if rc != 0 { return 0; }
let out_slice = unsafe { std::slice::from_raw_parts(out_ptr, out_len) };
if let Some((tag, sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
if trace { eprintln!("[JIT-SHIM i64] TLV tag={} sz={}", tag, sz); }
crate::jit::observe::trace_push(format!("i64.tlv tag={} sz={}", tag, sz));
match tag {
2 => { // I32
if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; }
}
3 => { // I64
if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; }
if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); }
}
1 => { // Bool
return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 };
}
5 => { // F64 → optional conversion to i64 when enabled
if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") {
if let Some((tag, sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
if trace { eprintln!("[JIT-SHIM i64] TLV tag={} sz={}", tag, sz); }
crate::jit::observe::trace_push(format!("i64.tlv tag={} sz={}", tag, sz));
match tag {
2 => { // I32
if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; }
}
3 => { // I64
if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; }
if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); }
}
8 => { // Handle(tag=8)
if sz == 8 {
let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]);
let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]);
let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i);
let box_type_name = crate::runtime::plugin_loader_unified::get_global_plugin_host()
.read().ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| m.into_iter().find(|(_k,v)| *v == r_type).map(|(k,_v)| k))
.unwrap_or_else(|| "PluginBox".to_string());
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke.unwrap());
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb);
let h = crate::jit::rt::handles::to_handle(arc);
return h as i64;
}
}
1 => { // Bool
return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 };
}
5 => { // F64 → optional conversion to i64 when enabled
if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") {
if sz == 8 {
let mut b=[0u8;8]; b.copy_from_slice(payload);
let f = f64::from_le_bytes(b);
@ -1084,6 +1100,22 @@ impl IRBuilder for CraneliftBuilder {
arg_vals.push(z);
}
// Ensure receiver (a0) is a runtime handle via nyash.handle.of (Handle-First)
{
use cranelift_module::Linkage;
use crate::jit::r#extern::handles as h;
let call_conv_h = self.module.isa().default_call_conv();
let mut sig_h = Signature::new(call_conv_h);
sig_h.params.push(AbiParam::new(types::I64));
sig_h.returns.push(AbiParam::new(types::I64));
let func_id_h = self.module
.declare_function(h::SYM_HANDLE_OF, Linkage::Import, &sig_h)
.expect("declare handle.of failed");
let fref_h = self.module.declare_func_in_func(func_id_h, fb.func);
let call_h = fb.ins().call(fref_h, &[arg_vals[0]]);
if let Some(rv) = fb.inst_results(call_h).get(0).copied() { arg_vals[0] = rv; }
}
// Choose f64 shim if allowlisted
let use_f64 = if has_ret {
if let Ok(list) = std::env::var("NYASH_JIT_PLUGIN_F64") {
@ -1136,6 +1168,23 @@ impl IRBuilder for CraneliftBuilder {
arg_vals.push(z);
}
// Ensure receiver (a0) is a runtime handle via nyash.handle.of
{
use cranelift_module::Linkage;
use crate::jit::r#extern::handles as h;
let call_conv_h = self.module.isa().default_call_conv();
let mut sig_h = Signature::new(call_conv_h);
sig_h.params.push(AbiParam::new(types::I64));
sig_h.returns.push(AbiParam::new(types::I64));
let func_id_h = self.module
.declare_function(h::SYM_HANDLE_OF, Linkage::Import, &sig_h)
.expect("declare handle.of failed");
let fref_h = self.module.declare_func_in_func(func_id_h, fb.func);
let call_h = fb.ins().call(fref_h, &[arg_vals[0]]);
// Replace a0 with handle result
if let Some(rv) = fb.inst_results(call_h).get(0).copied() { arg_vals[0] = rv; }
}
// Signature: (i64 argc, i64 a0, i64 a1, i64 a2) -> i64
let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(call_conv);
@ -1400,6 +1449,7 @@ pub struct ObjectBuilder {
desired_ret_is_f64: bool,
ret_hint_is_b1: bool,
local_slots: std::collections::HashMap<usize, cranelift_codegen::ir::StackSlot>,
block_param_counts: std::collections::HashMap<usize, usize>,
pub stats: (u64,u64,u64,u64,u64),
pub object_bytes: Option<Vec<u8>>,
}
@ -1435,6 +1485,7 @@ impl ObjectBuilder {
desired_ret_is_f64: false,
ret_hint_is_b1: false,
local_slots: std::collections::HashMap::new(),
block_param_counts: std::collections::HashMap::new(),
stats: (0,0,0,0,0),
object_bytes: None,
}
@ -1632,9 +1683,57 @@ impl IRBuilder for ObjectBuilder {
fn prepare_blocks(&mut self, count: usize) { use cranelift_frontend::FunctionBuilder; if count == 0 { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); if self.blocks.len() < count { for _ in 0..(count - self.blocks.len()) { self.blocks.push(fb.create_block()); } } fb.finalize(); }
fn switch_to_block(&mut self, index: usize) { use cranelift_frontend::FunctionBuilder; if index >= self.blocks.len() { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); fb.switch_to_block(self.blocks[index]); self.current_block_index = Some(index); fb.finalize(); }
fn seal_block(&mut self, index: usize) { use cranelift_frontend::FunctionBuilder; if index >= self.blocks.len() { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); fb.seal_block(self.blocks[index]); fb.finalize(); }
fn br_if_top_is_true(&mut self, _: usize, _: usize) { /* control-flow omitted for AOT PoC */ }
fn ensure_block_params_i64(&mut self, _index: usize, _count: usize) { /* PHI omitted for AOT PoC */ }
fn push_block_param_i64_at(&mut self, _pos: usize) { /* omitted */ }
fn br_if_top_is_true(&mut self, then_index: usize, else_index: usize) {
use cranelift_codegen::ir::{types, condcodes::IntCC};
use cranelift_frontend::FunctionBuilder;
if then_index >= self.blocks.len() || else_index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
let cond_b1 = if let Some(v) = self.value_stack.pop() {
let ty = fb.func.dfg.value_type(v);
if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v }
} else {
let z = fb.ins().iconst(types::I64, 0);
fb.ins().icmp_imm(IntCC::NotEqual, z, 0)
};
fb.ins().brif(cond_b1, self.blocks[then_index], &[], self.blocks[else_index], &[]);
self.stats.3 += 1;
fb.finalize();
}
fn ensure_block_params_i64(&mut self, index: usize, count: usize) {
use cranelift_frontend::FunctionBuilder;
if index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let have = self.block_param_counts.get(&index).copied().unwrap_or(0);
if count > have {
let b = self.blocks[index];
for _ in have..count { let _ = fb.append_block_param(b, cranelift_codegen::ir::types::I64); }
self.block_param_counts.insert(index, count);
}
fb.finalize();
}
fn push_block_param_i64_at(&mut self, pos: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() };
fb.switch_to_block(b);
let params = fb.func.dfg.block_params(b).to_vec();
if let Some(v) = params.get(pos).copied() { self.value_stack.push(v); }
else { let z = fb.ins().iconst(types::I64, 0); self.value_stack.push(z); }
fb.finalize();
}
fn jump_to(&mut self, target_index: usize) {
use cranelift_frontend::FunctionBuilder;
if target_index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); }
else if let Some(b) = self.entry_block { fb.switch_to_block(b); }
fb.ins().jump(self.blocks[target_index], &[]);
self.stats.3 += 1;
fb.finalize();
}
fn hint_ret_bool(&mut self, is_b1: bool) { self.ret_hint_is_b1 = is_b1; }
fn ensure_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{StackSlotData, StackSlotKind}; use cranelift_frontend::FunctionBuilder; if self.local_slots.contains_key(&index) { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let slot = fb.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8)); self.local_slots.insert(index, slot); fb.finalize(); }
fn store_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{types, condcodes::IntCC}; use cranelift_frontend::FunctionBuilder; if let Some(mut v) = self.value_stack.pop() { if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } let slot = self.local_slots.get(&index).copied(); let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let ty = fb.func.dfg.value_type(v); if ty != types::I64 { if ty == types::F64 { v = fb.ins().fcvt_to_sint(types::I64, v); } else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); let b1 = fb.ins().icmp_imm(IntCC::NotEqual, v, 0); v = fb.ins().select(b1, one, zero); } } if let Some(slot) = slot { fb.ins().stack_store(v, slot, 0); } fb.finalize(); } }
@ -1653,7 +1752,8 @@ impl CraneliftBuilder {
builder.symbol("nyash.host.stub0", nyash_host_stub0 as *const u8);
{
use crate::jit::r#extern::collections as c;
use super::extern_thunks::{nyash_plugin_invoke_name_getattr_i64, nyash_plugin_invoke_name_call_i64};
use crate::jit::r#extern::{handles as h, birth as b};
use super::extern_thunks::{nyash_plugin_invoke_name_getattr_i64, nyash_plugin_invoke_name_call_i64, nyash_handle_of, nyash_box_birth_h, nyash_box_birth_i64};
builder.symbol(c::SYM_ARRAY_LEN, nyash_array_len as *const u8);
builder.symbol(c::SYM_ARRAY_GET, nyash_array_get as *const u8);
builder.symbol(c::SYM_ARRAY_SET, nyash_array_set as *const u8);
@ -1683,6 +1783,10 @@ impl CraneliftBuilder {
builder.symbol(c::SYM_STRING_CHARCODE_AT_H, nyash_string_charcode_at_h as *const u8);
builder.symbol(c::SYM_STRING_BIRTH_H, nyash_string_birth_h as *const u8);
builder.symbol(c::SYM_INTEGER_BIRTH_H, nyash_integer_birth_h as *const u8);
builder.symbol(b::SYM_BOX_BIRTH_H, nyash_box_birth_h as *const u8);
builder.symbol("nyash.box.birth_i64", nyash_box_birth_i64 as *const u8);
// Handle helpers
builder.symbol(h::SYM_HANDLE_OF, nyash_handle_of as *const u8);
// Plugin invoke shims (i64/f64)
builder.symbol("nyash_plugin_invoke3_i64", nyash_plugin_invoke3_i64 as *const u8);
builder.symbol("nyash_plugin_invoke3_f64", nyash_plugin_invoke3_f64 as *const u8);

View File

@ -467,6 +467,12 @@ impl LowerCore {
}
if let Some(v) = self.known_i64.get(id).copied() {
b.emit_const_i64(v);
return;
}
// Load from a local slot if this ValueId was previously materialized (e.g., handle results)
if let Some(slot) = self.local_index.get(id).copied() {
b.load_local_i64(slot);
return;
}
}
@ -534,36 +540,61 @@ impl LowerCore {
}
}
I::NewBox { dst, box_type, args } => {
// Minimal JIT lowering for builtin pluginized boxes: birth() via handle-based shim
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") && args.is_empty() {
let bt = box_type.as_str();
match bt {
"StringBox" => {
// Emit host-call to create a new StringBox handle; push as i64
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_BIRTH_H, 0, true);
}
"IntegerBox" => {
b.emit_host_call(crate::jit::r#extern::collections::SYM_INTEGER_BIRTH_H, 0, true);
}
_ => {
// Any other NewBox (e.g., ArrayBox/MapBox/etc.) is UNSUPPORTED in JIT for now
// Allow plugin boxes to be created at runtime; treat as no-op for lowering
if bt != "PyRuntimeBox" && bt != "StringBox" && bt != "ConsoleBox" { self.unsupported += 1; }
}
}
} else {
// NewBox with args or NYASH_USE_PLUGIN_BUILTINS!=1
// Special-case: IntegerBox(v) → track known i64, but do not treat as unsupported
if box_type == "IntegerBox" {
if let Some(src) = args.get(0) { if let Some(iv) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, iv); } }
// no-op lowering; avoid marking unsupported
} else if box_type == "PyRuntimeBox" && args.is_empty() {
// Allow PyRuntimeBox creation as no-op in strict AOT path
} else if box_type == "StringBox" || box_type == "ConsoleBox" {
// Allow StringBox creation (with/without arg) as no-op; valueはシム/実行時にTLVへ
// 最適化は後段へ(現状は汎用・安全な実装に徹する)
// 通常経路:
// - 引数なし: 汎用 birth_htype_idのみでハンドル生成
// - 引数あり: 既存のチェーン(直後の plugin_invoke birth で初期化)を維持(段階的導入)
if args.is_empty() {
let decision = crate::jit::policy::invoke::decide_box_method(box_type, "birth", 0, true);
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = decision {
b.emit_const_i64(type_id as i64);
b.emit_host_call(crate::jit::r#extern::birth::SYM_BOX_BIRTH_H, 1, true);
self.handle_values.insert(*dst);
let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
} else {
self.unsupported += 1;
}
} else {
// 引数あり: 安全なパターンから段階的に birth_i64 に切替
// 1) IntegerBox(const i64)
if box_type == "IntegerBox" && args.len() == 1 {
if let Some(src) = args.get(0) {
if let Some(iv) = self.known_i64.get(src).copied() {
// 汎用 birth_i64(type_id, argc=1, a1=iv)
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = crate::jit::policy::invoke::decide_box_method(box_type, "birth", 1, true) {
b.emit_const_i64(type_id as i64);
b.emit_const_i64(1);
b.emit_const_i64(iv);
b.emit_host_call("nyash.box.birth_i64", 3, true);
self.handle_values.insert(*dst);
let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
// 値伝搬も継続
self.known_i64.insert(*dst, iv);
return Ok(());
}
}
}
}
// 2) 引数がハンドルStringBox等で既に存在する場合最大2引数
if args.len() <= 2 && args.iter().all(|a| self.handle_values.contains(a)) {
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = crate::jit::policy::invoke::decide_box_method(box_type, "birth", args.len(), true) {
b.emit_const_i64(type_id as i64);
b.emit_const_i64(args.len() as i64);
// a1, a2 を pushローカルに保存済みのハンドルをロード
for a in args.iter().take(2) { self.push_value_if_known_or_param(b, a); }
b.emit_host_call("nyash.box.birth_i64", 2 + args.len(), true);
self.handle_values.insert(*dst);
let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
return Ok(());
}
}
// フォールバック: 既存チェーンに委譲(互換)+ 既知値伝搬のみ
if box_type == "IntegerBox" {
if let Some(src) = args.get(0) { if let Some(iv) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, iv); } }
}
}
// Track boxed numeric literals to aid signature checks (FloatBox/IntegerBox)
if box_type == "FloatBox" {
@ -603,15 +634,27 @@ impl LowerCore {
let argc = 1 + args.len();
// push receiver param index (a0) if known
if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
// push primary arguments if availablea1, a2 ...
for a in args.iter() { self.push_value_if_known_or_param(b, a); }
b.emit_plugin_invoke_by_name(m, argc, dst.is_some());
if let Some(d) = dst { self.handle_values.insert(*d); }
if let Some(d) = dst {
self.handle_values.insert(*d);
// Store handle result into a local slot so it can be used as argument later
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
} else if self.handle_values.contains(box_val) && (m == "getattr" || m == "call") {
let argc = 1 + args.len();
// push receiver handle/param index if possible (here receiver is a handle result previously returned)
// We cannot reconstruct handle here; pass -1 to allow shim fallback.
b.emit_const_i64(-1);
for a in args.iter() { self.push_value_if_known_or_param(b, a); }
b.emit_plugin_invoke_by_name(m, argc, dst.is_some());
if let Some(d) = dst { self.handle_values.insert(*d); }
if let Some(d) = dst {
self.handle_values.insert(*d);
let slot = *self.local_index.entry(*d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
} else if (bt == "PyRuntimeBox" && (m == "birth" || m == "eval"))
|| (bt == "IntegerBox" && m == "birth")
|| (bt == "StringBox" && m == "birth")

View File

@ -11,6 +11,124 @@ use crate::runtime::plugin_loader_unified;
#[cfg(feature = "cranelift-jit")]
use crate::runtime::plugin_loader_v2::PluginBoxV2;
// ---- Generic Birth (handle) ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_box_birth_h(type_id: i64) -> i64 {
// Map type_id -> type name and create via plugin host; return runtime handle
if type_id <= 0 { return 0; }
let tid = type_id as u32;
let name_opt = crate::runtime::plugin_loader_unified::get_global_plugin_host()
.read().ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| m.into_iter().find(|(_k,v)| *v == tid).map(|(k,_v)| k));
if let Some(box_type) = name_opt {
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
if let Ok(b) = host.create_box(&box_type, &[]) {
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(b);
let h = crate::jit::rt::handles::to_handle(arc);
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": box_type, "type_id": tid, "handle": h}), "hostcall", "<jit>");
return h as i64;
} else {
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": box_type, "type_id": tid}), "hostcall", "<jit>");
}
}
} else {
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "type_map_failed", "type_id": tid}), "hostcall", "<jit>");
}
0
}
// Generic birth with args on JIT side: (type_id, argc, a1, a2) -> handle
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_box_birth_i64(type_id: i64, argc: i64, a1: i64, a2: i64) -> i64 {
use crate::runtime::plugin_loader_v2::PluginBoxV2;
if type_id <= 0 { return 0; }
// Resolve invoke for the type by creating a temp instance
let mut invoke: Option<unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize)->i32> = None;
let mut box_type = String::new();
if let Some(name) = crate::runtime::plugin_loader_unified::get_global_plugin_host()
.read().ok()
.and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone()))
.and_then(|m| m.into_iter().find(|(_k,v)| *v == (type_id as u32)).map(|(k,_v)| k))
{
box_type = name;
if let Ok(host) = crate::runtime::get_global_plugin_host().read() {
if let Ok(b) = host.create_box(&box_type, &[]) {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() { invoke = Some(p.inner.invoke_fn); }
}
}
}
if invoke.is_none() {
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "no_invoke", "type_id": type_id}), "hostcall", "<jit>");
return 0;
}
let method_id: u32 = 0; let instance_id: u32 = 0;
// Build TLV from a1/a2
let nargs = argc.max(0) as usize;
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(nargs as u16);
let mut encode_val = |h: i64| {
if h > 0 {
if let Some(obj) = crate::jit::rt::handles::get(h as u64) {
if let Some(p) = obj.as_any().downcast_ref::<PluginBoxV2>() {
let host = crate::runtime::get_global_plugin_host();
if let Ok(hg) = host.read() {
if p.box_type == "StringBox" {
if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) {
if let Some(s) = sb.as_any().downcast_ref::<crate::box_trait::StringBox>() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; }
}
} else if p.box_type == "IntegerBox" {
if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) {
if let Some(i) = ibx.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); return; }
}
}
}
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id());
return;
}
}
}
crate::runtime::plugin_ffi_common::encode::i64(&mut buf, h);
};
if nargs >= 1 { encode_val(a1); }
if nargs >= 2 { encode_val(a2); }
// Invoke
let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len();
let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
if rc != 0 { events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "invoke_failed", "type_id": type_id}), "hostcall", "<jit>"); return 0; }
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
if tag == 8 && payload.len()==8 {
let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]);
let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i);
let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke.unwrap());
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::new(pb);
let h = crate::jit::rt::handles::to_handle(arc);
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "box_type": box_type, "type_id": type_id, "argc": nargs, "handle": h}), "hostcall", "<jit>");
return h as i64;
}
}
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "decode_failed", "type_id": type_id}), "hostcall", "<jit>");
0
}
// ---- Handle helpers ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_handle_of(v: i64) -> i64 {
// If already a positive handle, pass through
if v > 0 { return v; }
// Otherwise interpret as legacy param index and convert BoxRef -> handle
if v >= 0 {
let idx = v as usize;
let mut out: i64 = 0;
crate::jit::rt::with_legacy_vm_args(|args| {
if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) {
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(b.clone());
out = crate::jit::rt::handles::to_handle(arc) as i64;
}
});
return out;
}
0
}
// ---- Math (native f64) ----
#[cfg(feature = "cranelift-jit")]
pub(super) extern "C" fn nyash_math_sin_f64(x: f64) -> f64 { x.sin() }
@ -243,7 +361,7 @@ pub(super) extern "C" fn nyash_plugin_invoke_name_call_i64(argc: i64, a0: i64, a
nyash_plugin_invoke_name_common_i64("call", argc, a0, a1, a2)
}
#[cfg(feature = "cranelift-jit")]
fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, _a1: i64, _a2: i64) -> i64 {
fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64, a2: i64) -> i64 {
// Resolve receiver
let mut instance_id: u32 = 0;
let mut type_id: u32 = 0;
@ -280,60 +398,87 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, _a1: i6
}
});
}
if invoke.is_none() { return 0; }
if invoke.is_none() { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "error": "no_invoke"}), "hostcall", "<jit>"); return 0; }
let box_type = box_type.unwrap_or_default();
// Resolve method_id via PluginHost
let mh = if let Ok(host) = plugin_loader_unified::get_global_plugin_host().read() { host.resolve_method(&box_type, method) } else { return 0 };
let method_id = match mh { Ok(h) => h.method_id, Err(_) => return 0 } as u32;
// Build TLV args from legacy (skip receiver=pos0)
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header((argc.saturating_sub(1).max(0) as u16));
let mut add_from_legacy = |pos: usize| {
crate::jit::rt::with_legacy_vm_args(|args| {
if let Some(v) = args.get(pos) {
use crate::backend::vm::VMValue as V;
match v {
V::String(s) => crate::runtime::plugin_ffi_common::encode::string(&mut buf, s),
V::Integer(i) => crate::runtime::plugin_ffi_common::encode::i64(&mut buf, *i),
V::Float(f) => crate::runtime::plugin_ffi_common::encode::f64(&mut buf, *f),
V::Bool(b) => crate::runtime::plugin_ffi_common::encode::bool(&mut buf, *b),
V::BoxRef(b) => {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
let host = crate::runtime::get_global_plugin_host();
if let Ok(hg) = host.read() {
if p.box_type == "StringBox" {
if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) {
if let Some(s) = sb.as_any().downcast_ref::<crate::box_trait::StringBox>() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; }
}
} else if p.box_type == "IntegerBox" {
if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) {
if let Some(i) = ibx.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); return; }
}
}
let mh = if let Ok(host) = plugin_loader_unified::get_global_plugin_host().read() { host.resolve_method(&box_type, method) } else { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "host_read_failed"}), "hostcall", "<jit>"); return 0 };
let method_id = match mh { Ok(h) => h.method_id, Err(_) => { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "resolve_failed"}), "hostcall", "<jit>"); return 0 } } as u32;
// Build TLV args from a1/a2 preferring handles; fallback to legacy (skip receiver=pos0)
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(argc.saturating_sub(1).max(0) as u16);
let mut encode_arg = |val: i64, pos: usize| {
let mut appended = false;
if val > 0 {
if let Some(obj) = crate::jit::rt::handles::get(val as u64) {
if let Some(p) = obj.as_any().downcast_ref::<PluginBoxV2>() {
let host = crate::runtime::get_global_plugin_host();
if let Ok(hg) = host.read() {
if p.box_type == "StringBox" {
if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) {
if let Some(s) = sb.as_any().downcast_ref::<crate::box_trait::StringBox>() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); appended = true; }
}
} else if p.box_type == "IntegerBox" {
if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) {
if let Some(i) = ibx.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); appended = true; }
}
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id());
} else {
let s = b.to_string_box().value; crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s)
}
}
_ => {}
if !appended { crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id()); appended = true; }
}
}
});
}
if !appended {
// Fallback: encode from legacy VM args at position
crate::jit::rt::with_legacy_vm_args(|args| {
if let Some(v) = args.get(pos) {
use crate::backend::vm::VMValue as V;
match v {
V::String(s) => crate::runtime::plugin_ffi_common::encode::string(&mut buf, s),
V::Integer(i) => crate::runtime::plugin_ffi_common::encode::i64(&mut buf, *i),
V::Float(f) => crate::runtime::plugin_ffi_common::encode::f64(&mut buf, *f),
V::Bool(b) => crate::runtime::plugin_ffi_common::encode::bool(&mut buf, *b),
V::BoxRef(b) => {
if let Some(p) = b.as_any().downcast_ref::<PluginBoxV2>() {
let host = crate::runtime::get_global_plugin_host();
if let Ok(hg) = host.read() {
if p.box_type == "StringBox" {
if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) {
if let Some(s) = sb.as_any().downcast_ref::<crate::box_trait::StringBox>() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; }
}
} else if p.box_type == "IntegerBox" {
if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) {
if let Some(i) = ibx.as_any().downcast_ref::<crate::box_trait::IntegerBox>() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); return; }
}
}
}
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id());
} else {
let s = b.to_string_box().value; crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s)
}
}
_ => {}
}
} else {
// No legacy arg: encode raw i64 as last resort
crate::runtime::plugin_ffi_common::encode::i64(&mut buf, val);
}
});
}
};
if argc >= 2 { add_from_legacy(1); }
if argc >= 3 { add_from_legacy(2); }
if argc >= 2 { encode_arg(a1, 1); }
if argc >= 3 { encode_arg(a2, 2); }
let mut out = vec![0u8; 4096]; let mut out_len: usize = out.len();
let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) };
if rc != 0 { return 0; }
if rc != 0 { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "invoke_failed"}), "hostcall", "<jit>"); return 0; }
let out_slice = &out[..out_len];
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) {
match tag {
3 => { if payload.len()==8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } }
1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; }
5 => { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref()==Some("1") { if payload.len()==8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f=f64::from_le_bytes(b); return f as i64; } } }
_ => {}
_ => { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "warn": "first_tlv_not_primitive_or_handle", "tag": tag}), "hostcall", "<jit>"); }
}
}
events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "decode_failed"}), "hostcall", "<jit>");
0
}