use alloc::raw_vec::RawVec;
use alloc::allocator::Layout;
use core;
use std::cell::Cell;
pub struct DoubleEndedAllocator {
stack: RawVec<u8>,
current_offset_top: Cell<*mut u8>,
current_offset_bottom: Cell<*mut u8>,
}
impl DoubleEndedAllocator {
pub fn with_capacity(capacity: usize) -> Self {
unsafe {
let stack: RawVec<u8> = RawVec::with_capacity(capacity);
let current_offset_bottom = Cell::new(stack.ptr() as *mut u8);
let current_offset_top = Cell::new(stack.ptr().offset(capacity as isize) as *mut u8);
DoubleEndedAllocator {
stack,
current_offset_bottom,
current_offset_top,
}
}
}
pub fn stack(&self) -> &RawVec<u8> {
&self.stack
}
pub fn reset_bottom(&self) {
self.current_offset_bottom.set(self.stack.ptr());
}
pub fn reset_top(&self) {
unsafe {
let top_stack = self.stack().ptr().offset(self.stack().cap() as isize);
self.current_offset_top.set(top_stack);
}
}
pub fn alloc_bottom<T>(&self, value: T) -> &mut T {
let layout = Layout::new::<T>(); let offset = layout.align() + layout.size();
let old_stack_top = self.current_offset_bottom.get();
unsafe {
let unaligned_ptr = old_stack_top.offset(offset as isize) as usize;
let mask = layout.align() - 1;
let misalignment = unaligned_ptr & mask;
let adjustment = layout.align() - misalignment;
let aligned_ptr = (unaligned_ptr + adjustment) as *mut u8;
assert!((self.stack.ptr().offset_to(aligned_ptr).unwrap() as usize) < self.stack.cap() / 2);
self.current_offset_bottom.set(aligned_ptr);
core::ptr::write::<T>(old_stack_top as *mut T, value);
&mut *(old_stack_top as *mut T)
}
}
pub fn alloc_top<T>(&self, value: T) -> &mut T {
let layout = Layout::new::<T>(); let offset = layout.align() + layout.size();
println!("\nalignment: {}-byte alignment", layout.align());
println!("size: {}", layout.size());
println!("Total amount of memory to allocate: {} bytes", offset);
let old_stack_top = self.current_offset_top.get();
println!("address of the current stack top : {:?}", old_stack_top);
unsafe {
let bottom_top_offset = self.stack().ptr().offset_to(old_stack_top).unwrap();
let unaligned_ptr = self.stack().ptr().offset(bottom_top_offset - offset as isize) as usize;
println!("unaligned location: {:?}", unaligned_ptr as *mut u8);
let mask = layout.align() - 1;
println!("mask (alignment - 1): {:#X} ", mask);
let misalignment = unaligned_ptr & mask;
println!("misalignment (unaligned ptr addr |bitwise AND| mask): {:#X}", misalignment);
let adjustment = layout.align() - misalignment;
println!("adjustment (current alignment - misalignment): {:#X}", adjustment);
let aligned_ptr = (unaligned_ptr - adjustment) as *mut u8;
println!("aligned ptr (unaligned ptr addr + adjustment): {:?}", aligned_ptr);
assert!((self.stack.ptr().offset_to(aligned_ptr).unwrap() as usize) > (self.stack.cap() / 2));
self.current_offset_top.set(aligned_ptr);
println!("Real amount of memory allocated: {}", offset + adjustment);
core::ptr::write::<T>(aligned_ptr as *mut T, value);
&mut *(aligned_ptr as *mut T)
}
}
pub fn marker_top(&self) -> *mut u8 {
self.current_offset_top.get()
}
pub fn marker_bottom(&self) -> *mut u8 {
self.current_offset_bottom.get()
}
pub fn reset_to_marker_top(&self, marker: *mut u8) {
self.current_offset_top.set(marker);
}
pub fn reset_to_marker_bottom(&self, marker: *mut u8) {
self.current_offset_bottom.set(marker);
}
}
#[cfg(test)]
mod stack_allocator_test {
use super::*;
extern crate time;
#[test]
fn creation_with_right_capacity() {
let alloc = DoubleEndedAllocator::with_capacity(200);
let cap_used = alloc.stack.ptr().offset_to(alloc.current_offset_bottom.get()).unwrap() as usize;
let cap_remaining = (alloc.stack.cap() - cap_used) as isize;
assert_eq!(cap_used, 0);
assert_eq!(cap_remaining, 200);
}
#[test]
fn allocation_test() {
let alloc = DoubleEndedAllocator::with_capacity(200);
let _test_1_byte = alloc.alloc_bottom::<u8>(2);
let cap_used = alloc.stack.ptr().offset_to(alloc.current_offset_bottom.get()).unwrap() as usize;
let cap_remaining = (alloc.stack.cap() - cap_used) as isize;
assert_eq!(cap_used, 3); assert_eq!(cap_remaining, 197);
let _test_4_bytes = alloc.alloc_bottom::<u32>(60000);
let cap_used = alloc.stack.ptr().offset_to(alloc.current_offset_bottom.get()).unwrap() as usize;
let cap_remaining = (alloc.stack.cap() - cap_used) as isize;
assert_eq!(cap_used, 12); assert_eq!(cap_remaining, 188);
let _test_8_bytes = alloc.alloc_bottom::<u64>(100000);
let cap_used = alloc.stack.ptr().offset_to(alloc.current_offset_bottom.get()).unwrap() as usize;
let cap_remaining = (alloc.stack.cap() - cap_used) as isize;
assert_eq!(cap_used, 32); assert_eq!(cap_remaining, 168); }
#[test]
fn test_alloc_from_top() {
let alloc = DoubleEndedAllocator::with_capacity(200);
let top_stack = alloc.marker_top();
let _test_1_byte = alloc.alloc_top::<u8>(2);
let cap_used = (top_stack.offset_to(alloc.current_offset_top.get()).unwrap() * -1) as usize;
let cap_remaining = (alloc.stack.cap() - cap_used) as isize;
assert_eq!(cap_used, 3); assert_eq!(cap_remaining, 197); }
#[test]
fn test_reset() {
let alloc = DoubleEndedAllocator::with_capacity(200);
let test_1_byte = alloc.alloc_bottom::<u8>(2);
assert_eq!(test_1_byte, &mut 2);
alloc.reset_bottom();
let test_1_byte = alloc.alloc_bottom::<u8>(5);
assert_eq!(test_1_byte, &mut 5);
}
}