use core::{alloc::Layout, ptr, ptr::NonNull};
macro_rules! const_try_result {
($x:expr) => {
match $x {
Ok(x) => x,
Err(x) => return Err(x),
}
};
}
pub struct ConstAllocator {
ref_count: *mut usize,
}
impl ConstAllocator {
#[inline]
pub const fn with<F, R>(f: F) -> R
where
F: ~const FnOnce(&ConstAllocator) -> R,
{
Self::with_inner(f)
}
#[inline]
pub const fn with_parametric<P, F, R>(p: P, f: F) -> R
where
F: ~const FnOnce(P, &ConstAllocator) -> R,
{
Self::with_inner((p, f))
}
const fn with_inner<F: ~const FnOnceConstAllocator>(f: F) -> F::Output {
struct RefCountGuard(usize);
impl const Drop for RefCountGuard {
fn drop(&mut self) {
if self.0 != 0 {
panic!(
"there are outstanding allocations or \
allocator references"
);
}
}
}
let mut ref_count = RefCountGuard(1);
let ref_count = (&mut ref_count.0) as *mut _;
let this = Self { ref_count };
f.call(&this)
}
}
#[const_trait]
trait FnOnceConstAllocator {
type Output;
fn call(self, allocator: &ConstAllocator) -> Self::Output;
}
impl<T: ~const FnOnce(&ConstAllocator) -> Output, Output> const FnOnceConstAllocator for T {
type Output = Output;
fn call(self, allocator: &ConstAllocator) -> Self::Output {
self(allocator)
}
}
impl<P, T: ~const FnOnce(P, &ConstAllocator) -> Output, Output> const FnOnceConstAllocator
for (P, T)
{
type Output = Output;
fn call(self, allocator: &ConstAllocator) -> Self::Output {
(self.1)(self.0, allocator)
}
}
impl const Clone for ConstAllocator {
fn clone(&self) -> Self {
unsafe { *self.ref_count += 1 };
Self {
ref_count: self.ref_count,
}
}
}
impl const Drop for ConstAllocator {
fn drop(&mut self) {
unsafe { *self.ref_count -= 1 };
}
}
#[derive(Clone, Copy)]
pub struct AllocError;
#[const_trait]
pub unsafe trait Allocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let ptr = const_try_result!(self.allocate(layout));
unsafe { ptr.as_ptr().cast::<u8>().write_bytes(0, ptr.len()) }
Ok(ptr)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(
new_layout.size() >= old_layout.size(),
"`new_layout.size()` must be greater than or equal to `old_layout.size()`"
);
let new_ptr = const_try_result!(self.allocate(new_layout));
unsafe {
ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), old_layout.size());
self.deallocate(ptr, old_layout);
}
Ok(new_ptr)
}
unsafe fn grow_zeroed(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(
new_layout.size() >= old_layout.size(),
"`new_layout.size()` must be greater than or equal to `old_layout.size()`"
);
let new_ptr = const_try_result!(self.allocate_zeroed(new_layout));
unsafe {
ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), old_layout.size());
self.deallocate(ptr, old_layout);
}
Ok(new_ptr)
}
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(
new_layout.size() <= old_layout.size(),
"`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
);
let new_ptr = const_try_result!(self.allocate(new_layout));
unsafe {
ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr().cast(), new_layout.size());
self.deallocate(ptr, old_layout);
}
Ok(new_ptr)
}
fn by_ref(&self) -> &Self
where
Self: Sized,
{
self
}
}
unsafe impl<A> const Allocator for &A
where
A: ~const Allocator + ?Sized,
{
#[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
(**self).allocate(layout)
}
#[inline]
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
(**self).allocate_zeroed(layout)
}
#[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { (**self).deallocate(ptr, layout) }
}
#[inline]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
unsafe { (**self).grow(ptr, old_layout, new_layout) }
}
#[inline]
unsafe fn grow_zeroed(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
unsafe { (**self).grow_zeroed(ptr, old_layout, new_layout) }
}
#[inline]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
unsafe { (**self).shrink(ptr, old_layout, new_layout) }
}
}
unsafe impl const Allocator for ConstAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let ptr = unsafe { core::intrinsics::const_allocate(layout.size(), layout.align()) };
if let Some(ptr) = NonNull::new(ptr) {
unsafe { *self.ref_count += 1 };
Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
} else {
Err(AllocError)
}
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { core::intrinsics::const_deallocate(ptr.as_ptr(), layout.size(), layout.align()) };
unsafe { *self.ref_count -= 1 };
}
}