use std::cell::RefCell;
use crate::region::Region;
use crate::static_arena::static_arena;
use crate::{AllocError, GcBox, GcPtr, Trace};
use std::alloc::Layout;
pub(crate) enum AllocCtx {
Static,
Region(*mut Region),
Invocation(*mut Region),
}
unsafe impl Send for AllocCtx {}
thread_local! {
pub(crate) static ALLOC_CTX: RefCell<Vec<AllocCtx>> = const { RefCell::new(Vec::new()) };
}
pub(crate) fn alloc_in_ctx<T: Trace + 'static>(value: T) -> GcPtr<T> {
ALLOC_CTX.with(|ctx| {
let ctx = ctx.borrow();
match ctx.last() {
None | Some(AllocCtx::Static) => static_arena().alloc(value),
Some(AllocCtx::Region(ptr) | AllocCtx::Invocation(ptr)) => {
unsafe { &mut **ptr }.alloc(value)
}
}
})
}
pub(crate) fn try_alloc_in_ctx<T: Trace + 'static>(value: T) -> Result<GcPtr<T>, AllocError<T>> {
ALLOC_CTX.with(|ctx| {
let ctx = ctx.borrow();
match ctx.last() {
None | Some(AllocCtx::Static) => static_arena().try_alloc(value),
Some(AllocCtx::Region(ptr) | AllocCtx::Invocation(ptr)) => {
let region = unsafe { &mut **ptr };
if let Some(bytes) = region.chunk_needed(Layout::new::<GcBox<T>>())
&& let Err(error) = crate::governor::try_reserve_current(bytes)
{
return Err(AllocError { value, error });
}
Ok(region.alloc(value))
}
}
})
}
pub fn invocation_is_active() -> bool {
ALLOC_CTX.with(|ctx| {
ctx.borrow()
.iter()
.any(|entry| matches!(entry, AllocCtx::Invocation(_)))
})
}
pub struct InvocationGuard {
region: Box<Region>,
in_ctx: bool,
}
impl InvocationGuard {
pub fn new(byte_limit: usize) -> Self {
assert!(
!invocation_is_active(),
"nested isolated allocation invocations are not supported"
);
let mut region = Box::new(Region::with_limit(byte_limit));
let ptr = region.as_mut() as *mut Region;
ALLOC_CTX.with(|ctx| ctx.borrow_mut().push(AllocCtx::Invocation(ptr)));
Self {
region,
in_ctx: true,
}
}
pub fn accounted_bytes(&self) -> usize {
self.region.accounted_bytes()
}
pub fn object_count(&self) -> usize {
self.region.object_count()
}
}
impl Drop for InvocationGuard {
fn drop(&mut self) {
if self.in_ctx {
ALLOC_CTX.with(|ctx| {
let popped = ctx.borrow_mut().pop();
debug_assert!(matches!(popped, Some(AllocCtx::Invocation(_))));
});
self.in_ctx = false;
}
self.region.reset();
}
}
pub struct ScratchGuard {
region: Option<Box<Region>>,
in_ctx: bool,
}
impl ScratchGuard {
pub fn new() -> Self {
if invocation_is_active() {
return Self {
region: None,
in_ctx: false,
};
}
let mut region = Box::new(Region::new());
let ptr = region.as_mut() as *mut Region;
ALLOC_CTX.with(|ctx| ctx.borrow_mut().push(AllocCtx::Region(ptr)));
Self {
region: Some(region),
in_ctx: true,
}
}
pub fn pop_for_return(&mut self) {
if self.in_ctx {
ALLOC_CTX.with(|ctx| ctx.borrow_mut().pop());
self.in_ctx = false;
}
}
}
impl Default for ScratchGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for ScratchGuard {
fn drop(&mut self) {
if self.in_ctx {
ALLOC_CTX.with(|ctx| ctx.borrow_mut().pop());
}
if let Some(region) = &mut self.region {
region.reset();
}
}
}
pub struct StaticCtxGuard {
pushed: bool,
}
impl StaticCtxGuard {
pub fn new() -> Self {
if invocation_is_active() {
return Self { pushed: false };
}
ALLOC_CTX.with(|ctx| ctx.borrow_mut().push(AllocCtx::Static));
Self { pushed: true }
}
}
impl Default for StaticCtxGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for StaticCtxGuard {
fn drop(&mut self) {
if self.pushed {
ALLOC_CTX.with(|ctx| ctx.borrow_mut().pop());
}
}
}