Phase 11.8/12: MIR Core-13 roadmap, Nyash ABI design, async/await enhancements with TaskGroupBox foundation

Major additions:
- Phase 11.8 MIR cleanup specification (Core-15→14→13 roadmap)
- Nyash ABI unified design document (3×u64 structure)
- TaskGroupBox foundation with cancelAll/joinAll methods
- Enhanced async/await with checkpoint auto-insertion
- Structured concurrency preparation (parent-child task relationships)

Documentation:
- docs/development/roadmap/phases/phase-11.8_mir_cleanup/: Complete Core-13 path
- docs/development/roadmap/phases/phase-12/NYASH-ABI-DESIGN.md: Unified ABI spec
- Updated Phase 12 README with AOT/JIT explanation for script performance
- Added async_task_system/ design docs

Implementation progress:
- FutureBox spawn tracking with weak/strong reference management
- VM checkpoint integration before/after await
- LLVM backend async support preparation
- Verifier rules for await-checkpoint enforcement
- Result<T,E> normalization for timeout/cancellation

Technical insights:
- MIR as 'atomic instructions', Box as 'molecules' philosophy
- 'Everything is Box' enables full-stack with minimal instructions
- Unified BoxCall for array/plugin/async operations future consolidation

Next steps:
- Complete TaskGroupBox implementation
- Migrate from global to scoped task management
- Implement LIFO cleanup on scope exit
- Continue Core-13 instruction consolidation

🚀 'From 15 atoms to infinite programs: The Nyash Box Theory'
This commit is contained in:
Moe Charm
2025-09-02 03:41:51 +09:00
parent 11506cee3b
commit c9366d5c54
37 changed files with 2203 additions and 90 deletions

View File

@ -779,6 +779,315 @@ pub extern "C" fn nyash_plugin_invoke_tagged_v_i64(
0
}
// 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::runtime::plugin_loader_v2::PluginBoxV2;
use nyash_rust::box_trait::{NyashBox, StringBox, IntegerBox};
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::<PluginBoxV2>() {
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::<PluginBoxV2>() {
// 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::<StringBox>() { 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::<IntegerBox>() { 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<dyn NyashBox>);
// 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;
}
}
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("<unknown>")));
}));
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::runtime::plugin_loader_v2::PluginBoxV2;
use nyash_rust::box_trait::{NyashBox, StringBox, IntegerBox};
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::<PluginBoxV2>() {
(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<String> = 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::<PluginBoxV2>() {
if p.box_type == "StringBox" {
let host = nyash_rust::runtime::get_global_plugin_host();
if let Ok(hg) = 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::<StringBox>() { 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<u8>, 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::<PluginBoxV2>() {
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::<StringBox>() { 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::<IntegerBox>() { 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<u8>, 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::<PluginBoxV2>() {
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::<StringBox>() { 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::<IntegerBox>() { 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<dyn NyashBox>);
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("<unknown>")));
return;
}
}));
handle as i64
}
// ---- Handle-based birth shims for AOT/JIT object linkage ----
// These resolve symbols like "nyash.string.birth_h" referenced by ObjectModule.