use core::alloc::{Allocator, GlobalAlloc, Layout};
use core::cell::RefCell;
use core::convert::TryInto;
use core::ptr::NonNull;
use crate::interrupt::free;
use bare_metal::{CriticalSection, Mutex};
use super::bump_allocator::{BumpAllocator, StartEnd};
use super::SendNonNull;
struct Block {
size: usize,
next: Option<SendNonNull<Block>>,
}
impl Block {
pub fn either_layout(layout: Layout) -> Layout {
let block_layout = Layout::new::<Block>();
let aligned_to = layout
.align_to(block_layout.align())
.expect("too large allocation");
Layout::from_size_align(
block_layout.size().max(aligned_to.size()),
aligned_to.align(),
)
.expect("too large allocation")
.align_to(8)
.expect("too large allocation")
.pad_to_align()
}
}
struct BlockAllocatorState {
first_free_block: Option<SendNonNull<Block>>,
}
pub struct BlockAllocator {
inner_allocator: BumpAllocator,
state: Mutex<RefCell<BlockAllocatorState>>,
}
impl BlockAllocator {
pub(crate) const unsafe fn new(start: StartEnd) -> Self {
Self {
inner_allocator: BumpAllocator::new(start),
state: Mutex::new(RefCell::new(BlockAllocatorState {
first_free_block: None,
})),
}
}
#[doc(hidden)]
#[cfg(any(test, feature = "testing"))]
pub unsafe fn number_of_blocks(&self) -> u32 {
free(|key| {
let mut state = self.state.borrow(key).borrow_mut();
let mut count = 0;
let mut list_ptr = &mut state.first_free_block;
while let Some(mut current) = list_ptr {
count += 1;
list_ptr = &mut current.as_mut().next;
}
count
})
}
fn new_block(&self, layout: Layout, cs: CriticalSection) -> Option<NonNull<u8>> {
let overall_layout = Block::either_layout(layout);
self.inner_allocator.alloc_critical(overall_layout, cs)
}
unsafe fn normalise(&self) {
free(|key| {
let mut state = self.state.borrow(key).borrow_mut();
let mut list_ptr = &mut state.first_free_block;
while let Some(mut current) = list_ptr {
if let Some(next_elem) = current.as_mut().next {
let difference = next_elem
.as_ptr()
.cast::<u8>()
.offset_from(current.as_ptr().cast::<u8>());
let usize_difference: usize = difference
.try_into()
.expect("distances in alloc'd blocks must be positive");
if usize_difference == current.as_mut().size {
let current = current.as_mut();
let next = next_elem.as_ref();
current.size += next.size;
current.next = next.next;
continue;
}
}
list_ptr = &mut current.as_mut().next;
}
});
}
pub unsafe fn alloc(&self, layout: Layout) -> Option<NonNull<u8>> {
let full_layout = Block::either_layout(layout);
let (block_after_layout, block_after_layout_offset) = full_layout
.extend(Layout::new::<Block>().align_to(8).unwrap().pad_to_align())
.unwrap();
free(|key| {
let mut state = self.state.borrow(key).borrow_mut();
let mut current_block = state.first_free_block;
let mut list_ptr = &mut state.first_free_block;
while let Some(mut current) = current_block {
let block_to_examine = current.as_mut();
if block_to_examine.size == full_layout.size() {
*list_ptr = block_to_examine.next;
return Some(current.cast());
} else if block_to_examine.size >= block_after_layout.size() {
let split_block = Block {
size: block_to_examine.size - block_after_layout_offset,
next: block_to_examine.next,
};
let split_ptr = current
.as_ptr()
.cast::<u8>()
.add(block_after_layout_offset)
.cast();
*split_ptr = split_block;
*list_ptr = NonNull::new(split_ptr).map(SendNonNull);
return Some(current.cast());
}
current_block = block_to_examine.next;
list_ptr = &mut block_to_examine.next;
}
self.new_block(layout, key)
})
}
pub unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.dealloc_no_normalise(ptr, layout);
self.normalise();
}
pub unsafe fn dealloc_no_normalise(&self, ptr: *mut u8, layout: Layout) {
let new_layout = Block::either_layout(layout).pad_to_align();
free(|key| {
let mut state = self.state.borrow(key).borrow_mut();
let mut list_ptr = &mut state.first_free_block;
loop {
match list_ptr {
Some(mut current_block) => {
if current_block.as_ptr().cast() > ptr {
let new_block_content = Block {
size: new_layout.size(),
next: Some(current_block),
};
*ptr.cast() = new_block_content;
*list_ptr = NonNull::new(ptr.cast()).map(SendNonNull);
break;
}
list_ptr = &mut current_block.as_mut().next;
}
None => {
let new_block_content = Block {
size: new_layout.size(),
next: None,
};
*ptr.cast() = new_block_content;
*list_ptr = NonNull::new(ptr.cast()).map(SendNonNull);
break;
}
}
}
});
}
}
unsafe impl GlobalAlloc for BlockAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
match self.alloc(layout) {
None => core::ptr::null_mut(),
Some(p) => p.as_ptr(),
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
self.dealloc(ptr, layout);
}
}
unsafe impl Allocator for BlockAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
match unsafe { self.alloc(layout) } {
None => Err(core::alloc::AllocError),
Some(p) => Ok(unsafe {
NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut(
p.as_ptr(),
layout.size(),
))
}),
}
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
self.dealloc(ptr.as_ptr(), layout);
}
}