use crate::atom::{Atom, AtomTable};
use crate::capability::Sandbox;
use crate::native::links::LinkError;
use crate::native::{
BifRegistryImpl, Capability, CapabilitySet, ExceptionClass, NativeFn, NativeRegistrationError,
ProcessContext, SpawnOptions,
};
use crate::process::{ExitReason, Priority};
use crate::term::Term;
use crate::term::boxed::{Closure, Cons, Tuple};
use crate::term::pid_ref::PidRef;
type Gate2Bif = (&'static str, u8, Capability, NativeFn);
const GATE2_BIFS: &[Gate2Bif] = &[
("self", 0, Capability::Pure, bif_self),
("spawn", 3, Capability::Spawn, bif_spawn),
("spawn", 4, Capability::Spawn, bif_spawn_4),
("spawn_link", 3, Capability::Spawn, bif_spawn_link),
("spawn_link", 4, Capability::Spawn, bif_spawn_link_4),
("spawn_monitor", 1, Capability::Spawn, bif_spawn_monitor_1),
("spawn_monitor", 3, Capability::Spawn, bif_spawn_monitor_3),
("spawn_monitor", 4, Capability::Spawn, bif_spawn_monitor_4),
("spawn_opt", 2, Capability::Spawn, bif_spawn_opt_2),
("spawn_opt", 4, Capability::Spawn, bif_spawn_opt_4),
("link", 1, Capability::ProcessLocal, bif_link),
("unlink", 1, Capability::ProcessLocal, bif_unlink),
(
"process_flag",
2,
Capability::ProcessLocal,
bif_process_flag,
),
("monitor", 2, Capability::ProcessLocal, bif_monitor),
("demonitor", 1, Capability::ProcessLocal, bif_demonitor),
("exit", 1, Capability::ProcessLocal, bif_exit_1),
("exit", 2, Capability::ProcessLocal, bif_exit),
];
pub fn register_gate2_bifs(
registry: &BifRegistryImpl,
atom_table: &AtomTable,
) -> Result<(), NativeRegistrationError> {
let erlang = atom_table.intern("erlang");
for &(function_name, arity, capability, native_function) in GATE2_BIFS {
let function = atom_table.intern(function_name);
registry.register(erlang, function, arity, native_function, capability)?;
}
Ok(())
}
pub fn bif_self(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
let pid = context.pid().ok_or_else(badarg)?;
Term::try_pid(pid).ok_or_else(badarg)
}
pub fn bif_spawn(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_impl(args, context, false)
}
pub fn bif_spawn_4(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
remote_spawn_impl(args, context, RemoteSpawnKind::Plain)
}
pub fn bif_spawn_link(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_impl(args, context, true)
}
pub fn bif_spawn_link_4(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
remote_spawn_impl(args, context, RemoteSpawnKind::Link)
}
pub fn bif_spawn_monitor_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_monitor_fun_impl(args, context)
}
pub fn bif_spawn_monitor_3(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_monitor_mfa_impl(args, context)
}
pub fn bif_spawn_monitor_4(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
remote_spawn_impl(args, context, RemoteSpawnKind::Monitor)
}
pub fn bif_spawn_opt_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_opt_fun_impl(args, context)
}
pub fn bif_spawn_opt_4(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_opt_mfa_impl(args, context)
}
pub fn bif_link(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [target_term] = args else {
return Err(badarg());
};
let target_pid = PidRef::new(*target_term).ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
match target_pid {
PidRef::Local(target_pid) => {
if caller_pid == target_pid {
return Ok(Term::atom(Atom::TRUE));
}
let facility = context.link_facility().ok_or_else(badarg)?;
match facility.link(caller_pid, target_pid) {
Ok(()) => Ok(Term::atom(Atom::TRUE)),
Err(LinkError::NoProc) => Err(Term::atom(Atom::NOPROC)),
Err(LinkError::NoCaller) => Err(badarg()),
}
}
PidRef::Remote(_) => {
let remote = target_pid.remote_pid().ok_or_else(badarg)?;
let facility = context.distribution_control_facility().ok_or_else(badarg)?;
facility
.link_remote(caller_pid, remote)
.map_err(|_| Term::atom(Atom::NOPROC))?;
Ok(Term::atom(Atom::TRUE))
}
}
}
pub fn bif_unlink(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [target_term] = args else {
return Err(badarg());
};
let target_pid = PidRef::new(*target_term).ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
match target_pid {
PidRef::Local(target_pid) => {
if caller_pid == target_pid {
return Ok(Term::atom(Atom::TRUE));
}
let facility = context.link_facility().ok_or_else(badarg)?;
facility
.unlink(caller_pid, target_pid)
.map_err(|_| badarg())?;
Ok(Term::atom(Atom::TRUE))
}
PidRef::Remote(_) => {
let remote = target_pid.remote_pid().ok_or_else(badarg)?;
let facility = context.distribution_control_facility().ok_or_else(badarg)?;
facility
.unlink_remote(caller_pid, remote)
.map_err(|_| badarg())?;
Ok(Term::atom(Atom::TRUE))
}
}
}
pub fn bif_process_flag(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [flag_term, value_term] = args else {
return Err(badarg());
};
let flag = flag_term.as_atom().ok_or_else(badarg)?;
if flag == Atom::TRAP_EXIT {
let new_value = atom_to_bool(*value_term).ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.link_facility().ok_or_else(badarg)?;
let old_value = facility
.set_trap_exit(caller_pid, new_value)
.map_err(|_| badarg())?;
Ok(bool_to_atom(old_value))
} else if flag == priority_flag_atom(context)? {
let new_priority = atom_to_priority(context, *value_term)?;
let old_priority = context.set_priority(new_priority)?;
Ok(Term::atom(priority_to_atom(context, old_priority)?))
} else {
Err(badarg())
}
}
fn priority_flag_atom(context: &ProcessContext) -> Result<Atom, Term> {
let atom_table = context.atom_table().ok_or_else(badarg)?;
Ok(atom_table.intern("priority"))
}
fn atom_to_priority(context: &ProcessContext, term: Term) -> Result<Priority, Term> {
let atom = term.as_atom().ok_or_else(badarg)?;
let atom_table = context.atom_table().ok_or_else(badarg)?;
if atom == atom_table.intern("low") {
Ok(Priority::Low)
} else if atom == Atom::NORMAL {
Ok(Priority::Normal)
} else if atom == atom_table.intern("high") {
Ok(Priority::High)
} else if atom == atom_table.intern("max") {
Ok(Priority::Max)
} else {
Err(badarg())
}
}
fn priority_to_atom(context: &ProcessContext, priority: Priority) -> Result<Atom, Term> {
let atom_table = context.atom_table().ok_or_else(badarg)?;
Ok(match priority {
Priority::Low => atom_table.intern("low"),
Priority::Normal => Atom::NORMAL,
Priority::High => atom_table.intern("high"),
Priority::Max => atom_table.intern("max"),
})
}
pub fn bif_monitor(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [type_term, pid_term] = args else {
return Err(badarg());
};
let type_atom = type_term.as_atom().ok_or_else(badarg)?;
if type_atom != Atom::PROCESS {
return Err(badarg());
}
let target_pid = pid_term.as_pid().ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
let result = facility
.monitor(caller_pid, target_pid)
.map_err(|_| badarg())?;
Term::try_small_int(result.reference as i64).ok_or_else(badarg)
}
pub fn bif_demonitor(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [ref_term] = args else {
return Err(badarg());
};
let reference = ref_term.as_small_int().ok_or_else(badarg)?;
if reference < 0 {
return Err(badarg());
}
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
facility
.demonitor(caller_pid, reference as u64)
.map_err(|_| badarg())?;
Ok(Term::atom(Atom::TRUE))
}
pub fn bif_exit_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [reason] = args else {
return Err(badarg());
};
context.set_exception_class(ExceptionClass::Exit);
Err(*reason)
}
pub fn bif_exit(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term, reason_term] = args else {
return Err(badarg());
};
let target_pid = pid_term.as_pid().ok_or_else(badarg)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let reason = exit_reason_from_term(*reason_term)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
facility
.exit_signal(caller_pid, target_pid, reason)
.map_err(|_| badarg())?;
Ok(Term::atom(Atom::TRUE))
}
fn exit_reason_from_term(term: Term) -> Result<ExitReason, Term> {
let atom = term.as_atom().ok_or_else(badarg)?;
match atom {
Atom::NORMAL => Ok(ExitReason::Normal),
Atom::KILL => Ok(ExitReason::Kill),
Atom::KILLED => Ok(ExitReason::Killed),
Atom::ERROR => Ok(ExitReason::Error),
Atom::NOCONNECTION => Ok(ExitReason::NoConnection),
_ => Err(badarg()),
}
}
fn spawn_impl(args: &[Term], context: &mut ProcessContext, link: bool) -> Result<Term, Term> {
let [module_term, function_term, args_term] = args else {
return Err(badarg());
};
let module = module_term.as_atom().ok_or_else(badarg)?;
let function = function_term.as_atom().ok_or_else(badarg)?;
let spawn_args = list_to_vec(*args_term)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let link_to = if link { Some(caller_pid) } else { None };
let facility = context.spawn_facility().ok_or_else(badarg)?;
let new_pid = facility
.spawn(caller_pid, module, function, spawn_args, link_to)
.map_err(|_| badarg())?;
Term::try_pid(new_pid).ok_or_else(badarg)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum RemoteSpawnKind {
Plain,
Link,
Monitor,
}
fn remote_spawn_impl(
args: &[Term],
context: &mut ProcessContext,
kind: RemoteSpawnKind,
) -> Result<Term, Term> {
let [node_term, module_term, function_term, args_term] = args else {
return Err(badarg());
};
let node = node_term.as_atom().ok_or_else(badarg)?;
let module = module_term.as_atom().ok_or_else(badarg)?;
let function = function_term.as_atom().ok_or_else(badarg)?;
let spawn_args = list_to_vec(*args_term)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let mut options = SpawnOptions::default();
match kind {
RemoteSpawnKind::Plain => {}
RemoteSpawnKind::Link => options.link = true,
RemoteSpawnKind::Monitor => options.monitor = true,
}
let facility = context.remote_spawn_facility().ok_or_else(badarg)?;
let result = facility
.remote_spawn(caller_pid, node, module, function, spawn_args, options)
.map_err(|_| badarg())?;
if result.node != node {
return Err(badarg());
}
if kind == RemoteSpawnKind::Monitor {
let reference = result.monitor_reference.ok_or_else(badarg)?;
context.ensure_heap_space(4 + 3 + 3)?;
let pid_term =
context.alloc_external_pid_prereserved(node, result.pid_number, result.serial)?;
let reference_term = context.alloc_external_reference_prereserved(node, reference)?;
context.alloc_tuple_prereserved(&[pid_term, reference_term])
} else {
context.alloc_external_pid(node, result.pid_number, result.serial)
}
}
fn spawn_monitor_mfa_impl(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [module_term, function_term, args_term] = args else {
return Err(badarg());
};
let module = module_term.as_atom().ok_or_else(badarg)?;
let function = function_term.as_atom().ok_or_else(badarg)?;
let spawn_args = list_to_vec(*args_term)?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.spawn_facility().ok_or_else(badarg)?;
let result = facility
.spawn_monitor(caller_pid, module, function, spawn_args)
.map_err(|_| badarg())?;
spawn_monitor_tuple(result.pid, result.reference, context)
}
fn spawn_monitor_fun_impl(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [fun_term] = args else {
return Err(badarg());
};
let closure = Closure::new(*fun_term).ok_or_else(badarg)?;
if closure.arity() != 0 || closure.num_free() != 0 {
return Err(badarg());
}
let module = closure.module().ok_or_else(badarg)?;
let lambda_index = closure.function_index() as u32;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.spawn_facility().ok_or_else(badarg)?;
let result = facility
.spawn_lambda_monitor(caller_pid, module, lambda_index)
.map_err(|_| badarg())?;
spawn_monitor_tuple(result.pid, result.reference, context)
}
fn spawn_opt_mfa_impl(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [module_term, function_term, args_term, options_term] = args else {
return Err(badarg());
};
let module = module_term.as_atom().ok_or_else(badarg)?;
let function = function_term.as_atom().ok_or_else(badarg)?;
let spawn_args = list_to_vec(*args_term)?;
let options = parse_spawn_options(*options_term, context)
.and_then(|options| attenuate_spawn_options(options, context))?;
spawn_with_options(module, function, spawn_args, options, context)
}
pub fn spawn_in_sandbox(
sandbox: Sandbox,
module: Atom,
function: Atom,
args: Vec<Term>,
context: &mut ProcessContext,
) -> Result<Term, Term> {
let options = SpawnOptions {
capabilities: Some(sandbox.capabilities()),
..SpawnOptions::default()
};
let options = attenuate_spawn_options(options, context)?;
spawn_with_options(module, function, args, options, context)
}
fn spawn_with_options(
module: Atom,
function: Atom,
args: Vec<Term>,
options: SpawnOptions,
context: &mut ProcessContext,
) -> Result<Term, Term> {
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.spawn_facility().ok_or_else(badarg)?;
let result = facility
.spawn_with_options(caller_pid, module, function, args, options)
.map_err(|_| badarg())?;
spawn_opt_result(result.pid, result.reference, context)
}
fn spawn_opt_fun_impl(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [fun_term, options_term] = args else {
return Err(badarg());
};
let closure = Closure::new(*fun_term).ok_or_else(badarg)?;
if closure.arity() != 0 || closure.num_free() != 0 {
return Err(badarg());
}
let module = closure.module().ok_or_else(badarg)?;
let lambda_index = closure.function_index() as u32;
let options = parse_spawn_options(*options_term, context)
.and_then(|options| attenuate_spawn_options(options, context))?;
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.spawn_facility().ok_or_else(badarg)?;
let result = facility
.spawn_lambda_with_options(caller_pid, module, lambda_index, options)
.map_err(|_| badarg())?;
spawn_opt_result(result.pid, result.reference, context)
}
fn spawn_opt_result(
child_pid: u64,
reference: Option<u64>,
context: &mut ProcessContext,
) -> Result<Term, Term> {
if let Some(reference) = reference {
spawn_monitor_tuple(child_pid, reference, context)
} else {
Term::try_pid(child_pid).ok_or_else(badarg)
}
}
fn spawn_monitor_tuple(
child_pid: u64,
reference: u64,
context: &mut ProcessContext,
) -> Result<Term, Term> {
let pid_term = Term::try_pid(child_pid).ok_or_else(badarg)?;
context.ensure_heap_space(2 + 3)?;
let reference_term = context.alloc_reference_prereserved(reference)?;
context.alloc_tuple_prereserved(&[pid_term, reference_term])
}
fn list_to_vec(term: Term) -> Result<Vec<Term>, Term> {
let mut elements = Vec::new();
let mut current = term;
loop {
if current.is_nil() {
return Ok(elements);
}
let cons = Cons::new(current).ok_or_else(badarg)?;
elements.push(cons.head());
current = cons.tail();
}
}
fn parse_spawn_options(term: Term, context: &ProcessContext) -> Result<SpawnOptions, Term> {
let atom_table = context.atom_table().ok_or_else(badarg)?;
let link_atom = atom_table.intern("link");
let monitor_atom = atom_table.intern("monitor");
let priority_atom = atom_table.intern("priority");
let min_heap_size_atom = atom_table.intern("min_heap_size");
let capabilities_atom = atom_table.intern("capabilities");
let sandbox_atom = atom_table.intern("sandbox");
let mut options = SpawnOptions::default();
for option in list_to_vec(term)? {
if option.as_atom() == Some(link_atom) {
options.link = true;
} else if option.as_atom() == Some(monitor_atom) {
options.monitor = true;
} else if let Some(tuple) = Tuple::new(option) {
if tuple.get(0) == Some(Term::atom(priority_atom)) {
if tuple.arity() != 2 {
return Err(badarg());
}
let level = tuple.get(1).ok_or_else(badarg)?;
options.priority = Some(atom_to_priority(context, level)?);
} else if tuple.get(0) == Some(Term::atom(min_heap_size_atom)) {
if tuple.arity() != 2 {
return Err(badarg());
}
let value = tuple.get(1).ok_or_else(badarg)?;
let size = value.as_small_int().ok_or_else(badarg)?;
if size < 0 {
return Err(badarg());
}
options.min_heap_size = Some(size as usize);
} else if tuple.get(0) == Some(Term::atom(capabilities_atom)) {
if tuple.arity() != 2 {
return Err(badarg());
}
let value = tuple.get(1).ok_or_else(badarg)?;
options.capabilities = Some(parse_capability_list(value, context)?);
} else if tuple.get(0) == Some(Term::atom(sandbox_atom)) {
if tuple.arity() != 2 {
return Err(badarg());
}
let value = tuple.get(1).ok_or_else(badarg)?;
options.capabilities = Some(sandbox_from_term(value, context)?.capabilities());
}
}
}
Ok(options)
}
fn parse_capability_list(term: Term, context: &ProcessContext) -> Result<CapabilitySet, Term> {
let capabilities = list_to_vec(term)?;
let mut parsed = Vec::with_capacity(capabilities.len());
for capability in capabilities {
parsed.push(capability_from_term(capability, context)?);
}
Ok(CapabilitySet::from_slice(&parsed))
}
fn sandbox_from_term(term: Term, context: &ProcessContext) -> Result<Sandbox, Term> {
let atom = term.as_atom().ok_or_else(badarg)?;
let atom_table = context.atom_table().ok_or_else(badarg)?;
if atom == atom_table.intern("pure") {
Ok(Sandbox::Pure)
} else if atom == atom_table.intern("worker") {
Ok(Sandbox::Worker)
} else if atom == atom_table.intern("supervisor") {
Ok(Sandbox::Supervisor)
} else if atom == atom_table.intern("unrestricted") {
Ok(Sandbox::Unrestricted)
} else {
Err(badarg())
}
}
fn capability_from_term(term: Term, context: &ProcessContext) -> Result<Capability, Term> {
let atom = term.as_atom().ok_or_else(badarg)?;
let atom_table = context.atom_table().ok_or_else(badarg)?;
if atom == atom_table.intern("pure") {
Ok(Capability::Pure)
} else if atom == atom_table.intern("process_local") {
Ok(Capability::ProcessLocal)
} else if atom == atom_table.intern("clock") {
Ok(Capability::Clock)
} else if atom == atom_table.intern("entropy") {
Ok(Capability::Entropy)
} else if atom == atom_table.intern("external_io")
|| atom == atom_table.intern("file")
|| atom == atom_table.intern("file_io")
|| atom == atom_table.intern("net")
|| atom == atom_table.intern("network")
{
Ok(Capability::ExternalIo)
} else if atom == atom_table.intern("spawn") {
Ok(Capability::Spawn)
} else {
Err(badarg())
}
}
fn attenuate_spawn_options(
options: SpawnOptions,
context: &mut ProcessContext,
) -> Result<SpawnOptions, Term> {
let Some(requested) = &options.capabilities else {
return Ok(options);
};
let Some(process) = context.process_mut() else {
return if requested.is_subset_of(&CapabilitySet::all()) {
Ok(options)
} else {
Err(badarg())
};
};
if requested.is_subset_of(process.capabilities()) {
Ok(options)
} else {
Err(badarg())
}
}
fn atom_to_bool(term: Term) -> Option<bool> {
let atom = term.as_atom()?;
if atom == Atom::TRUE {
Some(true)
} else if atom == Atom::FALSE {
Some(false)
} else {
None
}
}
const fn bool_to_atom(value: bool) -> Term {
if value {
Term::atom(Atom::TRUE)
} else {
Term::atom(Atom::FALSE)
}
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}
#[cfg(test)]
mod tests;