use super::GcState;
use super::Trace;
use crate::core::object::GcString;
use crate::core::object::LispHashTable;
use crate::core::object::{Gc, IntoObject, Object, UninternedSymbolMap, WithLifetime};
use bumpalo::collections::Vec as GcVec;
use std::cell::{Cell, RefCell};
use std::fmt::Debug;
use std::ops::Deref;
use std::sync::atomic::AtomicBool;
#[derive(Default, Debug)]
pub(crate) struct RootSet {
pub(super) roots: RefCell<Vec<*const dyn Trace>>,
}
#[expect(dead_code)]
pub(in crate::core) enum DropStackElem {
String(String),
ByteString(Vec<u8>),
Vec(Vec<Object<'static>>),
}
#[derive(Default)]
pub(crate) struct Block<const CONST: bool> {
pub(in crate::core) objects: bumpalo::Bump,
pub(in crate::core) drop_stack: RefCell<Vec<DropStackElem>>,
pub(in crate::core) lisp_hashtables: RefCell<Vec<*const LispHashTable>>,
pub(in crate::core) uninterned_symbol_map: UninternedSymbolMap,
}
unsafe impl<const C: bool> Send for Block<C> {}
pub(crate) struct Context<'rt> {
pub(crate) block: Block<false>,
root_set: &'rt RootSet,
next_limit: usize,
}
impl Drop for Context<'_> {
fn drop(&mut self) {
self.garbage_collect(true);
if self.block.objects.allocated_bytes() == 0 {
return;
}
if std::thread::panicking() {
eprintln!("Error: Context was dropped while still holding data");
} else {
panic!("Error: Context was dropped while still holding data");
}
}
}
thread_local! {
static SINGLETON_CHECK: Cell<bool> = const { Cell::new(false) };
}
static GLOBAL_CHECK: AtomicBool = AtomicBool::new(false);
impl Block<true> {
pub(crate) fn new_global() -> Self {
use std::sync::atomic::Ordering::SeqCst as Ord;
assert!(GLOBAL_CHECK.compare_exchange(false, true, Ord, Ord).is_ok());
Self::default()
}
}
impl Block<false> {
pub(crate) fn new_local() -> Self {
Self::assert_unique();
Self::default()
}
pub(crate) fn new_local_unchecked() -> Self {
Self::default()
}
pub(crate) fn assert_unique() {
SINGLETON_CHECK.with(|x| {
assert!(!x.get(), "There was already and active context when this context was created");
x.set(true);
});
}
}
impl<const CONST: bool> Block<CONST> {
pub(crate) fn add<'ob, T, Tx>(&'ob self, obj: T) -> Object<'ob>
where
T: IntoObject<Out<'ob> = Tx>,
Gc<Tx>: Into<Object<'ob>>,
{
obj.into_obj(self).into()
}
pub(crate) fn add_as<'ob, T, Tx, V>(&'ob self, obj: T) -> Gc<V>
where
T: IntoObject<Out<'ob> = Tx>,
Gc<Tx>: Into<Gc<V>>,
{
obj.into_obj(self).into()
}
pub(crate) fn string_with_capacity(&self, cap: usize) -> GcString<'_> {
GcString::with_capacity_in(cap, &self.objects)
}
pub(crate) fn vec_new(&self) -> GcVec<'_, Object<'_>> {
GcVec::new_in(&self.objects)
}
pub(crate) fn vec_with_capacity(&self, cap: usize) -> GcVec<'_, Object<'_>> {
GcVec::with_capacity_in(cap, &self.objects)
}
}
impl<'ob, 'rt> Context<'rt> {
const MIN_GC_BYTES: usize = 2000;
const GC_GROWTH_FACTOR: usize = 12; pub(crate) fn new(roots: &'rt RootSet) -> Self {
Self { block: Block::new_local(), root_set: roots, next_limit: Self::MIN_GC_BYTES }
}
pub(crate) fn from_block(block: Block<false>, roots: &'rt RootSet) -> Self {
Block::assert_unique();
Context { block, root_set: roots, next_limit: Self::MIN_GC_BYTES }
}
pub(crate) fn bind<T>(&'ob self, obj: T) -> <T as WithLifetime<'ob>>::Out
where
T: WithLifetime<'ob>,
{
unsafe { obj.with_lifetime() }
}
pub(crate) fn get_root_set(&'ob self) -> &'rt RootSet {
self.root_set
}
pub(crate) fn garbage_collect(&mut self, force: bool) {
let bytes = self.block.objects.allocated_bytes();
if cfg!(not(test)) && !force && bytes < self.next_limit {
return;
}
let mut state = GcState::new();
for x in self.root_set.roots.borrow().iter() {
unsafe {
(**x).trace(&mut state);
}
}
state.trace_stack();
self.next_limit = (state.to_space.allocated_bytes() * Self::GC_GROWTH_FACTOR) / 10;
self.block.drop_stack.borrow_mut().clear();
self.block.lisp_hashtables.borrow_mut().retain_mut(|ptr| {
let table = unsafe { &**ptr };
if let Some(fwd) = table.forwarding_ptr() {
*ptr = fwd.as_ptr().cast::<LispHashTable>();
true
} else {
unsafe { std::ptr::drop_in_place(*ptr as *mut LispHashTable) };
false
}
});
self.block.objects = state.to_space;
}
}
impl Deref for Context<'_> {
type Target = Block<false>;
fn deref(&self) -> &Self::Target {
&self.block
}
}
impl AsRef<Block<false>> for Context<'_> {
fn as_ref(&self) -> &Block<false> {
&self.block
}
}
impl<const CONST: bool> Drop for Block<CONST> {
fn drop(&mut self) {
SINGLETON_CHECK.with(|s| {
assert!(s.get(), "Context singleton check was overwritten");
s.set(false);
});
}
}
#[cfg(test)]
mod test {
use rune_core::macros::{list, rebind, root};
use crate::core::{
cons::Cons,
object::{HashTable, ObjectType, Symbol},
};
use super::*;
fn bind_to_mut<'ob>(cx: &'ob mut Context) -> Object<'ob> {
cx.add("invariant")
}
#[test]
fn test_reborrow() {
let roots = &RootSet::default();
let cx = &mut Context::new(roots);
let obj = rebind!(bind_to_mut(cx));
_ = "foo".into_obj(cx);
assert_eq!(obj, "invariant");
}
#[test]
fn test_garbage_collect() {
let roots = &RootSet::default();
let cx = &mut Context::new(roots);
root!(vec, new(Vec), cx);
cx.garbage_collect(true);
let cons = list!["foo", 1, false, "end"; cx];
vec.push(cons);
cx.garbage_collect(true);
}
#[test]
fn test_move_values() {
let roots = &RootSet::default();
let cx = &mut Context::new(roots);
let int = cx.add(1);
let float = cx.add(1.5);
let cons: Object = Cons::new(int, float, cx).into();
let string = cx.add("string");
let symbol = cx.add(Symbol::new_uninterned("sym", cx));
println!("sym: {:?}", symbol.into_raw());
let mut table = HashTable::default();
table.insert(symbol, string);
let _ = table.get(&symbol).unwrap();
root!(symbol, cx);
let table = cx.add(table);
let vec = vec![cons, table];
let vec = cx.add(vec);
root!(vec, cx);
cx.garbage_collect(true);
let vec = vec.bind(cx);
let ObjectType::Vec(vec) = vec.untag() else { unreachable!() };
let ObjectType::Cons(cons) = vec[0].get().untag() else { unreachable!() };
let ObjectType::HashTable(table) = vec[1].get().untag() else { unreachable!() };
let key = symbol.bind(cx);
println!("key: {:?}", key.into_raw());
let val = table.get(symbol.bind(cx)).unwrap();
let ObjectType::String(string) = val.untag() else { unreachable!() };
let ObjectType::Int(int) = cons.car().untag() else { unreachable!() };
let ObjectType::Float(float) = cons.cdr().untag() else { unreachable!() };
assert_eq!(string, "string");
assert_eq!(**float, 1.5);
assert_eq!(int, 1);
}
}