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),
Inactive(*mut Region),
Invocation(*mut Region),
}
unsafe impl Send for AllocCtx {}
type InvocationCache =
std::collections::HashMap<&'static str, std::collections::HashMap<String, usize>>;
thread_local! {
static INVOCATION_CACHE: RefCell<InvocationCache> = RefCell::new(Default::default());
pub(crate) static ALLOC_CTX: RefCell<Vec<AllocCtx>> = const { RefCell::new(Vec::new()) };
}
crate::mutator_slot!("cljrs-gc/ALLOC_CTX", refcell ALLOC_CTX: Vec<AllocCtx>);
crate::mutator_slot!("cljrs-gc/INVOCATION_CACHE", refcell INVOCATION_CACHE: InvocationCache);
pub(crate) fn alloc_in_ctx<T: Trace + 'static>(value: T) -> GcPtr<T> {
ALLOC_CTX.with(|ctx| {
let ctx = ctx.borrow();
match ctx
.iter()
.rev()
.find(|entry| !matches!(entry, AllocCtx::Inactive(_)))
{
Some(AllocCtx::Inactive(_)) => unreachable!(),
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
.iter()
.rev()
.find(|entry| !matches!(entry, AllocCtx::Inactive(_)))
{
Some(AllocCtx::Inactive(_)) => unreachable!(),
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 fn invocation_cache_get(kind: &'static str, key: &str) -> Option<usize> {
if !invocation_is_active() {
return None;
}
INVOCATION_CACHE.with(|cache| cache.borrow().get(kind)?.get(key).copied())
}
pub fn invocation_cache_insert(kind: &'static str, key: String, address: usize) {
assert!(invocation_is_active());
INVOCATION_CACHE.with(|cache| {
cache
.borrow_mut()
.entry(kind)
.or_default()
.insert(key, address);
});
}
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;
}
INVOCATION_CACHE.with(|cache| cache.borrow_mut().clear());
self.region.reset();
}
}
pub struct ScratchGuard {
reads: Option<crate::region::ReadScope>,
borrowed: crate::region::RegionLease,
region: Option<Box<Region>>,
in_ctx: bool,
}
impl ScratchGuard {
pub fn new() -> Self {
Self::with_capacity(4096)
}
pub fn with_capacity(capacity: usize) -> Self {
if invocation_is_active() {
return Self {
region: None,
reads: None,
borrowed: Default::default(),
in_ctx: false,
};
}
let mut region = Box::new(Region::with_capacity(capacity));
let ptr = region.as_mut() as *mut Region;
ALLOC_CTX.with(|ctx| ctx.borrow_mut().push(AllocCtx::Region(ptr)));
Self {
region: Some(region),
reads: Some(crate::region::ReadScope::nested()),
borrowed: Default::default(),
in_ctx: true,
}
}
pub fn pop_for_return(&mut self) {
if let Some(reads) = self.reads.take() {
self.borrowed = reads.finish();
}
if self.in_ctx {
ALLOC_CTX.with(|ctx| {
let mut ctx = ctx.borrow_mut();
let Some(AllocCtx::Region(ptr)) = ctx.last_mut() else {
panic!("scratch context out of order")
};
let ptr = *ptr;
*ctx.last_mut().unwrap() = AllocCtx::Inactive(ptr);
});
self.in_ctx = false;
}
}
}
impl Default for ScratchGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for ScratchGuard {
fn drop(&mut self) {
if let Some(region) = self.region.take() {
ALLOC_CTX.with(|ctx| {
ctx.borrow_mut().pop();
});
crate::region::close_scratch_region(region);
}
}
}
pub struct ContextGuard {
_owner: crate::region::RegionLease,
}
impl Drop for ContextGuard {
fn drop(&mut self) {
ALLOC_CTX.with(|ctx| {
ctx.borrow_mut().pop();
});
}
}
pub(crate) fn enter_target(
region: Option<*mut Region>,
owner: crate::region::RegionLease,
) -> ContextGuard {
ALLOC_CTX.with(|ctx| {
ctx.borrow_mut()
.push(region.map(AllocCtx::Region).unwrap_or(AllocCtx::Static))
});
ContextGuard { _owner: owner }
}
pub(crate) fn current_region() -> Option<*mut Region> {
ALLOC_CTX.with(|ctx| {
ctx.borrow()
.iter()
.rev()
.find_map(|entry| match entry {
AllocCtx::Inactive(_) => None,
_ => Some(match entry {
AllocCtx::Region(ptr) | AllocCtx::Invocation(ptr) => Some(*ptr),
_ => None,
}),
})
.flatten()
})
}
pub(crate) fn live_regions() -> Vec<*mut Region> {
ALLOC_CTX
.try_with(|ctx| {
ctx.borrow()
.iter()
.filter_map(|entry| match entry {
AllocCtx::Region(ptr) | AllocCtx::Inactive(ptr) | AllocCtx::Invocation(ptr) => {
Some(*ptr)
}
_ => None,
})
.collect()
})
.unwrap_or_default()
}
pub fn is_static_context() -> bool {
!invocation_is_active() && current_region().is_none_or(|ptr| unsafe { &*ptr }.is_permanent())
}
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());
}
}
}