diff --git a/Cargo.toml b/Cargo.toml index 76db6d0..fa5e379 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "toolshed" -version = "0.4.0" +version = "0.4.1" authors = ["maciejhirsz "] license = "MIT/Apache-2.0" description = "Arena allocator and a handful of useful data structures" diff --git a/src/arena.rs b/src/arena.rs index c435b4b..e725484 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -1,3 +1,6 @@ +//! Module containing the `Arena` and `Uninitialized` structs. For convenience the +//! `Arena` is exported at the root of the crate. + use std::mem::size_of; use std::cell::Cell; @@ -16,8 +19,57 @@ pub struct Arena { offset: Cell } +/// A pointer to an uninitialized region of memory. +pub struct Uninitialized<'arena, T: 'arena> { + pointer: &'arena mut T, +} + +impl<'arena, T: 'arena> Uninitialized<'arena, T> { + /// Initialize the memory at the pointer with a given value. + #[inline] + pub fn init(self, value: T) -> &'arena T { + *self.pointer = value; + + &*self.pointer + } + + /// Get a reference to the pointer without writing to it. + /// + /// **Reading from this reference without calling `init` is undefined behavior.** + #[inline] + pub unsafe fn as_ref(&self) -> &'arena T { + &*(self.pointer as *const T) + } + + /// Convert the `Uninitialized` to a regular mutable reference. + /// + /// **Reading from this reference without calling `init` is undefined behavior.** + #[inline] + pub unsafe fn into_mut(self) -> &'arena mut T { + self.pointer + } + + /// Convert a raw pointer to an `Uninitialized`. This method is unsafe since it can + /// bind to arbitrary lifetimes. + #[inline] + pub unsafe fn from_raw(pointer: *mut T) -> Self { + Uninitialized { + pointer: &mut *pointer, + } + } +} + +impl<'arena, T: 'arena> From<&'arena mut T> for Uninitialized<'arena, T> { + #[inline] + fn from(pointer: &'arena mut T) -> Self { + Uninitialized { + pointer + } + } +} + impl Arena { - /// Create a new arena with a single preallocated 64KiB page + /// Create a new arena with a single preallocated 64KiB page. pub fn new() -> Self { let mut store = vec![Vec::with_capacity(ARENA_BLOCK)]; let ptr = store[0].as_mut_ptr(); @@ -31,47 +83,45 @@ impl Arena { /// Put the value onto the page of the arena and return a reference to it. #[inline] - pub fn alloc<'a, T: Sized + Copy>(&'a self, val: T) -> &'a T { - unsafe { - let ptr = self.alloc_uninitialized(); - *ptr = val; - &*ptr - } + pub fn alloc<'arena, T: Sized + Copy>(&'arena self, value: T) -> &'arena T { + self.alloc_uninitialized().init(value) } - /// Allocate enough bytes for the type `T`, then return a pointer to the memory. - /// Memory behind the pointer is uninitialized, can contain garbage and reading - /// from it is undefined behavior. + /// Allocate enough bytes for the type `T`, then return an `Uninitialized` pointer to the memory. #[inline] - pub unsafe fn alloc_uninitialized<'a, T: Sized + Copy>(&'a self) -> &'a mut T { - &mut *(self.require(size_of::()) as *mut T) + pub fn alloc_uninitialized<'arena, T: Sized + Copy>(&'arena self) -> Uninitialized<'arena, T> { + Uninitialized { + pointer: unsafe { &mut *(self.require(size_of::()) as *mut T) } + } } - /// Allocate an `&str` slice onto the arena and return a reference to it. This is - /// useful when the original slice has an undefined lifetime. + /// Allocate a slice of `T` slice onto the arena and return a reference to it. + /// This is useful when the original slice has an undefined lifetime. /// - /// Note: static slices (`&'static str`) can be safely used in place of arena-bound + /// Note: static slices (`&'static [T]`) can be safely used in place of arena-bound /// slices without having to go through this method. - pub fn alloc_str<'a>(&'a self, val: &str) -> &'a str { - let offset = self.offset.get(); - let alignment = size_of::() - (val.len() % size_of::()); - let cap = offset + val.len() + alignment; - - if cap > ARENA_BLOCK { - return self.alloc_string(val.into()); - } - - self.offset.set(cap); + pub fn alloc_slice<'arena, T: Copy>(&'arena self, val: &[T]) -> &'arena [T] { + let ptr = self.require(val.len() * size_of::()) as *mut T; unsafe { use std::ptr::copy_nonoverlapping; - use std::str::from_utf8_unchecked; use std::slice::from_raw_parts; - let ptr = self.ptr.get().offset(offset as isize); copy_nonoverlapping(val.as_ptr(), ptr, val.len()); + from_raw_parts(ptr, val.len()) + } + } + + /// Allocate an `&str` slice onto the arena and return a reference to it. This is + /// useful when the original slice has an undefined lifetime. + /// + /// Note: static slices (`&'static str`) can be safely used in place of arena-bound + /// slices without having to go through this method. + pub fn alloc_str<'arena>(&'arena self, val: &str) -> &'arena str { + unsafe { + use std::str::from_utf8_unchecked; - from_utf8_unchecked(from_raw_parts(ptr, val.len())) + from_utf8_unchecked(self.alloc_slice(val.as_bytes())) } } @@ -79,7 +129,7 @@ impl Arena { /// No checks are performed on the source and whether or not it already contains /// any nul bytes. While this does not create any memory issues, it assumes that /// the reader of the source can deal with malformed source. - pub fn alloc_str_with_nul<'a>(&'a self, val: &str) -> *const u8 { + pub fn alloc_str_with_nul<'arena>(&'arena self, val: &str) -> *const u8 { let len_with_zero = val.len() + 1; let ptr = self.require(len_with_zero); @@ -94,7 +144,7 @@ impl Arena { /// Pushes the `String` as it's own page onto the arena and returns a reference to it. /// This does not copy or reallocate the original `String`. - pub fn alloc_string<'a>(&'a self, val: String) -> &'a str { + pub fn alloc_string<'arena>(&'arena self, val: String) -> &'arena str { let len = val.len(); let ptr = self.alloc_vec(val.into_bytes()); @@ -128,10 +178,9 @@ impl Arena { return self.alloc_bytes(size); } - // This should also be optimized away. let size = match size % size_of::() { 0 => size, - n => size + n + n => size + (size_of::() - n), }; let offset = self.offset.get(); @@ -209,7 +258,7 @@ mod test { assert_eq!(arena.alloc(0u64), &0); assert_eq!(arena.alloc(42u64), &42); - unsafe { arena.alloc_uninitialized::<[usize; 1024 * 1024]>() }; + arena.alloc_uninitialized::<[usize; 1024 * 1024]>(); // Still writes to the first page assert_eq!(arena.offset.get(), 8 * 2); @@ -226,6 +275,25 @@ mod test { assert_eq!(arena.store.get_mut()[1].capacity(), size_of::() * 1024 * 1024); } + #[test] + fn alloc_slice() { + let arena = Arena::new(); + + assert_eq!(arena.alloc_slice(&[10u16, 20u16]), &[10u16, 20u16][..]); + assert_eq!(arena.offset.get(), 8); + } + + #[test] + fn aligns_slice_allocs() { + let arena = Arena::new(); + + assert_eq!(arena.alloc_slice(b"foo"), b"foo"); + assert_eq!(arena.offset.get(), 8); + + assert_eq!(arena.alloc_slice(b"doge to the moon!"), b"doge to the moon!"); + assert_eq!(arena.offset.get(), 32); + } + #[test] fn aligns_str_allocs() { let arena = Arena::new(); diff --git a/src/lib.rs b/src/lib.rs index eef0ddc..13ab9e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,7 +96,7 @@ pub mod cell; pub mod map; pub mod set; pub mod list; -mod arena; +pub mod arena; mod bloom; mod impl_partial_eq; mod impl_debug; diff --git a/src/list.rs b/src/list.rs index 23a78bc..29d5f6a 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,6 +1,6 @@ //! A linked list and auxiliary types that can be used with the `Arena`. -use Arena; +use arena::Arena; use cell::CopyCell; #[derive(Debug, PartialEq, Clone)] @@ -190,8 +190,8 @@ pub struct GrowableList<'arena, T> where T: 'arena, { - first: CopyCell>>, last: CopyCell>>, + first: CopyCell>>, } impl<'arena, T> GrowableList<'arena, T> @@ -247,7 +247,7 @@ pub struct ListBuilder<'arena, T: 'arena> where T: 'arena, { - first: CopyCell<&'arena ListNode<'arena, T>>, + first: &'arena ListNode<'arena, T>, last: CopyCell<&'arena ListNode<'arena, T>>, } @@ -258,14 +258,14 @@ where /// Create a new builder with the first element. #[inline] pub fn new(arena: &'arena Arena, first: T) -> Self { - let first = CopyCell::new(arena.alloc(ListNode { + let first = arena.alloc(ListNode { value: first, next: CopyCell::new(None) - })); + }); ListBuilder { first, - last: first, + last: CopyCell::new(first), } } @@ -290,7 +290,7 @@ where #[inline] pub fn as_list(&self) -> List<'arena, T> { List { - root: CopyCell::new(Some(self.first.get())) + root: CopyCell::new(Some(self.first)) } } }