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

@ -11,6 +11,78 @@ Docs: docs/development/roadmap/phases/phase-11.7_jit_complete/{README.md, PLAN.m
以降は下記の旧計画LLVM準備をアーカイブ参照。スモークやツールは必要箇所を段階で引継ぎ。
次の候補(再開時)
- spawn を本当の非同期化TLV化Scheduler投入
- JIT/EXE用 nyash.future.spawn_method_h C-ABI 追加
Async Task System (Structured Concurrency)
- Spec/Plan: docs/development/roadmap/phases/phase-11.7_jit_complete/async_task_system/{SPEC.md, PLAN.md}
- 目的: Nyash→MIR→VM→JIT/EXE まで一貫した構造化並行を実現TaskGroup/Future を Box化
- 進捗P1: FutureBox を Mutex+Condvar 化、await は safepoint+timeoutNYASH_AWAIT_MAX_MSで無限待ち防止済み。
Update (2025-09-01 late / Async P2 skeleton + P3 await.Result unify)
- P2雛形
- CancellationToken 型cancel/is_cancelledを追加: src/runtime/scheduler.rs
- Scheduler::spawn_with_token / global_hooks::spawn_task_with_token現状はspawnへ委譲
- TaskGroupBox 雛形: src/boxes/task_group_box.rsAPI: new/cancel_all/is_cancelled
- P3awaitのResult化・第一弾
- VM Await: Futureの値を NyashResultBox::Ok で返すsrc/backend/vm_instructions.rs
- env.future.awaitVM/Unified: Ok(value)Err("Timeout") を返すtimeoutは NYASH_AWAIT_MAX_MS 既定5秒
- JIT Await: await_h は常にハンドル返却→ nyash.result.ok_h で Ok(handle) にラップ
- 追加: src/jit/extern/result.rsSYM_RESULT_OK_Hlowerで await 後に ok_h を差し込み
- Smokes/安全ガード
- tools/smoke_async_spawn.shtimeout 10s + NYASH_AWAIT_MAX_MS=5000。apps/tests/async-spawn-instance は nowait/awaitの安全版
- ハングした場合もOS timeoutで残存プロセスを避ける
次の実装順(合意があれば即着手)
1) Phase 2: VM 経路への最小配線(暗黙 TaskGroup
- nowait の着地点を TaskGroup.current().spawn(...) に切替(内部は現行 spawn_instance を踏む)
- scope 終了時の cancelAll→joinAllLIFOは雛形から段階導入まずは no-op フック)
2) Phase 3: JIT 側の Err 統一
- nyash.result.err_h(handle_or_msg) をNyRT/JITに追加し、timeout/キャンセル時に Err 化await ラッパで分岐)
- 0/None フォールバックを撤去VM/Externは実装済みJITを揃える
3) Verifier: await 前後の checkpoint 検証
- Lowerer/パスで ExternCall(env.runtime.checkpoint) の前後挿入・検証ルールを追加
4) CI/Smokes 最終化
- async-await-min, async-spawn-instanceVM/JIT、timeout系await_timeoutの3本を最小温存
- {vm,jit} × {default,strict} で timeout(10s) ガード
Update (2025-09-01 PM / Semantics unify + Async Phase-2 prep)
- Semantics layer in place and adopted by Interpreter/VM/JIT
- New: `src/runtime/semantics.rs` with `coerce_to_string` / `coerce_to_i64` and unified `+` ordering
- Interpreter/VM now delegate to semantics (string concat matches VM path; plugin String toUtf8→toString→fallback)
- JIT: added hostcall `nyash.semantics.add_hh` (handle,handle) for dynamic add; wired in builder/core
- Cross-backend smokes (Script/VM/JIT via VM+jit-exec)
- `apps/tests/mir-branch-ret` → 1 (all three)
- `apps/tests/semantics-unified` → 0 (concat/numeric semantics aligned across backends)
- Tool: `tools/apps_tri_backend_smoke.sh` (summarizes Result lines for 3 modes)
- Async Phase2 (front): minimal spawn API and await path
- Exposed `env.future.spawn_instance(recv, method_name, args...) -> FutureBox`
- For now resolves synchronously (safe baseline). Hooked to `PluginHost::invoke_instance_method`
- `env.runtime.checkpoint` now calls `global_hooks::safepoint_and_poll()` for future scheduler integration
- `env.future.await(value)` passthrough unless value is FutureBox (then wait_and_get)
- `apps/tests/async-await-min`: uses `nowait fut = 42; await fut;` → VM/JIT return 42; Interpreter runs (CLI does not print standardized Result line)
Delta to code
- Added: `src/runtime/semantics.rs`
- Updated: `src/interpreter/expressions/operators.rs`, `src/backend/vm_values.rs` → use semantics layer
- JIT: `src/jit/lower/{builder.rs, core_ops.rs, extern_thunks.rs}`, `src/jit/extern/collections.rs`
- NyRT CABI: `crates/nyrt/src/lib.rs` exported `nyash.semantics.add_hh`
- Runtime externs: `src/runtime/plugin_loader_v2.rs` added `env.future.spawn_instance`, improved `env.runtime.checkpoint`
- Hooks/Scheduler: `src/runtime/global_hooks.rs` (safepoint+poll, spawn_task stub), `src/runtime/scheduler.rs` (singlethread scheduler API scaffold present)
- Smokes/tools: `apps/tests/semantics-unified`, `apps/tests/gc-sync-stress`, `tools/apps_tri_backend_smoke.sh`
Open items / Next steps (proposed)
1) True async spawn (Phase2 complete)
- Change `spawn_instance` to queue a task (Scheduler) instead of inline: capture `recv(type_id/instance_id)`, `method_name`, and TLVencoded args
- On run: decode TLV and `invoke_instance_method`, set Future result; ensure safepoint via `checkpoint`
- Add NyRT CABI `nyash.future.spawn_method_h` for JIT/EXE and wire lowering
2) Interpreter CLI result normalization (optional)
- Align “Result:” line for static box Main return to make Script mode consistent with VM/JIT in smokes
3) Keep smokes minimal (avoid bloat); current trio is sufficient for gating
Update (2025-09-01 AM / JIT handoff follow-up)
- Cranelift 最小JITの下地は進捗良好LowerCore→CraneliftBuilder 経路)

View File

@ -0,0 +1,15 @@
// async-spawn-instance smoke
// Create an ArrayBox with 3 elements, then spawn length() asynchronously
// and await the future. Expect 3.
static box Main {
main() {
local arr = new ArrayBox()
arr.push(1)
arr.push(2)
arr.push(3)
nowait fut = arr.length()
local result = await fut
return result
}
}

View File

@ -0,0 +1,27 @@
// @env NYASH_PLUGIN_ONLY=1
// @env NYASH_AWAIT_MAX_MS=500
// Demo: TaskGroupBox.joinAll() scaffold
// Note: In plugins-only mode, new TaskGroupBox() may be a no-op; this demo
// focuses on end-of-program joinAll via runner and explicit call safety.
local fut, arr
// Prepare a plugin-backed Array and spawn a method via env.future.spawn_instance
arr = new ArrayBox()
arr.push(1)
arr.push(2)
arr.push(3)
// nowait: simulate async by invoking plugin via env.future.spawn_instance
// Note: In current pipeline, MIR builder lowers nowait ... to env.future.spawn_instance
nowait fut = arr.length()
// Optionally wait with timeout (builder inserts safepoint before/after)
local r
r = await fut
// Runner will best-effort join all registered futures at program end
// Print result for visual confirmation
print("Result: " + r)

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.

View File

@ -1,3 +1,98 @@
# 🎯 CURRENT TASK - 2025-09-01 SnapshotAsync Task System / Phase 11.7 + Plugin-First
このスナップショットは Phase 11.7 の Async Task System 進捗を反映しました。詳細仕様/計画は下記を参照。
- SPEC: docs/development/roadmap/phases/phase-11.7_jit_complete/async_task_system/SPEC.md
- PLAN: docs/development/roadmap/phases/phase-11.7_jit_complete/async_task_system/PLAN.md
## ✅ Async Task System進捗サマリ
- P1 完了: FutureBox を Mutex+Condvar化。await は safepoint + timeout でハング抑止。
- VM/Unified: `env.future.await``Result.Ok(value)` / `Result.Err("Timeout")` を返却。
- JIT: `await_h``result.ok_h` ラップ済。さらに `result.err_h` 追加と `Ok/Err` 分岐を Lowerer に実装。
- 修正: FutureBox クローンを共有化Arc<Inner>し、spawn側のsetterと呼び出し側のFutureが同一待機点を共有。
- P2着手・足場: CancellationToken / TaskGroup雛形
- VM 経路: `env.future.spawn_instance``spawn_task_with_token(current_group_token(), ..)` に配線no-opトークン
- 付随: 暗黙グループの子Futureをグローバル登録best-effortし、簡易joinAllの足場global_hooks.join_all_registered_futures
- TaskGroupBox: `cancelAll()`/`joinAll(ms?)` をVM BoxCallで受付plugins-only環境では new は不可)。
- Runner終端: `NYASH_JOIN_ALL_MS`(既定2000ms)で暗黙グループの子Futureをbest-effort join。
- グループ/トークンはスカフォールドPhase 2で実体実装: 伝播/キャンセル/join順序
- P3第一弾: Await の Result 化JIT側
- 新規シム: `nyash.result.err_h(handle)` 追加(<=0時は `Err("Timeout")` を生成)。
- Lowerer: `await``await_h → (ok_h, err_h) → handle==0 で select` に更新。
### MIR 層の設計(合意メモ)
- 原則: 「すべては箱」+「汎用呼び出し」で表現し、専用命令は最小限。
- 箱の面MIRから見えるもの
- TaskGroupBox: `spawn(recv, method, args…) -> Future`, `cancelAll()`, `joinAll(timeout_ms?)`, `fini(将来)`
- FutureBox: `await(timeout_ms?) -> Result<T, Err>`, `cancel()`, `isReady()`
- ResultBox: 既存Ok/Err
- MIR表現
- nowait: 当面は `ExternCall("env.future","spawn_instance", [recv, mname, ...])`。TaskGroup実体が固まり次第 `BoxCall TaskGroup.spawn(...)` に移行。
- await: 既存 `MirInstruction::Await` を使用Lowererが await 前後に `env.runtime.checkpoint` を自動挿入)。
- checkpoint: `ExternCall("env.runtime","checkpoint")` 相当。Verifierで Await の前後必須(実装済)。
- Loweringと実装対応
- VM: `spawn_instance`→scheduler enqueue、`Future.get()`+timeout→`Result.Ok/Err("Timeout")`、checkpointでGC+scheduler.poll
- JIT/AOT: `await_h`→i64 handle(0=timeout)→`result.ok_h/err_h`でResult化。checkpointは `nyash.rt.checkpoint` シムに集約。
- 効果: VM/JIT/AOTが同形のMIRを見て、JIT/EXEは既存のシムで統一挙動を実現。Verifierでawait安全性を機械チェック。
### 引き継ぎ2025-09-01, late
- これまでに着地したもの(コード)
- Await 正規化JIT: `nyash.result.err_h` 追加、`await_h → ok_h/err_h → select` で Result.Ok/Err に統一。
- Await 安全性: Builder が await 前後に `Safepoint` 自動挿入、Verifier が前後 checkpoint 必須を検証(`--verify`)。
- Future 共有/弱化: `FutureBox` を Arc 共有に、`FutureWeak` を追加(`downgrade()/is_ready()`)。
- 暗黙/明示 TaskGroup 足場:
- `global_hooks`: 強/弱レジストリ、関数スコープ `push_task_scope()/pop_task_scope()`外側でbesteffort join
- VM: 関数入口/出口にスコープ push/pop を配線JIT早期return含む
- TaskGroupBox: `inner.strong` で子Futureを強参照所有、`add_future()` / `joinAll(ms)` / `cancelAll()`scaffold
- `env.future.spawn_instance` 生成Futureは「現スコープのTaskGroup」または暗黙グループへ登録。
- 次の実装順(小粒から順に)
1) TaskGroupBox.spawn(recv, method, args…)->Future を実装(所有は TaskGroupBox
- Builder の nowait を `BoxCall TaskGroup.spawn` に段階移行fallback: `ExternCall env.future.spawn_instance`)。
2) LIFO join/cancel: スコープTaskGroupをネスト順で `cancelAll→joinAll`(まずは join、次段で token 伝播)。
3) Err 統一P3後半: Cancelled/Panic を Result.Err に統一JIT/AOT必要なら NyRT シム追加)。
4) テスト/CI:
- 単体feature-gated: Futureの強/弱参照・join・スコープjoinの確認。
- E2E: nowait→awaitOk/Timeout、終端join を {vm, jit, aot}×{default, strict} でスモーク化。
- CI に async 3本timeoutガード付きを最小マトリクスで追加。
- 実行・フラグ
- `NYASH_AWAIT_MAX_MS`既定5000: await のタイムアウト。
- `NYASH_JOIN_ALL_MS`既定2000: Runner 終端 join のタイムアウト。
- `NYASH_TASK_SCOPE_JOIN_MS`既定1000: 関数スコープ pop 時の join タイムアウト。
- 参考(動かし方)
- ビルド: `cargo build --release --features cranelift-jit`
- スモーク: `tools/smoke_async_spawn.sh`VM/JIT, timeout 10s + `NYASH_AWAIT_MAX_MS=5000`
- デモ: `apps/tests/taskgroup-join-demo/main.nyash`(スコープ/終端 join の挙動確認)
- 既知の制約/注意
- pluginsonly 環境では `new TaskGroupBox()` は未実装箱自体はVM側で動くが、プラグイン同梱は未
- cancel はフラグのみ(次段で CancellationToken 伝播と await 時の Cancelled をErrで返す
- いくつかの既存テストが赤別領域の初期化不備。asyncテストはfeatureゲートで段階導入予定。
### 参考コード(主要差分)
- Runtime/スケジューラ+フック
- `src/runtime/scheduler.rs`: `spawn_with_token` を含む Scheduler スケルトン。
- `src/runtime/global_hooks.rs`: `spawn_task_with_token``current_group_token()` を追加。
- TaskGroup雛形
- `src/boxes/task_group_box.rs`: 取消状態のみ保持(将来の伝播に備える)。
- Await の Result 化
- VM: `src/backend/vm_instructions.rs`Result.Okへ包む
- Unified/V2: `src/runtime/plugin_loader_{unified,v2}.rs``env.future.await` を Ok/Err(Timeout) で返却)。
- JIT: `src/jit/extern/{async.rs,result.rs}``await_h``ok_h/err_h`)、`src/jit/lower/core.rs`await分岐`src/jit/lower/builder.rs`(シンボル登録)。
- スモーク
- `tools/smoke_async_spawn.sh`timeout 10s + `NYASH_AWAIT_MAX_MS=5000`)。
- 参考デモ: `apps/tests/taskgroup-join-demo/main.nyash`Runner終端joinの動作確認
### 次の実装順(合意済み)
1) Phase 2: VMの暗黙TaskGroup配線現状no-opトークンで着地→次にグループ実体join/cancel実装
2) Phase 3: JIT側のErr統一Timeout以外: Cancelled/Panicの表出整理、0/None撤去の完了
3) Verifier: await前後のcheckpoint検証ルール追加実装済・--verifyで有効
4) CI/Smokes: async系3本を最小マトリクスでtimeoutガード
---
# 🎯 CURRENT TASK - 2025-08-30 Restart SnapshotPlugin-First / Core最小化
このスナップショットは最新の到達点へ更新済み。再起動時はここから辿る。

