Skip to content

Commit

Permalink
Implement fn alloc_bytes for Arena and fn clear for both arena types
Browse files Browse the repository at this point in the history
Closes #18471
Closes #18261
  • Loading branch information
pczarn committed Jan 5, 2016
1 parent 5f1b1ec commit 7899699
Showing 1 changed file with 143 additions and 18 deletions.
161 changes: 143 additions & 18 deletions src/libarena/lib.rs
Expand Up @@ -46,6 +46,7 @@ use std::intrinsics;
use std::marker::{PhantomData, Send};
use std::mem;
use std::ptr;
use std::slice;

use alloc::heap;
use alloc::raw_vec::RawVec;
Expand Down Expand Up @@ -122,9 +123,8 @@ impl Chunk {
/// plain-old-data (`Copy` types) and means we don't need to waste time running
/// their destructors.
pub struct Arena<'longer_than_self> {
// The head is separated out from the list as a unbenchmarked
// microoptimization, to avoid needing to case on the list to access the
// head.
// The heads are separated out from the list as a unbenchmarked
// microoptimization, to avoid needing to case on the list to access a head.
head: RefCell<Chunk>,
copy_head: RefCell<Chunk>,
chunks: RefCell<Vec<Chunk>>,
Expand Down Expand Up @@ -329,6 +329,37 @@ impl<'longer_than_self> Arena<'longer_than_self> {
}
}
}

/// Allocates a slice of bytes of requested length. The bytes are not guaranteed to be zero
/// if the arena has previously been cleared.
///
/// # Panics
///
/// Panics if the requested length is too large and causes overflow.
pub fn alloc_bytes(&self, len: usize) -> &mut [u8] {
unsafe {
// Check for overflow.
self.copy_head.borrow().fill.get().checked_add(len).expect("length overflow");
let ptr = self.alloc_copy_inner(len, 1);
intrinsics::assume(!ptr.is_null());
slice::from_raw_parts_mut(ptr as *mut _, len)
}
}

/// Clears the arena. Deallocates all but the longest chunk which may be reused.
pub fn clear(&mut self) {
unsafe {
self.head.borrow().destroy();
self.head.borrow().fill.set(0);
self.copy_head.borrow().fill.set(0);
for chunk in self.chunks.borrow().iter() {
if !chunk.is_copy.get() {
chunk.destroy();
}
}
self.chunks.borrow_mut().clear();
}
}
}

#[test]
Expand Down Expand Up @@ -495,6 +526,45 @@ impl<T> TypedArena<T> {
}
}
}
/// Clears the arena. Deallocates all but the longest chunk which may be reused.
pub fn clear(&mut self) {
unsafe {
// Clear the last chunk, which is partially filled.
let mut chunks_borrow = self.chunks.borrow_mut();
let last_idx = chunks_borrow.len() - 1;
self.clear_last_chunk(&mut chunks_borrow[last_idx]);
// If `T` is ZST, code below has no effect.
for mut chunk in chunks_borrow.drain(..last_idx) {
let cap = chunk.storage.cap();
chunk.destroy(cap);
}
}
}

// Drops the contents of the last chunk. The last chunk is partially empty, unlike all other
// chunks.
fn clear_last_chunk(&self, last_chunk: &mut TypedArenaChunk<T>) {
// Determine how much was filled.
let start = last_chunk.start() as usize;
// We obtain the value of the pointer to the first uninitialized element.
let end = self.ptr.get() as usize;
// We then calculate the number of elements to be dropped in the last chunk,
// which is the filled area's length.
let diff = if mem::size_of::<T>() == 0 {
// `T` is ZST. It can't have a drop flag, so the value here doesn't matter. We get
// the number of zero-sized values in the last and only chunk, just out of caution.
// Recall that `end` was incremented for each allocated value.
end - start
} else {
(end - start) / mem::size_of::<T>()
};
// Pass that to the `destroy` method.
unsafe {
last_chunk.destroy(diff);
}
// Reset the chunk.
self.ptr.set(last_chunk.start());
}
}

impl<T> Drop for TypedArena<T> {
Expand All @@ -504,24 +574,14 @@ impl<T> Drop for TypedArena<T> {
// Determine how much was filled.
let mut chunks_borrow = self.chunks.borrow_mut();
let mut last_chunk = chunks_borrow.pop().unwrap();
let start = last_chunk.start() as usize;
let end = self.ptr.get() as usize;
let diff = if mem::size_of::<T>() == 0 {
// Avoid division by zero.
end - start
} else {
(end - start) / mem::size_of::<T>()
};

// Pass that to the `destroy` method.
last_chunk.destroy(diff);
// Destroy this chunk.
let _: RawVec<T> = mem::transmute(last_chunk);

// Drop the contents of the last chunk.
self.clear_last_chunk(&mut last_chunk);
// The last chunk will be dropped. Destroy all other chunks.
for chunk in chunks_borrow.iter_mut() {
let cap = chunk.storage.cap();
chunk.destroy(cap);
}
// RawVec handles deallocation of `last_chunk` and `self.chunks`.
}
}
}
Expand All @@ -533,6 +593,7 @@ mod tests {
extern crate test;
use self::test::Bencher;
use super::{Arena, TypedArena};
use std::rc::Rc;

#[allow(dead_code)]
struct Point {
Expand Down Expand Up @@ -667,10 +728,74 @@ mod tests {
}

#[test]
pub fn test_zero_sized() {
pub fn test_typed_arena_zero_sized() {
let arena = TypedArena::new();
for _ in 0..100000 {
arena.alloc(());
}
}

#[test]
pub fn test_arena_zero_sized() {
let arena = Arena::new();
for _ in 0..1000 {
for _ in 0..100 {
arena.alloc(|| ());
}
arena.alloc(|| Point {
x: 1,
y: 2,
z: 3,
});
}
}

#[test]
pub fn test_typed_arena_clear() {
let mut arena = TypedArena::new();
for _ in 0..10 {
arena.clear();
for _ in 0..10000 {
arena.alloc(Point {
x: 1,
y: 2,
z: 3,
});
}
}
}

#[test]
pub fn test_arena_clear() {
let mut arena = Arena::new();
for _ in 0..10 {
arena.clear();
for _ in 0..10000 {
arena.alloc(|| Point {
x: 1,
y: 2,
z: 3,
});
arena.alloc(|| Noncopy {
string: "hello world".to_string(),
array: vec![],
});
}
}
}

#[test]
pub fn test_arena_alloc_bytes() {
let arena = Arena::new();
for i in 0..10000 {
arena.alloc(|| Point {
x: 1,
y: 2,
z: 3,
});
for byte in arena.alloc_bytes(i % 42).iter_mut() {
*byte = i as u8;
}
}
}
}

0 comments on commit 7899699

Please sign in to comment.