| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020 |
- use crate::noun::NounExt;
- use bitvec::prelude::{BitSlice, BitVec, Lsb0};
- use bitvec::view::BitView;
- use bitvec::{bits, bitvec};
- use bytes::Bytes;
- use either::Either;
- use intmap::IntMap;
- use nockvm::mem::NockStack;
- use nockvm::mug::{calc_atom_mug_u32, calc_cell_mug_u32, get_mug, set_mug};
- use nockvm::noun::{Atom, Cell, CellMemory, DirectAtom, IndirectAtom, Noun, NounAllocator, D};
- use nockvm::serialization::{met0_u64_to_usize, met0_usize};
- use std::alloc::Layout;
- use std::mem::size_of;
- use std::ptr::copy_nonoverlapping;
- use thiserror::Error;
- const CELL_MEM_WORD_SIZE: usize = (size_of::<CellMemory>() + 7) >> 3;
- /// A (mostly*) self-contained arena for allocating nouns.
- ///
- /// *Nouns may contain references to the PMA, but not other allocation arenas.
- #[derive(Debug)]
- pub struct NounSlab {
- root: Noun,
- slabs: Vec<(*mut u8, Layout)>,
- allocation_start: *mut u64,
- allocation_stop: *mut u64,
- }
- impl NounSlab {
- unsafe fn raw_alloc(new_layout: Layout) -> *mut u8 {
- if new_layout.size() == 0 {
- std::alloc::handle_alloc_error(new_layout);
- }
- assert!(new_layout.align().is_power_of_two(), "Invalid alignment");
- let slab = std::alloc::alloc(new_layout);
- if slab.is_null() {
- std::alloc::handle_alloc_error(new_layout);
- } else {
- slab
- }
- }
- pub fn to_vec(&self) -> Vec<Self> {
- self.root
- .list_iter()
- .map(|n| {
- let mut slab = Self::new();
- slab.copy_into(n);
- slab
- })
- .collect()
- }
- pub fn modify<F: FnOnce(Noun) -> Vec<Noun>>(&mut self, f: F) {
- let new_root_base = f(self.root);
- let new_root = nockvm::noun::T(self, &new_root_base);
- self.set_root(new_root);
- }
- pub fn modify_noun<F: FnOnce(Noun) -> Noun>(&mut self, f: F) {
- let new_root = f(self.root);
- self.set_root(new_root);
- }
- pub fn modify_with_imports3<F: FnOnce((Noun, Noun, Noun), Noun) -> Vec<Noun>>(
- &mut self,
- f: F,
- imports: (Noun, Noun, Noun),
- ) {
- self.copy_into(imports.0);
- self.copy_into(imports.1);
- self.copy_into(imports.2);
- let new_root_base = f(imports, self.root);
- let new_root = nockvm::noun::T(self, &new_root_base);
- self.set_root(new_root);
- }
- }
- impl Clone for NounSlab {
- fn clone(&self) -> Self {
- let mut slab = Self::new();
- slab.copy_into(self.root);
- slab
- }
- }
- impl NounAllocator for NounSlab {
- unsafe fn alloc_indirect(&mut self, words: usize) -> *mut u64 {
- let raw_size = words + 2;
- // Make sure we have enough space
- if self.allocation_start.is_null()
- || self.allocation_start.add(raw_size) > self.allocation_stop
- {
- let next_idx = std::cmp::max(self.slabs.len(), min_idx_for_size(raw_size));
- self.slabs
- .resize(next_idx + 1, (std::ptr::null_mut(), Layout::new::<u8>()));
- let new_size = idx_to_size(next_idx);
- let new_layout = Layout::array::<u64>(new_size).unwrap_or_else(|err| {
- panic!(
- "Panicked with {err:?} at {}:{} (git sha: {:?})",
- file!(),
- line!(),
- option_env!("GIT_SHA")
- )
- });
- let new_slab = Self::raw_alloc(new_layout);
- let new_slab_u64 = new_slab as *mut u64;
- self.slabs[next_idx] = (new_slab, new_layout);
- self.allocation_start = new_slab_u64;
- self.allocation_stop = new_slab_u64.add(new_size);
- }
- let new_indirect_ptr = self.allocation_start;
- self.allocation_start = self.allocation_start.add(raw_size);
- new_indirect_ptr
- }
- unsafe fn alloc_cell(&mut self) -> *mut CellMemory {
- if self.allocation_start.is_null()
- || self.allocation_start.add(CELL_MEM_WORD_SIZE) > self.allocation_stop
- // || (self.allocation_start as usize) + CELL_MEM_WORD_SIZE > (self.allocation_stop as usize)
- // || (self.allocation_start.expose_provenance()) + CELL_MEM_WORD_SIZE > (self.allocation_stop.expose_provenance())
- // || (self.allocation_start as usize) + (CELL_MEM_WORD_SIZE * std::mem::size_of::<u64>()) > (self.allocation_stop as usize)
- {
- let next_idx = std::cmp::max(self.slabs.len(), min_idx_for_size(CELL_MEM_WORD_SIZE));
- self.slabs
- .resize(next_idx + 1, (std::ptr::null_mut(), Layout::new::<u8>()));
- let new_size = idx_to_size(next_idx);
- let new_layout = Layout::array::<u64>(new_size).unwrap_or_else(|err| {
- panic!(
- "Panicked with {err:?} at {}:{} (git sha: {:?})",
- file!(),
- line!(),
- option_env!("GIT_SHA")
- )
- });
- let new_slab = Self::raw_alloc(new_layout);
- let new_slab_u64 = new_slab as *mut u64;
- self.slabs[next_idx] = (new_slab, new_layout);
- self.allocation_start = new_slab_u64;
- self.allocation_stop = new_slab_u64.add(new_size);
- }
- let new_cell_ptr = self.allocation_start as *mut CellMemory;
- // self.allocation_start = ((self.allocation_start.expose_provenance()) + CELL_MEM_WORD_SIZE) as *mut u64;
- self.allocation_start = std::ptr::with_exposed_provenance_mut(
- self.allocation_start.expose_provenance()
- + (CELL_MEM_WORD_SIZE * std::mem::size_of::<u64>()),
- );
- new_cell_ptr
- }
- unsafe fn alloc_struct<T>(&mut self, count: usize) -> *mut T {
- let layout = Layout::array::<T>(count).expect("Bad layout in alloc_struct");
- let word_size = (layout.size() + 7) >> 3;
- assert!(layout.align() <= std::mem::size_of::<u64>());
- if self.allocation_start.is_null()
- || self.allocation_start.add(word_size) > self.allocation_stop
- {
- let next_idx = std::cmp::max(self.slabs.len(), min_idx_for_size(word_size));
- self.slabs
- .resize(next_idx + 1, (std::ptr::null_mut(), Layout::new::<u8>()));
- let new_size = idx_to_size(next_idx);
- let new_layout = Layout::array::<u64>(new_size).unwrap_or_else(|err| {
- panic!(
- "Panicked with {err:?} at {}:{} (git sha: {:?})",
- file!(),
- line!(),
- option_env!("GIT_SHA")
- )
- });
- let new_slab = Self::raw_alloc(new_layout);
- let new_slab_u64 = new_slab as *mut u64;
- self.slabs[next_idx] = (new_slab, new_layout);
- self.allocation_start = new_slab_u64;
- self.allocation_stop = new_slab_u64.add(new_size);
- }
- let new_struct_ptr = self.allocation_start as *mut T;
- self.allocation_start = self.allocation_start.add(word_size);
- new_struct_ptr
- }
- }
- /// # Safety: no noun in this slab references a noun outside the slab, except in the PMA
- unsafe impl Send for NounSlab {}
- impl Default for NounSlab {
- fn default() -> Self {
- Self::new()
- }
- }
- impl From<Noun> for NounSlab {
- fn from(noun: Noun) -> Self {
- let mut slab = Self::new();
- slab.copy_into(noun);
- slab
- }
- }
- impl<const N: usize> From<[Noun; N]> for NounSlab {
- fn from(nouns: [Noun; N]) -> Self {
- let mut slab = Self::new();
- let new_root = nockvm::noun::T(&mut slab, &nouns);
- slab.set_root(new_root);
- slab
- }
- }
- impl NounSlab {
- /// Make a new noun slab with D(0) as the root
- #[tracing::instrument]
- pub fn new() -> Self {
- let slabs = Vec::new();
- let allocation_start: *mut u64 = std::ptr::null_mut();
- let allocation_stop: *mut u64 = std::ptr::null_mut();
- let root: Noun = D(0);
- NounSlab {
- root,
- slabs,
- allocation_start,
- allocation_stop,
- }
- }
- /// Copy a noun into this slab, only leaving references into the PMA. Set that noun as the root
- /// noun.
- pub fn copy_into(&mut self, copy_root: Noun) {
- let mut copied: IntMap<u64, Noun> = IntMap::new();
- // let mut copy_stack = vec![(copy_root, &mut self.root as *mut Noun)];
- let mut copy_stack = vec![(copy_root, std::ptr::addr_of_mut!(self.root))];
- while let Some((noun, dest)) = copy_stack.pop() {
- match noun.as_either_direct_allocated() {
- Either::Left(_direct) => {
- unsafe { *dest = noun };
- }
- Either::Right(allocated) => match allocated.as_either() {
- Either::Left(indirect) => {
- let indirect_ptr = unsafe { indirect.to_raw_pointer() };
- let indirect_mem_size = indirect.raw_size();
- if let Some(copied_noun) = copied.get(indirect_ptr as u64) {
- unsafe { *dest = *copied_noun };
- continue;
- }
- let indirect_new_mem = unsafe { self.alloc_indirect(indirect.size()) };
- unsafe {
- copy_nonoverlapping(indirect_ptr, indirect_new_mem, indirect_mem_size)
- };
- let copied_noun = unsafe {
- IndirectAtom::from_raw_pointer(indirect_new_mem)
- .as_atom()
- .as_noun()
- };
- copied.insert(indirect_ptr as u64, copied_noun);
- unsafe { *dest = copied_noun };
- }
- Either::Right(cell) => {
- let cell_ptr = unsafe { cell.to_raw_pointer() };
- if let Some(copied_noun) = copied.get(cell_ptr as u64) {
- unsafe { *dest = *copied_noun };
- continue;
- }
- let cell_new_mem = unsafe { self.alloc_cell() };
- unsafe { copy_nonoverlapping(cell_ptr, cell_new_mem, 1) };
- let copied_noun = unsafe { Cell::from_raw_pointer(cell_new_mem).as_noun() };
- copied.insert(cell_ptr as u64, copied_noun);
- unsafe { *dest = copied_noun };
- unsafe {
- // copy_stack
- // .push((cell.tail(), &mut (*cell_new_mem).tail as *mut Noun));
- // copy_stack
- // .push((cell.head(), &mut (*cell_new_mem).head as *mut Noun));
- copy_stack
- .push((cell.tail(), std::ptr::addr_of_mut!((*cell_new_mem).tail)));
- copy_stack
- .push((cell.head(), std::ptr::addr_of_mut!((*cell_new_mem).head)));
- }
- }
- },
- }
- }
- }
- /// Copy the root noun from this slab into the given NockStack, only leaving references into the PMA
- ///
- /// Note that this consumes the slab, the slab will be freed after and the root noun returned
- /// referencing the stack. Nouns referencing the slab should not be used past this point.
- #[tracing::instrument(skip(self, stack), level = "trace")]
- pub fn copy_to_stack(self, stack: &mut NockStack) -> Noun {
- let mut res = D(0);
- let mut copy_stack = vec![(self.root, &mut res as *mut Noun)];
- while let Some((noun, dest)) = copy_stack.pop() {
- if let Ok(allocated) = noun.as_allocated() {
- if let Some(forward) = unsafe { allocated.forwarding_pointer() } {
- unsafe { *dest = forward.as_noun() };
- } else {
- match allocated.as_either() {
- Either::Left(mut indirect) => {
- let raw_pointer = unsafe { indirect.to_raw_pointer() };
- let raw_size = indirect.raw_size();
- unsafe {
- let indirect_mem = stack.alloc_indirect(indirect.size());
- std::ptr::copy_nonoverlapping(raw_pointer, indirect_mem, raw_size);
- indirect.set_forwarding_pointer(indirect_mem);
- *dest = IndirectAtom::from_raw_pointer(indirect_mem)
- .as_atom()
- .as_noun();
- }
- }
- Either::Right(mut cell) => {
- let raw_pointer = unsafe { cell.to_raw_pointer() };
- unsafe {
- let cell_mem = stack.alloc_cell();
- copy_nonoverlapping(raw_pointer, cell_mem, 1);
- copy_stack.push((cell.tail(), &mut (*cell_mem).tail as *mut Noun));
- copy_stack.push((cell.head(), &mut (*cell_mem).head as *mut Noun));
- cell.set_forwarding_pointer(cell_mem);
- *dest = Cell::from_raw_pointer(cell_mem).as_noun()
- }
- }
- }
- }
- } else {
- unsafe {
- *dest = noun;
- } // Direct atom
- }
- }
- res
- }
- /// Set the root of the noun slab.
- ///
- /// Panics if the given root is not in the noun slab or PMA.
- pub fn set_root(&mut self, root: Noun) {
- if let Ok(allocated) = root.as_allocated() {
- match allocated.as_either() {
- Either::Left(indirect) => {
- let ptr = unsafe { indirect.to_raw_pointer() };
- let u8_ptr = ptr as *const u8;
- for slab in &self.slabs {
- if unsafe { u8_ptr >= slab.0 && u8_ptr < slab.0.add(slab.1.size()) } {
- self.root = root;
- return;
- }
- }
- panic!("Set root of NounSlab to noun from outside slab");
- }
- Either::Right(cell) => {
- let ptr = unsafe { cell.to_raw_pointer() };
- let u8_ptr = ptr as *const u8;
- for slab in &self.slabs {
- if unsafe { u8_ptr >= slab.0 && u8_ptr < slab.0.add(slab.1.size()) } {
- self.root = root;
- return;
- }
- }
- panic!("Set root of NounSlab to noun from outside slab");
- }
- }
- }
- self.root = root;
- }
- pub fn jam(&self) -> Bytes {
- let mut backref_map = NounMap::<usize>::new();
- let mut stack = vec![self.root];
- let mut buffer = bitvec![u8, Lsb0; 0; 0];
- while let Some(noun) = stack.pop() {
- if let Some(backref) = backref_map.get(noun) {
- if let Ok(atom) = noun.as_atom() {
- if met0_u64_to_usize(*backref as u64) < met0_usize(atom) {
- mat_backref(&mut buffer, *backref);
- } else {
- mat_atom(&mut buffer, atom)
- }
- } else {
- mat_backref(&mut buffer, *backref);
- }
- } else {
- backref_map.insert(noun, buffer.len());
- match noun.as_either_atom_cell() {
- Either::Left(atom) => {
- mat_atom(&mut buffer, atom);
- }
- Either::Right(cell) => {
- buffer.extend_from_bitslice(bits![u8, Lsb0; 1, 0]); // cell tag
- stack.push(cell.tail());
- stack.push(cell.head());
- }
- }
- }
- }
- Bytes::copy_from_slice(buffer.as_raw_slice())
- }
- pub fn cue_into(&mut self, jammed: Bytes) -> Result<Noun, CueError> {
- let mut backref_map = IntMap::new();
- let bitslice = jammed.view_bits::<Lsb0>();
- let mut cursor = 0usize;
- let mut res = D(0);
- let mut stack = vec![CueStackEntry::DestinationPointer(&mut res)];
- loop {
- match stack.pop() {
- Some(CueStackEntry::DestinationPointer(dest)) => {
- let backref = cursor as u64;
- if bitslice[cursor] {
- // 1
- cursor += 1;
- if bitslice[cursor] {
- // 1 - backref
- cursor += 1;
- let backref = rub_backref(&mut cursor, bitslice)?;
- if let Some(noun) = backref_map.get(backref as u64) {
- unsafe {
- *dest = *noun;
- }
- } else {
- Err(CueError::BadBackref)?
- }
- } else {
- // 0 - cell
- cursor += 1;
- let (cell, cell_mem) = unsafe { Cell::new_raw_mut(self) };
- unsafe {
- *dest = cell.as_noun();
- }
- unsafe {
- stack.push(CueStackEntry::BackRef(backref, dest as *const Noun));
- stack
- .push(CueStackEntry::DestinationPointer(&mut (*cell_mem).tail));
- stack
- .push(CueStackEntry::DestinationPointer(&mut (*cell_mem).head));
- }
- }
- } else {
- // 0 - atom
- cursor += 1;
- unsafe { *dest = rub_atom(self, &mut cursor, bitslice)?.as_noun() };
- backref_map.insert(backref, unsafe { *dest });
- }
- }
- Some(CueStackEntry::BackRef(backref, noun_ptr)) => {
- backref_map.insert(backref, unsafe { *noun_ptr });
- }
- None => {
- break;
- }
- }
- }
- Ok(res)
- }
- /// Get the root noun
- ///
- /// # Safety: The noun must not be used past the lifetime of the slab.
- pub unsafe fn root(&self) -> &Noun {
- &self.root
- }
- /// Get the root noun
- ///
- /// # Safety: The noun must not be used past the lifetime of the slab.
- pub unsafe fn root_mut(&mut self) -> &mut Noun {
- &mut self.root
- }
- }
- impl Drop for NounSlab {
- fn drop(&mut self) {
- for slab in self.slabs.drain(..) {
- if !slab.0.is_null() {
- unsafe { std::alloc::dealloc(slab.0, slab.1) };
- }
- }
- }
- }
- fn mat_backref(buffer: &mut BitVec<u8, Lsb0>, backref: usize) {
- if backref == 0 {
- buffer.extend_from_bitslice(bits![u8, Lsb0; 1, 1, 1]);
- return;
- }
- let backref_sz = met0_u64_to_usize(backref as u64);
- let backref_sz_sz = met0_u64_to_usize(backref_sz as u64);
- buffer.extend_from_bitslice(bits![u8, Lsb0; 1, 1]); // backref tag
- let buffer_len = buffer.len();
- buffer.resize(buffer_len + backref_sz_sz, false);
- buffer.push(true);
- buffer.extend_from_bitslice(
- &BitSlice::<_, Lsb0>::from_element(&backref_sz)[0..backref_sz_sz - 1],
- );
- buffer.extend_from_bitslice(&BitSlice::<_, Lsb0>::from_element(&backref)[0..backref_sz]);
- }
- fn mat_atom(buffer: &mut BitVec<u8, Lsb0>, atom: Atom) {
- if unsafe { atom.as_noun().raw_equals(&D(0)) } {
- buffer.extend_from_bitslice(bits![u8, Lsb0; 0, 1]);
- return;
- }
- let atom_sz = met0_usize(atom);
- let atom_sz_sz = met0_u64_to_usize(atom_sz as u64);
- buffer.push(false); // atom tag
- let buffer_len = buffer.len();
- buffer.resize(buffer_len + atom_sz_sz, false);
- buffer.push(true);
- buffer.extend_from_bitslice(&BitSlice::<_, Lsb0>::from_element(&atom_sz)[0..atom_sz_sz - 1]);
- buffer.extend_from_bitslice(&atom.as_bitslice()[0..atom_sz]);
- }
- fn rub_backref(cursor: &mut usize, buffer: &BitSlice<u8, Lsb0>) -> Result<usize, CueError> {
- if let Some(idx) = buffer[*cursor..].first_one() {
- if idx == 0 {
- *cursor += 1;
- Ok(0)
- } else {
- *cursor += idx + 1;
- let mut sz = 0usize;
- let sz_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut sz);
- if buffer.len() < *cursor + idx - 1 {
- Err(CueError::TruncatedBuffer)?;
- };
- sz_slice[0..idx - 1].clone_from_bitslice(&buffer[*cursor..*cursor + idx - 1]);
- sz_slice.set(idx - 1, true);
- *cursor += idx - 1;
- if sz > size_of::<usize>() << 3 {
- Err(CueError::BackrefTooBig)?;
- }
- if buffer.len() < *cursor + sz {
- Err(CueError::TruncatedBuffer)?;
- }
- let mut backref = 0usize;
- let backref_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut backref);
- backref_slice[0..sz].clone_from_bitslice(&buffer[*cursor..*cursor + sz]);
- *cursor += sz;
- Ok(backref)
- }
- } else {
- Err(CueError::TruncatedBuffer)
- }
- }
- fn rub_atom(
- slab: &mut NounSlab,
- cursor: &mut usize,
- buffer: &BitSlice<u8, Lsb0>,
- ) -> Result<Atom, CueError> {
- if let Some(idx) = buffer[*cursor..].first_one() {
- if idx == 0 {
- *cursor += 1;
- unsafe { Ok(DirectAtom::new_unchecked(0).as_atom()) }
- } else {
- *cursor += idx + 1;
- let mut sz = 0usize;
- let sz_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut sz);
- if buffer.len() < *cursor + idx - 1 {
- Err(CueError::TruncatedBuffer)?;
- }
- sz_slice[0..idx - 1].clone_from_bitslice(&buffer[*cursor..*cursor + idx - 1]);
- sz_slice.set(idx - 1, true);
- *cursor += idx - 1;
- if buffer.len() < *cursor + sz {
- Err(CueError::TruncatedBuffer)?;
- }
- if sz < 64 {
- // Direct atom: less than 64 bits
- let mut data = 0u64;
- let atom_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut data);
- atom_slice[0..sz].clone_from_bitslice(&buffer[*cursor..*cursor + sz]);
- *cursor += sz;
- Ok(unsafe { DirectAtom::new_unchecked(data).as_atom() })
- } else {
- // Indirect atom
- let indirect_words = (sz + 63) >> 6; // fast round to 64-bit words
- let (mut indirect, slice) =
- unsafe { IndirectAtom::new_raw_mut_bitslice(slab, indirect_words) };
- slice[0..sz].clone_from_bitslice(&buffer[*cursor..*cursor + sz]);
- *cursor += sz;
- Ok(unsafe { indirect.normalize_as_atom() })
- }
- }
- } else {
- Err(CueError::TruncatedBuffer)
- }
- }
- #[derive(Debug, Error)]
- pub enum CueError {
- #[error("cue: Bad backref")]
- BadBackref,
- #[error("cue: backref too big")]
- BackrefTooBig,
- #[error("cue: truncated buffer")]
- TruncatedBuffer,
- }
- /// Slab size from vector index, in 8-byte words
- fn idx_to_size(idx: usize) -> usize {
- 1 << (2 * idx + 9)
- }
- /// Inverse of idx_to_size
- fn min_idx_for_size(sz: usize) -> usize {
- let mut log2sz = sz.ilog2() as usize;
- let round_sz = 1 << log2sz;
- if round_sz != sz {
- log2sz += 1;
- };
- if log2sz <= 9 {
- 0
- } else {
- (log2sz - 9).div_ceil(2)
- }
- }
- struct NounMap<V>(IntMap<u64, Vec<(Noun, V)>>);
- impl<V> NounMap<V> {
- fn new() -> Self {
- NounMap(IntMap::new())
- }
- fn insert(&mut self, key: Noun, value: V) {
- let key_mug = slab_mug(key) as u64;
- if let Some(vec) = self.0.get_mut(key_mug) {
- let mut chain_iter = vec[..].iter_mut();
- if let Some(entry) = chain_iter.find(|entry| slab_noun_equality(&key, &entry.0)) {
- entry.1 = value;
- } else {
- vec.push((key, value))
- }
- } else {
- self.0.insert(key_mug, vec![(key, value)]);
- }
- }
- fn get(&mut self, key: Noun) -> Option<&V> {
- let key_mug = slab_mug(key) as u64;
- if let Some(vec) = self.0.get(key_mug) {
- let mut chain_iter = vec[..].iter();
- if let Some(entry) =
- chain_iter.find(|entry| slab_noun_equality(&(key as Noun), &entry.0))
- {
- Some(&entry.1)
- } else {
- None
- }
- } else {
- None
- }
- }
- }
- pub fn slab_equality(a: &NounSlab, b: &NounSlab) -> bool {
- slab_noun_equality(&a.root, &b.root)
- }
- // Does not unify: slabs are collected all-at-once so there's no point.
- pub fn slab_noun_equality(a: &Noun, b: &Noun) -> bool {
- let mut stack = vec![(a, b)];
- loop {
- if let Some((a, b)) = stack.pop() {
- if unsafe { a.raw_equals(b) } {
- continue;
- }
- match (
- a.as_ref_either_direct_allocated(),
- b.as_ref_either_direct_allocated(),
- ) {
- (Either::Right(a_allocated), Either::Right(b_allocated)) => {
- if let Some(a_mug) = a_allocated.get_cached_mug() {
- if let Some(b_mug) = b_allocated.get_cached_mug() {
- if a_mug != b_mug {
- break false;
- }
- }
- };
- match (a_allocated.as_ref_either(), b_allocated.as_ref_either()) {
- (Either::Left(a_indirect), Either::Left(b_indirect)) => {
- if a_indirect.as_slice() != b_indirect.as_slice() {
- break false;
- }
- continue;
- }
- (Either::Right(a_cell), Either::Right(b_cell)) => {
- stack.push((a_cell.tail_ref(), b_cell.tail_ref()));
- stack.push((a_cell.tail_ref(), b_cell.tail_ref()));
- continue;
- }
- _ => {
- break false;
- }
- }
- }
- _ => {
- break false;
- }
- }
- } else {
- break true;
- }
- }
- }
- fn slab_mug(a: Noun) -> u32 {
- let mut stack = vec![a];
- while let Some(noun) = stack.pop() {
- if let Ok(mut allocated) = noun.as_allocated() {
- if allocated.get_cached_mug().is_none() {
- match allocated.as_either() {
- Either::Left(indirect) => unsafe {
- set_mug(&mut allocated, calc_atom_mug_u32(indirect.as_atom()));
- },
- Either::Right(cell) => match (get_mug(cell.head()), get_mug(cell.tail())) {
- (Some(head_mug), Some(tail_mug)) => unsafe {
- set_mug(&mut allocated, calc_cell_mug_u32(head_mug, tail_mug));
- },
- _ => {
- stack.push(noun);
- stack.push(cell.tail());
- stack.push(cell.head());
- }
- },
- }
- }
- }
- }
- get_mug(a).expect("Noun should have a mug once mugged.")
- }
- enum CueStackEntry {
- DestinationPointer(*mut Noun),
- BackRef(u64, *const Noun),
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- use crate::AtomExt;
- use bitvec::prelude::*;
- use nockvm::noun::{D, T};
- use nockvm_macros::tas;
- #[test]
- #[cfg_attr(miri, ignore)]
- fn test_jam() {
- let mut slab = NounSlab::new();
- let test_noun = T(
- &mut slab,
- &[D(tas!(b"request")), D(tas!(b"block")), D(tas!(b"by-id")), D(0)],
- );
- slab.set_root(test_noun);
- let jammed: Vec<u8> = slab.jam().to_vec();
- println!("jammed: {:?}", jammed);
- let mut stack = NockStack::new(1000, 0);
- let mut nockvm_jammed: Vec<u8> = nockvm::serialization::jam(&mut stack, test_noun)
- .as_ne_bytes()
- .to_vec();
- let nockvm_suffix: Vec<u8> = nockvm_jammed.split_off(jammed.len());
- println!("nockvm_jammed: {:?}", nockvm_jammed);
- assert_eq!(jammed, nockvm_jammed, "Jammed results should be identical");
- assert!(
- nockvm_suffix.iter().all(|b| { *b == 0 }),
- "Extra bytes in nockvm jam should all be 0"
- );
- }
- #[test]
- fn test_jam_cue_roundtrip() {
- let mut original_slab = NounSlab::new();
- let original_noun = T(&mut original_slab, &[D(5), D(23)]);
- println!("original_noun: {:?}", original_noun);
- original_slab.set_root(original_noun);
- // Jam the original noun
- let jammed: Vec<u8> = original_slab.jam().to_vec();
- // Cue the jammed data into a new slab
- let mut cued_slab = NounSlab::new();
- let cued_noun = cued_slab
- .cue_into(jammed.into())
- .expect("Cue should succeed");
- println!("cued_noun: {:?}", cued_noun);
- // Compare the original and cued nouns
- assert!(
- slab_noun_equality(unsafe { original_slab.root() }, &cued_noun),
- "Original and cued nouns should be equal"
- );
- }
- #[test]
- fn test_complex_noun() {
- let mut slab = NounSlab::new();
- let complex_noun = T(
- &mut slab,
- &[D(tas!(b"request")), D(tas!(b"block")), D(tas!(b"by-id")), D(0)],
- );
- slab.set_root(complex_noun);
- let jammed = slab.jam();
- let mut cued_slab = NounSlab::new();
- let cued_noun = cued_slab.cue_into(jammed).expect("Cue should succeed");
- assert!(
- slab_noun_equality(unsafe { slab.root() }, &cued_noun),
- "Complex nouns should be equal after jam/cue roundtrip"
- );
- }
- #[test]
- fn test_indirect_atoms() {
- let mut slab = NounSlab::new();
- let large_number = u64::MAX as u128 + 1;
- let large_number_bytes = Bytes::from(large_number.to_le_bytes().to_vec());
- let indirect_atom = Atom::from_bytes(&mut slab, &large_number_bytes);
- let noun_with_indirect = T(&mut slab, &[D(1), indirect_atom.as_noun(), D(2)]);
- println!("noun_with_indirect: {:?}", noun_with_indirect);
- slab.set_root(noun_with_indirect);
- let jammed = slab.jam();
- let mut cued_slab = NounSlab::new();
- let cued_noun = cued_slab.cue_into(jammed).expect("Cue should succeed");
- println!("cued_noun: {:?}", cued_noun);
- assert!(
- slab_noun_equality(&noun_with_indirect, &cued_noun),
- "Nouns with indirect atoms should be equal after jam/cue roundtrip"
- );
- }
- #[test]
- fn test_tas_macro() {
- let mut slab = NounSlab::new();
- let tas_noun = T(
- &mut slab,
- &[D(tas!(b"foo")), D(tas!(b"bar")), D(tas!(b"baz"))],
- );
- slab.set_root(tas_noun);
- let jammed = slab.jam();
- let mut cued_slab = NounSlab::new();
- let cued_noun = cued_slab.cue_into(jammed).expect("Cue should succeed");
- assert!(
- slab_noun_equality(unsafe { slab.root() }, &cued_noun),
- "Nouns with tas! macros should be equal after jam/cue roundtrip"
- );
- }
- #[test]
- #[cfg_attr(miri, ignore)]
- fn test_cue_from_file() {
- use bytes::Bytes;
- use std::fs::File;
- use std::io::Read;
- use std::path::Path;
- // Path to the test file
- // For Bazel builds, we use the test-jams directory from the environment
- #[cfg(feature = "bazel_build")]
- let file_path = match std::env::var("TEST_JAMS_DIR") {
- Ok(dir) => format!("{}/cue-test.jam", dir),
- Err(_) => String::from("test-jams/cue-test.jam"),
- };
- // For Cargo builds, we use the regular path
- #[cfg(not(feature = "bazel_build"))]
- let file_path = "test-jams/cue-test.jam";
- // Check if the file exists
- if !Path::new(&file_path).exists() {
- println!("Test file not found at {}, skipping test", file_path);
- return; // Skip the test if the file doesn't exist
- }
- // Read the jammed data from the file
- let mut file = File::open(file_path).expect("Failed to open file");
- let mut jammed_data = Vec::new();
- file.read_to_end(&mut jammed_data)
- .expect("Failed to read file");
- let jammed = Bytes::from(jammed_data);
- // Create a new NounSlab and attempt to cue the data
- let mut slab = NounSlab::new();
- let result = slab.cue_into(jammed);
- // Assert that cue_into does not return an error
- assert!(
- result.is_ok(),
- "cue_into returned an error: {:?}",
- result.err()
- );
- }
- #[test]
- fn test_cyclic_structure() {
- let mut slab = NounSlab::new();
- // Create a jammed representation of a cyclic structure
- // [0 *] where * refers back to the entire cell, i.e. 0b11110001
- let mut jammed = BitVec::<u8, Lsb0>::new();
- jammed.extend_from_bitslice(bits![u8, Lsb0; 1, 1, 1]); //Backref to the entire structure
- jammed.extend_from_bitslice(bits![u8, Lsb0; 1, 0 ,0]); // Atom 0
- jammed.extend_from_bitslice(bits![u8, Lsb0; 0, 1]); // Cell
- let jammed_bytes = Bytes::from(jammed.into_vec());
- let result = slab.cue_into(jammed_bytes);
- assert!(
- result.is_err(),
- "Expected error due to cyclic structure, but cue_into completed successfully"
- );
- if let Err(e) = result {
- println!("Error type: {:?}", e);
- assert!(
- matches!(e, CueError::BadBackref),
- "Expected CueError::BadBackref, but got a different error"
- );
- }
- }
- #[test]
- fn test_cue_simple_cell() {
- let mut slab = NounSlab::new();
- // Create a jammed representation of [1 0] by hand
- let mut jammed = BitVec::<u8, Lsb0>::new();
- jammed.extend_from_bitslice(bits![u8, Lsb0; 1, 0, 0, 0, 1, 1, 0, 1]); // 0b10110001
- let jammed_bytes = Bytes::from(jammed.into_vec());
- let result = slab.cue_into(jammed_bytes);
- assert!(result.is_ok(), "cue_into should succeed");
- if let Ok(cued_noun) = result {
- let expected_noun = T(&mut slab, &[D(1), D(0)]);
- assert!(
- slab_noun_equality(&cued_noun, &expected_noun),
- "Cued noun should equal [1 0]"
- );
- }
- }
- #[test]
- fn test_cell_construction_for_noun_slab() {
- let mut slab = NounSlab::new();
- let (cell, cell_mem_ptr) = unsafe { Cell::new_raw_mut(&mut slab) };
- unsafe { assert!(cell_mem_ptr as *const CellMemory == cell.to_raw_pointer()) };
- }
- #[test]
- fn test_noun_slab_copy_into() {
- let mut slab = NounSlab::new();
- let test_noun = T(&mut slab, &[D(5), D(23)]);
- slab.set_root(test_noun);
- let mut copy_slab = NounSlab::new();
- copy_slab.copy_into(test_noun);
- }
- // Fails in Miri
- // #[test]
- // fn test_alloc_cell_for_noun_slab_uninit() {
- // let mut slab = NounSlab::new();
- // let cell_ptr = unsafe { slab.alloc_cell() };
- // let cell: Cell = unsafe { Cell::from_raw_pointer(cell_ptr) };
- // unsafe { assert_eq!(cell.head().as_raw(), 0) };
- // }
- #[test]
- fn test_alloc_cell_for_noun_slab_set_value() {
- let mut slab = NounSlab::new();
- let mut i = 0;
- while i < 100 {
- let cell_ptr = unsafe { slab.alloc_cell() };
- let cell_memory = CellMemory {
- metadata: 0,
- head: D(i),
- tail: D(i + 1),
- };
- unsafe { (*cell_ptr) = cell_memory };
- i += 1;
- println!("allocation_start: {:?}", slab.allocation_start);
- }
- // let cell_ptr = unsafe { slab.alloc_cell() };
- // // Set the cell_ptr to a value
- // let cell_memory = CellMemory { metadata: 0, head: D(5), tail: D(23) };
- // unsafe { (*cell_ptr) = cell_memory };
- // let cell: Cell = unsafe { Cell::from_raw_pointer(cell_ptr) };
- // unsafe { assert_eq!(cell.head().as_raw(), 5) };
- }
- #[test]
- fn test_nounslab_modify() {
- let mut slab = NounSlab::new();
- slab.modify(|root| vec![D(0), D(tas!(b"bind")), root]);
- let mut test_slab = NounSlab::new();
- slab_noun_equality(
- &slab.root,
- &T(&mut test_slab, &[D(0), D(tas!(b"bind")), D(0)]),
- );
- // let peek_res = unsafe { bind_slab.root_owned() };
- // let bind_noun = T(&mut bind_slab, &[D(pid), D(tas!(b"bind")), peek_res]);
- }
- // // This test _should_ fail under Miri
- // #[test]
- // #[should_panic(expected = "error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory")]
- // fn test_raw_alloc() {
- // let layout = Layout::array::<u64>(512).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
- // let slab = unsafe { NounSlab::raw_alloc(layout) };
- // assert!(!slab.is_null());
- // // cast doesn't hide it from Miri
- // let new_slab_u64 = slab as *mut u64;
- // let _huh = unsafe { *new_slab_u64 };
- // unsafe { std::alloc::dealloc(slab, layout) };
- // }
- }
|