View File

@ -0,0 +1,59 @@
# Async Task System — Phased Plan (P1P3)
## Phase 1: Foundations (stabilize + timeouts)
- FutureBox: switch to Mutex+Condvar (done).
- Await: poll scheduler + timeout gate in VM and JIT (done; unify to Result.Err in P3).
- env.future.spawn_instance: enqueue via Scheduler; fallback sync if no scheduler (done).
- Safepoint: ensure env.runtime.checkpoint is emitted around await (Lowerer rule).
- Smokes: async-await-min; async-spawn-instance (safe form, no env).
- Acceptance:
- No hangs (await respects timeout); CPU near idle while waiting.
- VM/JIT pass basic smokes; lingering processes do not remain.
## Phase 2: TaskGroup & CancellationToken
- Types:
- CancellationToken { cancel(), is_cancelled() } idempotent; parent→child propagation only.
- TaskGroup Box { spawn(fn)->Future, cancelAll(), joinAll() }, owns token; fini enforces cancel→join.
- API:
- nowait sugar targets current TaskGroup.
- Unsafe detached spawn hidden behind unsafe_spawn_detached() and verifier checks.
- VM implementation:
- Extend scheduler to accept token; tasks periodically check token or are cancelled at await.
- Group registry per scope; insert fini hooks in function epilogues and Main.
- JIT/EXE:
- NyRT shims accept optional token handle; if missing, use current groups token.
- Smokes:
- spawn_cancel_on_scope_exit; nested_groups; lifo_join_order.
- Acceptance:
- Parent exit cancels and joins children deterministically (LIFO); no leaks per leak tracker.
## Phase 3: Error Semantics & Unification
- Future.await returns Result<T, Err> (Timeout/Cancelled/Panic) consistently (VM/JIT).
- Remove 0/None fallbacks; map shims to Result at Nyash layer.
- Lowerer verifies checkpoints around await; add verifier rule.
- Observability: minimal counters and optional traces.
- Smokes:
- await_timeout distinct from cancel; panic_propagation; wakeup_race (no double resolve).
- Acceptance:
- Consistent error surface; result handling identical across VM/JIT/EXE.
## Test Matrix & CI
- Backends: {vm, jit, aot} × Modes: {default, strict}.
- Smokes kept minimal; timebounded via timeout(1) wrapper.
- CPU spin test: ensure idle waiting; measured via time/ps (besteffort).
## Migration & Compatibility
- Keep env.future.spawn_instance during transition; TaskGroup.spawn preferred.
- nowait sugar remains; mapped to TaskGroup.spawn.
- Document flags: NYASH_AWAIT_MAX_MS, NYASH_SCHED_*.
## Files & Ownership
- Spec & Plan live here; updates linked from CURRENT_TASK.md.
- Code changes limited to runtime/{scheduler,global_hooks}, boxes/future, jit/extern/async, lowerer await rules.

View File

@ -0,0 +1,14 @@
# Async Task System (Structured Concurrency) — Overview
Goal: A safe, structured, and portable async task system that runs endtoend across Nyash code → MIR → VM → JIT/EXE.
- Default is safe: tasks are scoped to an owning group; when the owner ends, children cancel and join.
- Everything is Box: TaskGroup and Future are Boxes; user APIs are Box methods; MIR uses BoxCall.
- No new MIR ops required: use BoxCall/PluginInvoke consistently; safepoints are inserted around await.
- Deterministic exits: parent exit triggers cancelAll → joinAll on children (LIFO), eliminating leaks.
This folder contains the spec, phase plan, and test plan:
- SPEC.md: User API, Box contracts, MIR/VM/JIT mapping, ABI, error semantics.
- PLAN.md: Phased rollout (P1P3), acceptance gates and checklists.

View File

@ -0,0 +1,83 @@
# Async Task System — SPEC
Scope: Define a structured concurrency model for Nyash with TaskGroup and Future as Boxes. Implementable across VM and JIT/EXE without adding new MIR instructions.
## UserFacing API (Everything is Box)
Box TaskGroup
- spawn(fn: () -> T) -> Future<T>
- cancelAll() -> void
- joinAll() -> void
- fini: must run cancelAll() then joinAll() (LIFO order) to ensure structured shutdown.
Box Future<T>
- await(timeout_ms?: int) -> Result<T, Err>
- Ok(value)
- Err(Timeout) | Err(Cancelled) | Err(Panic)
- cancel() -> void (idempotent)
Sugar
- nowait v = expr is sugar for: let g = current_group(); v = g.spawn(lambda: expr)
Default Ownership
- An implicit TaskGroup is created per function scope and for Main. It owns tasks spawned in that scope.
- Leaving the scope triggers cancelAll→joinAll on its group (LIFO), unless tasks were moved to a longerlived group explicitly.
Detachment (discouraged)
- unsafe_spawn_detached(fn) only in advanced modules. Verifier should disallow use in normal code paths.
## MIR Mapping
- No new MIR instructions. Use existing BoxCall/PluginInvoke forms.
- TaskGroup.spawn → BoxCall on TaskGroup Box, returns Future Box.
- Future.await → BoxCall on Future Box with optional timeout parameter.
- Lowerer inserts safepoint around await: ExternCall env.runtime.checkpoint before and after the await call.
Example Lowering (high level)
- AST: nowait fut = arr.length()
- MIR (normalized):
- recv = … (arr)
- mname = Const("length")
- fut = ExternCall iface="env.future", method="spawn_instance", args=[recv, mname]
- v = BoxCall Future.await(fut, timeout_ms?)
Note: In the final API, TaskGroup.spawn replaces env.future.spawn_instance, but the MIR contract remains BoxCall/ExternCallbased.
## VM Semantics
- Scheduler: SingleThreadScheduler initially; spawn enqueues closure in FIFO. safepoint_and_poll() runs due tasks.
- Future: implemented with Mutex+Condvar; set_result notifies; await waits with optional timeout; on cancel/timeout, returns Result.Err.
- CancellationToken: parent→child propagation only, idempotent cancel().
- TaskGroup: holds token and child registry; fini enforces cancelAll→joinAll (LIFO).
## JIT/EXE Semantics
- NyRT CABI Shims:
- nyash.future.spawn_method_h(type_id, method_id, argc, recv_h, vals*, tags*) -> i64 (Future handle)
- nyash.future.spawn_instance3_i64(a0, a1, a2, argc) -> i64 (Future handle, by name/first args)
- nyash.future.await_h(handle, timeout_ms?) -> i64/handle (Result encoding handled at Nyash layer)
- Await shim must poll safepoints and honor timeout; returns 0 or sentinel; Nyash layer maps to Result.Err.*
## Errors & Results
- Distinguish Timeout vs Cancelled vs Panic.
- Logging: concise, off by default; env flags can enable traces.
- No silent fallback: unimplemented paths error early with clear labels.
## GC & Safepoints
- Lowerer must emit env.runtime.checkpoint immediately before and after await calls.
- Scheduler.poll runs at checkpoints; long loops should explicitly insert checkpoints.
## Configuration
- NYASH_AWAIT_MAX_MS (default 5000) — global default timeout for await when not specified.
- NYASH_SCHED_POLL_BUDGET — tasks per poll, default 1.
- NYASH_SCHED_TRACE — prints poll/move/ran counts when 1.
## Security & Determinism
- Structured shutdown prevents orphan tasks after parent exit.
- LIFO joinAll reduces deadlock surfaces.
- Detached tasks are explicit and rare.

View File

