jrsonnet_gcmodule/
collect.rs1use crate::Cc;
8use crate::Trace;
9use crate::cc::CcDummy;
10use crate::cc::CcDyn;
11use crate::cc::GcClone;
12use crate::debug;
13use crate::ref_count::RefCount;
14use crate::ref_count::SingleThreadRefCount;
15use std::cell::Cell;
16use std::cell::RefCell;
17use std::cell::UnsafeCell;
18use std::marker::PhantomData;
19use std::mem;
20use std::ptr;
21use std::ptr::without_provenance;
22
23pub struct ObjectSpace {
60 pub(crate) list: RefCell<OwnedGcHeader>,
62
63 _phantom: PhantomData<Cc<()>>,
67}
68
69pub trait AbstractObjectSpace: 'static + Sized {
71 type RefCount: RefCount;
72 type Header;
73
74 fn insert(&self, header: *const Self::Header, value: &dyn CcDyn);
76
77 fn remove(header: &Self::Header);
79
80 fn new_ref_count(&self, tracked: bool) -> Self::RefCount;
82
83 fn empty_header(&self) -> Self::Header;
84}
85
86impl AbstractObjectSpace for ObjectSpace {
87 type RefCount = SingleThreadRefCount;
88 type Header = GcHeader;
89
90 fn insert(&self, header: *const Self::Header, value: &dyn CcDyn) {
91 let list = self.list.borrow();
92 let prev: &GcHeader = list.inner();
93 debug_assert!(unsafe { (*header).next.get() }.is_null());
94 let next = prev.next.get();
95 unsafe { (*header).prev.set(prev) };
96 unsafe { (*header).next.set(next) };
97 unsafe {
98 (*next).prev.set(header);
100 let fat_ptr: [*mut (); 2] = mem::transmute(value);
102 (*header).ccdyn_vptr.set(fat_ptr[1]);
103 }
104 prev.next.set(header);
105 }
106
107 #[inline]
108 fn remove(header: &Self::Header) {
109 let header: &GcHeader = header;
110 debug_assert!(!header.next.get().is_null());
111 debug_assert!(!header.prev.get().is_null());
112 let next = unmask_ptr(header.next.get());
113 let prev = unmask_ptr(header.prev.get());
114 unsafe {
116 (*prev).next.set(next);
117 (*next).prev.set(prev);
118 }
119 header.next.set(ptr::null_mut());
120 }
121
122 #[inline]
123 fn new_ref_count(&self, tracked: bool) -> Self::RefCount {
124 SingleThreadRefCount::new(tracked)
125 }
126
127 #[inline]
128 fn empty_header(&self) -> Self::Header {
129 GcHeader::empty()
130 }
131}
132
133impl Default for ObjectSpace {
134 fn default() -> Self {
136 let header = new_gc_list();
137 Self {
138 list: RefCell::new(header),
139 _phantom: PhantomData,
140 }
141 }
142}
143
144impl ObjectSpace {
145 pub fn count_tracked(&self) -> usize {
147 let list = self.list.borrow();
148 let list: &GcHeader = list.inner();
149 let mut count = 0;
150 visit_list(list, |_| count += 1);
151 count
152 }
153
154 pub fn collect_cycles(&self) -> usize {
157 let list = self.list.borrow();
158 let list: &GcHeader = list.inner();
159 collect_list(list, ())
160 }
161
162 pub fn create<T: Trace>(&self, value: T) -> Cc<T> {
168 Cc::new_in_space(value, self)
170 }
171
172 pub fn leak(&self) {
174 *self.list.borrow_mut() = new_gc_list();
175 }
176
177 }
180
181impl Drop for ObjectSpace {
182 fn drop(&mut self) {
183 self.collect_cycles();
184 }
185}
186
187pub trait Linked {
188 fn next(&self) -> *const Self;
189 fn prev(&self) -> *const Self;
190 fn set_prev(&self, other: *const Self);
191
192 fn value_ptr(this: *const Self) -> *const dyn CcDyn;
193 fn value(&self) -> &dyn CcDyn;
195}
196
197#[repr(C, align(8))]
199pub struct GcHeader {
200 pub(crate) next: Cell<*const GcHeader>,
201 pub(crate) prev: Cell<*const GcHeader>,
202
203 pub(crate) ccdyn_vptr: Cell<*const ()>,
205
206 pub(crate) _marker: UnsafeCell<()>,
208}
209
210impl Linked for GcHeader {
211 #[inline]
212 fn next(&self) -> *const Self {
213 self.next.get()
214 }
215 #[inline]
216 fn prev(&self) -> *const Self {
217 self.prev.get()
218 }
219 #[inline]
220 fn set_prev(&self, other: *const Self) {
221 self.prev.set(other);
222 }
223 fn value_ptr(this: *const Self) -> *const dyn CcDyn {
224 unsafe {
227 let fat_ptr: (*const (), *const ()) = (this.add(1).cast(), (*this).ccdyn_vptr.get());
228 mem::transmute(fat_ptr)
229 }
230 }
231 #[inline]
232 fn value(&self) -> &dyn CcDyn {
233 unsafe { &*Self::value_ptr(self) }
234 }
235}
236
237impl GcHeader {
238 pub(crate) fn empty() -> Self {
240 Self {
241 next: Cell::new(ptr::null()),
242 prev: Cell::new(ptr::null()),
243 ccdyn_vptr: Cell::new(CcDummy::ccdyn_vptr()),
244 _marker: UnsafeCell::new(()),
245 }
246 }
247}
248
249#[must_use]
253pub fn collect_thread_cycles() -> usize {
254 debug::log(|| ("collect", "collect_thread_cycles"));
255 THREAD_OBJECT_SPACE.with(ObjectSpace::collect_cycles)
256}
257
258#[must_use]
262pub fn count_thread_tracked() -> usize {
263 THREAD_OBJECT_SPACE.with(ObjectSpace::count_tracked)
264}
265
266thread_local!(pub(crate) static THREAD_OBJECT_SPACE: ObjectSpace = ObjectSpace::default());
267
268pub fn with_thread_object_space<R>(handler: impl FnOnce(&ObjectSpace) -> R) -> R {
270 THREAD_OBJECT_SPACE.with(handler)
271}
272
273pub(crate) struct OwnedGcHeader {
274 raw: *mut GcHeader,
275}
276impl OwnedGcHeader {
277 fn inner(&self) -> &GcHeader {
278 unsafe { &*self.raw }
279 }
280}
281impl Drop for OwnedGcHeader {
282 fn drop(&mut self) {
283 drop(unsafe { Box::from_raw(self.raw) });
284 }
285}
286
287pub(crate) fn new_gc_list() -> OwnedGcHeader {
289 let header = Box::into_raw(Box::new(GcHeader::empty()));
290 unsafe { (*header).prev.set(header) };
291 unsafe { (*header).next.set(header) };
292
293 OwnedGcHeader { raw: header }
294}
295
296pub(crate) fn collect_list<L: Linked, K>(list: &L, lock: K) -> usize {
298 update_refs(list);
299 subtract_refs(list);
300 release_unreachable(list, lock)
301}
302
303pub(crate) fn visit_list<'a, L: Linked>(list: &'a L, mut func: impl FnMut(&'a L)) {
305 let mut ptr = list.next();
307 while !ptr::eq(ptr, list) {
308 let header: &L = unsafe { &*ptr };
310 ptr = header.next();
311 func(header);
312 }
313}
314
315const PTR_MASK: usize = usize::MAX & !(0b11);
316const PREV_MASK_COLLECTING: usize = 1;
317const PREV_MASK_VISITED: usize = 2;
318const PREV_SHIFT: u32 = 2;
319
320#[inline]
321fn unmask_ptr<T>(ptr: *const T) -> *const T {
322 ptr.map_addr(|ptr| ptr & PTR_MASK)
323}
324
325fn update_refs<L: Linked>(list: &L) {
328 visit_list(list, |header| {
329 let ref_count = header.value().gc_ref_count();
330 if ref_count > 0 {
338 let shifted = (ref_count << PREV_SHIFT) | PREV_MASK_COLLECTING;
339 header.set_prev(without_provenance(shifted));
340 } else {
341 debug_assert!(header.prev() as usize & PREV_MASK_COLLECTING == 0);
342 }
343 });
344}
345
346fn subtract_refs<L: Linked>(list: &L) {
353 let mut tracer = |header: *const ()| {
354 let header = unsafe { &*header.cast::<L>() };
356 if is_collecting(header) {
357 debug_assert!(
358 !is_unreachable(header),
359 "bug: object {} becomes unreachable while trying to dec_ref (is Trace impl correct?)",
360 debug_name(header)
361 );
362 edit_gc_ref_count(header, -1);
363 }
364 };
365 visit_list(list, |header| {
366 set_visited(header);
367 header.value().gc_traverse(&mut tracer);
368 });
369}
370
371fn mark_reachable<L: Linked>(list: &L) {
375 fn revive<L: Linked>(header: *const ()) {
376 let header = unsafe { &*header.cast::<L>() };
378 if is_collecting(header) {
380 unset_collecting(header);
381 if is_unreachable(header) {
382 edit_gc_ref_count(header, 1); }
384 header.value().gc_traverse(&mut revive::<L>); }
386 }
387 visit_list(list, |header| {
388 if is_collecting(header) && !is_unreachable(header) {
389 unset_collecting(header);
390 header.value().gc_traverse(&mut revive::<L>);
391 }
392 });
393}
394
395fn release_unreachable<L: Linked, K>(list: &L, lock: K) -> usize {
397 mark_reachable(list);
401
402 let mut count = 0;
403
404 visit_list(list, |header| {
406 if is_unreachable(header) {
407 count += 1;
408 }
409 });
410
411 debug::log(|| ("collect", format!("{count} unreachable objects")));
412
413 let mut to_drop: Vec<Box<dyn GcClone>> = Vec::with_capacity(count);
419 visit_list(list, |header| {
420 if is_unreachable(header) {
421 to_drop.push(header.value().gc_clone());
422 }
423 });
424
425 restore_prev(list);
427
428 drop(lock);
434 #[cfg(feature = "debug")]
438 {
439 crate::debug::GC_DROPPING.with(|d| d.set(true));
440 }
441
442 for value in &to_drop {
446 value.gc_drop_t();
447 }
448
449 for value in &to_drop {
452 let ref_count = value.gc_ref_count();
453 assert_eq!(
454 ref_count, 1,
455 concat!(
456 "bug: unexpected ref-count after dropping cycles\n",
457 "This usually indicates a buggy Trace or Drop implementation."
458 )
459 );
460 }
461
462 #[cfg(feature = "debug")]
463 {
464 crate::debug::GC_DROPPING.with(|d| d.set(false));
465 }
466
467 count
468}
469
470fn restore_prev<L: Linked>(list: &L) {
472 let mut prev = list;
473 visit_list(list, |header| {
474 header.set_prev(prev);
475 prev = header;
476 });
477}
478
479fn is_unreachable<L: Linked>(header: &L) -> bool {
480 let prev = header.prev().addr();
481 is_collecting(header) && (prev >> PREV_SHIFT) == 0
482}
483
484pub(crate) fn is_collecting<L: Linked>(header: &L) -> bool {
485 let prev = header.prev().addr();
486 (prev & PREV_MASK_COLLECTING) != 0
487}
488
489fn set_visited<L: Linked>(header: &L) -> bool {
490 let prev = header.prev();
491 let visited = (prev.addr() & PREV_MASK_VISITED) != 0;
492 debug_assert!(
493 !visited,
494 "bug: double visit: {} (is Trace impl correct?)",
495 debug_name(header)
496 );
497 let new_prev = prev.map_addr(|prev| prev | PREV_MASK_VISITED);
498 header.set_prev(new_prev);
499 visited
500}
501
502fn unset_collecting<L: Linked>(header: &L) {
503 let prev = header.prev();
504 let new_prev = prev.map_addr(|prev| (prev & PREV_MASK_COLLECTING) ^ prev);
505 header.set_prev(new_prev);
506}
507
508fn edit_gc_ref_count<L: Linked>(header: &L, delta: isize) {
509 let prev = header.prev() as isize;
510 let new_prev = prev + (1 << PREV_SHIFT) * delta;
511 header.set_prev(without_provenance(new_prev.cast_unsigned()));
512}
513
514#[allow(unused_variables)]
515fn debug_name<L: Linked>(header: &L) -> String {
516 #[cfg(feature = "debug")]
517 {
518 header.value().gc_debug_name()
519 }
520
521 #[cfg(not(feature = "debug"))]
522 {
523 "(enable gcmodule \"debug\" feature for debugging)".to_string()
524 }
525}