cell_gc/
heap.rs

1//! GC heap allocator.
2//!
3//! ### Safety
4//!
5//! First, a few general observations about safety in Rust.
6//!
7//! *   If a `mut` reference to a value exists, and other references to the value
8//!     also exist, we can definitely crash.
9//!
10//!     One way it can happen is that the `mut` value is or contains an
11//!     `enum`. Using the other reference, we can borrow a refrence to a
12//!     current field of the `enum`. Then, using the `mut` reference, we can
13//!     assign some other variant to the `enum`, invalidating the reference.
14//!     Rust does not detect that the reference is invalid, so when we use it,
15//!     we are accessing gibberish.
16//!
17//!     Another way is if a callback is called while we're in the middle of
18//!     mutating a value, while it's in an invalid state. When you read
19//!     "callback", think of `deref` and `drop` methods, which are called
20//!     implicitly all over the place. These callbacks are normally safe,
21//!     because they simply can't have a reference to the value that's in an
22//!     intermediate state. But if another reference exists, it might be used
23//!     to try to read from the half-mutated value.
24//!
25//! *   If a data structure contains two paths to a value, under Rust's usual
26//!     rules, you can get two `mut` references to that value.
27//!
28//!     This is why you tend to build ownership trees in Rust and it's why GC
29//!     is particularly challenging in Rust: GCs build an arbitrary graph of
30//!     values and references.
31//!
32//! This GC takes the following approach to ensure safety.
33//!
34//! *   Minimize access to values stored in the heap. For the most part,
35//!     application code *never* sees a direct reference to any value that is
36//!     physically stored someplace where it's subject to GC.
37//!
38//! *   Minimize the times when direct references to in-heap values exist at
39//!     all, and during these operations, prevent control from escaping to
40//!     arbitrary application code.
41//!
42//! *   Ensure that when any direct references to in-heap values exist, they
43//!     obey Rust's rules: for any given value, either only non-`mut`
44//!     references, or at most one `mut` reference, exists at a time.
45//!
46//! Thus we are particularly keen to avoid the possibility of "reentering" the
47//! heap, creating new references to in-heap values while others already exist.
48//!
49//! References to heap values therefore exist only during the following
50//! operations:
51//!
52//! *   Allocation - That is, moving values into the heap. This is safe because
53//!     it never triggers any user code at all while heap references exist.
54//!
55//! * - Heap reads and writes - The only way to do these is via macro-generated
56//!     accessors which do not expose references.  Reads call `from_heap()` on
57//!     in-heap values, which is dangerous because `from_heap()` receives a
58//!     direct reference.  Writes call `drop()`, which is even more dangerous:
59//!     (1) it receives a direct `mut` reference; and (2) it leaves in-heap
60//!     values uninitialized.
61//!
62//! *   GC marking - The code for this is all macro-generated.
63//!
64//! *   GC sweeping - This calls `drop()`, which is dangerous for the reasons
65//!     noted above.
66//!
67//! To make this scheme safe, `from_heap()` and `drop()` must be tightly controlled.
68//! `from_heap()` is therefore in an unsafe trait; users are expected to use
69//! the `gc_heap_type!` to autogenerate instances.
70//!
71//! However, **we leave it up to the user to exercise care with `drop()`.**
72//! We suggest *never* implementing `Drop` for a heap type. If you must,
73//! avoid reading pointer fields while dropping, and avoid calling into
74//! arbitrary code.
75
76use std::cell::RefCell;
77use std::collections::HashMap;
78use std::marker::PhantomData;
79use std::ptr;
80use traits::IntoHeapAllocation;
81use pages::{heap_type_id, TypedPage, PageSet, PageSetRef};
82use ptr::{Pointer, UntypedPointer};
83use gcref::GcRef;
84use marking::{mark, MarkingTracer};
85
86pub struct Heap {
87    page_sets: HashMap<usize, PageSet>,
88    pins: RefCell<HashMap<UntypedPointer, usize>>,
89    marking_tracer: Option<MarkingTracer>,
90}
91
92// What does this do? You'll never guess!
93pub type HeapSessionId<'h> = PhantomData<::std::cell::Cell<&'h mut ()>>;
94
95pub struct HeapSession<'h> {
96    id: HeapSessionId<'h>,
97    heap: &'h mut Heap,
98}
99
100/// Create a heap, pass it to a callback, then destroy the heap.
101///
102/// The heap's lifetime is directly tied to this function call, for safety. (So
103/// the API is a little wonky --- but how many heaps were you planning on
104/// creating?)
105pub fn with_heap<R, F>(f: F) -> R
106where
107    F: for<'h> FnOnce(&mut HeapSession<'h>) -> R,
108{
109    Heap::new().enter(f)
110}
111
112impl Heap {
113    /// Create a new, empty heap.
114    pub fn new() -> Heap {
115        Heap {
116            page_sets: HashMap::new(),
117            pins: RefCell::new(HashMap::new()),
118            marking_tracer: Some(MarkingTracer::default()),
119        }
120    }
121
122    /// Run some code using this Heap.
123    ///
124    /// # Example
125    ///
126    ///     use cell_gc::{Heap, GCLeaf};
127    ///
128    ///     let mut heap = Heap::new();
129    ///     heap.enter(|hs| {
130    ///         // ... hs.alloc(MyHeapStruct { ... }) ...
131    ///         # hs.force_gc();
132    ///     });
133    ///
134    pub fn enter<R, F>(&mut self, f: F) -> R
135    where
136        F: for<'h> FnOnce(&mut HeapSession<'h>) -> R,
137    {
138        f(&mut HeapSession::new(self))
139    }
140
141    /// Add the value `*p` to the root set, protecting it from GC.
142    ///
143    /// A value that has been pinned *n* times stays in the root set
144    /// until it has been unpinned *n* times.
145    ///
146    /// # Safety
147    ///
148    /// `p` must point to a live allocation of type `T` in this heap.
149    pub unsafe fn pin<'h, T: IntoHeapAllocation<'h>>(&self, p: Pointer<T::In>) {
150        let mut pins = self.pins.borrow_mut();
151        let entry = pins.entry(p.into()).or_insert(0);
152        *entry += 1;
153    }
154
155    /// Unpin a heap-allocated value (see `pin`).
156    ///
157    /// # Safety
158    ///
159    /// `p` must point to a pinned allocation of type `T` in this heap.
160    pub unsafe fn unpin<'h, T: IntoHeapAllocation<'h>>(&self, p: Pointer<T::In>) {
161        let mut pins = self.pins.borrow_mut();
162        let done = {
163            let entry = pins.entry(p.into()).or_insert(0);
164            assert!(*entry != 0);
165            *entry -= 1;
166            *entry == 0
167        };
168        if done {
169            pins.remove(&p.into());
170        }
171    }
172
173    /// Call the given function on each pinned root.
174    pub fn each_pin<F>(&self, mut f: F)
175    where
176        F: FnMut(UntypedPointer),
177    {
178        for (&ptr, _) in self.pins.borrow().iter() {
179            f(ptr);
180        }
181    }
182
183    pub unsafe fn from_allocation<'h, T: IntoHeapAllocation<'h>>(ptr: Pointer<T::In>) -> *const Heap {
184        (*TypedPage::find(ptr)).header.heap
185    }
186
187    pub unsafe fn get_mark_bit<'h, T: IntoHeapAllocation<'h>>(ptr: Pointer<T::In>) -> bool {
188        (*TypedPage::find(ptr)).get_mark_bit(ptr)
189    }
190
191    pub unsafe fn set_mark_bit<'h, T: IntoHeapAllocation<'h>>(ptr: Pointer<T::In>) {
192        (*TypedPage::find(ptr)).set_mark_bit(ptr);
193    }
194
195    fn take_marking_tracer(&mut self) -> MarkingTracer {
196        self.marking_tracer.take().unwrap()
197    }
198
199    fn replace_marking_tracer(&mut self, tracer: MarkingTracer) {
200        assert!(self.marking_tracer.is_none());
201        assert!(tracer.mark_stack_is_empty());
202        self.marking_tracer = Some(tracer);
203    }
204
205    /// Run the given function with the marking tracer.
206    ///
207    /// The marking tracer is taken out of the heap and replaced again so we can
208    /// have two independent borrows of the heap and the marking tracer and the
209    /// same time.
210    pub(crate) fn with_marking_tracer<F, O>(&mut self, mut f: F) -> O
211    where
212        F: FnMut(&mut Self, &mut MarkingTracer) -> O,
213    {
214        let mut tracer = self.take_marking_tracer();
215        let retval = f(self, &mut tracer);
216        self.replace_marking_tracer(tracer);
217        retval
218    }
219
220    /// Clear all mark bits in preparation for GC.
221    ///
222    /// # Safety
223    ///
224    /// This must be called only at the beginning of a GC cycle.
225    pub(crate) unsafe fn clear_mark_bits(&mut self) {
226        for page_set in self.page_sets.values_mut() {
227            page_set.clear_mark_bits();
228        }
229    }
230
231    fn gc(&mut self) {
232        mark(self);
233
234        // sweep phase
235        for page_set in self.page_sets.values_mut() {
236            unsafe {
237                page_set.sweep();
238            }
239        }
240    }
241}
242
243impl Drop for Heap {
244    fn drop(&mut self) {
245        // Perform a final GC to call destructors on any remaining allocations.
246        assert!(self.pins.borrow().is_empty());
247        self.gc();
248
249        for page_set in self.page_sets.values() {
250            page_set.assert_no_allocations();
251        }
252    }
253}
254
255impl<'h> HeapSession<'h> {
256    fn new(heap: &'h mut Heap) -> HeapSession<'h> {
257        HeapSession {
258            id: PhantomData,
259            heap,
260        }
261    }
262
263    fn get_page_set<'a, T: IntoHeapAllocation<'h> + 'a>(&'a mut self) -> PageSetRef<'a, 'h, T> {
264        let key = heap_type_id::<T>();
265        let heap: *mut Heap = self.heap;
266        let page_ref =
267            self.heap
268            .page_sets
269            .entry(key)
270            .or_insert_with(|| unsafe { PageSet::new(heap) });
271        unsafe { page_ref.unchecked_downcast_mut() }
272    }
273
274    pub fn set_page_limit<T: IntoHeapAllocation<'h>>(&mut self, limit: Option<usize>) {
275        self.get_page_set::<T>().set_page_limit(limit);
276    }
277
278    pub fn try_alloc<T: IntoHeapAllocation<'h>>(&mut self, value: T) -> Option<T::Ref> {
279        // For now, this is done very early, so that if it panics, the heap is
280        // left in an OK state. Better wrapping of raw pointers would make it
281        // possible to do this later, closer to the `ptr::write()` call. (And
282        // the compiler might optimize away this temporary if we do it that
283        // way.) Looking forward to placement new!
284        let u = value.into_heap();
285        unsafe {
286            let alloc = self.get_page_set::<T>().try_alloc();
287            alloc
288                .or_else(|| {
289                    self.heap.gc();
290                    self.get_page_set::<T>().try_alloc()
291                })
292                .map(move |p| {
293                    ptr::write(p.as_raw() as *mut _, u);
294                    T::wrap_gcref(GcRef::new(p))
295                })
296        }
297    }
298
299    pub fn alloc<T: IntoHeapAllocation<'h>>(&mut self, value: T) -> T::Ref {
300        self.try_alloc(value)
301            .expect("out of memory (gc did not collect anything)")
302    }
303
304    pub fn force_gc(&mut self) {
305        self.heap.gc();
306    }
307}