@ -0,0 +1,224 @@
# Phase 11.8: MIR命令セット究極整理 - Core-13への道
## 🎯 概要
ChatGPT5さんの深い洞察「**MIRは接着剤、Boxが世界**」を実現する究極のMIR整理。
現在の26命令 → Core-15 → Core-14Phase 12→ **Core-13最終目標**への段階的削減。
### 基本哲学
- **MIR = マイクロカーネル**: 最小限の制御と計算のみ
- **Box = すべての実データと操作**: Everything is Box の究極形
- **ExternCall = システムコール**: 外界との最小インターフェース
## 📊 現状分析
### 現在のCore-15Phase 11.7
```
基本演算(5): Const, UnaryOp, BinOp, Compare, TypeOp
メモリ(2): Load, Store
制御(4): Branch, Jump, Return, Phi
Box(3): NewBox, BoxCall, PluginInvoke
配列(2): ArrayGet, ArraySet
外部(1): ExternCall
```
### Core-14Phase 12予定
```
基本演算(5): Const, UnaryOp, BinOp, Compare, TypeOp
メモリ(2): Load, Store
制御(4): Branch, Jump, Return, Phi
Box(2): NewBox, BoxCall ← PluginInvoke統合
配列(2): ArrayGet, ArraySet
外部(1): ExternCall
```
## 🚀 Core-13への道筋
### Step 1: 配列操作のBoxCall統合Core-14 → Core-12
```mir
// 現在
%val = ArrayGet %arr, %idx
ArraySet %arr, %idx, %val
// 統合後
%val = BoxCall %arr, "get", [%idx]
BoxCall %arr, "set", [%idx, %val]
```
**実装方針**:
- Optimizer: ArrayGet/ArraySet → BoxCall 変換
- VM: 高頻度パスは内部最適化維持
- JIT: 既知型の場合はインライン展開
### Step 2: Load/Store の再考Core-12 → Core-11
**SSAの威力を活かす**:
- ローカル変数のLoad/Store → SSA変数で代替
- 真に必要なのはBoxフィールドアクセスのみ
- それもBoxCall("getField"/"setField")で統合可能
```mir
// 現在
Store %slot, %value
%val = Load %slot
// SSA化
%val = %value // 直接参照Copyも実質不要
```
### Step 3: 定数統合とUnaryOp簡素化Core-11 → Core-13
**Const統合案**:
```mir
// 現在
Const::Integer(i64)
Const::Float(f64)
Const::Bool(bool)
Const::String(String)
Const::Null
// 統合後
Const { type: Type, value: u64 } // 全て64bitに収める
```
**UnaryOp削減**:
- Neg → BinOp(Sub, 0, x)
- Not → BinOp(Xor, x, 1)
- BitNot → BinOp(Xor, x, -1)
## 🎯 最終形Core-13
```yaml
定数(1):
- Const統合型i64/f64/bool/null/handle
計算(2):
- BinOpAdd/Sub/Mul/Div/Mod/And/Or/Xor/Shl/Shr
- CompareEq/Ne/Lt/Le/Gt/Ge
制御(4):
- Branch条件分岐
- Jump無条件ジャンプ
- PhiSSA合流
- Return関数終了
呼出(3):
- CallNyash関数呼び出し
- BoxCallBox操作統一
- ExternCall環境アクセス
メタ(3):
- TypeOp型チェック/キャスト)
- SafepointGCセーフポイント
- Barrierメモリバリア
合計: 13命令
```
## 💡 なぜCore-13で十分なのか
### 1. チューリング完全性の保証
最小限必要なもの:
- 定数
- 算術演算
- 条件分岐
- ループJump + Branch
- 関数呼び出し
これらはすべてCore-13に含まれる。
### 2. Everything is Box の威力
```nyash
// すべてがBoxCall経由
arr[0] → BoxCall(arr, "get", [0])
arr[0] = 42 → BoxCall(arr, "set", [0, 42])
obj.field → BoxCall(obj, "getField", ["field"])
obj.field = val → BoxCall(obj, "setField", ["field", val])
weak.get() → BoxCall(weak, "get", [])
```
### 3. SSAによるメモリ命令の削減
- 一時変数 → SSA変数Load/Store不要
- フィールド → BoxCall
- 配列要素 → BoxCall
- 真のメモリアクセスはBoxの中に隠蔽
## 📋 実装ロードマップ
### Phase 11.8.1: 準備と分析1週間
- [ ] 現在のMIR使用状況の詳細分析
- [ ] ArrayGet/ArraySet → BoxCall 変換の影響調査
- [ ] Load/Store 削除可能性の検証
- [ ] パフォーマンスベンチマーク基準値測定
### Phase 11.8.2: ArrayGet/ArraySet統合2週間
- [ ] Optimizer: ArrayGet/ArraySet → BoxCall 変換パス
- [ ] VM: BoxCall("get"/"set") の最適化パス
- [ ] JIT: 既知ArrayBoxの特殊化維持
- [ ] テスト: 既存配列操作の回帰テスト
### Phase 11.8.3: Load/Store削減3週間
- [ ] Builder: SSA最大活用でLoad/Store削減
- [ ] フィールドアクセス → BoxCall 変換
- [ ] VM/JIT: 最適化パスの調整
- [ ] ベンチマーク: パフォーマンス影響測定
### Phase 11.8.4: 最終統合2週間
- [ ] Const型統合実装
- [ ] UnaryOp → BinOp 変換
- [ ] Core-13命令セット確定
- [ ] ドキュメント最終更新
## ⚠️ リスクと緩和策
### パフォーマンスリスク
**リスク**: BoxCall統合によるオーバーヘッド
**緩和策**:
- VM層での型別最適化維持
- JIT時の積極的インライン展開
- 高頻度パスのNyRTシム化
### 互換性リスク
**リスク**: 既存MIRコードの非互換
**緩和策**:
- Rewriteパスで自動変換
- 段階的移行(警告→エラー)
- 環境変数でレガシーモード
### 複雑性リスク
**リスク**: BoxCallの過度な多重化
**緩和策**:
- 明確な命名規約get/set/getField等
- 型情報による静的検証強化
- デバッグ情報の充実
## 🎯 成功指標
1. **命令数**: 26 → 1350%削減)
2. **パフォーマンス**: ベンチマークで±5%以内
3. **コードサイズ**: MIRダンプサイズ20%削減
4. **保守性**: 新Box追加時のMIR変更不要
## 📚 関連ドキュメント
- [MIR Instruction Set](../../../reference/mir/INSTRUCTION_SET.md)
- [Phase 12: PluginInvoke統合](../phase-12/README.md)
- [Everything is Box哲学](../../../philosophy/everything-is-box.md)
---
*「少ないほど豊かである」- MIRは最小の接着剤、Boxが無限の世界を創る*

View File

@ -0,0 +1,357 @@
# Phase 11.8 技術仕様書Core-13 MIR命令セット
## 1. ArrayGet/ArraySet → BoxCall 統合仕様
### 1.1 変換規則
```rust
// MIR Optimizer での変換
match instruction {
MirInstruction::ArrayGet { dst, array, index } => {
MirInstruction::BoxCall {
dst: Some(*dst),
box_val: *array,
method: "get".to_string(),
method_id: Some(UNIVERSAL_GET_ID), // 予約ID: 4
args: vec![*index],
effects: EffectMask::READS_MEMORY,
}
}
MirInstruction::ArraySet { array, index, value } => {
MirInstruction::BoxCall {
dst: None,
box_val: *array,
method: "set".to_string(),
method_id: Some(UNIVERSAL_SET_ID), // 予約ID: 5
args: vec![*index, *value],
effects: EffectMask::WRITES_MEMORY | EffectMask::MAY_GC,
}
}
}
```
### 1.2 VM最適化
```rust
// VM execute_boxcall での特殊化
fn execute_boxcall(...) {
// 高速パスArrayBoxの既知メソッド
if let Some(method_id) = method_id {
match (type_id, method_id) {
(ARRAY_BOX_TYPE, UNIVERSAL_GET_ID) => {
// 直接配列アクセスBoxCall経由でも高速
return fast_array_get(receiver, args[0]);
}
(ARRAY_BOX_TYPE, UNIVERSAL_SET_ID) => {
return fast_array_set(receiver, args[0], args[1]);
}
_ => {}
}
}
// 通常パス
plugin_invoke(...)
}
```
### 1.3 JIT最適化
```rust
// JIT Lowering での認識
fn lower_boxcall(builder: &mut IRBuilder, ...) {
if is_known_array_type(receiver_type) {
match method_id {
Some(UNIVERSAL_GET_ID) => {
// GEP + Load にインライン展開
emit_array_bounds_check(...);
emit_array_get_inline(...);
return;
}
Some(UNIVERSAL_SET_ID) => {
// Write barrier + GEP + Store
emit_write_barrier(...);
emit_array_set_inline(...);
return;
}
_ => {}
}
}
// 通常のBoxCall
emit_plugin_invoke(...);
}
```
## 2. Load/Store 削減仕様
### 2.1 SSA変数活用の最大化
```mir
// BeforeLoad/Store使用
bb0:
Store %slot1, %x
Branch %cond, bb1, bb2
bb1:
Store %slot1, %y
Jump bb3
bb2:
// slot1 は x のまま
Jump bb3
bb3:
%result = Load %slot1
Return %result
// AfterPhi使用
bb0:
Branch %cond, bb1, bb2
bb1:
Jump bb3(%y)
bb2:
Jump bb3(%x)
bb3(%result):
Return %result
```
### 2.2 フィールドアクセスの統合
```mir
// BeforeRefGet/RefSet
%field_val = RefGet %obj, "field"
RefSet %obj, "field", %new_val
// AfterBoxCall
%field_val = BoxCall %obj, "getField", ["field"]
BoxCall %obj, "setField", ["field", %new_val]
```
### 2.3 残すべきLoad/Store
- **スタックスロット**: JIT/AOTでの一時変数
- **C FFI境界**: 外部関数とのやり取り
- **最適化中間状態**: Phi導入前の一時的使用
## 3. Const統合仕様
### 3.1 統一表現
```rust
pub enum MirConst {
// Before: 5種類
Integer(i64),
Float(f64),
Bool(bool),
String(String),
Null,
// After: 1種類
Unified {
ty: ConstType,
bits: u64, // i64/f64/bool/null はビット表現
aux: Option<Arc<String>>, // 文字列用
}
}
pub enum ConstType {
I64, F64, Bool, Null, String, Handle
}
```
### 3.2 エンコーディング
```rust
impl MirConst {
fn encode_i64(val: i64) -> Self {
Self::Unified {
ty: ConstType::I64,
bits: val as u64,
aux: None,
}
}
fn encode_f64(val: f64) -> Self {
Self::Unified {
ty: ConstType::F64,
bits: val.to_bits(),
aux: None,
}
}
fn encode_bool(val: bool) -> Self {
Self::Unified {
ty: ConstType::Bool,
bits: val as u64,
aux: None,
}
}
}
```
## 4. パフォーマンス保証
### 4.1 ベンチマーク項目
```yaml
必須ベンチマーク:
- array_access_sequential: 配列順次アクセス
- array_access_random: 配列ランダムアクセス
- field_access: フィールド読み書き
- local_variables: ローカル変数操作
- arithmetic_loop: 算術演算ループ
許容範囲:
- 速度: ベースライン ±5%
- メモリ: ベースライン ±10%
- MIRサイズ: -20%以上の削減
```
### 4.2 最適化保証
```rust
// 必須最適化パス
const REQUIRED_OPTIMIZATIONS: &[&str] = &[
"array_bounds_elim", // 配列境界チェック除去
"boxcall_devirt", // BoxCall脱仮想化
"const_fold", // 定数畳み込み
"dead_store_elim", // 不要Store除去
"phi_simplify", // Phi簡約
];
```
## 5. 移行戦略
### 5.1 段階的有効化
```rust
// 環境変数による制御
pub struct MirConfig {
// Phase 11.8.1
pub array_to_boxcall: bool, // NYASH_MIR_ARRAY_BOXCALL=1
// Phase 11.8.2
pub eliminate_load_store: bool, // NYASH_MIR_NO_LOAD_STORE=1
// Phase 11.8.3
pub unified_const: bool, // NYASH_MIR_UNIFIED_CONST=1
// Phase 11.8.4
pub core_13_strict: bool, // NYASH_MIR_CORE13=1
}
```
### 5.2 互換性レイヤー
```rust
// Rewrite パス
pub fn rewrite_legacy_mir(module: &mut MirModule) {
for (_, func) in &mut module.functions {
for (_, block) in &mut func.blocks {
let mut new_instructions = vec![];
for inst in &block.instructions {
match inst {
// ArrayGet/ArraySet → BoxCall
MirInstruction::ArrayGet { .. } => {
new_instructions.push(convert_array_get(inst));
}
MirInstruction::ArraySet { .. } => {
new_instructions.push(convert_array_set(inst));
}
// RefGet/RefSet → BoxCall
MirInstruction::RefGet { .. } => {
new_instructions.push(convert_ref_get(inst));
}
MirInstruction::RefSet { .. } => {
new_instructions.push(convert_ref_set(inst));
}
// そのまま
_ => new_instructions.push(inst.clone()),
}
}
block.instructions = new_instructions;
}
}
}
```
## 6. 検証項目
### 6.1 正当性検証
```rust
#[cfg(test)]
mod core13_tests {
// 各変換の意味保存を検証
#[test]
fn test_array_get_conversion() {
let before = MirInstruction::ArrayGet { ... };
let after = convert_to_boxcall(before);
assert_semantic_equivalence(before, after);
}
// SSA形式の保持を検証
#[test]
fn test_ssa_preservation() {
let module = build_test_module();
eliminate_load_store(&mut module);
assert_is_valid_ssa(&module);
}
}
```
### 6.2 性能検証
```rust
// ベンチマークハーネス
pub fn benchmark_core13_migration() {
let scenarios = vec![
"array_intensive",
"field_intensive",
"arithmetic_heavy",
"mixed_workload",
];
for scenario in scenarios {
let baseline = run_with_core15(scenario);
let core13 = run_with_core13(scenario);
assert!(
(core13.time - baseline.time).abs() / baseline.time < 0.05,
"Performance regression in {}", scenario
);
}
}
```
## 7. エラーハンドリング
### 7.1 診断メッセージ
```rust
pub enum Core13Error {
UnsupportedInstruction(String),
ConversionFailed { from: String, to: String },
PerformanceRegression { metric: String, delta: f64 },
}
impl Core13Error {
fn diagnostic(&self) -> Diagnostic {
match self {
Self::UnsupportedInstruction(inst) => {
Diagnostic::error()
.with_message(format!("Instruction '{}' not supported in Core-13", inst))
.with_note("Consider using BoxCall for this operation")
.with_help("Set NYASH_MIR_LEGACY=1 for compatibility mode")
}
// ...
}
}
}
```
---
*この仕様に従い、MIRを「最小の接着剤」として純化し、Boxに「無限の可能性」を委ねる*

