mod accessors;
pub use crate::io::resource::FdResource;
pub use accessors::{
BigInt, Closure, Cons, ExternalPid, ExternalReference, Float, Map, ProcBin, Reference,
SubBinary, Tuple,
};
use crate::{atom::Atom, term::Term};
const HEADER_TAG_BITS: u32 = 8;
const HEADER_TAG_MASK: u64 = (1 << HEADER_TAG_BITS) - 1;
const BIGINT_NEGATIVE_SIGN: u64 = 1;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(u8)]
pub enum BoxedTag {
Tuple = 0x10,
Float = 0x11,
BigInt = 0x12,
Closure = 0x13,
Map = 0x14,
Reference = 0x15,
Binary = 0x16,
BinaryBuilder = 0x17,
MatchContext = 0x18,
ProcBin = 0x19,
SubBinary = 0x1A,
FdResource = 0x1B,
ExternalPid = 0x1C,
ExternalReference = 0x1D,
}
impl BoxedTag {
const fn from_bits(bits: u64) -> Option<Self> {
match bits {
bits if bits == Self::Tuple as u64 => Some(Self::Tuple),
bits if bits == Self::Float as u64 => Some(Self::Float),
bits if bits == Self::BigInt as u64 => Some(Self::BigInt),
bits if bits == Self::Closure as u64 => Some(Self::Closure),
bits if bits == Self::Map as u64 => Some(Self::Map),
bits if bits == Self::Reference as u64 => Some(Self::Reference),
bits if bits == Self::Binary as u64 => Some(Self::Binary),
bits if bits == Self::BinaryBuilder as u64 => Some(Self::BinaryBuilder),
bits if bits == Self::MatchContext as u64 => Some(Self::MatchContext),
bits if bits == Self::ProcBin as u64 => Some(Self::ProcBin),
bits if bits == Self::SubBinary as u64 => Some(Self::SubBinary),
bits if bits == Self::FdResource as u64 => Some(Self::FdResource),
bits if bits == Self::ExternalPid as u64 => Some(Self::ExternalPid),
bits if bits == Self::ExternalReference as u64 => Some(Self::ExternalReference),
_ => None,
}
}
}
pub struct BoxedHeader;
impl BoxedHeader {
#[allow(clippy::new_ret_no_self)]
pub const fn new(tag: BoxedTag, size: usize) -> u64 {
((size as u64) << HEADER_TAG_BITS) | tag as u64
}
pub const fn tag(header_word: u64) -> Option<BoxedTag> {
BoxedTag::from_bits(header_word & HEADER_TAG_MASK)
}
pub const fn size(header_word: u64) -> usize {
(header_word >> HEADER_TAG_BITS) as usize
}
}
pub fn write_tuple(heap: &mut [u64], elements: &[Term]) -> Option<Term> {
if heap.len() < 1 + elements.len() {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::Tuple, elements.len());
for (slot, element) in heap[1..].iter_mut().zip(elements.iter()) {
*slot = element.raw();
}
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub fn write_external_pid(
heap: &mut [u64],
node: Atom,
pid_number: u64,
serial: u64,
) -> Option<Term> {
if heap.len() < 4 {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::ExternalPid, 3);
heap[1] = Term::atom(node).raw();
heap[2] = pid_number;
heap[3] = serial;
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub fn write_external_reference(heap: &mut [u64], node: Atom, id: u64) -> Option<Term> {
if heap.len() < 3 {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::ExternalReference, 2);
heap[1] = Term::atom(node).raw();
heap[2] = id;
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub fn write_cons(heap: &mut [u64], head: Term, tail: Term) -> Option<Term> {
if heap.len() < 2 {
return None;
}
heap[0] = head.raw();
heap[1] = tail.raw();
Some(Term::list_ptr(heap.as_ptr()))
}
pub fn write_float(heap: &mut [u64], value: f64) -> Option<Term> {
if heap.len() < 2 {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::Float, 1);
heap[1] = value.to_bits();
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub fn write_bigint(heap: &mut [u64], negative: bool, limbs: &[u64]) -> Option<Term> {
if heap.len() < 3 + limbs.len() {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::BigInt, 2 + limbs.len());
heap[1] = u64::from(negative);
heap[2] = limbs.len() as u64;
heap[3..3 + limbs.len()].copy_from_slice(limbs);
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub const EXPORT_FUN_GENERATION: u64 = u64::MAX;
pub fn write_export_fun(heap: &mut [u64], module: Atom, function: Atom, arity: u8) -> Option<Term> {
if heap.len() < 7 {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::Closure, 6);
heap[1] = Term::atom(module).raw();
heap[2] = Term::atom(function).raw();
heap[3] = u64::from(arity);
heap[4] = 0;
heap[5] = EXPORT_FUN_GENERATION;
heap[6] = 0;
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub fn write_closure(
heap: &mut [u64],
module: Atom,
function_index: u64,
arity: u8,
generation: u64,
unique_id: u64,
free_vars: &[Term],
) -> Option<Term> {
if heap.len() < 7 + free_vars.len() {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::Closure, 6 + free_vars.len());
heap[1] = Term::atom(module).raw();
heap[2] = function_index;
heap[3] = u64::from(arity);
heap[4] = free_vars.len() as u64;
heap[5] = generation;
heap[6] = unique_id;
for (slot, free_var) in heap[7..].iter_mut().zip(free_vars.iter()) {
*slot = free_var.raw();
}
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub fn write_map(heap: &mut [u64], keys: &[Term], values: &[Term]) -> Option<Term> {
if keys.len() != values.len() || heap.len() < 2 + keys.len() + values.len() {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::Map, 1 + keys.len() + values.len());
heap[1] = keys.len() as u64;
let key_start = 2;
let value_start = key_start + keys.len();
for (slot, key) in heap[key_start..value_start].iter_mut().zip(keys.iter()) {
*slot = key.raw();
}
for (slot, value) in heap[value_start..value_start + values.len()]
.iter_mut()
.zip(values.iter())
{
*slot = value.raw();
}
Some(Term::boxed_ptr(heap.as_ptr()))
}
pub fn write_reference(heap: &mut [u64], id: u64) -> Option<Term> {
if heap.len() < 2 {
return None;
}
heap[0] = BoxedHeader::new(BoxedTag::Reference, 1);
heap[1] = id;
Some(Term::boxed_ptr(heap.as_ptr()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn boxed_header_encodes_distinct_tags_and_payload_size() {
let tags = [
BoxedTag::Tuple,
BoxedTag::Float,
BoxedTag::BigInt,
BoxedTag::Closure,
BoxedTag::Map,
BoxedTag::Reference,
BoxedTag::Binary,
BoxedTag::BinaryBuilder,
BoxedTag::MatchContext,
BoxedTag::ProcBin,
BoxedTag::SubBinary,
BoxedTag::FdResource,
BoxedTag::ExternalPid,
BoxedTag::ExternalReference,
];
for (index, tag) in tags.iter().copied().enumerate() {
let header = BoxedHeader::new(tag, index);
assert_eq!(BoxedHeader::tag(header), Some(tag));
assert_eq!(BoxedHeader::size(header), index);
assert!((tag as u64) > 0b111);
}
for (left_index, left) in tags.iter().enumerate() {
for right in &tags[left_index + 1..] {
assert_ne!(*left as u8, *right as u8);
}
}
}
#[test]
fn tuple_write_then_read_round_trip_and_bounds_check() {
let elements = [Term::small_int(1), Term::atom(Atom::OK), Term::NIL];
let mut heap = [0_u64; 4];
let term = write_tuple(&mut heap, &elements).expect("tuple should fit");
assert!(term.is_boxed());
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::Tuple));
assert_eq!(BoxedHeader::size(heap[0]), 3);
assert_eq!(heap.len(), 4);
let tuple = Tuple::new(term).expect("tuple accessor");
assert_eq!(tuple.arity(), 3);
assert_eq!(tuple.get(0), Some(elements[0]));
assert_eq!(tuple.get(1), Some(elements[1]));
assert_eq!(tuple.get(2), Some(elements[2]));
assert_eq!(tuple.get(3), None);
}
#[test]
fn empty_tuple_is_valid_one_word_boxed_value() {
let mut heap = [0_u64; 1];
let term = write_tuple(&mut heap, &[]).expect("empty tuple should fit");
let tuple = Tuple::new(term).expect("tuple accessor");
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::Tuple));
assert_eq!(tuple.arity(), 0);
assert_eq!(tuple.get(0), None);
}
#[test]
fn cons_cell_write_then_read_round_trip_without_header() {
let head = Term::small_int(1);
let tail = Term::NIL;
let mut heap = [0_u64; 2];
let term = write_cons(&mut heap, head, tail).expect("cons should fit");
assert!(term.is_list());
assert!(!term.is_boxed());
assert_ne!(term.tag(), Term::boxed_ptr(heap.as_ptr()).tag());
assert_eq!(heap, [head.raw(), tail.raw()]);
let cons = Cons::new(term).expect("cons accessor");
assert_eq!(cons.head(), head);
assert_eq!(cons.tail(), tail);
}
#[test]
fn proper_list_has_three_cons_cells_ending_in_nil() {
let mut cell3 = [0_u64; 2];
let mut cell2 = [0_u64; 2];
let mut cell1 = [0_u64; 2];
let third = write_cons(&mut cell3, Term::small_int(3), Term::NIL).expect("third cell");
let second = write_cons(&mut cell2, Term::small_int(2), third).expect("second cell");
let first = write_cons(&mut cell1, Term::small_int(1), second).expect("first cell");
assert_eq!(Cons::new(first).expect("first").head(), Term::small_int(1));
let second_cons = Cons::new(Cons::new(first).expect("first").tail()).expect("second");
assert_eq!(second_cons.head(), Term::small_int(2));
let third_cons = Cons::new(second_cons.tail()).expect("third");
assert_eq!(third_cons.head(), Term::small_int(3));
assert_eq!(third_cons.tail(), Term::NIL);
}
#[test]
fn external_pid_write_then_read_round_trip() {
let mut heap = [0_u64; 4];
let term = write_external_pid(&mut heap, Atom::OK, 123, 4).expect("external pid fits");
assert!(term.is_boxed());
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::ExternalPid));
assert_eq!(BoxedHeader::size(heap[0]), 3);
let pid = ExternalPid::new(term).expect("external pid accessor");
assert_eq!(pid.node(), Some(Atom::OK));
assert_eq!(pid.pid_number(), 123);
assert_eq!(pid.serial(), 4);
}
#[test]
fn external_reference_write_then_read_round_trip() {
let mut heap = [0_u64; 3];
let term = write_external_reference(&mut heap, Atom::OK, 987).expect("external ref fits");
assert!(term.is_boxed());
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::ExternalReference));
assert_eq!(BoxedHeader::size(heap[0]), 2);
let reference = ExternalReference::new(term).expect("external reference accessor");
assert_eq!(reference.node(), Some(Atom::OK));
assert_eq!(reference.id(), 987);
}
#[test]
fn float_write_then_read_round_trip() {
for value in [3.125, 0.0, -1.5] {
let mut heap = [0_u64; 2];
let term = write_float(&mut heap, value).expect("float should fit");
let float = Float::new(term).expect("float accessor");
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::Float));
assert_eq!(BoxedHeader::size(heap[0]), 1);
assert_eq!(float.value(), value);
}
}
#[test]
fn bigint_write_then_read_round_trip() {
let limbs = [0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210];
let mut heap = [0_u64; 5];
let term = write_bigint(&mut heap, true, &limbs).expect("bigint should fit");
let bigint = BigInt::new(term).expect("bigint accessor");
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::BigInt));
assert_eq!(BoxedHeader::size(heap[0]), 4);
assert!(bigint.is_negative());
assert_eq!(bigint.limb_count(), 2);
assert_eq!(bigint.limbs(), limbs);
}
#[test]
fn closure_write_then_read_round_trip_and_bounds_check() {
let free_vars = [Term::small_int(42), Term::atom(Atom::ERROR)];
let mut heap = [0_u64; 9];
let term = write_closure(&mut heap, Atom::OK, 9, 2, 3, 0xfeed, &free_vars)
.expect("closure should fit");
let closure = Closure::new(term).expect("closure accessor");
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::Closure));
assert_eq!(BoxedHeader::size(heap[0]), 8);
assert_eq!(closure.module(), Some(Atom::OK));
assert_eq!(closure.function_index(), 9);
assert_eq!(closure.arity(), 2);
assert_eq!(closure.num_free(), 2);
assert_eq!(closure.generation(), 3);
assert_eq!(closure.unique_id(), 0xfeed);
assert_eq!(closure.free_var(0), Some(free_vars[0]));
assert_eq!(closure.free_var(1), Some(free_vars[1]));
assert_eq!(closure.free_var(2), None);
}
#[test]
fn map_write_then_read_round_trip_and_linear_get() {
let keys = [Term::small_int(1), Term::small_int(2)];
let values = [Term::atom(Atom::OK), Term::atom(Atom::ERROR)];
let mut heap = [0_u64; 6];
let term = write_map(&mut heap, &keys, &values).expect("map should fit");
let map = Map::new(term).expect("map accessor");
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::Map));
assert_eq!(BoxedHeader::size(heap[0]), 5);
assert_eq!(map.len(), 2);
assert_eq!(map.key(0), Some(keys[0]));
assert_eq!(map.value(0), Some(values[0]));
assert_eq!(map.get(keys[0]), Some(values[0]));
assert_eq!(map.get(keys[1]), Some(values[1]));
assert_eq!(map.get(Term::small_int(3)), None);
}
#[test]
fn map_rejects_mismatched_key_value_counts() {
let mut heap = [0_u64; 4];
assert_eq!(write_map(&mut heap, &[Term::small_int(1)], &[]), None);
}
#[test]
fn reference_write_then_read_round_trip() {
let mut heap = [0_u64; 2];
let term = write_reference(&mut heap, 0xfeed_face_cafe_beef).expect("reference should fit");
let reference = Reference::new(term).expect("reference accessor");
assert_eq!(BoxedHeader::tag(heap[0]), Some(BoxedTag::Reference));
assert_eq!(BoxedHeader::size(heap[0]), 1);
assert_eq!(reference.id(), 0xfeed_face_cafe_beef);
}
}