#![expect(clippy::undocumented_unsafe_blocks)]
#![allow(dead_code)]
use std::{
alloc::Layout,
cmp, fmt, mem,
ptr::{self, NonNull},
};
#[cold]
#[inline(never)]
pub fn handle_alloc_error(layout: Layout) -> ! {
panic!("encountered allocation error: {layout:?}")
}
#[derive(Debug)]
pub struct Excess(pub NonNull<u8>, pub usize);
fn size_align<T>() -> (usize, usize) {
(mem::size_of::<T>(), mem::align_of::<T>())
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AllocErr;
impl fmt::Display for AllocErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("memory allocation failed")
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CannotReallocInPlace;
impl CannotReallocInPlace {
#[expect(clippy::unused_self, reason = "part of public API")]
pub fn description(&self) -> &'static str {
"cannot reallocate allocator's memory in place"
}
}
impl fmt::Display for CannotReallocInPlace {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description())
}
}
pub unsafe trait Alloc {
unsafe fn alloc(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr>;
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout);
#[inline]
fn usable_size(&self, layout: &Layout) -> (usize, usize) {
(layout.size(), layout.size())
}
unsafe fn realloc(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
) -> Result<NonNull<u8>, AllocErr> {
let old_size = layout.size();
#[expect(clippy::collapsible_else_if)]
if new_size >= old_size {
if unsafe { self.grow_in_place(ptr, layout, new_size) }.is_ok() {
return Ok(ptr);
}
} else {
if unsafe { self.shrink_in_place(ptr, layout, new_size) }.is_ok() {
return Ok(ptr);
}
}
let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
let result = unsafe { self.alloc(new_layout) };
if let Ok(new_ptr) = result {
unsafe {
ptr::copy_nonoverlapping(
ptr.as_ptr(),
new_ptr.as_ptr(),
cmp::min(old_size, new_size),
);
self.dealloc(ptr, layout);
}
}
result
}
unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<NonNull<u8>, AllocErr> {
let size = layout.size();
let res = unsafe { self.alloc(layout) };
if let Ok(ptr) = res {
unsafe { ptr::write_bytes(ptr.as_ptr(), 0, size) };
}
res
}
unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr> {
let usable_size = self.usable_size(&layout);
unsafe { self.alloc(layout) }.map(|ptr| Excess(ptr, usable_size.1))
}
unsafe fn realloc_excess(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
) -> Result<Excess, AllocErr> {
let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
let usable_size = self.usable_size(&new_layout);
unsafe { self.realloc(ptr, layout, new_size) }.map(|ptr| Excess(ptr, usable_size.1))
}
unsafe fn grow_in_place(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
) -> Result<(), CannotReallocInPlace> {
let _ = ptr; debug_assert!(new_size >= layout.size());
let (_l, u) = self.usable_size(&layout);
if new_size <= u { Ok(()) } else { Err(CannotReallocInPlace) }
}
unsafe fn shrink_in_place(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
) -> Result<(), CannotReallocInPlace> {
let _ = ptr; debug_assert!(new_size <= layout.size());
let (l, _u) = self.usable_size(&layout);
if l <= new_size { Ok(()) } else { Err(CannotReallocInPlace) }
}
fn alloc_one<T>(&mut self) -> Result<NonNull<T>, AllocErr>
where
Self: Sized,
{
let k = Layout::new::<T>();
if k.size() > 0 { unsafe { self.alloc(k).map(NonNull::cast) } } else { Err(AllocErr) }
}
unsafe fn dealloc_one<T>(&mut self, ptr: NonNull<T>)
where
Self: Sized,
{
let k = Layout::new::<T>();
if k.size() > 0 {
unsafe { self.dealloc(ptr.cast(), k) };
}
}
fn alloc_array<T>(&mut self, n: usize) -> Result<NonNull<T>, AllocErr>
where
Self: Sized,
{
match Layout::array::<T>(n) {
Ok(layout) if layout.size() > 0 => unsafe { self.alloc(layout).map(NonNull::cast) },
_ => Err(AllocErr),
}
}
unsafe fn realloc_array<T>(
&mut self,
ptr: NonNull<T>,
n_old: usize,
n_new: usize,
) -> Result<NonNull<T>, AllocErr>
where
Self: Sized,
{
match (Layout::array::<T>(n_old), Layout::array::<T>(n_new)) {
(Ok(ref k_old), Ok(ref k_new)) if k_old.size() > 0 && k_new.size() > 0 => {
debug_assert_eq!(k_old.align(), k_new.align());
unsafe { self.realloc(ptr.cast(), *k_old, k_new.size()) }.map(NonNull::cast)
}
_ => Err(AllocErr),
}
}
unsafe fn dealloc_array<T>(&mut self, ptr: NonNull<T>, n: usize) -> Result<(), AllocErr>
where
Self: Sized,
{
match Layout::array::<T>(n) {
Ok(k) if k.size() > 0 => {
unsafe { self.dealloc(ptr.cast(), k) };
Ok(())
}
_ => Err(AllocErr),
}
}
}