use std::sync::{Arc, Mutex};
use crate::atom::Atom;
use crate::ets::{OwnedTerm, copy_term_to_ets};
use crate::native::native_process::{
NativeBody, NativeContext, NativeHandlerFactory, NativeOutcome,
};
use crate::native::spawn::{
SpawnError, SpawnFacility, SpawnMonitorResult, SpawnOptions, SpawnOptionsResult,
};
use crate::native::{CapabilitySet, LocalSendError, LocalSendFacility, LocalSendRequest};
use crate::process::heap::DEFAULT_HEAP_SIZE;
use crate::process::{ExitReason, Priority, Process, ProcessStatus};
use crate::term::Term;
use super::WasmScheduler;
struct DeferredSpawn {
pid: u64,
factory: NativeHandlerFactory,
link_to: Option<u64>,
}
struct DeferredSend {
target_pid: u64,
message: OwnedTerm,
}
#[derive(Default)]
struct DeferredEffects {
spawns: Vec<DeferredSpawn>,
sends: Vec<DeferredSend>,
}
type SharedEffects = Arc<Mutex<DeferredEffects>>;
struct CooperativeSpawn {
effects: SharedEffects,
next_pid: Arc<Mutex<u64>>,
}
impl SpawnFacility for CooperativeSpawn {
fn spawn(
&self,
_caller_pid: u64,
_module: Atom,
_function: Atom,
_args: Vec<Term>,
_link_to: Option<u64>,
) -> Result<u64, SpawnError> {
Err(SpawnError::UnresolvedMfa)
}
fn spawn_native(
&self,
_caller_pid: u64,
factory: NativeHandlerFactory,
link_to: Option<u64>,
) -> Result<u64, SpawnError> {
let pid = {
let mut guard = lock(&self.next_pid);
let pid = *guard;
*guard = guard.saturating_add(1);
pid
};
lock(&self.effects).spawns.push(DeferredSpawn {
pid,
factory,
link_to,
});
Ok(pid)
}
fn spawn_monitor(
&self,
_caller_pid: u64,
_module: Atom,
_function: Atom,
_args: Vec<Term>,
) -> Result<SpawnMonitorResult, SpawnError> {
Err(SpawnError::UnresolvedMfa)
}
fn spawn_lambda(
&self,
_caller_pid: u64,
_module: Atom,
_lambda_index: u32,
_link_to: Option<u64>,
) -> Result<u64, SpawnError> {
Err(SpawnError::UnresolvedMfa)
}
fn spawn_lambda_monitor(
&self,
_caller_pid: u64,
_module: Atom,
_lambda_index: u32,
) -> Result<SpawnMonitorResult, SpawnError> {
Err(SpawnError::UnresolvedMfa)
}
fn spawn_with_options(
&self,
_caller_pid: u64,
_module: Atom,
_function: Atom,
_args: Vec<Term>,
_options: SpawnOptions,
) -> Result<SpawnOptionsResult, SpawnError> {
Err(SpawnError::UnresolvedMfa)
}
fn spawn_lambda_with_options(
&self,
_caller_pid: u64,
_module: Atom,
_lambda_index: u32,
_options: SpawnOptions,
) -> Result<SpawnOptionsResult, SpawnError> {
Err(SpawnError::UnresolvedMfa)
}
}
struct CooperativeLocalSend {
effects: SharedEffects,
}
impl LocalSendFacility for CooperativeLocalSend {
fn send_local(&self, request: LocalSendRequest<'_>) -> Result<(), LocalSendError> {
if let Ok(message) = copy_term_to_ets(request.message) {
lock(&self.effects).sends.push(DeferredSend {
target_pid: request.target_pid,
message,
});
}
Ok(())
}
}
enum NativeSliceResult {
Continue,
Wait,
Stop(ExitReason),
}
impl WasmScheduler {
pub fn spawn_native_root(&mut self, factory: NativeHandlerFactory) -> u64 {
let pid = self.alloc_pid();
let mut process = Process::with_capabilities(pid, DEFAULT_HEAP_SIZE, CapabilitySet::all());
process.set_group_leader(Term::pid(pid));
process.set_priority(Priority::Normal);
process.set_native_body(NativeBody::new(factory));
self.ready.push(pid, process.priority());
self.processes.insert(pid, process);
pid
}
pub fn run_native_until_idle(&mut self) -> Vec<u64> {
let mut exited = Vec::new();
let budget = self.ready_len();
for _ in 0..budget {
let Some(pid) = self.ready.pop() else {
break;
};
if self.waiting.contains(&pid) {
continue;
}
let Some(mut process) = self.processes.remove(&pid) else {
continue;
};
if !process.is_native() {
let priority = process.priority();
self.processes.insert(pid, process);
self.ready.push(pid, priority);
continue;
}
match self.run_one_native_slice(&mut process) {
NativeSliceResult::Continue => {
let priority = process.priority();
let _transition = process.transition_to(ProcessStatus::Yielded);
self.processes.insert(pid, process);
self.ready.push(pid, priority);
}
NativeSliceResult::Wait => {
let _transition = process.transition_to(ProcessStatus::Waiting);
self.processes.insert(pid, process);
self.waiting.insert(pid);
}
NativeSliceResult::Stop(reason) => {
let result = capture_exit_result(&process);
process.terminate(reason);
self.record_native_exit(pid, reason, result);
exited.push(pid);
}
}
}
exited
}
fn run_one_native_slice(&mut self, process: &mut Process) -> NativeSliceResult {
if transition_to_running(process).is_err() {
return NativeSliceResult::Stop(ExitReason::Error);
}
let mut handler = match process.native_body_mut() {
Some(body) => body.handler.take().unwrap_or_else(|| (body.factory)()),
None => return NativeSliceResult::Stop(ExitReason::Normal),
};
let effects: SharedEffects = Arc::new(Mutex::new(DeferredEffects::default()));
let local_send: Arc<dyn LocalSendFacility> = Arc::new(CooperativeLocalSend {
effects: Arc::clone(&effects),
});
let spawn: Arc<dyn SpawnFacility> = Arc::new(CooperativeSpawn {
effects: Arc::clone(&effects),
next_pid: Arc::clone(&self.shared_next_pid),
});
let outcome = {
let mut context = NativeContext::new(process, local_send, spawn, None, None);
handler.handle(&mut context)
};
if let Some(body) = process.native_body_mut() {
body.handler = Some(handler);
}
self.apply_deferred_effects(&effects);
match outcome {
NativeOutcome::Continue => NativeSliceResult::Continue,
NativeOutcome::Wait => NativeSliceResult::Wait,
NativeOutcome::Stop(reason) => NativeSliceResult::Stop(reason),
}
}
fn apply_deferred_effects(&mut self, effects: &SharedEffects) {
let drained = {
let mut guard = lock(effects);
std::mem::take(&mut *guard)
};
for spawn in drained.spawns {
self.materialize_native_child(spawn);
}
for send in drained.sends {
let _delivered = self.send_owned(send.target_pid, &send.message);
}
}
fn materialize_native_child(&mut self, spawn: DeferredSpawn) {
let DeferredSpawn {
pid,
factory,
link_to: _link_to,
} = spawn;
let mut process = Process::with_capabilities(pid, DEFAULT_HEAP_SIZE, CapabilitySet::all());
process.set_group_leader(Term::pid(pid));
process.set_priority(Priority::Normal);
process.set_native_body(NativeBody::new(factory));
self.ready.push(pid, process.priority());
self.processes.insert(pid, process);
}
}
fn transition_to_running(process: &mut Process) -> Result<(), ()> {
match process.status() {
ProcessStatus::Running => Ok(()),
ProcessStatus::New | ProcessStatus::Yielded | ProcessStatus::Waiting => process
.transition_to(ProcessStatus::Running)
.map_err(|_| ()),
_ => Err(()),
}
}
fn capture_exit_result(process: &Process) -> OwnedTerm {
copy_term_to_ets(process.x_reg(0))
.unwrap_or_else(|_| OwnedTerm::immediate(Term::atom(Atom::UNDEFINED)))
}
fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(test)]
#[path = "wasm_native_tests.rs"]
mod tests;