View File

@ -0,0 +1,232 @@
# Nyash ABI 統合設計図 v1.0 (2025-09-01)
## 🎯 概要
Nyash ABIは、既存のC ABIプラグインを維持しながら、より型安全で拡張性の高いプラグインシステムを実現する統一ブリッジ規格です。
### 設計原則
1. **後方互換性**: 既存のC ABIプラグインはそのまま動作
2. **最小侵襲**: MIR層の変更を最小限に
3. **段階的移行**: nyash.tomlで個別に移行可能
4. **ゼロコスト抽象化**: インライン値で不要なボクシング回避
## 📐 基本構造
### NyashValue - 3×u64統一表現
```c
// nyash_abi.h
typedef struct NyashValue {
uint64_t type_id; // 型識別子上位16bit: カテゴリ、下位48bit: ID
uint64_t box_handle; // Arc<dyn NyashBox>のポインタ or インライン値
uint64_t metadata; // フラグ・メソッドID・追加データ
} NyashValue;
// メタデータフラグ上位16bit
#define NYASH_META_INLINE 0x0001 // box_handleがインライン値
#define NYASH_META_ASYNC 0x0002 // 非同期結果
#define NYASH_META_WEAK 0x0004 // 弱参照
#define NYASH_META_BORROWED 0x0008 // 借用(参照カウント不要)
#define NYASH_META_ERROR 0x0010 // エラー値
// 型カテゴリtype_idの上位16bit
#define NYASH_TYPE_PRIMITIVE 0x0001 // i64/f64/bool/null
#define NYASH_TYPE_STRING 0x0002 // 文字列
#define NYASH_TYPE_ARRAY 0x0003 // 配列
#define NYASH_TYPE_MAP 0x0004 // マップ
#define NYASH_TYPE_CUSTOM 0x1000 // ユーザー定義Box
#define NYASH_TYPE_PLUGIN 0x2000 // プラグインBox
```
### NyashFunc - 統一関数シグネチャ
```c
typedef NyashValue (*NyashFunc)(
uint32_t argc, // 引数の数
NyashValue* args, // 引数配列args[0]はレシーバー)
void* context // ランタイムコンテキスト
);
// プラグインエントリーポイント
typedef struct NyashPlugin {
const char* name; // プラグイン名
const char* version; // バージョン
uint32_t method_count; // メソッド数
const char** method_names; // メソッド名配列
NyashFunc* method_funcs; // メソッド関数配列
NyashFunc init; // 初期化関数
NyashFunc drop; // 破棄関数
} NyashPlugin;
// エクスポート関数
extern "C" const NyashPlugin* nyash_plugin_init(void);
```
## 🔄 既存C ABIとの共存
### トランポリン戦略
```rust
// src/runtime/abi_bridge.rs
// 既存のC ABI関数シグネチャ
type OldCFunc = extern "C" fn(*mut c_void, *const c_void) -> *mut c_void;
// 自動生成トランポリン
fn create_c_abi_trampoline(old_func: OldCFunc) -> NyashFunc {
Box::into_raw(Box::new(move |argc, args, ctx| {
// NyashValue → 旧C ABI形式に変換
let old_args = convert_to_old_format(args);
let old_result = old_func(old_args.as_ptr(), ctx);
// 旧C ABI形式 → NyashValueに変換
convert_from_old_format(old_result)
})) as NyashFunc
}
```
### nyash.toml設定
```toml
# nyash.toml v2.1
[plugin.math]
path = "plugins/math.so"
abi = "c" # 既存C ABIデフォルト
[plugin.advanced_math]
path = "plugins/advanced_math.so"
abi = "nyash" # 新Nyash ABI
[plugin.hybrid]
path = "plugins/hybrid.so"
abi = "auto" # 自動検出(シンボル名で判定)
```
## 💨 インライン最適化
### 基本型のインライン表現
```c
// インライン値のエンコーディングmetadataにINLINEフラグ必須
// 整数i64: box_handleに直接格納
NyashValue inline_i64(int64_t val) {
return (NyashValue){
.type_id = NYASH_TYPE_PRIMITIVE | (1 << 16), // subtype=1 (i64)
.box_handle = (uint64_t)val,
.metadata = NYASH_META_INLINE
};
}
// 浮動小数点f64: ビットパターンをbox_handleに
NyashValue inline_f64(double val) {
union { double d; uint64_t u; } conv = { .d = val };
return (NyashValue){
.type_id = NYASH_TYPE_PRIMITIVE | (2 << 16), // subtype=2 (f64)
.box_handle = conv.u,
.metadata = NYASH_META_INLINE
};
}
// Bool: box_handleの最下位ビット
NyashValue inline_bool(bool val) {
return (NyashValue){
.type_id = NYASH_TYPE_PRIMITIVE | (3 << 16), // subtype=3 (bool)
.box_handle = val ? 1 : 0,
.metadata = NYASH_META_INLINE
};
}
```
## 🏗️ 実装フェーズ
### Phase 1: 基盤整備1週間
- [x] nyash_abi.h ヘッダー定義
- [ ] NyashValue ↔ Arc<dyn NyashBox> 変換関数
- [ ] C ABIトランポリン自動生成
- [ ] nyash.toml v2.1パーサー拡張
### Phase 2: ランタイム統合2週間
- [ ] 統一レジストリ実装abi種別管理
- [ ] VM層でのNyashFunc呼び出し
- [ ] インライン値の高速パス
- [ ] エラーハンドリング統一
### Phase 3: プラグイン移行3週間
- [ ] 既存プラグイン1つをNyash ABIに移行
- [ ] パフォーマンスベンチマーク
- [ ] 移行ガイドライン作成
- [ ] デバッグツール整備
### Phase 4: バインディング生成4週間
- [ ] Rust: #[nyash_abi]マクロ
- [ ] C++: NYASH_PLUGIN()マクロ
- [ ] Python: @nyash_plugin デコレータ
- [ ] JavaScript: NyashPlugin基底クラス
## 🔍 型レジストリ設計
### 型IDの生成戦略
```rust
// 型ID = カテゴリ(16bit) + ハッシュ(48bit)
fn generate_type_id(category: u16, type_name: &str) -> u64 {
let hash = xxhash64(type_name.as_bytes());
((category as u64) << 48) | (hash & 0xFFFF_FFFF_FFFF)
}
// 既知の型は事前定義
const TYPE_ID_STRING: u64 = 0x0002_0000_0000_0001;
const TYPE_ID_ARRAY: u64 = 0x0003_0000_0000_0002;
const TYPE_ID_MAP: u64 = 0x0004_0000_0000_0003;
```
## ⚡ 最適化戦略
### JIT/AOT統合
```rust
// JIT時の特殊化
if method_id == KNOWN_METHOD_ADD && is_inline_i64(arg1) && is_inline_i64(arg2) {
// 直接的な整数加算にコンパイル
emit_add_i64(arg1.box_handle, arg2.box_handle);
} else {
// 通常のNyashFunc呼び出し
emit_nyash_func_call(func_ptr, args);
}
```
### メモリ管理
```rust
// 参照カウント最適化
if metadata & NYASH_META_BORROWED != 0 {
// 借用フラグ付き → Arc::clone不要
use_without_clone(box_handle);
} else {
// 通常の参照カウント
Arc::clone(box_handle);
}
```
## 📊 互換性マトリックス
| 機能 | C ABI | Nyash ABI | 自動変換 |
|------|-------|-----------|----------|
| 基本型引数 | ✅ | ✅ | 自動 |
| Box引数 | ポインタ | ハンドル | トランポリン |
| 戻り値 | malloc | NyashValue | トランポリン |
| エラー処理 | NULL | ERRORフラグ | 変換可能 |
| 非同期 | ❌ | ASYNCフラグ | - |
| メソッドID | 文字列 | u32 | ハッシュ |
## 🚀 次のステップ
1. **nyash_abi.hの作成**crates/nyrt/include/
2. **最小実装プラグイン作成**SimpleMathの両ABI版
3. **ベンチマーク測定**(オーバーヘッド評価)
4. **移行判断**(データに基づく方向性決定)
---
*既存を壊さず、新しい世界を開く - これがNyash ABIの道*

View File

@ -81,6 +81,13 @@ NyashエコシステムビルトインBox廃止後
- 複雑な型の相互運用が必要
- 将来の拡張性を重視する場合
### 📝 MIR命令統合Phase 12での変更
- **PluginInvoke → BoxCall 統合**
- ビルトインBox廃止によりフォールバックがなくなる
- BoxCallとPluginInvokeの区別が不要に
- VM層でC ABI/Nyash ABI/Scriptを自動判定
- Core-15 → Core-14 へ(命令数削減)
## 🛣️ 実装ロードマップ(修正版)
### Phase 12.1: export/import構文2週間
@ -100,8 +107,20 @@ NyashエコシステムビルトインBox廃止後
- [ ] VSCode拡張補完・定義ジャンプ
- [ ] サンプルパッケージ作成
## 📚 関連ドキュメント
### 🎯 主要設計ドキュメント
- **[Nyash ABI統合設計図](./NYASH-ABI-DESIGN.md)** ← 🆕 具体的な技術仕様!
- [export/import仕様](./export-import-spec.md)
- [パッケージマネージャー設計](./package-manager-design.md)
- [なぜ天才AIたちは間違えたのか](./WHY-AIS-FAILED.md)
### 📂 議論の過程
- ABI戦略議論: `abi-strategy-discussion/`
- Nyash ABI詳細: `nyash-abi-discussion/`
- 初期提案アーカイブ: `archive/`
---
### 🗄️ 議論の過程
AIたちがなぜ複雑な解決策を提案したのか、その議論の過程は `archive/` ディレクトリに保存されています。良い教訓として残しておきます。
*AIたちがなぜ複雑な解決策を提案したのか、その議論の過程は `archive/` ディレクトリに保存されています。良い教訓として残しておきます。*

View File

@ -48,6 +48,25 @@ AIの頭の中
- AI「JIT/AOTで高速化しなければ
- 実際:「インタープリター言語として十分高速」
### 🚀 なぜスクリプトファイルでも高速なのか
**重要な真実NyashはAOT/JITをサポートしている**
```bash
# スクリプトファイルをAOTコンパイル可能
./nyash --aot script.nyash -o script.exe
# JITで実行ホットコードを自動最適化
./nyash --backend vm --jit script.nyash
```
つまり:
1. **開発時**: スクリプトファイルで即座に実行・修正
2. **本番時**: AOTコンパイルで最高速度
3. **中間**: JITで自動的に最適化
**スクリプト言語の利便性 + ネイティブコードの速度 = 両方手に入る!**
### 4. Everything is Boxの哲学を忘れた
```nyash
# これが既に「スクリプトプラグイン」!

View File

@ -71,9 +71,15 @@ MirInstruction::BoxCall {
### 理由
1. **80/20ルール**: まず動くものを作る
2. **MIR無変更**: 15命令の美しさ維持
2. **MIR無変更**: 15命令の美しさ維持 → さらに14命令へPluginInvoke統合
3. **段階的進化**: 必要になったらOption Bへ
### Phase 12での追加統合
- **PluginInvoke → BoxCall 完全統合**
- ビルトインBox廃止により区別が不要
- VM層でのABI判定で十分
- Core-15 → Core-14 への削減
### 実装ステップ1週間
```yaml

