// Spawn a plugin instance method asynchronously and return a Future handle (i64) // Exported as: nyash.future.spawn_method_h(type_id, method_id, argc, recv_h, vals*, tags*) -> i64 (FutureBox handle) #[export_name = "nyash.future.spawn_method_h"] pub extern "C" fn nyash_future_spawn_method_h( type_id: i64, method_id: i64, argc: i64, recv_h: i64, vals: *const i64, tags: *const i64, ) -> i64 { use nyash_rust::box_trait::{IntegerBox, NyashBox, StringBox}; use nyash_rust::runtime::plugin_loader_v2::PluginBoxV2; if recv_h <= 0 { return 0; } // Resolve receiver invoke let mut instance_id: u32 = 0; let mut real_type_id: u32 = type_id as u32; let mut invoke: Option< unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, > = None; if let Some(obj) = nyash_rust::jit::rt::handles::get(recv_h as u64) { if let Some(p) = obj.as_any().downcast_ref::() { instance_id = p.instance_id(); real_type_id = p.inner.type_id; invoke = Some(p.inner.invoke_fn); } } if invoke.is_none() { return 0; } // Build TLV from tagged arrays (argc includes receiver) let nargs = argc.saturating_sub(1).max(0) as usize; let mut buf = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(nargs as u16); let vals_slice = if !vals.is_null() && nargs > 0 { unsafe { std::slice::from_raw_parts(vals, nargs) } } else { &[] }; let tags_slice = if !tags.is_null() && nargs > 0 { unsafe { std::slice::from_raw_parts(tags, nargs) } } else { &[] }; for i in 0..nargs { let v = vals_slice.get(i).copied().unwrap_or(0); let t = tags_slice.get(i).copied().unwrap_or(3); // default i64 match t { 3 => nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, v), 5 => { let bits = v as u64; let f = f64::from_bits(bits); nyash_rust::runtime::plugin_ffi_common::encode::f64(&mut buf, f); } 8 => { if v > 0 { if let Some(obj) = nyash_rust::jit::rt::handles::get(v as u64) { if let Some(p) = obj.as_any().downcast_ref::() { // Try common coercions: String/Integer to TLV primitives let host = nyash_rust::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::() { nyash_rust::runtime::plugin_ffi_common::encode::string( &mut buf, &s.value, ); continue; } } } 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::() { nyash_rust::runtime::plugin_ffi_common::encode::i64( &mut buf, i.value, ); continue; } } } } nyash_rust::runtime::plugin_ffi_common::encode::plugin_handle( &mut buf, p.inner.type_id, p.instance_id(), ); } else { let s = obj.to_string_box().value; nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, &s); } } else { nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, v); } } else { nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, 0); } } _ => nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, v), } } // Prepare FutureBox and register handle let fut_box = std::sync::Arc::new(nyash_rust::boxes::future::FutureBox::new()); let handle = nyash_rust::jit::rt::handles::to_handle(fut_box.clone() as std::sync::Arc); // Copy data for async task let mut cap: usize = 512; let tlv = buf.clone(); let inv = invoke.unwrap(); nyash_rust::runtime::global_hooks::spawn_task( "nyash.future.spawn_method_h", Box::new(move || { // Growable output buffer loop let mut out = vec![0u8; cap]; let mut out_len: usize = out.len(); let rc = unsafe { inv( real_type_id, method_id as u32, instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len, ) }; if rc != 0 { // Set simple error string on failure fut_box.set_result(Box::new(StringBox::new(format!("invoke_failed rc={}", rc)))); return; } let slice = &out[..out_len]; if let Some((tag, sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(slice) { match tag { 3 => { // I64 if payload.len() == 8 { let mut b = [0u8; 8]; b.copy_from_slice(payload); let n = i64::from_le_bytes(b); fut_box.set_result(Box::new(IntegerBox::new(n))); return; } } 2 => { if let Some(v) = nyash_rust::runtime::plugin_ffi_common::decode::i32(payload) { fut_box.set_result(Box::new(IntegerBox::new(v as i64))); return; } } 1 => { // Bool let v = nyash_rust::runtime::plugin_ffi_common::decode::bool(payload) .unwrap_or(false); fut_box.set_result(Box::new(nyash_rust::box_trait::BoolBox::new(v))); return; } 5 => { // F64 if payload.len() == 8 { let mut b = [0u8; 8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); fut_box.set_result(Box::new( nyash_rust::boxes::math_box::FloatBox::new(f), )); return; } } 6 | 7 => { // String/Bytes as string let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload); fut_box.set_result(Box::new(StringBox::new(s))); return; } 8 => { // Handle -> PluginBoxV2 boxed 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); // Map type_id -> box type name (best-effort) let box_type_name = nyash_rust::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 = nyash_rust::runtime::plugin_loader_v2::construct_plugin_box( box_type_name, r_type, inv, r_inst, None, ); fut_box.set_result(Box::new(pb)); return; } } 9 => { // Void fut_box.set_result(Box::new(nyash_rust::box_trait::VoidBox::new())); return; } _ => {} } } // Fallback: store raw buffer as string preview fut_box.set_result(Box::new(StringBox::new(""))); }), ); handle as i64 } // Simpler spawn shim for JIT: pass argc(total explicit args incl. method_name), // receiver handle (a0), method name (a1), and first payload (a2). Extra args // are read from legacy VM args, same as plugin_invoke3_*. // Returns a handle (i64) to FutureBox. #[export_name = "nyash.future.spawn_instance3_i64"] pub extern "C" fn nyash_future_spawn_instance3_i64(a0: i64, a1: i64, a2: i64, argc: i64) -> i64 { use nyash_rust::box_trait::{IntegerBox, NyashBox, StringBox}; use nyash_rust::runtime::plugin_loader_v2::PluginBoxV2; if a0 <= 0 { return 0; } // Resolve receiver invoke and type id/name let (instance_id, real_type_id, invoke) = if let Some(obj) = nyash_rust::jit::rt::handles::get(a0 as u64) { if let Some(p) = obj.as_any().downcast_ref::() { (p.instance_id(), p.inner.type_id, Some(p.inner.invoke_fn)) } else { (0, 0, None) } } else { (0, 0, None) }; if invoke.is_none() { return 0; } let invoke = invoke.unwrap(); // Determine box type name from type_id let box_type_name = nyash_rust::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 == real_type_id) .map(|(k, _v)| k) }) .unwrap_or_else(|| "PluginBox".to_string()); // Determine method name string (from a1 handle→StringBox, or a1 as C string pointer, or legacy VM args) let mut method_name: Option = None; if a1 > 0 { if let Some(obj) = nyash_rust::jit::rt::handles::get(a1 as u64) { if let Some(p) = obj.as_any().downcast_ref::() { if p.box_type == "StringBox" { // Limit the lifetime of the read guard to this inner block by avoiding an outer binding if let Ok(hg) = nyash_rust::runtime::get_global_plugin_host().read() { if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { if let Some(s) = sb.as_any().downcast_ref::() { method_name = Some(s.value.clone()); } } } } } } // If not a handle, try to decode as C string pointer (LLVM path) if method_name.is_none() { let cptr = a1 as *const i8; if !cptr.is_null() { unsafe { if let Ok(cs) = std::ffi::CStr::from_ptr(cptr).to_str() { method_name = Some(cs.to_string()); } } } } } if method_name.is_none() { nyash_rust::jit::rt::with_legacy_vm_args(|args| { // method name is explicit arg position 1 (after receiver) if let Some(nyash_rust::backend::vm::VMValue::String(s)) = args.get(1) { method_name = Some(s.clone()); } }); } let method_name = match method_name { Some(s) => s, None => return 0, }; // Resolve method_id via PluginHost let mh_opt = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host() .read() .ok() .and_then(|h| h.resolve_method(&box_type_name, &method_name).ok()); let method_id = if let Some(mh) = mh_opt { mh.method_id } else { 0 }; if method_id == 0 { /* dynamic plugins may use 0 for birth; disallow here */ } // Build TLV args for payload (excluding method name) let nargs_total = argc.max(0) as usize; // includes method_name let nargs_payload = nargs_total.saturating_sub(1); let mut buf = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(nargs_payload as u16); let mut encode_from_legacy_into = |dst: &mut Vec, pos: usize| { nyash_rust::jit::rt::with_legacy_vm_args(|args| { if let Some(v) = args.get(pos) { use nyash_rust::backend::vm::VMValue; match v { VMValue::String(s) => { nyash_rust::runtime::plugin_ffi_common::encode::string(dst, s) } VMValue::Integer(i) => { nyash_rust::runtime::plugin_ffi_common::encode::i64(dst, *i) } VMValue::Float(f) => { nyash_rust::runtime::plugin_ffi_common::encode::f64(dst, *f) } VMValue::Bool(b) => { nyash_rust::runtime::plugin_ffi_common::encode::bool(dst, *b) } VMValue::BoxRef(b) => { if let Some(p) = b.as_any().downcast_ref::() { let host = nyash_rust::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::() { nyash_rust::runtime::plugin_ffi_common::encode::string( dst, &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::() { nyash_rust::runtime::plugin_ffi_common::encode::i64( dst, i.value, ); return; } } } } nyash_rust::runtime::plugin_ffi_common::encode::plugin_handle( dst, p.inner.type_id, p.instance_id(), ); return; } // Fallback: stringify let s = b.to_string_box().value; nyash_rust::runtime::plugin_ffi_common::encode::string(dst, &s); } _ => {} } } }); }; let mut encode_arg_into = |dst: &mut Vec, val: i64, pos: usize| { let mut appended = false; if val > 0 { if let Some(obj) = nyash_rust::jit::rt::handles::get(val as u64) { if let Some(p) = obj.as_any().downcast_ref::() { let host = nyash_rust::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::() { nyash_rust::runtime::plugin_ffi_common::encode::string( dst, &s.value, ); appended = true; 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::() { nyash_rust::runtime::plugin_ffi_common::encode::i64( dst, i.value, ); appended = true; return; } } } } nyash_rust::runtime::plugin_ffi_common::encode::plugin_handle( dst, p.inner.type_id, p.instance_id(), ); appended = true; return; } } } let before = dst.len(); encode_from_legacy_into(dst, pos); if dst.len() != before { appended = true; } if !appended { nyash_rust::runtime::plugin_ffi_common::encode::i64(dst, val); } }; // a1 is method name; payload starts at position 2 if nargs_payload >= 1 { encode_arg_into(&mut buf, a2, 2); } if nargs_payload > 1 && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1") { for pos in 3..=nargs_payload { encode_from_legacy_into(&mut buf, pos); } } // Create Future and schedule async invoke let fut_box = std::sync::Arc::new(nyash_rust::boxes::future::FutureBox::new()); let handle = nyash_rust::jit::rt::handles::to_handle(fut_box.clone() as std::sync::Arc); let tlv = buf.clone(); nyash_rust::runtime::global_hooks::spawn_task( "nyash.future.spawn_instance3_i64", Box::new(move || { // Dynamic output buffer with growth let mut cap: usize = 512; loop { let mut out = vec![0u8; cap]; let mut out_len: usize = out.len(); let rc = unsafe { invoke( real_type_id, method_id as u32, instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len, ) }; if rc == -1 || out_len > cap { cap = cap.saturating_mul(2).max(out_len + 16); if cap > 1 << 20 { break; } continue; } if rc != 0 { fut_box .set_result(Box::new(StringBox::new(format!("invoke_failed rc={}", rc)))); return; } let slice = &out[..out_len]; if let Some((tag, sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(slice) { match tag { 3 => { if payload.len() == 8 { let mut b = [0u8; 8]; b.copy_from_slice(payload); let n = i64::from_le_bytes(b); fut_box.set_result(Box::new(IntegerBox::new(n))); return; } } 1 => { let v = nyash_rust::runtime::plugin_ffi_common::decode::bool(payload) .unwrap_or(false); fut_box.set_result(Box::new(nyash_rust::box_trait::BoolBox::new(v))); return; } 5 => { if payload.len() == 8 { let mut b = [0u8; 8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); fut_box.set_result(Box::new( nyash_rust::boxes::math_box::FloatBox::new(f), )); return; } } 6 | 7 => { let s = nyash_rust::runtime::plugin_ffi_common::decode::string(payload); fut_box.set_result(Box::new(StringBox::new(s))); return; } 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 pb = nyash_rust::runtime::plugin_loader_v2::construct_plugin_box( box_type_name.clone(), r_type, invoke, r_inst, None, ); fut_box.set_result(Box::new(pb)); return; } } 9 => { fut_box.set_result(Box::new(nyash_rust::box_trait::VoidBox::new())); return; } _ => {} } } fut_box.set_result(Box::new(StringBox::new(""))); return; } }), ); handle as i64 }