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:
@ -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));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
77
src/boxes/task_group_box.rs
Normal file
77
src/boxes/task_group_box.rs
Normal 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() }
|
||||
}
|
||||
18
src/jit/extern/async.rs
vendored
18
src/jit/extern/async.rs
vendored
@ -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
|
||||
|
||||
1
src/jit/extern/mod.rs
vendored
1
src/jit/extern/mod.rs
vendored
@ -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
41
src/jit/extern/result.rs
vendored
Normal 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
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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); }
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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", _) => {
|
||||
|
||||
@ -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) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user