use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::mem::{self, MaybeUninit};
use core::ptr::{NonNull, null_mut};
#[cfg(not(feature = "polyfill"))]
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "polyfill")]
use atomic_polyfill::{AtomicUsize, Ordering};
use crate::leaked::LeakBox;
use alloc_traits::{AllocTime, LocalAlloc, NonZeroLayout};
pub struct Bump<T> {
consumed: AtomicUsize,
storage: UnsafeCell<MaybeUninit<T>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct LeakError<T> {
val: T,
failure: Failure,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Level(pub(crate) usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Allocation<'a, T=u8> {
pub ptr: NonNull<T>,
pub lifetime: AllocTime<'a>,
pub level: Level,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Failure {
Exhausted,
Mismatch {
observed: Level,
},
}
impl<T> Bump<T> {
pub const fn uninit() -> Self {
Bump {
consumed: AtomicUsize::new(0),
storage: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub fn zeroed() -> Self {
Bump {
consumed: AtomicUsize::new(0),
storage: UnsafeCell::new(MaybeUninit::zeroed()),
}
}
pub const fn new(storage: T) -> Self {
Bump {
consumed: AtomicUsize::new(0),
storage: UnsafeCell::new(MaybeUninit::new(storage)),
}
}
pub fn reset(&mut self) {
*self.consumed.get_mut() = 0;
}
pub fn alloc(&self, layout: Layout) -> Option<NonNull<u8>> {
Some(self.try_alloc(layout)?.ptr)
}
pub fn alloc_at(&self, layout: Layout, level: Level)
-> Result<Allocation, Failure>
{
let Allocation { ptr, lifetime, level } = self.try_alloc_at(layout, level.0)?;
Ok(Allocation {
ptr: ptr.cast(),
lifetime,
level,
})
}
pub fn get_layout(&self, layout: Layout)
-> Option<Allocation<'_>>
{
self.try_alloc(layout)
}
pub fn get_layout_at(&self, layout: Layout, at: Level)
-> Result<Allocation<'_>, Failure>
{
self.try_alloc_at(layout, at.0)
}
pub fn get<V>(&self) -> Option<Allocation<V>> {
if mem::size_of::<V>() == 0 {
return Some(self.zst_fake_alloc());
}
let layout = Layout::new::<V>();
let Allocation { ptr, lifetime, level, } = self.try_alloc(layout)?;
Some(Allocation {
ptr: ptr.cast(),
lifetime,
level,
})
}
pub fn get_at<V>(&self, level: Level) -> Result<Allocation<V>, Failure> {
if mem::size_of::<V>() == 0 {
let fake = self.zst_fake_alloc();
if fake.level != level {
return Err(Failure::Mismatch {
observed: fake.level,
});
}
return Ok(fake);
}
let layout = Layout::new::<V>();
let Allocation { ptr, lifetime, level, } = self.try_alloc_at(layout, level.0)?;
Ok(Allocation {
ptr: ptr.cast(),
lifetime,
level,
})
}
pub fn leak_box<V>(&self, val: V) -> Option<LeakBox<'_, V>> {
let Allocation { ptr, lifetime, .. } = self.get::<V>()?;
Some(unsafe {
LeakBox::new_from_raw_non_null(ptr, val, lifetime)
})
}
pub fn leak_box_at<V>(&self, val: V, level: Level) -> Result<LeakBox<'_, V>, Failure> {
let Allocation { ptr, lifetime, .. } = self.get_at::<V>(level)?;
Ok(unsafe {
LeakBox::new_from_raw_non_null(ptr, val, lifetime)
})
}
pub fn level(&self) -> Level {
Level(self.consumed.load(Ordering::SeqCst))
}
fn try_alloc(&self, layout: Layout)
-> Option<Allocation<'_>>
{
let mut consumed = 0;
loop {
match self.try_alloc_at(layout, consumed) {
Ok(alloc) => return Some(alloc),
Err(Failure::Exhausted) => return None,
Err(Failure::Mismatch{ observed }) => consumed = observed.0,
}
}
}
fn try_alloc_at(&self, layout: Layout, expect_consumed: usize)
-> Result<Allocation<'_>, Failure>
{
assert!(layout.size() > 0);
let length = mem::size_of::<T>();
let base_ptr = self.storage.get()
as *mut T
as *mut u8;
let alignment = layout.align();
let requested = layout.size();
assert!(expect_consumed <= length);
let available = length.checked_sub(expect_consumed).unwrap();
let ptr_to = base_ptr.wrapping_add(expect_consumed);
let offset = ptr_to.align_offset(alignment);
if requested > available.saturating_sub(offset) {
return Err(Failure::Exhausted); }
assert!(offset < available);
let at_aligned = expect_consumed.checked_add(offset).unwrap();
let new_consumed = at_aligned.checked_add(requested).unwrap();
assert!(new_consumed <= length);
assert!(at_aligned < length);
match self.bump(expect_consumed, new_consumed) {
Ok(()) => (),
Err(observed) => {
return Err(Failure::Mismatch { observed: Level(observed) });
},
}
let aligned = unsafe {
(base_ptr as *mut u8).add(at_aligned)
};
Ok(Allocation {
ptr: NonNull::new(aligned).unwrap(),
lifetime: AllocTime::default(),
level: Level(new_consumed),
})
}
pub fn leak<V>(&self, val: V) -> Result<&mut V, LeakError<V>> {
match self.get::<V>() {
Some(alloc) => Ok(unsafe { alloc.leak(val) }),
None => Err(LeakError::new(val, Failure::Exhausted)),
}
}
pub fn leak_at<V>(&self, val: V, level: Level)
-> Result<(&mut V, Level), LeakError<V>>
{
let alloc = match self.get_at::<V>(level) {
Ok(alloc) => alloc,
Err(err) => return Err(LeakError::new(val, err)),
};
let level = alloc.level;
let mutref = unsafe { alloc.leak(val) };
Ok((mutref, level))
}
fn zst_fake_alloc<Z>(&self) -> Allocation<'_, Z> {
Allocation::for_zst(self.level())
}
fn bump(&self, expect_consumed: usize, new_consumed: usize) -> Result<(), usize> {
assert!(expect_consumed <= new_consumed);
assert!(new_consumed <= mem::size_of::<T>());
self.consumed.compare_exchange(
expect_consumed,
new_consumed,
Ordering::SeqCst,
Ordering::SeqCst,
).map(drop)
}
}
impl<'alloc, T> Allocation<'alloc, T> {
pub unsafe fn leak(self, val: T) -> &'alloc mut T {
core::ptr::write(self.ptr.as_ptr(), val);
&mut *self.ptr.as_ptr()
}
pub unsafe fn boxed(self, val: T) -> LeakBox<'alloc, T> {
core::ptr::write(self.ptr.as_ptr(), val);
LeakBox::from_raw(self.ptr.as_ptr())
}
pub unsafe fn uninit(self) -> &'alloc mut MaybeUninit<T> {
&mut *self.ptr.cast().as_ptr()
}
pub(crate) fn for_zst(level: Level) -> Self {
assert!(mem::size_of::<T>() == 0);
let alloc: &[T; 0] = &[];
Allocation {
ptr: NonNull::from(alloc).cast(),
lifetime: AllocTime::default(),
level: level,
}
}
}
impl<T> LeakError<T> {
fn new(val: T, failure: Failure) -> Self {
LeakError { val, failure, }
}
pub fn kind(&self) -> Failure {
self.failure
}
pub fn into_inner(self) -> T {
self.val
}
}
unsafe impl<T> Sync for Bump<T> { }
unsafe impl<T> GlobalAlloc for Bump<T> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
Bump::alloc(self, layout)
.map(NonNull::as_ptr)
.unwrap_or_else(null_mut)
}
unsafe fn realloc(
&self,
ptr: *mut u8,
current: Layout,
new_size: usize,
) -> *mut u8 {
let current = NonZeroLayout::from_layout(current.into()).unwrap();
let new_size = core::num::NonZeroUsize::new_unchecked(new_size);
let target = match layout_reallocated(current, new_size) {
Some(target) => target,
None => return core::ptr::null_mut(),
};
let fake = alloc_traits::Allocation {
ptr: NonNull::new_unchecked(ptr),
layout: current,
lifetime: AllocTime::default(),
};
alloc_traits::LocalAlloc::realloc(self, fake, target)
.map(|alloc| alloc.ptr.as_ptr())
.unwrap_or_else(core::ptr::null_mut)
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
}
}
fn layout_reallocated(layout: NonZeroLayout, target: core::num::NonZeroUsize)
-> Option<NonZeroLayout>
{
let layout = Layout::from_size_align(target.get(), layout.align()).ok()?;
Some(NonZeroLayout::from_layout(layout.into()).unwrap())
}
unsafe impl<'alloc, T> LocalAlloc<'alloc> for Bump<T> {
fn alloc(&'alloc self, layout: NonZeroLayout)
-> Option<alloc_traits::Allocation<'alloc>>
{
let raw_alloc = Bump::get_layout(self, layout.into())?;
Some(alloc_traits::Allocation {
ptr: raw_alloc.ptr,
layout: layout,
lifetime: AllocTime::default(),
})
}
unsafe fn realloc(
&'alloc self,
alloc: alloc_traits::Allocation<'alloc>,
layout: NonZeroLayout,
) -> Option<alloc_traits::Allocation<'alloc>> {
if alloc.ptr.as_ptr() as usize % layout.align() == 0
&& alloc.layout.size() >= layout.size()
{
return Some(alloc_traits::Allocation {
ptr: alloc.ptr,
layout,
lifetime: alloc.lifetime,
});
}
let new_alloc = LocalAlloc::alloc(self, layout)?;
core::ptr::copy_nonoverlapping(
alloc.ptr.as_ptr(),
new_alloc.ptr.as_ptr(),
layout.size().min(alloc.layout.size()).into());
return Some(new_alloc);
}
unsafe fn dealloc(&'alloc self, _: alloc_traits::Allocation<'alloc>) {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zst_no_drop() {
#[derive(Debug)]
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("No instance of this should ever get dropped");
}
}
let alloc = Bump::<()>::uninit();
let _ = alloc.leak(PanicOnDrop).unwrap();
}
}