use core::{
alloc::{AllocError, Allocator, GlobalAlloc, Layout},
ffi::c_void,
ptr::{self, NonNull},
};
use gneiss_sys::{free, malloc, realloc};
const DANGLING: *mut u8 = ptr::without_provenance_mut(0xE000_0000);
pub struct PebbleAlloc;
impl PebbleAlloc {
fn alloc(layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
const _: () = assert!(size_of::<usize>() == size_of::<u32>());
let size = layout.size();
let align = layout.align();
if size == 0 {
return Ok(unsafe { NonNull::new_unchecked(DANGLING) }.cast_slice(0));
}
let ptr = if align <= 4 {
unsafe { NonNull::new(malloc(size as u32).cast()).ok_or(AllocError) }
} else {
let alloc_size = size + align;
let base_ptr = unsafe { malloc(alloc_size as u32) };
if base_ptr.is_null() {
return Err(AllocError);
}
let offset = match base_ptr.align_offset(align) {
0 => align,
usize::MAX => return Err(AllocError),
off => off,
};
#[allow(
clippy::cast_ptr_alignment,
reason = "ptr from malloc() is always at least align(4)"
)]
unsafe {
let aligned_ptr = base_ptr.add(offset);
aligned_ptr.cast::<*mut c_void>().sub(1).write(base_ptr);
NonNull::new(aligned_ptr.cast()).ok_or(AllocError)
}
}?;
Ok(ptr.cast_slice(size))
}
unsafe fn dealloc(ptr: NonNull<u8>, layout: Layout) {
if layout.size() == 0 {
return;
}
let align = layout.align();
if align <= 4 {
unsafe { free(ptr.as_ptr().cast()) }
} else {
unsafe {
#[allow(
clippy::cast_ptr_alignment,
reason = "ptr from malloc() is always at least align(4)"
)]
let base_ptr = ptr.cast::<*mut c_void>().sub(1).read();
free(base_ptr);
}
}
}
unsafe fn realloc(
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
) -> Result<NonNull<[u8]>, AllocError> {
let align = layout.align();
let ptr = if align <= 4 {
unsafe {
NonNull::new(realloc(ptr.as_ptr().cast(), new_size as u32).cast())
.ok_or(AllocError)?
.cast_slice(new_size)
}
} else {
unsafe {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
let new_ptr = Self::alloc(new_layout)?;
ptr::copy_nonoverlapping(
ptr.as_ptr(),
new_ptr.as_ptr().cast(),
new_size.min(layout.size()),
);
Self::dealloc(ptr, layout);
new_ptr
}
};
Ok(ptr)
}
}
unsafe impl GlobalAlloc for PebbleAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
PebbleAlloc::alloc(layout).map_or(ptr::null_mut(), |non_null| non_null.as_ptr().cast())
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { PebbleAlloc::dealloc(NonNull::new_unchecked(ptr), layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
unsafe {
PebbleAlloc::realloc(NonNull::new_unchecked(ptr), layout, new_size)
.map_or(ptr::null_mut(), |non_null| non_null.as_ptr().cast())
}
}
}
unsafe impl Allocator for PebbleAlloc {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
Self::alloc(layout)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { Self::dealloc(ptr, layout) }
}
}