#![no_std]
#![feature(allocator_api)]
#![feature(doc_auto_cfg)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::alloc::Global;
use core::alloc::{AllocError, Allocator, Layout};
use core::{cell::Cell, mem::size_of, ptr::NonNull};
unsafe fn next_multiple(size: usize, align: usize) -> usize {
let am = align - 1;
(size + am) & !am
}
#[cfg(test)]
#[test]
fn next_multiple_power_of_two() {
for size in 0..512 {
for align in [1, 2, 4, 8, 16, 32, 64] {
let nm = unsafe { next_multiple(size, align) };
assert_eq!(nm % align, 0);
let q = nm / align;
assert!(q * align >= size);
if q > 0 {
assert!((q - 1) * align < size);
}
}
}
}
pub struct BumpCar<
#[cfg(feature = "alloc")] A: Allocator = Global,
#[cfg(not(feature = "alloc"))] A: Allocator,
> {
pointer: NonNull<[u8]>,
position: Cell<usize>,
allocator: A,
}
impl<A: Allocator> BumpCar<A> {
#[allow(clippy::missing_panics_doc)]
pub fn new_in(capacity: usize, allocator: A) -> Result<Self, AllocError> {
if capacity > isize::MAX as _
|| unsafe { next_multiple(capacity, size_of::<usize>()) } > isize::MAX as _
{
return Err(AllocError);
}
let pointer = allocator
.allocate(Layout::from_size_align(capacity, core::mem::size_of::<usize>()).unwrap())?;
Ok(Self {
pointer,
position: Cell::new(0),
allocator,
})
}
pub fn capacity(&self) -> usize {
self.pointer.len()
}
pub fn remaining_capacity(&self) -> usize {
self.capacity() - self.position.get()
}
pub fn can_allocate(&self, layout: Layout) -> bool {
let closest_align = unsafe { next_multiple(self.position.get(), layout.align()) };
let Some(new_pos) = closest_align.checked_add(layout.size()) else {
return false;
};
new_pos <= self.pointer.len()
}
pub fn reset(&mut self) {
self.position.set(0);
}
pub fn checkpoint<'a>(&'a self) -> BumpCar<&'a BumpCar<A>> {
BumpCar::new_in(
self.remaining_capacity() - self.remaining_capacity() % size_of::<usize>(),
self,
)
.unwrap()
}
}
#[cfg(feature = "alloc")]
impl BumpCar {
pub fn new(capacity: usize) -> Result<Self, AllocError> {
Self::new_in(capacity, Global)
}
}
impl<A: Allocator> Drop for BumpCar<A> {
fn drop(&mut self) {
let ptr = self.pointer.cast::<u8>();
unsafe {
self.allocator.deallocate(
ptr,
Layout::from_size_align_unchecked(self.pointer.len(), size_of::<usize>()),
);
}
}
}
unsafe impl<A: Allocator> Allocator for &BumpCar<A> {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let closest_align = unsafe { next_multiple(self.position.get(), layout.align()) };
let new_pos = closest_align.checked_add(layout.size()).ok_or(AllocError)?;
if new_pos > self.pointer.len() {
return Err(AllocError);
}
let ptr = unsafe { self.pointer.as_ptr().cast::<u8>().add(closest_align) };
self.position.set(new_pos);
Ok(NonNull::slice_from_raw_parts(
unsafe { NonNull::new_unchecked(ptr) },
layout.size(),
))
}
unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) {}
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()`"
);
if old_layout.align() < new_layout.align() {
return Err(AllocError);
}
Ok(NonNull::slice_from_raw_parts(ptr, new_layout.size()))
}
}