#![no_std]
#![warn(unsafe_op_in_unsafe_fn)]
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![warn(clippy::cargo)]
use core::alloc::Layout;
use core::cell::Cell;
use core::ptr::{addr_of_mut, NonNull};
extern crate alloc;
#[cfg(feature = "bumpalo")]
pub mod bumpalo;
pub mod fallback;
#[cfg(test)]
mod tests;
pub trait ArenaAlloc {
type Error;
fn try_alloc_layout(&self, layout: Layout) -> Result<NonNull<u8>, Self::Error>;
}
struct Header {
previous: Option<NonNull<Header>>,
finalizer: unsafe fn(NonNull<Header>),
#[cfg(debug_assertions)]
layout: Layout,
}
impl Header {
unsafe fn ptr<T>(this: NonNull<Self>) -> *mut T {
#[cfg(debug_assertions)]
unsafe {
let this = this.as_ref();
assert_eq!(
this.layout,
Layout::new::<T>(),
"inconsistent memory layout"
);
}
let ptr = this.as_ptr().cast::<(Self, T)>();
let ptr: *mut T = unsafe { addr_of_mut!((*ptr).1) };
#[cfg(debug_assertions)]
unsafe {
let this = this.as_ref();
assert!(
(ptr as usize) & (this.layout.align() - 1) == 0,
"not aligned pointer"
);
}
ptr
}
unsafe fn drop_finalizer<T>(this: NonNull<Self>) {
unsafe {
Self::ptr::<T>(this).drop_in_place();
}
}
fn finalize(header: NonNull<Self>) {
unsafe {
let dropper = header.as_ref().finalizer;
dropper(header);
}
}
}
#[cfg(feature = "bumpalo")]
type Alloc = ::bumpalo::Bump;
#[cfg(not(feature = "bumpalo"))]
type Alloc = fallback::LeakingAlloc;
#[derive(Default)]
pub struct Rodeo<A: ArenaAlloc> {
allocator: A,
last: Cell<Option<NonNull<Header>>>,
}
impl Rodeo<Alloc> {
#[must_use]
pub fn new() -> Self {
Self {
allocator: Alloc::default(),
last: Cell::default(),
}
}
}
impl<A> Rodeo<A>
where
A: ArenaAlloc,
{
#[must_use]
pub fn with_allocator(allocator: A) -> Self {
Self {
allocator,
last: Cell::default(),
}
}
pub fn alloc<T>(&self, value: T) -> &mut T {
let Ok(ref_mut) = self.try_alloc(value) else { oom() };
ref_mut
}
pub fn try_alloc<T>(&self, value: T) -> Result<&mut T, A::Error> {
if core::mem::needs_drop::<T>() {
self.try_alloc_with_drop(value)
} else {
self.try_alloc_without_drop(value)
}
}
fn try_alloc_without_drop<T>(&self, value: T) -> Result<&mut T, A::Error> {
let layout = Layout::new::<T>();
let mut ptr = self.allocator.try_alloc_layout(layout)?.cast::<T>();
unsafe {
ptr.as_ptr().write(value);
Ok(ptr.as_mut())
}
}
fn try_alloc_with_drop<T>(&self, value: T) -> Result<&mut T, A::Error> {
let mut header = Header {
previous: None,
finalizer: Header::drop_finalizer::<T>,
#[cfg(debug_assertions)]
layout: Layout::new::<T>(),
};
let layout = Layout::new::<(Header, T)>();
let raw = self.allocator.try_alloc_layout(layout)?;
header.previous = self.last.take();
let ptr = raw.cast::<(Header, T)>().as_ptr();
unsafe {
ptr.write((header, value));
}
let header_ptr = unsafe { NonNull::new_unchecked(addr_of_mut!((*ptr).0)) };
self.last.set(Some(header_ptr));
Ok(unsafe { &mut (*ptr).1 })
}
pub const fn allocator(&self) -> &A {
&self.allocator
}
}
#[inline(never)]
#[cold]
fn oom() -> ! {
panic!("out of memory")
}
impl<A: ArenaAlloc> Drop for Rodeo<A> {
fn drop(&mut self) {
let mut current = self.last.get();
while let Some(header) = current {
Header::finalize(header);
current = unsafe { header.as_ref().previous };
}
}
}
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
extern "C" {}
pub const HEADER_LAYOUT: Layout = Layout::new::<Header>();