View File

@ -84,14 +84,16 @@ abi = "nyash" # 型安全・拡張性
### BoxCall拡張による実装
**重要な発見**MIR層の変更は不要VM実行時の型判定で十分。
**Phase 12での追加発見**PluginInvokeも不要BoxCallに統合可能。
```rust
// MIR層変更なし15命令維持
// MIR層変更なし → さらにシンプルに14命令
MirInstruction::BoxCall {
receiver: Value,
method: String,
args: Vec<Value>,
}
// PluginInvoke は廃止BoxCallに統合
// VM層賢い判定
fn execute_boxcall(...) {

View File

@ -1,9 +1,9 @@
# Nyash MIR Instruction Set (Canonical 26 → migrating to Core-15)
# Nyash MIR Instruction Set (Canonical 26 → migrating to Core-15 → Core-14)
Status: Canonical (Source of Truth) — transitioning
Last Updated: 2025-08-25
Last Updated: 2025-09-01
この文書はNyashのMIR命令セットの唯一の参照現状は26命令だよ。Core-15 への段階移行を進めており、安定次第この文書を15命令へ更新し、テスト固定数も切り替える移行中は26を維持
この文書はNyashのMIR命令セットの唯一の参照現状は26命令だよ。Core-15 への段階移行を進めており、さらに Phase 12 で Core-14 へPluginInvoke → BoxCall 統合)。安定次第この文書を14命令へ更新し、テスト固定数も切り替える移行中は26を維持
注意: Debug/Nop/Safepointはビルドモードでの降格用メタ命令であり、コア命令数には数えない。
@ -15,8 +15,11 @@ Transition Note
- BarrierRead/BarrierWrite → Barrier
- Print → ExternCall(env.console.log)Deprecated
- PluginInvoke → BoxCallDeprecated; 名前/スロット解決はBoxCall側で処理
- Phase 12ビルトインBox廃止での追加統合
- PluginInvoke → BoxCall 完全統合(ビルトインフォールバックがなくなるため区別不要)
- VM層でC ABI/Nyash ABI/Scriptプラグインを自動判定
- VM/JIT の代表的な Core-15 カバー手順は `docs/reference/mir/MIR15_COVERAGE_CHECKLIST.md` を参照。
- Core-15 安定後に本ドキュメントの「Core Instructions」を15命令へ更新し、マッピング表を併記する。
- Core-14 安定後に本ドキュメントの「Core Instructions」を14命令へ更新し、マッピング表を併記する。
## Core Instructions26
- Const
@ -54,10 +57,19 @@ Transition Note
- 配列(2): ArrayGet, ArraySet
- 外部(1): ExternCall
## Core-14Phase 12 Target; PluginInvoke統合後
- 基本演算(5): Const, UnaryOp, BinOp, Compare, TypeOp
- メモリ(2): Load, Store
- 制御(4): Branch, Jump, Return, Phi
- Box(2): NewBox, BoxCall ← PluginInvokeを吸収
- 配列(2): ArrayGet, ArraySet
- 外部(1): ExternCall
Notes
- Print/Debug/Safepointはメタ/Extern化Print→ExternCall
- WeakRef/Barrier は統合済み旧WeakNew/WeakLoad/BarrierRead/WriteはRewriteで互換
- Call は BoxCall へ集約PluginInvokeはDeprecated
- Call は BoxCall へ集約PluginInvokeはDeprecated → Phase 12で完全統合)。
- Phase 12: ビルトインBox廃止により、BoxCallとPluginInvokeの区別が不要に。VM層でABI判定。
## Meta (降格対象; カウント外)
- Debug

View File

@ -788,6 +788,58 @@ impl LLVMCompiler {
let rv = call.try_as_basic_value().left().ok_or("readline returned void".to_string())?;
vmap.insert(*d, rv);
}
} else if iface_name == "env.future" && method_name == "spawn_instance" {
// Lower to NyRT: i64 nyash.future.spawn_instance3_i64(i64 a0, i64 a1, i64 a2, i64 argc)
// a0: receiver handle (or param index→handle via nyash.handle.of upstream if needed)
// a1: method name pointer (i8*) or handle; we pass pointer as i64 here
// a2: first payload (i64/handle); more args currently unsupported in LLVM lowering
if args.len() < 2 { return Err("env.future.spawn_instance expects at least (recv, method_name)".to_string()); }
let i64t = codegen.context.i64_type();
let i8p = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
// a0
let a0_v = *vmap.get(&args[0]).ok_or("recv missing")?;
let a0 = to_i64_any(codegen.context, &codegen.builder, a0_v)?;
// a1 (method name)
let a1_v = *vmap.get(&args[1]).ok_or("method_name missing")?;
let a1 = match a1_v {
BasicValueEnum::PointerValue(pv) => codegen.builder.build_ptr_to_int(pv, i64t, "mname_p2i").map_err(|e| e.to_string())?,
_ => to_i64_any(codegen.context, &codegen.builder, a1_v)?,
};
// a2 (first payload if any)
let a2 = if args.len() >= 3 {
let v = *vmap.get(&args[2]).ok_or("arg2 missing")?;
to_i64_any(codegen.context, &codegen.builder, v)?
} else { i64t.const_zero() };
let argc_total = i64t.const_int(args.len().saturating_sub(1) as u64, false);
// declare and call
let fnty = i64t.fn_type(&[i64t.into(), i64t.into(), i64t.into(), i64t.into()], false);
let callee = codegen
.module
.get_function("nyash.future.spawn_instance3_i64")
.unwrap_or_else(|| codegen.module.add_function("nyash.future.spawn_instance3_i64", fnty, None));
let call = codegen
.builder
.build_call(callee, &[a0.into(), a1.into(), a2.into(), argc_total.into()], "spawn_i3")
.map_err(|e| e.to_string())?;
if let Some(d) = dst {
let rv = call
.try_as_basic_value()
.left()
.ok_or("spawn_instance3 returned void".to_string())?;
// Treat as handle → pointer for Box return types; otherwise keep i64
if let Some(mt) = func.metadata.value_types.get(d) {
match mt {
crate::mir::MirType::Integer | crate::mir::MirType::Bool => { vmap.insert(*d, rv); }
crate::mir::MirType::Box(_) | crate::mir::MirType::String | crate::mir::MirType::Array(_) | crate::mir::MirType::Future(_) | crate::mir::MirType::Unknown => {
let iv = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("spawn ret expected i64".to_string()); };
let pty = codegen.context.i8_type().ptr_type(AddressSpace::from(0));
let ptr = codegen.builder.build_int_to_ptr(iv, pty, "ret_handle_to_ptr").map_err(|e| e.to_string())?;
vmap.insert(*d, ptr.into());
}
_ => { vmap.insert(*d, rv); }
}
} else { vmap.insert(*d, rv); }
}
} else {
return Err(format!("ExternCall lowering unsupported: {}.{} (enable NYASH_LLVM_ALLOW_BY_NAME=1 to try by-name, or add a NyRT shim)", iface_name, method_name));
}

View File

@ -580,6 +580,7 @@ impl VM {
// Enter a new scope for this function
self.scope_tracker.push_scope();
crate::runtime::global_hooks::push_task_scope();
// Phase 10_c: try a JIT dispatch when enabled; fallback to VM on trap/miss
// Prepare arguments from current frame params before borrowing jit_manager mutably
@ -599,6 +600,7 @@ impl VM {
// Exit scope before returning
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(val);
} else if std::env::var("NYASH_JIT_STATS").ok().as_deref() == Some("1") ||
std::env::var("NYASH_JIT_TRAP_LOG").ok().as_deref() == Some("1") {
@ -606,6 +608,7 @@ impl VM {
if jit_only {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT trap occurred for {}", function.signature.name)));
}
}
@ -616,15 +619,18 @@ impl VM {
if let Some(val) = jm_mut.execute_compiled(&function.signature.name, &function.signature.return_type, &args_vec) {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(val);
} else {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Err(VMError::InvalidInstruction(format!("JIT-only enabled and JIT execution failed for {}", function.signature.name)));
}
} else {
self.leave_root_region();
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Err(VMError::InvalidInstruction(format!("JIT-only enabled but function not compiled: {}", function.signature.name)));
}
}
@ -673,6 +679,7 @@ impl VM {
if let Some(return_value) = should_return {
// Exit scope before returning
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(return_value);
} else if let Some(target) = next_block {
// Update previous block before jumping and record transition via control_flow helper
@ -683,6 +690,7 @@ impl VM {
// but let's handle it gracefully by returning void
// Exit scope before returning
self.scope_tracker.pop_scope();
crate::runtime::global_hooks::pop_task_scope();
return Ok(VMValue::Void);
}
}

View File

@ -262,6 +262,22 @@ impl VM {
}
}
// TaskGroupBox methods (scaffold → instance内の所有Futureに対して実行)
if box_value.as_any().downcast_ref::<crate::boxes::task_group_box::TaskGroupBox>().is_some() {
let mut owned = box_value;
if let Some(tg) = (&mut *owned).as_any_mut().downcast_mut::<crate::boxes::task_group_box::TaskGroupBox>() {
match method {
"cancelAll" | "cancel_all" => { return Ok(tg.cancelAll()); }
"joinAll" | "join_all" => {
let ms = _args.get(0).map(|a| a.to_string_box().value.parse::<i64>().unwrap_or(2000));
return Ok(tg.joinAll(ms));
}
_ => { return Ok(Box::new(VoidBox::new())); }
}
}
return Ok(Box::new(VoidBox::new()));
}
// P2PBox methods (minimal)
if let Some(p2p) = box_value.as_any().downcast_ref::<crate::boxes::p2p_box::P2PBox>() {
match method {

View File

@ -589,10 +589,11 @@ impl VM {
let future_val = self.get_value(future)?;
if let VMValue::Future(ref future_box) = future_val {
// This blocks until the future is ready
// This blocks until the future is ready (Condvar-based)
let result = future_box.get();
// Convert NyashBox back to VMValue
let vm_value = VMValue::from_nyash_box(result);
// Wrap into Result.Ok for unified semantics
let ok = crate::boxes::result::NyashResultBox::new_ok(result);
let vm_value = VMValue::from_nyash_box(Box::new(ok));
self.set_value(dst, vm_value);
Ok(ControlFlow::Continue)
} else {

View File

@ -4,69 +4,74 @@
use crate::box_trait::{NyashBox, StringBox, BoolBox, BoxCore, BoxBase};
use std::any::Any;
use std::sync::RwLock;
use std::sync::{Mutex, Condvar, Arc, Weak};
#[derive(Debug)]
pub struct NyashFutureBox {
pub result: RwLock<Option<Box<dyn NyashBox>>>,
pub is_ready: RwLock<bool>,
inner: Arc<Inner>,
base: BoxBase,
}
#[derive(Debug)]
struct FutureState {
result: Option<Box<dyn NyashBox>>,
ready: bool,
}
#[derive(Debug)]
struct Inner {
state: Mutex<FutureState>,
cv: Condvar,
}
/// A weak handle to a Future's inner state.
/// Used for non-owning registries (TaskGroup/implicit group) to avoid leaks.
#[derive(Clone, Debug)]
pub struct FutureWeak {
pub(crate) inner: Weak<Inner>,
}
impl Clone for NyashFutureBox {
fn clone(&self) -> Self {
let result_guard = self.result.read().unwrap();
let result_val = match result_guard.as_ref() {
Some(box_value) => Some(box_value.clone_box()),
None => None,
};
let is_ready_val = *self.is_ready.read().unwrap();
Self {
result: RwLock::new(result_val),
is_ready: RwLock::new(is_ready_val),
base: BoxBase::new(), // Create a new base with unique ID for the clone
}
Self { inner: self.inner.clone(), base: BoxBase::new() }
}
}
impl NyashFutureBox {
pub fn new() -> Self {
Self {
result: RwLock::new(None),
is_ready: RwLock::new(false),
inner: Arc::new(Inner {
state: Mutex::new(FutureState { result: None, ready: false }),
cv: Condvar::new(),
}),
base: BoxBase::new(),
}
}
/// Set the result of the future
pub fn set_result(&self, value: Box<dyn NyashBox>) {
let mut result = self.result.write().unwrap();
*result = Some(value);
let mut ready = self.is_ready.write().unwrap();
*ready = true;
let mut st = self.inner.state.lock().unwrap();
st.result = Some(value);
st.ready = true;
self.inner.cv.notify_all();
}
/// Get the result (blocks until ready)
pub fn get(&self) -> Box<dyn NyashBox> {
// Simple busy wait (could be improved with condvar)
loop {
let ready = self.is_ready.read().unwrap();
if *ready {
break;
}
drop(ready);
std::thread::yield_now();
let mut st = self.inner.state.lock().unwrap();
while !st.ready {
st = self.inner.cv.wait(st).unwrap();
}
let result = self.result.read().unwrap();
result.as_ref().unwrap().clone_box()
st.result.as_ref().unwrap().clone_box()
}
/// Check if the future is ready
pub fn ready(&self) -> bool {
*self.is_ready.read().unwrap()
self.inner.state.lock().unwrap().ready
}
/// Create a non-owning weak handle to this Future's state
pub fn downgrade(&self) -> FutureWeak { FutureWeak { inner: Arc::downgrade(&self.inner) } }
}
impl NyashBox for NyashFutureBox {
@ -80,10 +85,10 @@ impl NyashBox for NyashFutureBox {
}
fn to_string_box(&self) -> StringBox {
let ready = *self.is_ready.read().unwrap();
let ready = self.inner.state.lock().unwrap().ready;
if ready {
let result = self.result.read().unwrap();
if let Some(value) = result.as_ref() {
let st = self.inner.state.lock().unwrap();
if let Some(value) = st.result.as_ref() {
StringBox::new(format!("Future(ready: {})", value.to_string_box().value))
} else {
StringBox::new("Future(ready: void)".to_string())
@ -118,10 +123,10 @@ impl BoxCore for NyashFutureBox {
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ready = *self.is_ready.read().unwrap();
let ready = self.inner.state.lock().unwrap().ready;
if ready {
let result = self.result.read().unwrap();
if let Some(value) = result.as_ref() {
let st = self.inner.state.lock().unwrap();
if let Some(value) = st.result.as_ref() {
write!(f, "Future(ready: {})", value.to_string_box().value)
} else {
write!(f, "Future(ready: void)")
@ -155,3 +160,10 @@ impl FutureBox {
Ok(self.get())
}
}
impl FutureWeak {
/// Try to upgrade and check readiness
pub(crate) fn is_ready(&self) -> Option<bool> {
self.inner.upgrade().map(|arc| arc.state.lock().unwrap().ready)
}
}

View File

@ -84,6 +84,7 @@ pub mod debug_config_box;
pub mod gc_config_box;
pub mod aot_config_box;
pub mod aot_compiler_box;
pub mod task_group_box;
// Web専用Box群ブラウザ環境でのみ利用可能
#[cfg(target_arch = "wasm32")]
@ -122,6 +123,7 @@ pub use jit_strict_box::JitStrictBox;
pub use jit_hostcall_registry_box::JitHostcallRegistryBox;
pub use aot_config_box::AotConfigBox;
pub use aot_compiler_box::AotCompilerBox;
pub use task_group_box::TaskGroupBox;
// EguiBoxの再エクスポート非WASM環境のみ
#[cfg(all(feature = "gui", not(target_arch = "wasm32")))]
@ -158,7 +160,7 @@ pub use null_box::{NullBox, null};
pub use array::ArrayBox;
pub use buffer::BufferBox;
pub use file::FileBox;
pub use future::{NyashFutureBox, FutureBox};
pub use future::{NyashFutureBox, FutureBox, FutureWeak};
pub use json::JSONBox;
pub use result::{NyashResultBox, ResultBox};
pub use http::HttpClientBox;

View File

@ -0,0 +1,77 @@
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox, VoidBox};
use std::any::Any;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub(crate) struct TaskGroupInner {
pub(super) strong: Mutex<Vec<crate::boxes::future::FutureBox>>,
}
#[derive(Debug, Clone)]
pub struct TaskGroupBox {
base: BoxBase,
// Skeleton: cancellation token owned by this group (future wiring)
cancelled: bool,
pub(crate) inner: Arc<TaskGroupInner>,
}
impl TaskGroupBox {
pub fn new() -> Self {
Self { base: BoxBase::new(), cancelled: false, inner: Arc::new(TaskGroupInner { strong: Mutex::new(Vec::new()) }) }
}
pub fn cancel_all(&mut self) { self.cancelled = true; }
/// Cancel all child tasks (scaffold) and return void
pub fn cancelAll(&mut self) -> Box<dyn NyashBox> {
self.cancel_all();
Box::new(VoidBox::new())
}
/// Join all child tasks with optional timeout (ms); returns void
pub fn joinAll(&self, timeout_ms: Option<i64>) -> Box<dyn NyashBox> {
let ms = timeout_ms.unwrap_or(2000).max(0) as u64;
self.join_all_inner(ms);
Box::new(VoidBox::new())
}
pub fn is_cancelled(&self) -> bool { self.cancelled }
/// Register a Future into this group's ownership
pub fn add_future(&self, fut: &crate::boxes::future::FutureBox) {
if let Ok(mut v) = self.inner.strong.lock() {
v.push(fut.clone());
}
}
fn join_all_inner(&self, timeout_ms: u64) {
use std::time::{Duration, Instant};
let deadline = Instant::now() + Duration::from_millis(timeout_ms);
loop {
let mut all_ready = true;
if let Ok(mut list) = self.inner.strong.lock() {
list.retain(|f| !f.ready());
if !list.is_empty() { all_ready = false; }
}
if all_ready { break; }
if Instant::now() >= deadline { break; }
crate::runtime::global_hooks::safepoint_and_poll();
std::thread::yield_now();
}
}
}
impl BoxCore for TaskGroupBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { None }
fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "TaskGroup(cancelled={})", self.cancelled)
}
fn as_any(&self) -> &dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any { self }
}
impl NyashBox for TaskGroupBox {
fn to_string_box(&self) -> StringBox { StringBox::new(format!("TaskGroup(cancelled={})", self.cancelled)) }
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(g) = other.as_any().downcast_ref::<TaskGroupBox>() { BoolBox::new(self.base.id == g.base.id) } else { BoolBox::new(false) }
}
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
}

View File

@ -5,6 +5,7 @@ use crate::{backend::vm::VMValue, box_trait::{NyashBox, IntegerBox, BoolBox, Str
/// Symbol name for awaiting a FutureBox and returning a value/handle (i64)
pub const SYM_FUTURE_AWAIT_H: &str = "nyash.future.await_h";
pub const SYM_FUTURE_SPAWN_INSTANCE3_I64: &str = "nyash.future.spawn_instance3_i64";
#[cfg(feature = "cranelift-jit")]
pub extern "C" fn nyash_future_await_h(arg0: i64) -> i64 {
@ -34,12 +35,19 @@ pub extern "C" fn nyash_future_await_h(arg0: i64) -> i64 {
});
}
let Some(fut) = fut_opt else { return 0; };
// Block until completion, get NyashBox result
// Cooperative wait with scheduler polling and timeout
let max_ms: u64 = std::env::var("NYASH_AWAIT_MAX_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(5000);
let start = std::time::Instant::now();
while !fut.ready() {
crate::runtime::global_hooks::safepoint_and_poll();
std::thread::yield_now();
if start.elapsed() >= std::time::Duration::from_millis(max_ms) {
// Timeout: return 0 (caller may handle as failure)
return 0;
}
}
// Get NyashBox result and always return a handle
let out_box: Box<dyn NyashBox> = fut.get();
// Fast-path: primitive returns
if let Some(ib) = out_box.as_any().downcast_ref::<IntegerBox>() { return ib.value; }
if let Some(bb) = out_box.as_any().downcast_ref::<BoolBox>() { return if bb.value { 1 } else { 0 }; }
// Otherwise, register handle and return id (works for String/Map/Array/Instance/etc.)
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::from(out_box);
let h = handles::to_handle(arc);
h as i64

View File

@ -9,3 +9,4 @@ pub mod handles;
pub mod birth;
pub mod runtime;
pub mod r#async;
pub mod result;

41
src/jit/extern/result.rs vendored Normal file
View File

@ -0,0 +1,41 @@
//! Result-related JIT extern symbols
use crate::box_trait::NyashBox;
/// Symbol name for wrapping a handle into Result.Ok(handle)
pub const SYM_RESULT_OK_H: &str = "nyash.result.ok_h";
/// Symbol name for wrapping a handle into Result.Err(handle)
pub const SYM_RESULT_ERR_H: &str = "nyash.result.err_h";
#[cfg(feature = "cranelift-jit")]
pub extern "C" fn nyash_result_ok_h(handle: i64) -> i64 {
use crate::jit::rt::handles;
use crate::boxes::result::NyashResultBox;
if handle <= 0 { return 0; }
if let Some(obj) = handles::get(handle as u64) {
let boxed = obj.clone_box();
let res = NyashResultBox::new_ok(boxed);
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(res);
let h = handles::to_handle(arc);
return h as i64;
}
0
}
#[cfg(feature = "cranelift-jit")]
pub extern "C" fn nyash_result_err_h(handle: i64) -> i64 {
use crate::jit::rt::handles;
use crate::boxes::result::NyashResultBox;
// If handle <= 0, synthesize a Timeout StringBox error for await paths.
let err_box: Box<dyn NyashBox> = if handle <= 0 {
Box::new(crate::box_trait::StringBox::new("Timeout".to_string()))
} else if let Some(obj) = handles::get(handle as u64) {
obj.clone_box()
} else {
Box::new(crate::box_trait::StringBox::new("UnknownError".to_string()))
};
let res = NyashResultBox::new_err(err_box);
let arc: std::sync::Arc<dyn NyashBox> = std::sync::Arc::new(res);
let h = handles::to_handle(arc);
h as i64
}

View File

@ -557,6 +557,8 @@ use super::extern_thunks::{
};
#[cfg(feature = "cranelift-jit")]
use crate::jit::r#extern::r#async::nyash_future_await_h;
#[cfg(feature = "cranelift-jit")]
use crate::jit::r#extern::result::{nyash_result_ok_h, nyash_result_err_h};
#[cfg(feature = "cranelift-jit")]
use crate::{
@ -1941,9 +1943,13 @@ impl CraneliftBuilder {
builder.symbol("nyash.jit.dbg_i64", nyash_jit_dbg_i64 as *const u8);
// Async/Future
builder.symbol(crate::jit::r#extern::r#async::SYM_FUTURE_AWAIT_H, nyash_future_await_h as *const u8);
builder.symbol(crate::jit::r#extern::result::SYM_RESULT_OK_H, nyash_result_ok_h as *const u8);
builder.symbol(crate::jit::r#extern::result::SYM_RESULT_ERR_H, nyash_result_err_h as *const u8);
builder.symbol("nyash.jit.block_enter", nyash_jit_block_enter as *const u8);
// Async/Future
builder.symbol(crate::jit::r#extern::r#async::SYM_FUTURE_AWAIT_H, nyash_future_await_h as *const u8);
builder.symbol(crate::jit::r#extern::result::SYM_RESULT_OK_H, nyash_result_ok_h as *const u8);
builder.symbol(crate::jit::r#extern::result::SYM_RESULT_ERR_H, nyash_result_err_h as *const u8);
builder.symbol("nyash.jit.dbg_i64", nyash_jit_dbg_i64 as *const u8);
{
use crate::jit::r#extern::collections as c;

View File

@ -578,8 +578,30 @@ impl LowerCore {
I::Await { dst, future } => {
// Push future param index when known; otherwise -1 to trigger legacy search in shim
if let Some(pidx) = self.param_index.get(future).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
// Call await_h to obtain a handle to the value (0 on timeout)
b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_AWAIT_H, 1, true);
// Treat result as handle (or primitive packed into i64). Store for reuse.
// Store the awaited handle temporarily
let hslot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(hslot);
// Build Ok result: ok_h(handle)
b.load_local_i64(hslot);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_OK_H, 1, true);
let ok_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(ok_slot);
// Build Err result: err_h(0) → Timeout
b.emit_const_i64(0);
b.emit_host_call(crate::jit::r#extern::result::SYM_RESULT_ERR_H, 1, true);
let err_slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(err_slot);
// Cond: (handle == 0)
b.load_local_i64(hslot);
b.emit_const_i64(0);
b.emit_compare(crate::jit::lower::builder::CmpKind::Eq);
// Stack for select: cond, then(err), else(ok)
b.load_local_i64(err_slot);
b.load_local_i64(ok_slot);
b.emit_select_i64();
// Store selected Result handle to destination
let 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 });
@ -757,6 +779,29 @@ impl LowerCore {
if dst.is_some() { b.emit_const_i64(0); }
}
} else {
// Async spawn bridge: env.future.spawn_instance(recv, method_name, args...)
if iface_name == "env.future" && method_name == "spawn_instance" {
// Stack layout for hostcall: argc_total, a0(recv), a1(method_name), a2(first payload)
// 1) receiver
if let Some(recv) = args.get(0) {
if let Some(pidx) = self.param_index.get(recv).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
} else { b.emit_const_i64(-1); }
// 2) method name (best-effort)
if let Some(meth) = args.get(1) { self.push_value_if_known_or_param(b, meth); } else { b.emit_const_i64(0); }
// 3) first payload argument if present
if let Some(arg2) = args.get(2) { self.push_value_if_known_or_param(b, arg2); } else { b.emit_const_i64(0); }
// argc_total = explicit args including method name and payload (exclude receiver)
let argc_total = args.len().saturating_sub(1).max(0);
b.emit_const_i64(argc_total as i64);
// Call spawn shim; it returns Future handle
b.emit_host_call(crate::jit::r#extern::r#async::SYM_FUTURE_SPAWN_INSTANCE3_I64, 4, true);
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);
}
return Ok(());
}
// Unknown extern: strictではno-opにしてfailを避ける
if dst.is_some() { b.emit_const_i64(0); }
}

View File

@ -1445,19 +1445,31 @@ impl MirBuilder {
/// Build nowait statement: nowait variable = expression
fn build_nowait_statement(&mut self, variable: String, expression: ASTNode) -> Result<ValueId, String> {
// Evaluate the expression
// If expression is a method call, prefer true async via env.future.spawn_instance
if let ASTNode::MethodCall { object, method, arguments, .. } = expression.clone() {
let recv_val = self.build_expression(*object)?;
let mname_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: mname_id, value: crate::mir::ConstValue::String(method.clone()) })?;
let mut arg_vals: Vec<ValueId> = Vec::with_capacity(2 + arguments.len());
arg_vals.push(recv_val);
arg_vals.push(mname_id);
for a in arguments.into_iter() { arg_vals.push(self.build_expression(a)?); }
let future_id = self.value_gen.next();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(future_id),
iface_name: "env.future".to_string(),
method_name: "spawn_instance".to_string(),
args: arg_vals,
effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io),
})?;
self.variable_map.insert(variable.clone(), future_id);
return Ok(future_id);
}
// Fallback: resolved future
let expression_value = self.build_expression(expression)?;
// Create a new Future with the evaluated expression as the initial value
let future_id = self.value_gen.next();
self.emit_instruction(MirInstruction::FutureNew {
dst: future_id,
value: expression_value,
})?;
// Store the future in the variable
self.emit_instruction(MirInstruction::FutureNew { dst: future_id, value: expression_value })?;
self.variable_map.insert(variable.clone(), future_id);
Ok(future_id)
}
@ -1466,6 +1478,9 @@ impl MirBuilder {
// Evaluate the expression (should be a Future)
let future_value = self.build_expression(expression)?;
// Insert checkpoint before await (safepoint)
self.emit_instruction(MirInstruction::Safepoint)?;
// Create destination for await result
let result_id = self.value_gen.next();
@ -1474,6 +1489,8 @@ impl MirBuilder {
dst: result_id,
future: future_value,
})?;
// Insert checkpoint after await (safepoint)
self.emit_instruction(MirInstruction::Safepoint)?;
Ok(result_id)
}

View File

@ -142,19 +142,36 @@ impl MirBuilder {
/// Build nowait statement: nowait variable = expression
pub(super) fn build_nowait_statement(&mut self, variable: String, expression: ASTNode) -> Result<ValueId, String> {
// Evaluate the expression
// If the expression is a method call on a receiver, spawn it asynchronously via env.future.spawn_instance
if let ASTNode::MethodCall { object, method, arguments, .. } = expression.clone() {
// Build receiver value
let recv_val = self.build_expression(*object)?;
// Build method name as Const String
let mname_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: mname_id, value: crate::mir::ConstValue::String(method.clone()) })?;
// Build argument values
let mut arg_vals: Vec<ValueId> = Vec::with_capacity(2 + arguments.len());
arg_vals.push(recv_val);
arg_vals.push(mname_id);
for a in arguments.into_iter() { arg_vals.push(self.build_expression(a)?); }
// Emit extern call to env.future.spawn_instance, capturing Future result
let future_id = self.value_gen.next();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(future_id),
iface_name: "env.future".to_string(),
method_name: "spawn_instance".to_string(),
args: arg_vals,
effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io),
})?;
// Store the future in the variable
self.variable_map.insert(variable.clone(), future_id);
return Ok(future_id);
}
// Fallback: evaluate synchronously and wrap into a resolved Future
let expression_value = self.build_expression(expression)?;
// Create a new Future with the evaluated expression as the initial value
let future_id = self.value_gen.next();
self.emit_instruction(MirInstruction::FutureNew {
dst: future_id,
value: expression_value,
})?;
// Store the future in the variable
self.emit_instruction(MirInstruction::FutureNew { dst: future_id, value: expression_value })?;
self.variable_map.insert(variable.clone(), future_id);
Ok(future_id)
}
}

View File

@ -81,6 +81,12 @@ pub enum VerificationError {
instruction_index: usize,
name: String,
},
/// Await must be surrounded by checkpoints (before and after)
MissingCheckpointAroundAwait {
block: BasicBlockId,
instruction_index: usize,
position: &'static str, // "before" | "after"
},
}
/// MIR verifier for SSA form and semantic correctness
@ -152,6 +158,10 @@ impl MirVerifier {
if let Err(mut legacy_errors) = self.verify_no_legacy_ops(function) {
local_errors.append(&mut legacy_errors);
}
// 8. Async semantics: ensure checkpoints around await
if let Err(mut await_cp) = self.verify_await_checkpoints(function) {
local_errors.append(&mut await_cp);
}
if local_errors.is_empty() {
Ok(())
@ -246,6 +256,34 @@ impl MirVerifier {
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
/// Ensure that each Await instruction is immediately preceded and followed by a checkpoint
/// A checkpoint is either MirInstruction::Safepoint or ExternCall("env.runtime", "checkpoint").
fn verify_await_checkpoints(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
use super::MirInstruction as I;
let mut errors = Vec::new();
let is_cp = |inst: &I| match inst {
I::Safepoint => true,
I::ExternCall { iface_name, method_name, .. } => iface_name == "env.runtime" && method_name == "checkpoint",
_ => false,
};
for (bid, block) in &function.blocks {
let instrs = &block.instructions;
for (idx, inst) in instrs.iter().enumerate() {
if let I::Await { .. } = inst {
// Check immediate previous
if idx == 0 || !is_cp(&instrs[idx - 1]) {
errors.push(VerificationError::MissingCheckpointAroundAwait { block: *bid, instruction_index: idx, position: "before" });
}
// Check immediate next (within instructions list)
if idx + 1 >= instrs.len() || !is_cp(&instrs[idx + 1]) {
errors.push(VerificationError::MissingCheckpointAroundAwait { block: *bid, instruction_index: idx, position: "after" });
}
}
}
}
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
/// Verify WeakRef/Barrier minimal semantics
fn verify_weakref_and_barrier(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
use super::MirInstruction;
@ -696,6 +734,9 @@ impl std::fmt::Display for VerificationError {
VerificationError::UnsupportedLegacyInstruction { block, instruction_index, name } => {
write!(f, "Unsupported legacy instruction '{}' in block {} at {} (enable rewrite passes)", name, block, instruction_index)
},
VerificationError::MissingCheckpointAroundAwait { block, instruction_index, position } => {
write!(f, "Missing {} checkpoint around await in block {} at instruction {}", position, block, instruction_index)
},
}
}
}

View File

@ -320,6 +320,9 @@ impl NyashRunner {
Ok(result) => {
println!("✅ Execution completed successfully!");
println!("Result: {}", result.to_string_box().value);
// Structured concurrency: best-effort join of spawned tasks at program end
let join_ms: u64 = std::env::var("NYASH_JOIN_ALL_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
nyash_rust::runtime::global_hooks::join_all_registered_futures(join_ms);
},
Err(e) => {
// Use enhanced error reporting with source context

View File

@ -4,20 +4,140 @@ use once_cell::sync::OnceCell;
use std::sync::{Arc, RwLock};
use super::{gc::GcHooks, scheduler::Scheduler};
use super::scheduler::CancellationToken;
static GLOBAL_GC: OnceCell<RwLock<Option<Arc<dyn GcHooks>>>> = OnceCell::new();
static GLOBAL_SCHED: OnceCell<RwLock<Option<Arc<dyn Scheduler>>>> = OnceCell::new();
// Phase 2 scaffold: current task group's cancellation token (no-op default)
static GLOBAL_CUR_TOKEN: OnceCell<RwLock<Option<CancellationToken>>> = OnceCell::new();
// Phase 2 scaffold: current group's child futures registry (best-effort)
static GLOBAL_GROUP_FUTURES: OnceCell<RwLock<Vec<crate::boxes::future::FutureWeak>>> = OnceCell::new();
// Strong ownership list for implicit group (pre-TaskGroup actualization)
static GLOBAL_GROUP_STRONG: OnceCell<RwLock<Vec<crate::boxes::future::FutureBox>>> = OnceCell::new();
// Simple scope depth counter for implicit group (join-at-scope-exit footing)
static TASK_SCOPE_DEPTH: OnceCell<RwLock<usize>> = OnceCell::new();
// TaskGroup scope stack (explicit group ownership per function scope)
static TASK_GROUP_STACK: OnceCell<RwLock<Vec<std::sync::Arc<crate::boxes::task_group_box::TaskGroupInner>>>> = OnceCell::new();
fn gc_cell() -> &'static RwLock<Option<Arc<dyn GcHooks>>> { GLOBAL_GC.get_or_init(|| RwLock::new(None)) }
fn sched_cell() -> &'static RwLock<Option<Arc<dyn Scheduler>>> { GLOBAL_SCHED.get_or_init(|| RwLock::new(None)) }
fn token_cell() -> &'static RwLock<Option<CancellationToken>> { GLOBAL_CUR_TOKEN.get_or_init(|| RwLock::new(None)) }
fn futures_cell() -> &'static RwLock<Vec<crate::boxes::future::FutureWeak>> { GLOBAL_GROUP_FUTURES.get_or_init(|| RwLock::new(Vec::new())) }
fn strong_cell() -> &'static RwLock<Vec<crate::boxes::future::FutureBox>> { GLOBAL_GROUP_STRONG.get_or_init(|| RwLock::new(Vec::new())) }
fn scope_depth_cell() -> &'static RwLock<usize> { TASK_SCOPE_DEPTH.get_or_init(|| RwLock::new(0)) }
fn group_stack_cell() -> &'static RwLock<Vec<std::sync::Arc<crate::boxes::task_group_box::TaskGroupInner>>> { TASK_GROUP_STACK.get_or_init(|| RwLock::new(Vec::new())) }
pub fn set_from_runtime(rt: &crate::runtime::nyash_runtime::NyashRuntime) {
if let Ok(mut g) = gc_cell().write() { *g = Some(rt.gc.clone()); }
if let Ok(mut s) = sched_cell().write() { *s = rt.scheduler.as_ref().cloned(); }
// Optional: initialize a fresh token for the runtime's root group (Phase 2 wiring)
if let Ok(mut t) = token_cell().write() { if t.is_none() { *t = Some(CancellationToken::new()); } }
// Reset group futures registry on new runtime
if let Ok(mut f) = futures_cell().write() { f.clear(); }
if let Ok(mut s) = strong_cell().write() { s.clear(); }
if let Ok(mut d) = scope_depth_cell().write() { *d = 0; }
if let Ok(mut st) = group_stack_cell().write() { st.clear(); }
}
pub fn set_gc(gc: Arc<dyn GcHooks>) { if let Ok(mut g) = gc_cell().write() { *g = Some(gc); } }
pub fn set_scheduler(s: Arc<dyn Scheduler>) { if let Ok(mut w) = sched_cell().write() { *w = Some(s); } }
/// Set the current task group's cancellation token (scaffold).
pub fn set_current_group_token(tok: CancellationToken) { if let Ok(mut w) = token_cell().write() { *w = Some(tok); } }
/// Get the current task group's cancellation token (no-op default).
pub fn current_group_token() -> CancellationToken {
if let Ok(r) = token_cell().read() {
if let Some(t) = r.as_ref() { return t.clone(); }
}
CancellationToken::new()
}
/// Register a Future into the current group's registry (best-effort; clones share state)
pub fn register_future_to_current_group(fut: &crate::boxes::future::FutureBox) {
// Prefer explicit current TaskGroup at top of stack
if let Ok(st) = group_stack_cell().read() {
if let Some(inner) = st.last() {
if let Ok(mut v) = inner.strong.lock() { v.push(fut.clone()); return; }
}
}
// Fallback to implicit global group
if let Ok(mut list) = futures_cell().write() { list.push(fut.downgrade()); }
if let Ok(mut s) = strong_cell().write() { s.push(fut.clone()); }
}
/// Join all currently registered futures with a coarse timeout guard.
pub fn join_all_registered_futures(timeout_ms: u64) {
use std::time::{Duration, Instant};
let deadline = Instant::now() + Duration::from_millis(timeout_ms);
loop {
let mut all_ready = true;
// purge list of dropped or completed futures opportunistically
{
// purge weak list: keep only upgradeable futures
if let Ok(mut list) = futures_cell().write() { list.retain(|fw| fw.is_ready().is_some()); }
// purge strong list: remove completed futures to reduce retention
if let Ok(mut s) = strong_cell().write() { s.retain(|f| !f.ready()); }
}
// check readiness
{
if let Ok(list) = futures_cell().read() {
for fw in list.iter() {
if let Some(ready) = fw.is_ready() {
if !ready { all_ready = false; break; }
}
}
}
}
if all_ready { break; }
if Instant::now() >= deadline { break; }
safepoint_and_poll();
std::thread::yield_now();
}
// Final sweep
if let Ok(mut s) = strong_cell().write() { s.retain(|f| !f.ready()); }
if let Ok(mut list) = futures_cell().write() { list.retain(|fw| matches!(fw.is_ready(), Some(false))); }
}
/// Push a task scope (footing). On pop of the outermost scope, perform a best-effort join.
pub fn push_task_scope() {
if let Ok(mut d) = scope_depth_cell().write() { *d += 1; }
// Push a new explicit TaskGroup for this scope
if let Ok(mut st) = group_stack_cell().write() {
st.push(std::sync::Arc::new(crate::boxes::task_group_box::TaskGroupInner { strong: std::sync::Mutex::new(Vec::new()) }));
}
}
/// Pop a task scope. When depth reaches 0, join outstanding futures.
pub fn pop_task_scope() {
let mut do_join = false;
let mut popped: Option<std::sync::Arc<crate::boxes::task_group_box::TaskGroupInner>> = None;
{
if let Ok(mut d) = scope_depth_cell().write() {
if *d > 0 { *d -= 1; }
if *d == 0 { do_join = true; }
}
}
// Pop explicit group for this scope
if let Ok(mut st) = group_stack_cell().write() { popped = st.pop(); }
if do_join {
let ms: u64 = std::env::var("NYASH_TASK_SCOPE_JOIN_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(1000);
if let Some(inner) = popped {
// Join this group's outstanding futures
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(ms);
loop {
let mut all_ready = true;
if let Ok(mut list) = inner.strong.lock() { list.retain(|f| !f.ready()); if !list.is_empty() { all_ready = false; } }
if all_ready { break; }
if std::time::Instant::now() >= deadline { break; }
safepoint_and_poll();
std::thread::yield_now();
}
} else {
// Fallback to implicit global group
join_all_registered_futures(ms);
}
}
}
/// Perform a runtime safepoint and poll the scheduler if available.
pub fn safepoint_and_poll() {
@ -30,8 +150,27 @@ pub fn safepoint_and_poll() {
}
/// Try to schedule a task on the global scheduler. Returns true if scheduled.
pub fn spawn_task(_name: &str, f: Box<dyn FnOnce() + 'static>) -> bool {
// Minimal inline execution to avoid Send bounds; upgrade to true scheduling later
pub fn spawn_task(name: &str, f: Box<dyn FnOnce() + Send + 'static>) -> bool {
// If a scheduler is registered, enqueue the task; otherwise run inline.
if let Ok(s) = sched_cell().read() {
if let Some(sched) = s.as_ref() {
sched.spawn(name, f);
return true;
}
}
// Fallback inline execution
f();
true
false
}
/// Spawn a task bound to a cancellation token when available (skeleton).
pub fn spawn_task_with_token(name: &str, token: crate::runtime::scheduler::CancellationToken, f: Box<dyn FnOnce() + Send + 'static>) -> bool {
if let Ok(s) = sched_cell().read() {
if let Some(sched) = s.as_ref() {
sched.spawn_with_token(name, token, f);
return true;
}
}
f();
false
}

View File

@ -118,6 +118,31 @@ impl PluginHost {
method_name: &str,
args: &[Box<dyn crate::box_trait::NyashBox>],
) -> BidResult<Option<Box<dyn crate::box_trait::NyashBox>>> {
// Special-case env.future.await to avoid holding loader RwLock while polling scheduler
if iface_name == "env.future" && method_name == "await" {
use crate::boxes::result::NyashResultBox;
if let Some(arg0) = args.get(0) {
if let Some(fut) = arg0.as_any().downcast_ref::<crate::boxes::future::FutureBox>() {
let max_ms: u64 = std::env::var("NYASH_AWAIT_MAX_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(5000);
let start = std::time::Instant::now();
let mut spins = 0usize;
while !fut.ready() {
crate::runtime::global_hooks::safepoint_and_poll();
std::thread::yield_now();
spins += 1;
if spins % 1024 == 0 { std::thread::sleep(std::time::Duration::from_millis(1)); }
if start.elapsed() >= std::time::Duration::from_millis(max_ms) {
let err = crate::box_trait::StringBox::new("Timeout");
return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err)))));
}
}
return Ok(fut.wait_and_get().ok().map(|v| Box::new(NyashResultBox::new_ok(v)) as Box<dyn crate::box_trait::NyashBox>));
} else {
return Ok(Some(Box::new(NyashResultBox::new_ok(arg0.clone_box()))));
}
}
return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(crate::box_trait::StringBox::new("InvalidArgs"))))));
}
let l = self.loader.read().unwrap();
l.extern_call(iface_name, method_name, args)
}

View File

@ -500,18 +500,35 @@ impl PluginLoaderV2 {
Ok(None)
}
("env.future", "await") => {
// await(future) -> value (pass-through if not a FutureBox)
// await(future) -> Result.Ok(value) / Result.Err(Timeout|Error)
use crate::boxes::result::NyashResultBox;
if let Some(arg) = args.get(0) {
if let Some(fut) = arg.as_any().downcast_ref::<crate::boxes::future::FutureBox>() {
match fut.wait_and_get() { Ok(v) => return Ok(Some(v)), Err(e) => {
eprintln!("[env.future.await] error: {}", e);
return Ok(None);
} }
let max_ms: u64 = std::env::var("NYASH_AWAIT_MAX_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(5000);
let start = std::time::Instant::now();
let mut spins = 0usize;
while !fut.ready() {
crate::runtime::global_hooks::safepoint_and_poll();
std::thread::yield_now();
spins += 1;
if spins % 1024 == 0 { std::thread::sleep(std::time::Duration::from_millis(1)); }
if start.elapsed() >= std::time::Duration::from_millis(max_ms) {
let err = crate::box_trait::StringBox::new("Timeout");
return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err)))));
}
}
return match fut.wait_and_get() {
Ok(v) => Ok(Some(Box::new(NyashResultBox::new_ok(v)))),
Err(e) => {
let err = crate::box_trait::StringBox::new(format!("Error: {}", e));
Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err)))))
}
};
} else {
return Ok(Some(arg.clone_box()));
return Ok(Some(Box::new(NyashResultBox::new_ok(arg.clone_box()))));
}
}
Ok(None)
Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(crate::box_trait::StringBox::new("InvalidArgs"))))))
}
("env.future", "spawn_instance") => {
// spawn_instance(recv, method_name, args...) -> FutureBox
@ -530,7 +547,9 @@ impl PluginLoaderV2 {
let method_name_inline = method_name.clone();
let tail_inline: Vec<Box<dyn NyashBox>> = tail.iter().map(|a| a.clone_box()).collect();
let fut_setter = fut.clone();
let scheduled = crate::runtime::global_hooks::spawn_task("spawn_instance", Box::new(move || {
// Phase 2: attempt to bind to current task group's token (no-op if unset)
let token = crate::runtime::global_hooks::current_group_token();
let scheduled = crate::runtime::global_hooks::spawn_task_with_token("spawn_instance", token, Box::new(move || {
let host = crate::runtime::get_global_plugin_host();
let read_res = host.read();
if let Ok(ro) = read_res {
@ -551,11 +570,14 @@ impl PluginLoaderV2 {
}
}
}
// Register into current TaskGroup (if any) or implicit group (best-effort)
crate::runtime::global_hooks::register_future_to_current_group(&fut);
return Ok(Some(Box::new(fut)));
}
}
// Fallback: resolved future of first arg
if let Some(v) = args.get(0) { fut.set_result(v.clone_box()); }
crate::runtime::global_hooks::register_future_to_current_group(&fut);
Ok(Some(Box::new(fut)))
}
("env.canvas", _) => {

View File

@ -11,6 +11,11 @@ pub trait Scheduler: Send + Sync {
fn poll(&self) {}
/// Cooperative yield point (no-op for single-thread).
fn yield_now(&self) { }
/// Optional: spawn with a cancellation token. Default delegates to spawn.
fn spawn_with_token(&self, name: &str, _token: CancellationToken, f: Box<dyn FnOnce() + Send + 'static>) {
self.spawn(name, f)
}
}
use std::collections::VecDeque;
@ -67,3 +72,15 @@ impl Scheduler for SingleThreadScheduler {
}
}
}
use std::sync::atomic::{AtomicBool, Ordering};
/// Simple idempotent cancellation token for structured concurrency (skeleton)
#[derive(Clone, Debug)]
pub struct CancellationToken(Arc<AtomicBool>);
impl CancellationToken {
pub fn new() -> Self { Self(Arc::new(AtomicBool::new(false))) }
pub fn cancel(&self) { self.0.store(true, Ordering::SeqCst); }
pub fn is_cancelled(&self) -> bool { self.0.load(Ordering::SeqCst) }
}

View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd)
BIN="$ROOT_DIR/target/release/nyash"
APP="$ROOT_DIR/apps/tests/async-spawn-instance/main.nyash"
echo "[smoke] Building nyash (cranelift-jit)"
cargo build --release --features cranelift-jit >/dev/null
echo "[smoke] VM run (10s timeout)"
timeout 10s env NYASH_PLUGIN_ONLY=1 NYASH_AWAIT_MAX_MS=5000 "$BIN" --backend vm "$APP" | tee /tmp/ny_vm.out || true
echo "[smoke] JIT run (10s timeout)"
timeout 10s env NYASH_PLUGIN_ONLY=1 NYASH_AWAIT_MAX_MS=5000 "$BIN" --backend cranelift "$APP" | tee /tmp/ny_jit.out || true
echo "[smoke] LLVM AOT skipped for this test (no 'env' binding in source)"
echo "[smoke] Done"