use std::cell::RefCell;
use std::{cmp, mem, ptr};
struct Block {
buffer: Vec<u8>,
size: usize,
}
impl Block {
fn new(size: usize) -> Block {
Block {
buffer: Vec::with_capacity(size),
size: 0,
}
}
unsafe fn reserve(&mut self, size: usize, align: usize) -> *mut u8 {
if self.has_room(size, align) {
let align_offset =
align_address(self.buffer.as_ptr().offset(self.size as isize), align);
let ptr = self.buffer
.as_mut_ptr()
.offset((self.size + align_offset) as isize);
self.size += size + align_offset;
ptr
} else {
ptr::null_mut()
}
}
fn has_room(&self, size: usize, align: usize) -> bool {
let ptr = unsafe { self.buffer.as_ptr().offset(self.size as isize) };
let align_offset = align_address(ptr, align);
self.buffer.capacity() - self.size >= size + align_offset
}
}
fn align_address(ptr: *const u8, align: usize) -> usize {
let addr = ptr as usize;
if addr % align != 0 {
align - addr % align
} else {
0
}
}
pub struct MemoryArena {
blocks: Vec<Block>,
block_size: usize,
}
impl MemoryArena {
pub fn new(block_size_mb: usize) -> MemoryArena {
let block_size = block_size_mb * 1024 * 1024;
MemoryArena {
blocks: vec![Block::new(block_size)],
block_size: block_size,
}
}
pub fn allocator(&mut self) -> Allocator {
Allocator {
arena: RefCell::new(self),
}
}
unsafe fn reserve(&mut self, size: usize, align: usize) -> *mut u8 {
for b in &mut self.blocks[..] {
if b.has_room(size, align) {
return b.reserve(size, align);
}
}
let new_block_size = cmp::max(self.block_size, size + align);
self.blocks.push(Block::new(new_block_size));
let b = &mut self.blocks.last_mut().unwrap();
b.reserve(size, align)
}
}
pub struct Allocator<'a> {
arena: RefCell<&'a mut MemoryArena>,
}
impl<'a> Allocator<'a> {
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
pub fn alloc_slice<T: Sized + Copy>(&self, len: usize) -> &mut [T] {
let mut arena = self.arena.borrow_mut();
let size = len * mem::size_of::<T>();
unsafe {
let ptr = arena.reserve(size, mem::align_of::<T>()) as *mut T;
std::slice::from_raw_parts_mut(ptr, len)
}
}
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
pub fn alloc<T: Sized + Copy>(&self, object: T) -> &mut T {
assert!(!mem::needs_drop::<T>());
let mut arena = self.arena.borrow_mut();
unsafe {
let ptr = arena.reserve(mem::size_of::<T>(), mem::align_of::<T>());
ptr::write(ptr as *mut T, object);
&mut *(ptr as *mut T)
}
}
}
impl<'a> Drop for Allocator<'a> {
fn drop(&mut self) {
let mut arena = self.arena.borrow_mut();
for b in &mut arena.blocks[..] {
b.size = 0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aligner() {
assert_eq!(align_address(4 as *const u8, 4), 0);
assert_eq!(align_address(5 as *const u8, 4), 3);
assert_eq!(align_address(17 as *const u8, 1), 0);
}
#[test]
fn block() {
let mut b = Block::new(16);
assert!(b.has_room(16, 1));
let a = unsafe { b.reserve(3, 1) };
let c = unsafe { b.reserve(4, 4) };
assert_eq!(c as usize - a as usize, 4);
assert_eq!(b.size, 8);
assert!(!b.has_room(32, 4));
let d = unsafe { b.reserve(32, 4) };
assert_eq!(d, ptr::null_mut());
}
#[test]
fn memory_arena() {
let mut arena = MemoryArena::new(1);
let a = unsafe { arena.reserve(1024, 4) };
assert_eq!(align_address(a, 4), 0);
assert_eq!(arena.blocks[0].size, 1024);
let two_mb = 2 * 1024 * 1024;
let b = unsafe { arena.reserve(two_mb, 32) };
assert_eq!(align_address(b, 32), 0);
assert_eq!(arena.blocks.len(), 2);
assert_eq!(arena.blocks[1].buffer.capacity(), two_mb + 32);
assert_eq!(arena.blocks[1].size, two_mb);
}
}