use crate::backend::Backend;
use crate::ptr_list::PtrList;
use crate::split_memory_block;
use crate::{MAX_CACHE_SIZE, MEMORY_CHUNK_SIZE, MIN_CACHE_SIZE};
use core::alloc::{AllocErr, AllocRef, Layout};
use core::mem::size_of;
use core::ptr::NonNull;
use core::result::Result;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct LayoutBulkAllocator<'a, B: 'a + AllocRef> {
layout: Layout,
pool: PtrList,
to_free: PtrList,
backend: Backend<'a, B>,
}
impl<B> LayoutBulkAllocator<'static, B>
where
B: AllocRef + Default,
{
pub fn from_layout(layout: Layout) -> Self {
Self::check_layout(layout);
Self {
layout,
pool: Default::default(),
to_free: Default::default(),
backend: Default::default(),
}
}
}
impl<B: AllocRef> LayoutBulkAllocator<'static, B> {
pub fn from_layout_backend(layout: Layout, backend: B) -> Self {
Self::check_layout(layout);
Self {
layout,
pool: Default::default(),
to_free: Default::default(),
backend: Backend::from(backend),
}
}
}
impl<'a, B: 'a + AllocRef> LayoutBulkAllocator<'a, B> {
pub fn from_layout_mut_backend(layout: Layout, backend: &'a mut B) -> Self {
Self::check_layout(layout);
Self {
layout,
pool: Default::default(),
to_free: Default::default(),
backend: Backend::from(backend),
}
}
}
impl<B: AllocRef> Drop for LayoutBulkAllocator<'_, B> {
fn drop(&mut self) {
let barrier = AtomicBool::new(false);
barrier.load(Ordering::SeqCst);
while let Some(ptr) = self.to_free.pop() {
unsafe {
let ptr = ptr
.add(size_of::<PtrList>())
.sub(self.memory_chunk_layout().size());
self.backend
.dealloc(NonNull::new_unchecked(ptr), self.memory_chunk_layout());
}
}
}
}
unsafe impl<B: AllocRef> AllocRef for LayoutBulkAllocator<'_, B> {
fn alloc(&mut self, layout: Layout) -> Result<NonNull<[u8]>, AllocErr> {
if layout != self.layout {
self.backend.alloc(layout)
} else {
let size = core::cmp::max(MIN_CACHE_SIZE, layout.pad_to_align().size());
let mut ptr = self.pool.pop();
if ptr.is_none() {
let mut block = self.backend.alloc(self.memory_chunk_layout())?;
{
let first_size = self.memory_chunk_layout().size() - size_of::<PtrList>();
let (f, s) = split_memory_block(block, first_size);
debug_assert!(size_of::<PtrList>() <= s.len());
self.to_free.push(s.cast::<u8>());
block = f;
}
while size <= block.len() {
let (f, s) = split_memory_block(block, size);
block = s;
self.pool.push(f.cast::<u8>());
}
ptr = self.pool.pop();
}
unsafe {
let slice = core::slice::from_raw_parts(ptr.unwrap(), size);
Ok(From::from(slice))
}
}
}
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
if layout == self.layout {
self.pool.push(ptr);
} else {
self.backend.dealloc(ptr, layout);
}
}
}
impl<B: AllocRef> LayoutBulkAllocator<'_, B> {
fn memory_chunk_layout(&self) -> Layout {
Layout::from_size_align(MEMORY_CHUNK_SIZE, self.layout.align()).unwrap()
}
fn check_layout(layout: Layout) {
assert!(layout.size() <= MAX_CACHE_SIZE);
assert!(layout.align() <= MAX_CACHE_SIZE);
}
#[cfg(test)]
fn memory_chunk_count(&self) -> usize {
self.to_free.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{MAX_CACHE_SIZE, MIN_CACHE_SIZE};
use std::alloc::Global;
const SIZES: [usize; 10] = [
1,
2,
3,
4,
5,
MIN_CACHE_SIZE - 1,
MIN_CACHE_SIZE,
MIN_CACHE_SIZE + 1,
MAX_CACHE_SIZE - 1,
MAX_CACHE_SIZE,
];
const ALIGNS: [usize; 4] = [2, 4, MIN_CACHE_SIZE, MAX_CACHE_SIZE];
const LARGE_LAYOUTS: [Layout; 3] = unsafe {
[
Layout::from_size_align_unchecked(MAX_CACHE_SIZE + 1, MAX_CACHE_SIZE),
Layout::from_size_align_unchecked(MAX_CACHE_SIZE, 2 * MAX_CACHE_SIZE),
Layout::from_size_align_unchecked(MAX_CACHE_SIZE + 1, 2 * MAX_CACHE_SIZE),
]
};
#[test]
fn from_layout_constructor() {
let layout = Layout::from_size_align(35, 16).unwrap();
let _ = LayoutBulkAllocator::<'static, Global>::from_layout(layout);
}
#[test]
fn from_layout_backend_constructor() {
let layout = Layout::from_size_align(64, 32).unwrap();
let global = Default::default();
let _ = LayoutBulkAllocator::<'static, Global>::from_layout_backend(layout, global);
}
#[test]
fn from_layout_mut_backend_constructor() {
let layout = Layout::from_size_align(64, 32).unwrap();
let mut global = Default::default();
let _ = LayoutBulkAllocator::<'_, Global>::from_layout_mut_backend(layout, &mut global);
}
#[test]
fn alloc_and_dealloc_works() {
let build = |size, align| {
let layout = Layout::from_size_align(size, align).unwrap();
LayoutBulkAllocator::<'static, Global>::from_layout(layout)
};
let check = |size, align, alloc: &mut LayoutBulkAllocator<'static, Global>| {
let layout = Layout::from_size_align(size, align).unwrap();
let block = alloc.alloc(layout).unwrap();
assert!(layout.size() <= block.len());
let ptr = unsafe { block.as_ref().as_ptr() } as usize;
assert_eq!(0, ptr % layout.align());
unsafe {
alloc.dealloc(block.cast::<u8>(), layout);
}
};
for &s in &SIZES {
for &a in &ALIGNS {
let mut alloc = build(s, a);
for &s in &SIZES {
for &a in &ALIGNS {
check(s, a, &mut alloc);
}
}
for &l in &LARGE_LAYOUTS {
check(l.size(), l.align(), &mut alloc);
}
}
}
}
#[test]
fn chunk_count() {
let layout = Layout::from_size_align(35, 16).unwrap();
let mut alloc = LayoutBulkAllocator::<'static, Global>::from_layout(layout);
let size = core::cmp::max(MIN_CACHE_SIZE, layout.pad_to_align().size());
assert_eq!(0, alloc.memory_chunk_count());
let block = alloc.alloc(layout).unwrap();
assert_eq!(1, alloc.memory_chunk_count());
unsafe {
alloc.dealloc(block.cast::<u8>(), layout);
}
for _ in 0..1024 {
let block = alloc.alloc(layout).unwrap();
assert_eq!(1, alloc.memory_chunk_count());
unsafe {
alloc.dealloc(block.cast::<u8>(), layout);
}
}
let mut ptrs = Vec::new();
for i in 1..10 {
for _ in 0..((MEMORY_CHUNK_SIZE - size_of::<PtrList>()) / size) {
let block = alloc.alloc(layout).unwrap();
ptrs.push(block);
assert_eq!(i, alloc.memory_chunk_count());
}
}
let chunk_count = alloc.memory_chunk_count();
for &s in &SIZES {
for &a in &ALIGNS {
let layout_ = Layout::from_size_align(s, a).unwrap();
if layout == layout_ {
continue;
}
let block = alloc.alloc(layout_).unwrap();
assert_eq!(chunk_count, alloc.memory_chunk_count());
unsafe {
alloc.dealloc(block.cast::<u8>(), layout);
}
}
}
for &ptr in &ptrs {
unsafe {
alloc.dealloc(ptr.cast::<u8>(), layout);
}
assert_eq!(chunk_count, alloc.memory_chunk_count());
}
}
}