use crate::ets::EtsError;
use crate::process::heap::Heap;
use crate::term::Term;
use crate::term::binary::Binary;
use crate::term::boxed::{
self, BigInt, Closure, Cons, Float, Map, ProcBin, Reference, SubBinary, Tuple,
};
#[derive(Debug)]
pub struct OwnedTerm {
root: Term,
allocations: Vec<Box<[u64]>>,
}
impl OwnedTerm {
pub(crate) fn from_allocations(root: Term, allocations: Vec<Box<[u64]>>) -> Self {
Self { root, allocations }
}
#[must_use]
pub fn immediate(term: Term) -> Self {
debug_assert!(
term.heap_ptr().is_none(),
"OwnedTerm::immediate requires a non-heap term"
);
Self {
root: term,
allocations: Vec::new(),
}
}
#[must_use]
pub const fn root(&self) -> Term {
self.root
}
pub fn copy_to_heap(&self, heap: &mut Heap) -> Result<Term, EtsError> {
copy_term_to_heap(self.root, heap)
}
#[must_use]
pub fn allocation_count(&self) -> usize {
self.allocations.len()
}
#[must_use]
pub fn total_words(&self) -> usize {
self.allocations
.iter()
.map(|allocation| allocation.len())
.sum()
}
}
pub fn copy_term_to_ets(term: Term) -> Result<OwnedTerm, EtsError> {
let mut copier = EtsCopier {
allocations: Vec::new(),
};
let root = copier.copy_term(term)?;
Ok(OwnedTerm {
root,
allocations: copier.allocations,
})
}
pub fn copy_term_to_heap(term: Term, heap: &mut Heap) -> Result<Term, EtsError> {
if term.is_list() {
copy_cons_to_heap(term, heap)
} else if term.is_boxed() {
copy_boxed_to_heap(term, heap)
} else {
Ok(term)
}
}
struct EtsCopier {
allocations: Vec<Box<[u64]>>,
}
impl EtsCopier {
fn copy_term(&mut self, term: Term) -> Result<Term, EtsError> {
if term.is_list() {
self.copy_cons(term)
} else if term.is_boxed() {
self.copy_boxed(term)
} else {
Ok(term)
}
}
fn copy_cons(&mut self, term: Term) -> Result<Term, EtsError> {
let cons = Cons::new(term).ok_or(EtsError::InvalidBoxedTerm)?;
let head = self.copy_term(cons.head())?;
let tail = self.copy_term(cons.tail())?;
self.write_words(2, |words| boxed::write_cons(words, head, tail))
}
fn copy_boxed(&mut self, term: Term) -> Result<Term, EtsError> {
if let Some(tuple) = Tuple::new(term) {
return self.copy_tuple(tuple);
}
if let Some(float) = Float::new(term) {
return self.copy_float(float);
}
if let Some(bigint) = BigInt::new(term) {
return self.copy_bigint(bigint);
}
if let Some(closure) = Closure::new(term) {
return self.copy_closure(closure);
}
if let Some(map) = Map::new(term) {
return self.copy_map(map);
}
if let Some(reference) = Reference::new(term) {
return self.copy_reference(reference);
}
if let Some(binary) = Binary::new(term) {
return self.copy_binary(binary.as_bytes());
}
if let Some(proc_bin) = ProcBin::new(term) {
return self.copy_binary(proc_bin.as_bytes());
}
if let Some(sub_binary) = SubBinary::new(term) {
return self.copy_binary(sub_binary.as_bytes());
}
Err(EtsError::InvalidBoxedTerm)
}
fn copy_tuple(&mut self, tuple: Tuple) -> Result<Term, EtsError> {
let elements = (0..tuple.arity())
.map(|index| {
tuple
.get(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|element| self.copy_term(element))
})
.collect::<Result<Vec<_>, _>>()?;
self.write_words(1 + elements.len(), |words| {
boxed::write_tuple(words, &elements)
})
}
fn copy_float(&mut self, float: Float) -> Result<Term, EtsError> {
self.write_words(2, |words| boxed::write_float(words, float.value()))
}
fn copy_bigint(&mut self, bigint: BigInt) -> Result<Term, EtsError> {
let limbs = bigint.limbs();
self.write_words(3 + limbs.len(), |words| {
boxed::write_bigint(words, bigint.is_negative(), limbs)
})
}
fn copy_closure(&mut self, closure: Closure) -> Result<Term, EtsError> {
let module = closure.module().ok_or(EtsError::InvalidBoxedTerm)?;
let free_vars = (0..closure.num_free())
.map(|index| {
closure
.free_var(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|free_var| self.copy_term(free_var))
})
.collect::<Result<Vec<_>, _>>()?;
self.write_words(7 + free_vars.len(), |words| {
boxed::write_closure(
words,
module,
closure.function_index(),
closure.arity(),
closure.generation(),
closure.unique_id(),
&free_vars,
)
})
}
fn copy_map(&mut self, map: Map) -> Result<Term, EtsError> {
let keys = (0..map.len())
.map(|index| {
map.key(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|key| self.copy_term(key))
})
.collect::<Result<Vec<_>, _>>()?;
let values = (0..map.len())
.map(|index| {
map.value(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|value| self.copy_term(value))
})
.collect::<Result<Vec<_>, _>>()?;
self.write_words(2 + keys.len() + values.len(), |words| {
boxed::write_map(words, &keys, &values)
})
}
fn copy_reference(&mut self, reference: Reference) -> Result<Term, EtsError> {
self.write_words(2, |words| boxed::write_reference(words, reference.id()))
}
fn copy_binary(&mut self, bytes: &[u8]) -> Result<Term, EtsError> {
let word_count = 2usize
.checked_add(crate::term::binary::packed_word_count(bytes.len()))
.ok_or(EtsError::AllocationFailed)?;
self.write_words(word_count, |words| {
crate::term::binary::write_binary(words, bytes)
})
}
fn write_words(
&mut self,
word_count: usize,
write: impl FnOnce(&mut [u64]) -> Option<Term>,
) -> Result<Term, EtsError> {
let mut words = vec![0; word_count].into_boxed_slice();
let term = write(&mut words).ok_or(EtsError::InvalidBoxedTerm)?;
self.allocations.push(words);
Ok(term)
}
}
fn copy_cons_to_heap(term: Term, heap: &mut Heap) -> Result<Term, EtsError> {
let cons = Cons::new(term).ok_or(EtsError::InvalidBoxedTerm)?;
let head = copy_term_to_heap(cons.head(), heap)?;
let tail = copy_term_to_heap(cons.tail(), heap)?;
let words = heap
.alloc_slice(2)
.map_err(|_error| EtsError::AllocationFailed)?;
boxed::write_cons(words, head, tail).ok_or(EtsError::InvalidBoxedTerm)
}
fn copy_boxed_to_heap(term: Term, heap: &mut Heap) -> Result<Term, EtsError> {
if let Some(tuple) = Tuple::new(term) {
return copy_tuple_to_heap(tuple, heap);
}
if let Some(float) = Float::new(term) {
let words = heap
.alloc_slice(2)
.map_err(|_error| EtsError::AllocationFailed)?;
return boxed::write_float(words, float.value()).ok_or(EtsError::InvalidBoxedTerm);
}
if let Some(bigint) = BigInt::new(term) {
let limbs = bigint.limbs();
let words = heap
.alloc_slice(3 + limbs.len())
.map_err(|_error| EtsError::AllocationFailed)?;
return boxed::write_bigint(words, bigint.is_negative(), limbs)
.ok_or(EtsError::InvalidBoxedTerm);
}
if let Some(closure) = Closure::new(term) {
return copy_closure_to_heap(closure, heap);
}
if let Some(map) = Map::new(term) {
return copy_map_to_heap(map, heap);
}
if let Some(reference) = Reference::new(term) {
let words = heap
.alloc_slice(2)
.map_err(|_error| EtsError::AllocationFailed)?;
return boxed::write_reference(words, reference.id()).ok_or(EtsError::InvalidBoxedTerm);
}
if let Some(binary) = Binary::new(term) {
return copy_binary_to_heap(binary.as_bytes(), heap);
}
if let Some(proc_bin) = ProcBin::new(term) {
return copy_binary_to_heap(proc_bin.as_bytes(), heap);
}
if let Some(sub_binary) = SubBinary::new(term) {
return copy_binary_to_heap(sub_binary.as_bytes(), heap);
}
Err(EtsError::InvalidBoxedTerm)
}
fn copy_tuple_to_heap(tuple: Tuple, heap: &mut Heap) -> Result<Term, EtsError> {
let elements = (0..tuple.arity())
.map(|index| {
tuple
.get(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|element| copy_term_to_heap(element, heap))
})
.collect::<Result<Vec<_>, _>>()?;
let words = heap
.alloc_slice(1 + elements.len())
.map_err(|_error| EtsError::AllocationFailed)?;
boxed::write_tuple(words, &elements).ok_or(EtsError::InvalidBoxedTerm)
}
fn copy_closure_to_heap(closure: Closure, heap: &mut Heap) -> Result<Term, EtsError> {
let module = closure.module().ok_or(EtsError::InvalidBoxedTerm)?;
let free_vars = (0..closure.num_free())
.map(|index| {
closure
.free_var(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|free_var| copy_term_to_heap(free_var, heap))
})
.collect::<Result<Vec<_>, _>>()?;
let words = heap
.alloc_slice(7 + free_vars.len())
.map_err(|_error| EtsError::AllocationFailed)?;
boxed::write_closure(
words,
module,
closure.function_index(),
closure.arity(),
closure.generation(),
closure.unique_id(),
&free_vars,
)
.ok_or(EtsError::InvalidBoxedTerm)
}
fn copy_map_to_heap(map: Map, heap: &mut Heap) -> Result<Term, EtsError> {
let keys = (0..map.len())
.map(|index| {
map.key(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|key| copy_term_to_heap(key, heap))
})
.collect::<Result<Vec<_>, _>>()?;
let values = (0..map.len())
.map(|index| {
map.value(index)
.ok_or(EtsError::InvalidBoxedTerm)
.and_then(|value| copy_term_to_heap(value, heap))
})
.collect::<Result<Vec<_>, _>>()?;
let words = heap
.alloc_slice(2 + keys.len() + values.len())
.map_err(|_error| EtsError::AllocationFailed)?;
boxed::write_map(words, &keys, &values).ok_or(EtsError::InvalidBoxedTerm)
}
fn copy_binary_to_heap(bytes: &[u8], heap: &mut Heap) -> Result<Term, EtsError> {
let word_count = 2usize
.checked_add(crate::term::binary::packed_word_count(bytes.len()))
.ok_or(EtsError::AllocationFailed)?;
let words = heap
.alloc_slice(word_count)
.map_err(|_error| EtsError::AllocationFailed)?;
crate::term::binary::write_binary(words, bytes).ok_or(EtsError::InvalidBoxedTerm)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::term::binary::Binary;
use crate::term::boxed::{Cons, Map, Tuple};
use crate::term::shared_binary::{SharedBinary, write_proc_bin};
use crate::term::sub_binary::write_sub_binary;
#[test]
fn tuple_copy_survives_source_heap_reset_and_copies_out_to_new_heap() {
let mut source_heap = Heap::new(16);
let original = {
let words = source_heap.alloc_slice(3).expect("source tuple allocation");
boxed::write_tuple(words, &[Term::small_int(1), Term::small_int(2)])
.expect("source tuple write")
};
let original_ptr = original.heap_ptr().expect("tuple has heap pointer");
let owned = copy_term_to_ets(original).expect("copy into ETS");
let owned_ptr = owned
.root()
.heap_ptr()
.expect("owned tuple has heap pointer");
assert_ne!(owned_ptr, original_ptr);
assert_eq!(owned.allocation_count(), 1);
source_heap.reset_young();
let mut target_heap = Heap::new(16);
let copied = owned
.copy_to_heap(&mut target_heap)
.expect("copy out of ETS");
let copied_ptr = copied.heap_ptr().expect("copied tuple has heap pointer");
assert_ne!(copied_ptr, owned_ptr);
assert!(target_heap.contains(copied_ptr));
let tuple = Tuple::new(copied).expect("copied tuple accessor");
assert_eq!(tuple.get(0), Some(Term::small_int(1)));
assert_eq!(tuple.get(1), Some(Term::small_int(2)));
}
#[test]
fn ets_owned_term_survives_source_heap_drop() {
let owned = {
let mut source_heap = Heap::new(16);
let original = {
let words = source_heap.alloc_slice(2).expect("source tuple allocation");
boxed::write_tuple(words, &[Term::pid(55)]).expect("source tuple write")
};
copy_term_to_ets(original).expect("copy into ETS")
};
let mut target_heap = Heap::new(16);
let copied = owned
.copy_to_heap(&mut target_heap)
.expect("copy out of ETS");
let tuple = Tuple::new(copied).expect("copied tuple accessor");
assert_eq!(tuple.get(0), Some(Term::pid(55)));
}
#[test]
fn nested_list_map_and_binary_copy_are_independent_of_source_heap() {
let mut source_heap = Heap::new(64);
let source = {
let binary_words = source_heap.alloc_slice(3).expect("binary allocation");
let binary =
crate::term::binary::write_binary(binary_words, b"ets").expect("binary write");
let cons_tail_words = source_heap.alloc_slice(2).expect("tail cons allocation");
let tail =
boxed::write_cons(cons_tail_words, binary, Term::NIL).expect("tail cons write");
let cons_head_words = source_heap.alloc_slice(2).expect("head cons allocation");
let list = boxed::write_cons(cons_head_words, Term::small_int(7), tail)
.expect("head cons write");
let map_words = source_heap.alloc_slice(4).expect("map allocation");
let map = boxed::write_map(map_words, &[Term::small_int(1)], &[Term::pid(99)])
.expect("map write");
let tuple_words = source_heap.alloc_slice(3).expect("tuple allocation");
boxed::write_tuple(tuple_words, &[list, map]).expect("tuple write")
};
let owned = copy_term_to_ets(source).expect("copy into ETS");
source_heap.reset_young();
let mut target_heap = Heap::new(64);
let copied = owned.copy_to_heap(&mut target_heap).expect("copy out");
let tuple = Tuple::new(copied).expect("tuple accessor");
let copied_list = tuple.get(0).expect("list element");
let first = Cons::new(copied_list).expect("first cons");
assert_eq!(first.head(), Term::small_int(7));
let second = Cons::new(first.tail()).expect("second cons");
let copied_binary = Binary::new(second.head()).expect("binary accessor");
assert_eq!(copied_binary.as_bytes(), b"ets");
assert_eq!(second.tail(), Term::NIL);
let copied_map = Map::new(tuple.get(1).expect("map element")).expect("map accessor");
assert_eq!(copied_map.key(0), Some(Term::small_int(1)));
assert_eq!(copied_map.value(0), Some(Term::pid(99)));
}
#[test]
fn proc_bin_and_sub_binary_copy_as_independent_inline_binaries() {
let shared = SharedBinary::new(b"0123456789".to_vec());
let (owned_proc_bin, owned_sub_binary) = {
let mut proc_heap = Heap::new(8);
let proc_bin = {
let words = proc_heap.alloc_slice(3).expect("proc bin allocation");
write_proc_bin(words, &shared).expect("proc bin write")
};
let mut sub_heap = Heap::new(8);
let sub_binary = {
let words = sub_heap.alloc_slice(5).expect("sub binary allocation");
write_sub_binary(words, proc_bin, 2, 4).expect("sub binary write")
};
(
copy_term_to_ets(proc_bin).expect("copy proc bin into ETS"),
copy_term_to_ets(sub_binary).expect("copy sub binary into ETS"),
)
};
let mut target_heap = Heap::new(16);
let copied_proc_bin = owned_proc_bin
.copy_to_heap(&mut target_heap)
.expect("copy proc bin out");
let copied_proc_binary =
Binary::new(copied_proc_bin).expect("copied proc bin is inline binary");
assert_eq!(copied_proc_binary.as_bytes(), b"0123456789");
let copied_sub_binary = owned_sub_binary
.copy_to_heap(&mut target_heap)
.expect("copy sub binary out");
let copied_sub_binary =
Binary::new(copied_sub_binary).expect("copied sub binary is inline binary");
assert_eq!(copied_sub_binary.as_bytes(), b"2345");
}
}