scoped_gc/
gc_scope.rs

1use ::std::cell::{Cell, RefCell};
2use ::std::mem::{size_of, size_of_val};
3use ::std::ptr::NonNull;
4use gc::Gc;
5use gc_alloc_err::GcAllocErr;
6use gc_box::GcBox;
7use trace::Trace;
8
9/// Defines a scope for garbage collection.
10///
11/// It lets you allocate garbage-collected values. They can have cycles. Their reachability is
12/// tracked so they can be deallocated once unreachable.
13/// All the values are deallocated once the scope is dropped.
14#[derive(Debug)]
15pub struct GcScope<'gc> {
16  state: RefCell<GcState<'gc>>,
17}
18
19impl<'gc> GcScope<'gc> {
20  pub fn new() -> GcScope<'gc> {
21    GcScope { state: RefCell::new(GcState::new()) }
22  }
23
24  /// Allocates `value` in this garbage-collected scope and returns a `Gc` smart pointer to it.
25  pub fn alloc<T: Trace + 'gc>(&'gc self, value: T) -> Result<Gc<'gc, T>, GcAllocErr> {
26    unsafe { value.unroot() }
27    self.state.borrow_mut()
28      .alloc(value)
29      .map(|ptr| Gc::new(ptr))
30  }
31
32  pub fn collect_garbage(&self) {
33    self.state.borrow_mut().collect_garbage()
34  }
35}
36
37#[derive(Debug)]
38struct GcState<'gc> {
39  pub(crate) allocated_bytes: usize,
40  //  threshold: usize,
41  // Linked-list of boxes
42  pub(crate) boxes: Option<NonNull<GcBox<'gc, dyn Trace>>>,
43}
44
45impl<'gc> GcState<'gc> {
46  pub(crate) fn new() -> GcState<'gc> {
47    GcState {
48      allocated_bytes: 0,
49      boxes: None,
50    }
51  }
52
53  // Allocates GC-managed memory for T
54  pub(crate) fn alloc<T: Trace + 'gc>(&mut self, value: T) -> Result<NonNull<GcBox<'gc, T>>, GcAllocErr> {
55    // into_raw -> mem::forget, so we need to make sure we deallocate it ourselve
56    let gc_box_ptr: *mut GcBox<T> = Box::into_raw(Box::new(GcBox {
57      roots: Cell::new(1),
58      marked: Cell::new(false),
59      next: self.boxes,
60      value: value,
61    }));
62    self.allocated_bytes += size_of::<GcBox<T>>();
63    // We know that `gc_box` is not null so we can use `new_unchecked`
64    let box_ptr: NonNull<GcBox<T>> = unsafe { NonNull::new_unchecked(gc_box_ptr) };
65    self.boxes = Some(box_ptr);
66    Ok(unsafe { NonNull::new_unchecked(gc_box_ptr) })
67  }
68
69  pub(crate) fn collect_garbage(&mut self) {
70    {
71      // Mark
72      let mut next_gc_box_ptr = self.boxes;
73      while let Some(gc_box_ptr) = next_gc_box_ptr {
74        let gc_box: &GcBox<dyn Trace> = unsafe { gc_box_ptr.as_ref() };
75        if gc_box.roots.get() > 0 {
76          gc_box.mark_box();
77        }
78        next_gc_box_ptr = gc_box.next;
79      }
80    }
81
82    let mut unmarked: Vec<*mut GcBox<dyn Trace>> = Vec::new();
83    unsafe {
84      // Collect
85      let mut next_gc_box_ptr_ref = &mut self.boxes;
86      while let Some(gc_box_ptr) = *next_gc_box_ptr_ref {
87        let gc_box_ptr = gc_box_ptr.as_ptr();
88        if (*gc_box_ptr).marked.get() {
89          (*gc_box_ptr).marked.set(false);
90          next_gc_box_ptr_ref = &mut (*gc_box_ptr).next;
91        } else {
92          *next_gc_box_ptr_ref = (*gc_box_ptr).next;
93          unmarked.push(gc_box_ptr);
94        }
95      }
96    }
97
98    for gc_box_ptr in unmarked.iter() {
99      let gc_box = unsafe { Box::from_raw(*gc_box_ptr) };
100      self.allocated_bytes = self.allocated_bytes.checked_sub(size_of_val::<GcBox<_>>(gc_box.as_ref())).unwrap()
101      // Implicitly drops `gc_box` and frees the associated memory
102    }
103  }
104}
105
106unsafe impl<#[may_dangle] 'gc> Drop for GcState<'gc> {
107  fn drop(&mut self) {
108    let mut cur_box = self.boxes;
109    while let Some(gc_box_ptr) = cur_box {
110      let gc_box = unsafe { Box::from_raw(gc_box_ptr.as_ptr()) };
111      cur_box = (*gc_box).next;
112      // Implicitly drops `gc_box` and frees the associated memory
113    }
114  }
115}