rune/core/gc/
context.rs
1use super::GcState;
2use super::Trace;
3use crate::core::object::GcString;
4use crate::core::object::LispHashTable;
5use crate::core::object::{Gc, IntoObject, Object, UninternedSymbolMap, WithLifetime};
6use bumpalo::collections::Vec as GcVec;
7use std::cell::{Cell, RefCell};
8use std::fmt::Debug;
9use std::ops::Deref;
10use std::sync::atomic::AtomicBool;
11
12#[derive(Default, Debug)]
15pub(crate) struct RootSet {
16 pub(super) roots: RefCell<Vec<*const dyn Trace>>,
17}
18
19#[derive(Debug)]
26pub(crate) struct ThreadSafeRootSet {
27 pub(super) roots: std::sync::Mutex<Vec<*const dyn Trace>>,
28}
29
30impl Default for ThreadSafeRootSet {
31 fn default() -> Self {
32 Self { roots: std::sync::Mutex::new(Vec::new()) }
33 }
34}
35
36#[expect(dead_code)]
37pub(in crate::core) enum DropStackElem {
39 String(String),
40 ByteString(Vec<u8>),
41 Vec(Vec<Object<'static>>),
42}
43
44#[derive(Default)]
47pub(crate) struct Block<const CONST: bool> {
48 pub(in crate::core) objects: bumpalo::Bump,
49 pub(in crate::core) drop_stack: RefCell<Vec<DropStackElem>>,
55 pub(in crate::core) lisp_hashtables: RefCell<Vec<*const LispHashTable>>,
59 pub(in crate::core) uninterned_symbol_map: UninternedSymbolMap,
60}
61
62unsafe impl<const C: bool> Send for Block<C> {}
63
64pub(crate) struct Context<'rt> {
68 pub(crate) block: Block<false>,
69 root_set: &'rt RootSet,
70 next_limit: usize,
71}
72
73impl Drop for Context<'_> {
74 fn drop(&mut self) {
75 self.garbage_collect(true);
76 if self.block.objects.allocated_bytes() == 0 {
77 return;
78 }
79 if std::thread::panicking() {
80 eprintln!("Error: Context was dropped while still holding data");
81 } else {
82 panic!("Error: Context was dropped while still holding data");
83 }
84 }
85}
86
87thread_local! {
88 static SINGLETON_CHECK: Cell<bool> = const { Cell::new(false) };
90}
91
92static GLOBAL_CHECK: AtomicBool = AtomicBool::new(false);
94
95impl Block<true> {
96 pub(crate) fn new_global() -> Self {
97 use std::sync::atomic::Ordering::SeqCst as Ord;
98 assert!(GLOBAL_CHECK.compare_exchange(false, true, Ord, Ord).is_ok());
99 Self::default()
100 }
101}
102
103impl Block<false> {
104 pub(crate) fn new_local() -> Self {
105 Self::assert_unique();
106 Self::default()
107 }
108
109 pub(crate) fn new_local_unchecked() -> Self {
110 Self::default()
111 }
112
113 pub(crate) fn assert_unique() {
114 SINGLETON_CHECK.with(|x| {
115 assert!(!x.get(), "There was already and active context when this context was created");
116 x.set(true);
117 });
118 }
119}
120
121impl<const CONST: bool> Block<CONST> {
122 pub(crate) fn add<'ob, T, Tx>(&'ob self, obj: T) -> Object<'ob>
123 where
124 T: IntoObject<Out<'ob> = Tx>,
125 Gc<Tx>: Into<Object<'ob>>,
126 {
127 obj.into_obj(self).into()
128 }
129
130 pub(crate) fn add_as<'ob, T, Tx, V>(&'ob self, obj: T) -> Gc<V>
131 where
132 T: IntoObject<Out<'ob> = Tx>,
133 Gc<Tx>: Into<Gc<V>>,
134 {
135 obj.into_obj(self).into()
136 }
137
138 pub(crate) fn string_with_capacity(&self, cap: usize) -> GcString<'_> {
142 GcString::with_capacity_in(cap, &self.objects)
143 }
144
145 pub(crate) fn vec_new(&self) -> GcVec<'_, Object<'_>> {
149 GcVec::new_in(&self.objects)
150 }
151
152 pub(crate) fn vec_with_capacity(&self, cap: usize) -> GcVec<'_, Object<'_>> {
153 GcVec::with_capacity_in(cap, &self.objects)
154 }
155}
156
157impl<'ob, 'rt> Context<'rt> {
158 pub(crate) const MIN_GC_BYTES: usize = 2000;
159 pub(crate) const GC_GROWTH_FACTOR: usize = 12; pub(crate) fn new(roots: &'rt RootSet) -> Self {
161 Self { block: Block::new_local(), root_set: roots, next_limit: Self::MIN_GC_BYTES }
162 }
163
164 pub(crate) fn from_block(block: Block<false>, roots: &'rt RootSet) -> Self {
165 Block::assert_unique();
166 Context { block, root_set: roots, next_limit: Self::MIN_GC_BYTES }
167 }
168
169 pub(crate) fn bind<T>(&'ob self, obj: T) -> <T as WithLifetime<'ob>>::Out
170 where
171 T: WithLifetime<'ob>,
172 {
173 unsafe { obj.with_lifetime() }
174 }
175
176 pub(crate) fn get_root_set(&'ob self) -> &'rt RootSet {
177 self.root_set
178 }
179
180 pub(crate) fn garbage_collect(&mut self, force: bool) {
181 let bytes = self.block.objects.allocated_bytes();
182 if cfg!(not(test)) && !force && bytes < self.next_limit {
183 return;
184 }
185
186 let mut state = GcState::new();
187 for x in self.root_set.roots.borrow().iter() {
188 unsafe {
191 (**x).trace(&mut state);
192 }
193 }
194
195 state.trace_stack();
196
197 self.next_limit = (state.to_space.allocated_bytes() * Self::GC_GROWTH_FACTOR) / 10;
198 self.block.drop_stack.borrow_mut().clear();
199 self.block.lisp_hashtables.borrow_mut().retain_mut(|ptr| {
202 let table = unsafe { &**ptr };
203 if let Some(fwd) = table.forwarding_ptr() {
204 *ptr = fwd.as_ptr().cast::<LispHashTable>();
205 true
206 } else {
207 unsafe { std::ptr::drop_in_place(*ptr as *mut LispHashTable) };
208 false
209 }
210 });
211
212 self.block.objects = state.to_space;
213 }
214}
215
216pub(crate) unsafe fn collect_garbage_raw(
227 block: &mut Block<false>,
228 root_set: &ThreadSafeRootSet,
229 force: bool,
230 next_limit: &mut usize,
231) {
232 let bytes = block.objects.allocated_bytes();
233 if cfg!(not(test)) && !force && bytes < *next_limit {
234 return;
235 }
236
237 let mut state = GcState::new();
238 for x in root_set.roots.lock().unwrap().iter() {
239 unsafe {
242 (**x).trace(&mut state);
243 }
244 }
245
246 state.trace_stack();
247
248 *next_limit = (state.to_space.allocated_bytes() * Context::GC_GROWTH_FACTOR) / 10;
249 block.drop_stack.borrow_mut().clear();
250 block.lisp_hashtables.borrow_mut().retain_mut(|ptr| {
253 let table = unsafe { &**ptr };
254 if let Some(fwd) = table.forwarding_ptr() {
255 *ptr = fwd.as_ptr().cast::<LispHashTable>();
256 true
257 } else {
258 unsafe { std::ptr::drop_in_place(*ptr as *mut LispHashTable) };
259 false
260 }
261 });
262
263 block.objects = state.to_space;
264}
265
266impl Deref for Context<'_> {
267 type Target = Block<false>;
268
269 fn deref(&self) -> &Self::Target {
270 &self.block
271 }
272}
273
274impl AsRef<Block<false>> for Context<'_> {
275 fn as_ref(&self) -> &Block<false> {
276 &self.block
277 }
278}
279
280impl<const CONST: bool> Drop for Block<CONST> {
281 fn drop(&mut self) {
284 SINGLETON_CHECK.with(|s| {
285 assert!(s.get(), "Context singleton check was overwritten");
286 s.set(false);
287 });
288 }
289}
290
291#[cfg(test)]
292mod test {
293 use rune_core::macros::{list, rebind, root};
294
295 use crate::core::{
296 cons::Cons,
297 object::{HashTable, ObjectType, Symbol},
298 };
299
300 use super::*;
301 fn bind_to_mut<'ob>(cx: &'ob mut Context) -> Object<'ob> {
302 cx.add("invariant")
303 }
304
305 #[test]
306 fn test_reborrow() {
307 let roots = &RootSet::default();
308 let cx = &mut Context::new(roots);
309 let obj = rebind!(bind_to_mut(cx));
310 _ = "foo".into_obj(cx);
311 assert_eq!(obj, "invariant");
312 }
313
314 #[test]
315 fn test_garbage_collect() {
316 let roots = &RootSet::default();
317 let cx = &mut Context::new(roots);
318 root!(vec, new(Vec), cx);
319 cx.garbage_collect(true);
320 let cons = list!["foo", 1, false, "end"; cx];
321 vec.push(cons);
322 cx.garbage_collect(true);
323 }
324
325 #[test]
326 fn test_move_values() {
327 let roots = &RootSet::default();
328 let cx = &mut Context::new(roots);
329 let int = cx.add(1);
330 let float = cx.add(1.5);
331 let cons: Object = Cons::new(int, float, cx).into();
332 let string = cx.add("string");
333 let symbol = cx.add(Symbol::new_uninterned("sym", cx));
334 println!("sym: {:?}", symbol.into_raw());
335 let mut table = HashTable::default();
336 table.insert(symbol, string);
337 let _ = table.get(&symbol).unwrap();
338 root!(symbol, cx);
339 let table = cx.add(table);
340 let vec = vec![cons, table];
341 let vec = cx.add(vec);
342 root!(vec, cx);
343 cx.garbage_collect(true);
344 let vec = vec.bind(cx);
345 let ObjectType::Vec(vec) = vec.untag() else { unreachable!() };
346 let ObjectType::Cons(cons) = vec[0].get().untag() else { unreachable!() };
347 let ObjectType::HashTable(table) = vec[1].get().untag() else { unreachable!() };
348 let key = symbol.bind(cx);
349 println!("key: {:?}", key.into_raw());
350 let val = table.get(symbol.bind(cx)).unwrap();
351 let ObjectType::String(string) = val.untag() else { unreachable!() };
352 let ObjectType::Int(int) = cons.car().untag() else { unreachable!() };
353 let ObjectType::Float(float) = cons.cdr().untag() else { unreachable!() };
354 assert_eq!(string, "string");
355 assert_eq!(**float, 1.5);
356 assert_eq!(int, 1);
357 }
358}