Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "toolshed"
version = "0.4.0"
version = "0.4.1"
authors = ["maciejhirsz <maciej.hirsz@gmail.com>"]
license = "MIT/Apache-2.0"
description = "Arena allocator and a handful of useful data structures"
Expand Down
134 changes: 101 additions & 33 deletions src/arena.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -16,8 +19,57 @@ pub struct Arena {
offset: Cell<usize>
}

/// 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();
Expand All @@ -31,55 +83,53 @@ 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::<T>()) as *mut T)
pub fn alloc_uninitialized<'arena, T: Sized + Copy>(&'arena self) -> Uninitialized<'arena, T> {
Uninitialized {
pointer: unsafe { &mut *(self.require(size_of::<T>()) 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::<usize>() - (val.len() % size_of::<usize>());
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::<T>()) 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()))
}
}

/// Allocate an `&str` slice onto the arena as null terminated C-style string.
/// 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);

Expand All @@ -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());

Expand Down Expand Up @@ -128,10 +178,9 @@ impl Arena {
return self.alloc_bytes(size);
}

// This should also be optimized away.
let size = match size % size_of::<usize>() {
0 => size,
n => size + n
n => size + (size_of::<usize>() - n),
};

let offset = self.offset.get();
Expand Down Expand Up @@ -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);
Expand All @@ -226,6 +275,25 @@ mod test {
assert_eq!(arena.store.get_mut()[1].capacity(), size_of::<usize>() * 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();
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 7 additions & 7 deletions src/list.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -190,8 +190,8 @@ pub struct GrowableList<'arena, T>
where
T: 'arena,
{
first: CopyCell<Option<&'arena ListNode<'arena, T>>>,
last: CopyCell<Option<&'arena ListNode<'arena, T>>>,
first: CopyCell<Option<&'arena ListNode<'arena, T>>>,
}

impl<'arena, T> GrowableList<'arena, T>
Expand Down Expand Up @@ -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>>,
}

Expand All @@ -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),
}
}

Expand All @@ -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))
}
}
}
Expand Down