use crate::core_alloc::{AllocError, Allocator, Layout};
use core::cell::Cell;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use crate::{alloc_result, Arena};
use crate::fallback::C_ALLOCATOR;
const BUMP_MAX: usize = 512;
const BUMP_CHUNK: usize = 16 * 1024;
pub struct AstAllocState {
bump_cursor: usize,
spill: *const Arena,
owned_spill: Option<Arena>,
bump_chunk: [MaybeUninit<u8>; BUMP_CHUNK],
}
impl AstAllocState {
fn new_boxed() -> Box<Self> {
let mut boxed = Box::<Self>::new_uninit();
let p = boxed.as_mut_ptr();
unsafe {
(&raw mut (*p).bump_cursor).write(0);
(&raw mut (*p).spill).write(core::ptr::null_mut());
(&raw mut (*p).owned_spill).write(None);
boxed.assume_init()
}
}
#[inline]
pub fn reset(&mut self) {
self.bump_cursor = 0;
self.spill = core::ptr::null_mut();
self.owned_spill = None;
}
#[inline]
pub fn set_spill_heap(&mut self, arena: *const Arena) {
debug_assert!(
self.owned_spill.is_none(),
"AstAllocState: switching an owned spill heap to a borrowed one would strand its contents"
);
self.spill = arena;
}
#[inline]
fn bump_alloc(&mut self, size: usize, align: usize) -> Option<*mut u8> {
debug_assert!(size != 0 && size <= BUMP_MAX && align.is_power_of_two());
debug_assert!(self.bump_cursor <= BUMP_CHUNK);
let cur = unsafe {
self.bump_chunk
.as_mut_ptr()
.cast::<u8>()
.add(self.bump_cursor)
};
let remaining = BUMP_CHUNK - self.bump_cursor;
let pad = cur.align_offset(align);
if pad <= remaining && size <= remaining - pad {
unsafe {
let aligned = cur.add(pad);
self.bump_cursor += pad + size;
Some(aligned)
}
} else {
None
}
}
#[inline]
fn spill_handle(&mut self) -> *const Arena {
if !self.spill.is_null() {
return self.spill;
}
let owned = self.owned_spill.insert(Arena::new()) as *mut Arena;
self.spill = owned;
self.spill
}
}
#[cfg(bao_nightly)]
#[thread_local]
static AST_ALLOC: Cell<Option<Box<AstAllocState>>> = Cell::new(None);
#[cfg(not(bao_nightly))]
std::thread_local! {
static AST_ALLOC: Cell<Option<::core::mem::ManuallyDrop<Box<AstAllocState>>>> =
const { Cell::new(None) };
}
std::thread_local! {
static AST_ALLOC_SPARE: Cell<Option<Box<AstAllocState>>> = const { Cell::new(None) };
}
#[inline(always)]
fn with_active_state<R>(f: impl FnOnce(Option<&mut AstAllocState>) -> R) -> R {
#[cfg(bao_nightly)]
{
unsafe { f((*AST_ALLOC.as_ptr()).as_deref_mut()) }
}
#[cfg(not(bao_nightly))]
{
AST_ALLOC.with(|slot| {
f(unsafe {
(*slot.as_ptr()).as_mut().map(|md| {
let b: &mut Box<AstAllocState> = md;
&mut **b
})
})
})
}
}
#[inline]
pub fn acquire_state() -> Box<AstAllocState> {
AST_ALLOC_SPARE
.try_with(Cell::take)
.ok()
.flatten()
.unwrap_or_else(AstAllocState::new_boxed)
}
#[inline]
pub fn release_state(mut state: Box<AstAllocState>) {
state.reset();
drop(AST_ALLOC_SPARE.try_with(|slot| slot.replace(Some(state))));
}
#[inline]
pub fn swap_state(state: Option<Box<AstAllocState>>) -> Option<Box<AstAllocState>> {
#[cfg(bao_nightly)]
{
AST_ALLOC.replace(state)
}
#[cfg(not(bao_nightly))]
{
AST_ALLOC
.with(|slot| slot.replace(state.map(::core::mem::ManuallyDrop::new)))
.map(|mut md| unsafe { ::core::mem::ManuallyDrop::take(&mut md) })
}
}
#[inline]
pub fn active_state_id() -> *const AstAllocState {
#[cfg(bao_nightly)]
{
unsafe { (*AST_ALLOC.as_ptr()).as_deref() }.map_or(core::ptr::null(), core::ptr::from_ref)
}
#[cfg(not(bao_nightly))]
{
AST_ALLOC.with(|slot| {
unsafe {
(*slot.as_ptr()).as_ref().map(|md| {
let b: &Box<AstAllocState> = md;
&**b
})
}
.map_or(core::ptr::null(), core::ptr::from_ref)
})
}
}
#[inline]
pub fn reset_active_state() {
with_active_state(|state| {
if let Some(state) = state {
state.reset();
}
});
}
#[inline]
pub fn set_active_spill_heap(arena: *const Arena) {
with_active_state(|state| {
if let Some(state) = state {
state.set_spill_heap(arena);
}
});
}
pub struct DetachAstHeap(Option<Box<AstAllocState>>);
impl DetachAstHeap {
#[inline]
pub fn new() -> Self {
Self(swap_state(None))
}
}
impl Drop for DetachAstHeap {
#[inline]
fn drop(&mut self) {
let displaced = swap_state(self.0.take());
debug_assert!(
displaced.is_none(),
"AstAlloc scope installed during a DetachAstHeap window was not uninstalled"
);
}
}
pub struct ScopedAstAlloc {
prev: Option<Box<AstAllocState>>,
}
impl ScopedAstAlloc {
#[inline]
pub fn with_spill(spill_arena: *const Arena) -> Self {
let mut state = acquire_state();
state.set_spill_heap(spill_arena);
Self {
prev: swap_state(Some(state)),
}
}
#[inline]
pub fn new() -> Self {
Self {
prev: swap_state(Some(acquire_state())),
}
}
#[inline]
pub fn take_state(self) -> Option<Box<AstAllocState>> {
let mut this = core::mem::ManuallyDrop::new(self);
let installed = swap_state(this.prev.take());
debug_assert!(
installed.is_some(),
"ScopedAstAlloc state was uninstalled by someone else"
);
installed
}
}
impl Default for ScopedAstAlloc {
fn default() -> Self {
Self::new()
}
}
impl Drop for ScopedAstAlloc {
#[inline]
fn drop(&mut self) {
match swap_state(self.prev.take()) {
Some(state) => release_state(state),
None => debug_assert!(
false,
"ScopedAstAlloc state was uninstalled by someone else"
),
}
}
}
#[derive(Clone, Copy, Default)]
pub struct AstAlloc;
#[cfg(bao_nightly)]
pub type AstVec<T> = alloc::vec::Vec<T, AstAlloc>;
#[cfg(not(bao_nightly))]
pub type AstVec<T> = crate::core_alloc::AllocVec<T, AstAlloc>;
#[cfg(bao_nightly)]
pub type AstBox<T> = alloc::boxed::Box<T, AstAlloc>;
#[cfg(not(bao_nightly))]
pub type AstBox<T> = crate::core_alloc::AllocBox<T, AstAlloc>;
#[inline(always)]
fn heap_alloc(layout: Layout) -> *mut u8 {
with_active_state(|state| match state {
None => C_ALLOCATOR
.raw_alloc(
layout.size(),
crate::Alignment::from_byte_units(layout.align()),
0,
)
.unwrap_or_else(core::ptr::null_mut),
Some(state) => {
if layout.size() != 0 && layout.size() <= BUMP_MAX {
if let Some(p) = state.bump_alloc(layout.size(), layout.align()) {
return p;
}
}
unsafe { &*state.spill_handle() }.alloc_layout(layout).as_ptr()
}
})
}
unsafe impl Allocator for AstAlloc {
#[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
alloc_result(heap_alloc(layout), layout.size())
}
#[inline]
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let p: *mut u8 = with_active_state(|state| match state {
None => {
let p = C_ALLOCATOR
.raw_alloc(
layout.size(),
crate::Alignment::from_byte_units(layout.align()),
0,
)
.unwrap_or_else(core::ptr::null_mut);
if !p.is_null() {
unsafe { core::ptr::write_bytes(p, 0, layout.size()) };
}
p
}
Some(state) => match unsafe { &*state.spill_handle() }.allocate_zeroed(layout) {
Ok(p) => p.as_ptr().cast::<u8>(),
Err(_) => core::ptr::null_mut(),
},
});
alloc_result(p, layout.size())
}
#[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
let _ = (ptr, layout);
}
#[inline]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
let p = NonNull::new(heap_alloc(new)).ok_or(AllocError)?;
unsafe { core::ptr::copy_nonoverlapping(ptr.as_ptr(), p.as_ptr(), old.size()) };
Ok(NonNull::slice_from_raw_parts(p, new.size()))
}
#[inline]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old: Layout,
new: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
debug_assert!(new.align() <= old.align());
let _ = old;
Ok(NonNull::slice_from_raw_parts(ptr, new.size()))
}
}
impl AstAlloc {
#[inline]
pub const fn vec<T>() -> AstVec<T> {
crate::core_alloc::AllocVec::new_in(AstAlloc)
}
#[inline]
pub fn vec_with_capacity<T>(cap: usize) -> AstVec<T> {
crate::core_alloc::AllocVec::with_capacity_in(cap, AstAlloc)
}
#[inline]
pub fn vec_from_slice<T: Clone>(items: &[T]) -> AstVec<T> {
let mut v = crate::core_alloc::AllocVec::with_capacity_in(items.len(), AstAlloc);
v.extend_from_slice(items);
v
}
#[inline]
pub fn vec_from_iter<T, I: IntoIterator<Item = T>>(iter: I) -> AstVec<T> {
let iter = iter.into_iter();
let (lo, _) = iter.size_hint();
let mut v = crate::core_alloc::AllocVec::with_capacity_in(lo, AstAlloc);
v.extend(iter);
v
}
}
impl AstAlloc {
#[inline]
pub fn take<T>(v: &mut AstVec<T>) -> AstVec<T> {
core::mem::replace(v, crate::core_alloc::AllocVec::new_in(AstAlloc))
}
}