mod additional;
mod registry_bifs;
mod type_conversion;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use crate::atom::{Atom, AtomTable};
use crate::distribution::DEFAULT_NODE_NAME;
use crate::native::{
BifRegistryImpl, Capability, NativeFn, NativeRegistrationError, ProcessContext,
};
use crate::term::Term;
use crate::term::binary::Binary;
use crate::term::binary_ref::BinaryRef;
use crate::term::boxed::{Cons, Float, Tuple};
use crate::term::pid_ref::PidRef;
use crate::term::reference_ref::ReferenceRef;
pub use additional::{
bif_binary_part, bif_bit_size, bif_is_bitstring, bif_is_map_key, bif_map_size, bif_round,
bif_trunc, bif_unary_minus,
};
pub use registry_bifs::{bif_register, bif_unregister, bif_whereis};
pub use type_conversion::{
bif_atom_to_binary, bif_atom_to_binary_1, bif_atom_to_binary_2, bif_atom_to_list,
bif_binary_to_atom, bif_binary_to_existing_atom, bif_binary_to_existing_atom_2,
bif_binary_to_list, bif_float_to_binary_2, bif_float_to_list, bif_list_to_atom,
bif_list_to_binary, bif_list_to_existing_atom, bif_list_to_float, bif_list_to_integer,
bif_map_get,
};
type Gate3Bif = (&'static str, u8, Capability, NativeFn);
const GATE3_BIFS: &[Gate3Bif] = &[
("element", 2, Capability::Pure, bif_element),
("send", 2, Capability::Pure, bif_send),
("tuple_size", 1, Capability::Pure, bif_tuple_size),
("make_ref", 0, Capability::Pure, bif_make_ref),
("node", 0, Capability::Pure, bif_node_0),
("node", 1, Capability::Pure, bif_node_1),
("is_alive", 0, Capability::Pure, bif_is_alive_0),
("nodes", 0, Capability::ExternalIo, bif_nodes_0),
(
"disconnect_node",
1,
Capability::ExternalIo,
bif_disconnect_node_1,
),
(
"is_process_alive",
1,
Capability::Pure,
bif_is_process_alive,
),
("spawn", 1, Capability::Spawn, bif_spawn_1),
("spawn_link", 1, Capability::Spawn, bif_spawn_link_1),
("list_to_atom", 1, Capability::Pure, bif_list_to_atom),
("atom_to_list", 1, Capability::Pure, bif_atom_to_list),
(
"list_to_existing_atom",
1,
Capability::Pure,
bif_list_to_existing_atom,
),
("list_to_integer", 1, Capability::Pure, bif_list_to_integer),
("list_to_float", 1, Capability::Pure, bif_list_to_float),
("float_to_list", 1, Capability::Pure, bif_float_to_list),
(
"float_to_binary",
2,
Capability::Pure,
bif_float_to_binary_2,
),
("binary_to_atom", 1, Capability::Pure, bif_binary_to_atom),
("atom_to_binary", 1, Capability::Pure, bif_atom_to_binary_1),
("atom_to_binary", 2, Capability::Pure, bif_atom_to_binary_2),
(
"binary_to_existing_atom",
1,
Capability::Pure,
bif_binary_to_existing_atom,
),
(
"binary_to_existing_atom",
2,
Capability::Pure,
bif_binary_to_existing_atom_2,
),
("binary_to_list", 1, Capability::Pure, bif_binary_to_list),
("list_to_binary", 1, Capability::Pure, bif_list_to_binary),
("map_get", 2, Capability::Pure, bif_map_get),
("register", 2, Capability::Pure, bif_register),
("unregister", 1, Capability::Pure, bif_unregister),
("whereis", 1, Capability::Pure, bif_whereis),
("demonitor", 2, Capability::Pure, bif_demonitor_2),
("pid_to_list", 1, Capability::Pure, bif_pid_to_list),
("byte_size", 1, Capability::Pure, bif_byte_size),
("iolist_size", 1, Capability::Pure, bif_iolist_size),
("++", 2, Capability::Pure, bif_list_append),
("not", 1, Capability::Pure, bif_not),
("length", 1, Capability::Pure, bif_length),
("round", 1, Capability::Pure, bif_round),
("trunc", 1, Capability::Pure, bif_trunc),
("is_bitstring", 1, Capability::Pure, bif_is_bitstring),
("is_map_key", 2, Capability::Pure, bif_is_map_key),
("map_size", 1, Capability::Pure, bif_map_size),
("binary_part", 3, Capability::Pure, bif_binary_part),
("bit_size", 1, Capability::Pure, bif_bit_size),
("-", 1, Capability::Pure, bif_unary_minus),
("phash2", 1, Capability::Pure, bif_phash2_1),
("phash2", 2, Capability::Pure, bif_phash2_2),
("monotonic_time", 0, Capability::Clock, bif_monotonic_time_0),
("system_time", 0, Capability::Clock, bif_system_time_0),
("monotonic_time", 1, Capability::Clock, bif_monotonic_time_1),
("system_time", 1, Capability::Clock, bif_system_time_1),
("time_offset", 0, Capability::Clock, bif_time_offset_0),
("unique_integer", 0, Capability::Pure, bif_unique_integer_0),
("unique_integer", 1, Capability::Pure, bif_unique_integer_1),
("min", 2, Capability::Pure, bif_min_2),
("max", 2, Capability::Pure, bif_max_2),
("abs", 1, Capability::Pure, bif_abs_1),
];
static REF_COUNTER: AtomicU64 = AtomicU64::new(1);
static UNIQUE_INTEGER_COUNTER: AtomicU64 = AtomicU64::new(1);
static MONOTONIC_EPOCH: OnceLock<Instant> = OnceLock::new();
const PHASH2_DEFAULT_RANGE: u64 = 1 << 27;
pub fn register_gate3_bifs(
registry: &BifRegistryImpl,
atom_table: &AtomTable,
) -> Result<(), NativeRegistrationError> {
let erlang = atom_table.intern("erlang");
for &(function_name, arity, capability, native_function) in GATE3_BIFS {
let function = atom_table.intern(function_name);
registry.register(erlang, function, arity, native_function, capability)?;
}
Ok(())
}
pub fn bif_element(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [index_term, tuple_term] = args else {
return Err(badarg());
};
let index = index_term.as_small_int().ok_or_else(badarg)?;
if index < 1 {
return Err(badarg());
}
let tuple = Tuple::new(*tuple_term).ok_or_else(badarg)?;
let zero_based = (index - 1) as usize;
tuple.get(zero_based).ok_or_else(badarg)
}
pub fn bif_send(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term, message_term] = args else {
return Err(badarg());
};
let target = PidRef::new(*pid_term).ok_or_else(badarg)?;
if target.is_local() {
let _ = context.send_to_attached_self(target.pid_number(), *message_term);
} else {
let noconnection = Term::atom(
context
.atom_table()
.map_or(Atom::ERROR, |table| table.intern("noconnection")),
);
let facility = context.distribution_send_facility().ok_or(noconnection)?;
facility
.send_remote(*pid_term, *message_term)
.map_err(|_| noconnection)?;
}
Ok(*message_term)
}
pub fn bif_tuple_size(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [tuple_term] = args else {
return Err(badarg());
};
let tuple = Tuple::new(*tuple_term).ok_or_else(badarg)?;
let arity = tuple.arity();
i64::try_from(arity)
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_make_ref(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
let id = REF_COUNTER.fetch_add(1, Ordering::Relaxed);
context.alloc_reference(id)
}
pub fn bif_node_0(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
let local_node = context.local_node().ok_or_else(badarg)?;
Ok(Term::atom(local_node.name))
}
pub fn bif_node_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
let local_node = context.local_node().ok_or_else(badarg)?;
if let Some(pid) = PidRef::new(*term) {
return Ok(Term::atom(pid.node().unwrap_or(local_node.name)));
}
if let Some(reference) = ReferenceRef::new(*term) {
return Ok(Term::atom(reference.node().unwrap_or(local_node.name)));
}
Err(badarg())
}
pub fn bif_is_alive_0(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
let alive = context
.local_node()
.and_then(|node| {
context
.atom_table()
.and_then(|table| table.resolve(node.name))
})
.is_some_and(|name| name != DEFAULT_NODE_NAME);
Ok(Term::atom(if alive { Atom::TRUE } else { Atom::FALSE }))
}
pub fn bif_nodes_0(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
let elements: Vec<Term> = context
.net_kernel()
.map(|net_kernel| net_kernel.nodes().into_iter().map(Term::atom).collect())
.unwrap_or_default();
context.alloc_list(&elements)
}
pub fn bif_disconnect_node_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [node_term] = args else {
return Err(badarg());
};
let node = node_term.as_atom().ok_or_else(badarg)?;
let disconnected = context
.net_kernel()
.is_some_and(|net_kernel| net_kernel.disconnect_node(node));
Ok(Term::atom(if disconnected {
Atom::TRUE
} else {
Atom::FALSE
}))
}
pub fn bif_phash2_1(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
phash2(*term, PHASH2_DEFAULT_RANGE)
}
pub fn bif_phash2_2(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [term, range_term] = args else {
return Err(badarg());
};
let range = range_term
.as_small_int()
.and_then(|value| u64::try_from(value).ok())
.filter(|value| *value > 0)
.ok_or_else(badarg)?;
phash2(*term, range)
}
fn phash2(term: Term, range: u64) -> Result<Term, Term> {
let value = crate::term::hash::term_hash(term) % range;
i64::try_from(value)
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_monotonic_time_0(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
if let Some(value) = replay_native_result(context, "monotonic_time", 0)? {
return Ok(value);
}
native_time_term(monotonic_time_native()?)
}
pub fn bif_system_time_0(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
if let Some(value) = replay_native_result(context, "system_time", 0)? {
return Ok(value);
}
native_time_term(system_time_native()?)
}
pub fn bif_monotonic_time_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [unit_term] = args else {
return Err(badarg());
};
let unit = parse_time_unit(*unit_term, context)?;
if let Some(value) = replay_native_result(context, "monotonic_time", 1)? {
return Ok(value);
}
native_time_term(convert_native_time(monotonic_time_native()?, unit)?)
}
pub fn bif_system_time_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [unit_term] = args else {
return Err(badarg());
};
let unit = parse_time_unit(*unit_term, context)?;
if let Some(value) = replay_native_result(context, "system_time", 1)? {
return Ok(value);
}
native_time_term(convert_native_time(system_time_native()?, unit)?)
}
pub fn bif_time_offset_0(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
if let Some(value) = replay_native_result(context, "time_offset", 0)? {
return Ok(value);
}
let offset = i128::from(system_time_native()?) - i128::from(monotonic_time_native()?);
i64::try_from(offset)
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_unique_integer_0(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
if !args.is_empty() {
return Err(badarg());
}
if let Some(value) = replay_native_result(context, "unique_integer", 0)? {
return Ok(value);
}
next_unique_integer()
}
pub fn bif_unique_integer_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [options_term] = args else {
return Err(badarg());
};
parse_unique_integer_options(*options_term, context)?;
if let Some(value) = replay_native_result(context, "unique_integer", 1)? {
return Ok(value);
}
next_unique_integer()
}
pub fn bif_min_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [left, right] = args else {
return Err(badarg());
};
let atom_table = context.atom_table().ok_or_else(badarg)?;
if crate::term::compare::cmp(*left, *right, atom_table).is_gt() {
Ok(*right)
} else {
Ok(*left)
}
}
pub fn bif_max_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [left, right] = args else {
return Err(badarg());
};
let atom_table = context.atom_table().ok_or_else(badarg)?;
if crate::term::compare::cmp(*left, *right, atom_table).is_lt() {
Ok(*right)
} else {
Ok(*left)
}
}
pub fn bif_abs_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [value] = args else {
return Err(badarg());
};
if let Some(integer) = value.as_small_int()
&& let Some(absolute) = integer.checked_abs().and_then(Term::try_small_int)
{
return Ok(absolute);
}
if let Some(integer) = crate::term::bigint_math::BigIntValue::from_term(*value) {
return crate::native::bifs::integer_result(integer.abs(), context);
}
let value = Float::new(*value).ok_or_else(badarg)?.value();
if !value.is_finite() {
return Err(badarg());
}
context.alloc_float(value.abs())
}
#[derive(Copy, Clone)]
enum TimeUnit {
Native,
Nanosecond,
Microsecond,
Millisecond,
Second,
}
fn replay_native_result(
context: &mut ProcessContext,
function_name: &str,
arity: u8,
) -> Result<Option<Term>, Term> {
let Some(driver) = context.replay_driver().cloned() else {
return Ok(None);
};
let atom_table = context.atom_table().ok_or_else(badarg)?;
let module = atom_table.intern("erlang");
let function = atom_table.intern(function_name);
let pid = context.pid().ok_or_else(badarg)?;
let recorded = match driver.lock() {
Ok(mut guard) => guard.next_native_call(pid, module, function, arity),
Err(error) => error
.into_inner()
.next_native_call(pid, module, function, arity),
}
.map_err(|_| badarg())?;
match recorded.outcome.result {
Ok(value) => Ok(Some(value)),
Err(reason) => {
context.set_exception_class(recorded.outcome.exception_class);
context.set_exception_stacktrace(recorded.outcome.exception_stacktrace);
Err(reason)
}
}
}
fn monotonic_time_native() -> Result<i64, Term> {
let epoch = MONOTONIC_EPOCH.get_or_init(Instant::now);
duration_to_native(epoch.elapsed())
}
fn system_time_native() -> Result<i64, Term> {
let elapsed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| badarg())?;
duration_to_native(elapsed)
}
fn duration_to_native(duration: Duration) -> Result<i64, Term> {
i64::try_from(duration.as_micros()).map_err(|_| badarg())
}
fn native_time_term(value: i64) -> Result<Term, Term> {
Term::try_small_int(value).ok_or_else(badarg)
}
fn parse_time_unit(unit_term: Term, context: &mut ProcessContext) -> Result<TimeUnit, Term> {
let unit_atom = unit_term.as_atom().ok_or_else(badarg)?;
let atom_table = context.atom_table().ok_or_else(badarg)?;
match atom_table.resolve(unit_atom) {
Some("native") => Ok(TimeUnit::Native),
Some("nanosecond") => Ok(TimeUnit::Nanosecond),
Some("microsecond") => Ok(TimeUnit::Microsecond),
Some("millisecond") => Ok(TimeUnit::Millisecond),
Some("second") => Ok(TimeUnit::Second),
_ => Err(badarg()),
}
}
fn convert_native_time(value: i64, unit: TimeUnit) -> Result<i64, Term> {
match unit {
TimeUnit::Native | TimeUnit::Microsecond => Ok(value),
TimeUnit::Nanosecond => value.checked_mul(1_000).ok_or_else(badarg),
TimeUnit::Millisecond => Ok(value / 1_000),
TimeUnit::Second => Ok(value / 1_000_000),
}
}
fn parse_unique_integer_options(
options_term: Term,
context: &mut ProcessContext,
) -> Result<(), Term> {
let atom_table = context.atom_table().ok_or_else(badarg)?;
let mut current = options_term;
loop {
if current.is_nil() {
return Ok(());
}
let cons = Cons::new(current).ok_or_else(badarg)?;
let option = cons.head().as_atom().ok_or_else(badarg)?;
match atom_table.resolve(option) {
Some("positive" | "monotonic") => {}
_ => return Err(badarg()),
}
current = cons.tail();
}
}
fn next_unique_integer() -> Result<Term, Term> {
let id = UNIQUE_INTEGER_COUNTER.fetch_add(1, Ordering::Relaxed);
i64::try_from(id)
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_is_process_alive(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term] = args else {
return Err(badarg());
};
let target_pid = pid_term.as_pid().ok_or_else(badarg)?;
if let Some(caller_pid) = context.pid()
&& caller_pid == target_pid
{
return Ok(bool_term(true));
}
if let Some(facility) = context.supervision_facility() {
let caller_pid = context.pid().ok_or_else(badarg)?;
match facility.monitor(caller_pid, target_pid) {
Ok(result) => {
let _ = facility.demonitor(caller_pid, result.reference);
Ok(bool_term(true))
}
Err(_) => Ok(bool_term(false)),
}
} else {
Ok(bool_term(false))
}
}
pub fn bif_spawn_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_from_fun(args, context, false)
}
pub fn bif_spawn_link_1(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
spawn_from_fun(args, context, true)
}
fn spawn_from_fun(args: &[Term], context: &mut ProcessContext, link: bool) -> Result<Term, Term> {
let [fun_term] = args else {
return Err(badarg());
};
let closure = crate::term::boxed::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 link_to = if link { Some(caller_pid) } else { None };
let facility = context.spawn_facility().ok_or_else(badarg)?;
let new_pid = facility
.spawn_lambda(caller_pid, module, lambda_index, link_to)
.map_err(|_| badarg())?;
Term::try_pid(new_pid).ok_or_else(badarg)
}
pub fn bif_demonitor_2(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [ref_term, opts_term] = args else {
return Err(badarg());
};
let reference = if let Some(reference) = ReferenceRef::new(*ref_term) {
reference.id()
} else {
let legacy_reference = ref_term.as_small_int().ok_or_else(badarg)?;
if legacy_reference < 0 {
return Err(badarg());
}
legacy_reference as u64
};
let mut opt_flush = false;
let mut opt_info = false;
let mut current = *opts_term;
loop {
if current.is_nil() {
break;
}
let cons = Cons::new(current).ok_or_else(badarg)?;
let opt = cons.head().as_atom().ok_or_else(badarg)?;
if opt == Atom::FLUSH {
opt_flush = true;
} else if opt == Atom::INFO {
opt_info = true;
} else {
return Err(badarg());
}
current = cons.tail();
}
let caller_pid = context.pid().ok_or_else(badarg)?;
let facility = context.supervision_facility().ok_or_else(badarg)?;
let result = facility.demonitor(caller_pid, reference);
let _ = opt_flush;
if opt_info {
Ok(bool_term(result.is_ok()))
} else {
result.map_err(|_| badarg())?;
Ok(bool_term(true))
}
}
pub fn bif_pid_to_list(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [pid_term] = args else {
return Err(badarg());
};
let pid = pid_term.as_pid().ok_or_else(badarg)?;
let repr = format!("<0.{pid}.0>");
let bytes = repr.as_bytes();
let elements: Vec<_> = bytes
.iter()
.copied()
.map(|byte| Term::small_int(i64::from(byte)))
.collect();
context.alloc_list(&elements)
}
pub fn bif_byte_size(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [binary_term] = args else {
return Err(badarg());
};
binary_size(*binary_term)
}
pub fn bif_iolist_size(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let _ = context;
let [binary_term] = args else {
return Err(badarg());
};
binary_size(*binary_term)
}
fn binary_size(term: Term) -> Result<Term, Term> {
let binary = Binary::new(term).ok_or_else(badarg)?;
i64::try_from(binary.len())
.ok()
.and_then(Term::try_small_int)
.ok_or_else(badarg)
}
pub fn bif_list_append(args: &[Term], context: &mut ProcessContext) -> Result<Term, Term> {
let [list_a, list_b] = args else {
return Err(badarg());
};
if list_a.is_nil() {
return Ok(*list_b);
}
if list_b.is_nil() {
if let Some(binary) = BinaryRef::new(*list_a) {
return context.alloc_binary(binary.as_bytes());
}
if Cons::new(*list_a).is_none() {
return Ok(*list_a);
}
}
let mut elements = Vec::new();
let mut current = *list_a;
loop {
if current.is_nil() {
break;
}
let cons = Cons::new(current).ok_or_else(badarg)?;
elements.push(cons.head());
current = cons.tail();
}
context.alloc_list_with_tail(&elements, *list_b)
}
pub fn bif_not(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [term] = args else {
return Err(badarg());
};
let atom = term.as_atom().ok_or_else(badarg)?;
if atom == Atom::TRUE {
Ok(Term::atom(Atom::FALSE))
} else if atom == Atom::FALSE {
Ok(Term::atom(Atom::TRUE))
} else {
Err(badarg())
}
}
pub fn bif_length(args: &[Term], _context: &mut ProcessContext) -> Result<Term, Term> {
let [list_term] = args else {
return Err(badarg());
};
let mut count: i64 = 0;
let mut current = *list_term;
loop {
if current.is_nil() {
return Term::try_small_int(count).ok_or_else(badarg);
}
let cons = Cons::new(current).ok_or_else(badarg)?;
count = count.checked_add(1).ok_or_else(badarg)?;
current = cons.tail();
}
}
fn bool_term(value: bool) -> Term {
Term::atom(if value { Atom::TRUE } else { Atom::FALSE })
}
fn badarg() -> Term {
Term::atom(Atom::BADARG)
}
#[cfg(test)]
#[path = "additional_tests.rs"]
mod additional_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
#[path = "demonitor2_tests.rs"]
mod demonitor2_tests;