use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use crate::atom::Atom;
use crate::error::ExecError;
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::{CodePosition, ExitReason, Priority, Process, ProcessStatus};
use crate::supervision::link;
use crate::term::Term;
use super::{WasmAsyncCompletion, WasmRunSummary, WasmScheduler};
struct DeferredSpawn {
pid: u64,
factory: NativeHandlerFactory,
link_to: Option<u64>,
}
struct DeferredMfaSpawn {
pid: u64,
module: Atom,
function: Atom,
args: Vec<OwnedTerm>,
}
struct DeferredSend {
target_pid: u64,
message: OwnedTerm,
}
#[derive(Default)]
pub(super) struct DeferredEffects {
spawns: Vec<DeferredSpawn>,
mfa_spawns: Vec<DeferredMfaSpawn>,
sends: Vec<DeferredSend>,
}
pub(super) 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> {
if link_to.is_some() {
return Err(SpawnError::UnresolvedMfa);
}
let mut owned_args = Vec::with_capacity(args.len());
for arg in &args {
let owned = copy_term_to_ets(*arg).map_err(|_| SpawnError::UnresolvedMfa)?;
owned_args.push(owned);
}
let pid = {
let mut guard = lock(&self.next_pid);
let pid = *guard;
*guard = guard.saturating_add(1);
pid
};
lock(&self.effects).mfa_spawns.push(DeferredMfaSpawn {
pid,
module,
function,
args: owned_args,
});
Ok(pid)
}
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));
let ready_was_empty = self.ready.len() == 0;
self.ready.push(pid, process.priority());
self.note_ready_push(ready_was_empty);
self.processes.insert(pid, process);
pid
}
pub(super) fn deliver_native_async_completion(
&mut self,
pid: u64,
completion: WasmAsyncCompletion,
) -> bool {
let (tag, payload) = match completion {
WasmAsyncCompletion::Ok(term) => (Atom::OK, term),
WasmAsyncCompletion::Error(term) => (Atom::ERROR, term),
};
let was_waiting = self.waiting.contains(&pid);
let Some(process) = self.processes.get_mut(&pid) else {
return false;
};
match copy_payload_into_tuple(process, tag, payload) {
Some(envelope) => {
process.mailbox_mut().push_owned(envelope);
self.after_successful_enqueue(pid);
}
None => {
let _woken = self.wake(pid);
}
}
was_waiting
}
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);
self.propagate_native_exit(&mut process, reason);
process.terminate(reason);
self.record_native_exit(pid, reason, result);
exited.push(pid);
}
}
}
exited
}
pub(super) fn dispatch_native_in_turn(
&mut self,
pid: u64,
priority: Priority,
mut process: Process,
summary: &mut WasmRunSummary,
yielded_next_tick: &mut Vec<(u64, Priority)>,
) {
summary.executed += 1;
match self.run_one_native_slice(&mut process) {
NativeSliceResult::Continue => {
let _transition = process.transition_to(ProcessStatus::Yielded);
self.processes.insert(pid, process);
yielded_next_tick.push((pid, priority));
summary.yielded.push(pid);
}
NativeSliceResult::Wait => {
let _transition = process.transition_to(ProcessStatus::Waiting);
self.processes.insert(pid, process);
self.waiting.insert(pid);
summary.waiting.push(pid);
}
NativeSliceResult::Stop(reason) => {
let result = capture_exit_result(&process);
self.propagate_native_exit(&mut process, reason);
process.terminate(reason);
self.record_native_exit(pid, reason, result);
summary.exited.push(pid);
}
}
}
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 timers = Arc::clone(&self.native_timers);
let async_facility = self.wasm_async_nif_facility.clone();
let outcome = {
let mut context = NativeContext::new(process, local_send, spawn, None, Some(timers));
context.set_wasm_async_nif_facility(async_facility);
handler.handle(&mut context)
};
if let Some(body) = process.native_body_mut() {
body.handler = Some(handler);
}
self.apply_deferred_effects(process, &effects);
match outcome {
NativeOutcome::Continue => NativeSliceResult::Continue,
NativeOutcome::Wait => NativeSliceResult::Wait,
NativeOutcome::Stop(reason) => NativeSliceResult::Stop(reason),
}
}
fn apply_deferred_effects(&mut self, running: &mut Process, effects: &SharedEffects) {
let drained = {
let mut guard = lock(effects);
std::mem::take(&mut *guard)
};
let running_pid = running.pid();
for spawn in drained.spawns {
if spawn.link_to == Some(running_pid) {
let _linked = running.add_link(spawn.pid);
}
self.materialize_native_child(spawn);
}
for spawn in drained.mfa_spawns {
self.materialize_mfa_child(spawn);
}
for send in drained.sends {
let _delivered = self.send_owned(send.target_pid, &send.message);
}
}
pub(super) fn bytecode_effect_facilities(
&self,
) -> (
SharedEffects,
Arc<dyn LocalSendFacility>,
Arc<dyn SpawnFacility>,
) {
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),
});
(effects, local_send, spawn)
}
pub(super) fn apply_bytecode_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 spawn in drained.mfa_spawns {
self.materialize_mfa_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,
} = 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));
if let Some(parent_pid) = link_to {
let _child_linked = process.add_link(parent_pid);
if let Some(parent) = self.processes.get_mut(&parent_pid) {
let _parent_linked = parent.add_link(pid);
}
}
let ready_was_empty = self.ready.len() == 0;
self.ready.push(pid, process.priority());
self.note_ready_push(ready_was_empty);
self.processes.insert(pid, process);
}
fn materialize_mfa_child(&mut self, spawn: DeferredMfaSpawn) {
let DeferredMfaSpawn {
pid,
module,
function,
args,
} = spawn;
let Ok(arity) = u8::try_from(args.len()) else {
self.record_exit_error(pid, ExecError::Badarg);
return;
};
let entry = match self.module_registry().lookup_mfa(module, function, arity) {
Ok(entry) => entry,
Err(error) => {
self.record_exit_error(pid, error);
return;
}
};
let instruction_pointer = match entry.module.label_ip(entry.label) {
Ok(instruction_pointer) => instruction_pointer,
Err(error) => {
self.record_exit_error(pid, error);
return;
}
};
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_code_position(Some(CodePosition {
module,
instruction_pointer,
}));
process.set_current_module(entry.module);
for (index, arg) in args.iter().enumerate().take(1024) {
if let Ok(register) = u16::try_from(index) {
let copied = match arg.copy_to_heap(process.heap_mut()) {
Ok(copied) => copied,
Err(_) => {
self.record_exit_error(pid, ExecError::Badarg);
return;
}
};
process.set_x_reg(register, copied);
}
}
let ready_was_empty = self.ready.len() == 0;
self.ready.push(pid, process.priority());
self.note_ready_push(ready_was_empty);
self.processes.insert(pid, process);
}
fn propagate_native_exit(&mut self, exiting: &mut Process, reason: ExitReason) {
let mut cascade: VecDeque<(u64, u64, ExitReason)> = exiting
.take_links()
.into_iter()
.map(|linked_pid| (exiting.pid(), linked_pid, reason))
.collect();
while let Some((source_pid, linked_pid, signal_reason)) = cascade.pop_front() {
let Some(target) = self.processes.get_mut(&linked_pid) else {
continue;
};
let _unlinked = target.remove_link(source_pid);
if link::should_die_from_signal(target, signal_reason) {
let terminal = link::terminal_reason(signal_reason);
let onward = target.take_links();
target.terminate(terminal);
cascade.extend(onward.into_iter().map(|next| (linked_pid, next, terminal)));
self.processes.remove(&linked_pid);
self.waiting.remove(&linked_pid);
self.record_native_exit(linked_pid, terminal, undefined_result());
} else if target.trap_exit() {
link::enqueue_exit_message_pub(target, source_pid, signal_reason);
let _woken = self.wake(linked_pid);
}
}
}
}
fn undefined_result() -> OwnedTerm {
OwnedTerm::immediate(Term::atom(Atom::UNDEFINED))
}
fn copy_payload_into_tuple(process: &mut Process, tag: Atom, payload: OwnedTerm) -> Option<Term> {
let value = payload.copy_to_heap(process.heap_mut()).ok()?;
let elements = [Term::atom(tag), value];
let words = 1usize.checked_add(elements.len())?;
let slice = process.heap_mut().alloc_slice(words).ok()?;
crate::term::boxed::write_tuple(slice, &elements)
}
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;