gc_arena/
arena.rs

1use alloc::boxed::Box;
2use core::{f64, marker::PhantomData};
3
4use crate::{
5    context::{Context, EarlyStop, Finalization, Mutation, Phase},
6    metrics::Metrics,
7    Collect,
8};
9
10/// A trait that produces a [`Collect`]-able type for the given lifetime. This is used to produce
11/// the root [`Collect`] instance in an [`Arena`].
12///
13/// In order to use an implementation of this trait in an [`Arena`], it must implement
14/// `Rootable<'a>` for *any* possible `'a`. This is necessary so that the `Root` types can be
15/// branded by the unique, invariant lifetimes that makes an `Arena` sound.
16pub trait Rootable<'a>: 'static {
17    type Root: Collect + 'a;
18}
19
20/// A marker type used by the `Rootable!` macro instead of a bare trait object.
21///
22/// Prevents having to include extra ?Sized bounds on every `for<'a> Rootable<'a>`.
23#[doc(hidden)]
24pub struct __DynRootable<T: ?Sized>(PhantomData<T>);
25
26impl<'a, T: ?Sized + Rootable<'a>> Rootable<'a> for __DynRootable<T> {
27    type Root = <T as Rootable<'a>>::Root;
28}
29
30/// A convenience macro for quickly creating a type that implements `Rootable`.
31///
32/// The macro takes a single argument, which should be a generic type with elided lifetimes.
33/// When used as a root object, every instance of the elided lifetime will be replaced with
34/// the branding lifetime.
35///
36/// ```
37/// # use gc_arena::{Arena, Collect, Gc, Rootable};
38/// #
39/// # fn main() {
40/// #[derive(Collect)]
41/// #[collect(no_drop)]
42/// struct MyRoot<'gc> {
43///     ptr: Gc<'gc, i32>,
44/// }
45///
46/// type MyArena = Arena<Rootable![MyRoot<'_>]>;
47///
48/// // If desired, the branding lifetime can also be explicitely named:
49/// type MyArena2 = Arena<Rootable!['gc => MyRoot<'gc>]>;
50/// # }
51/// ```
52///
53/// The macro can also be used to create implementations of `Rootable` that use other generic
54/// parameters, though in complex cases it may be better to implement `Rootable` directly.
55///
56/// ```
57/// # use gc_arena::{Arena, Collect, Gc, Rootable, StaticCollect};
58/// #
59/// # fn main() {
60/// #[derive(Collect)]
61/// #[collect(no_drop)]
62/// struct MyGenericRoot<'gc, T: 'static> {
63///     ptr: Gc<'gc, StaticCollect<T>>,
64/// }
65///
66/// type MyGenericArena<T> = Arena<Rootable![MyGenericRoot<'_, T>]>;
67/// # }
68/// ```
69#[macro_export]
70macro_rules! Rootable {
71    ($gc:lifetime => $root:ty) => {
72        // Instead of generating an impl of `Rootable`, we use a trait object. Thus, we avoid the
73        // need to generate a new type for each invocation of this macro.
74        $crate::__DynRootable::<dyn for<$gc> $crate::Rootable<$gc, Root = $root>>
75    };
76    ($root:ty) => {
77        $crate::Rootable!['__gc => $crate::__unelide_lifetimes!('__gc; $root)]
78    };
79}
80
81/// A helper type alias for a `Rootable::Root` for a specific lifetime.
82pub type Root<'a, R> = <R as Rootable<'a>>::Root;
83
84#[derive(Debug, Copy, Clone, Eq, PartialEq)]
85pub enum CollectionPhase {
86    /// The arena is done with a collection cycle and is waiting to be restarted.
87    Sleeping,
88    /// The arena is currently tracing objects from the root to determine reachability.
89    Marking,
90    /// The arena has finished tracing, all reachable objects are marked. This may transition
91    /// back to `Marking` if write barriers occur.
92    Marked,
93    /// The arena has determined a set of unreachable objects and has started collecting them.
94    /// At this point, marking is no longer taking place so the root may have reachable, unmarked
95    /// pointers
96    Collecting,
97}
98
99/// A generic, garbage collected arena.
100///
101/// Garbage collected arenas allow for isolated sets of garbage collected objects with zero-overhead
102/// garbage collected pointers. It provides incremental mark and sweep garbage collection which
103/// must be manually triggered outside the `mutate` method, and works best when units of work inside
104/// `mutate` can be kept relatively small. It is designed primarily to be a garbage collector for
105/// scripting language runtimes.
106///
107/// The arena API is able to provide extremely cheap Gc pointers because it is based around
108/// "generativity". During construction and access, the root type is branded by a unique, invariant
109/// lifetime `'gc` which ensures that `Gc` pointers must be contained inside the root object
110/// hierarchy and cannot escape the arena callbacks or be smuggled inside another arena. This way,
111/// the arena can be sure that during mutation, all `Gc` pointers come from the arena we expect
112/// them to come from, and that they're all either reachable from root or have been allocated during
113/// the current `mutate` call. When not inside the `mutate` callback, the arena knows that all `Gc`
114/// pointers must be either reachable from root or they are unreachable and safe to collect. In
115/// this way, incremental garbage collection can be achieved (assuming "sufficiently small" calls
116/// to `mutate`) that is both extremely safe and zero overhead vs what you would write in C with raw
117/// pointers and manually ensuring that invariants are held.
118pub struct Arena<R: for<'a> Rootable<'a>> {
119    context: Box<Context>,
120    root: Root<'static, R>,
121}
122
123impl<R: for<'a> Rootable<'a>> Arena<R> {
124    /// Create a new arena with the given garbage collector tuning parameters. You must provide a
125    /// closure that accepts a `&Mutation<'gc>` and returns the appropriate root.
126    pub fn new<F>(f: F) -> Arena<R>
127    where
128        F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Root<'gc, R>,
129    {
130        unsafe {
131            let context = Box::new(Context::new());
132            // Note - we cast the `&Mutation` to a `'static` lifetime here,
133            // instead of transmuting the root type returned by `f`. Transmuting the root
134            // type is allowed in nightly versions of rust
135            // (see https://github.com/rust-lang/rust/pull/101520#issuecomment-1252016235)
136            // but is not yet stable. Casting the `&Mutation` is completely invisible
137            // to the callback `f` (since it needs to handle an arbitrary lifetime),
138            // and lets us stay compatible with older versions of Rust
139            let mc: &'static Mutation<'_> = &*(context.mutation_context() as *const _);
140            let root: Root<'static, R> = f(mc);
141            Arena { context, root }
142        }
143    }
144
145    /// Similar to `new`, but allows for constructor that can fail.
146    pub fn try_new<F, E>(f: F) -> Result<Arena<R>, E>
147    where
148        F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Result<Root<'gc, R>, E>,
149    {
150        unsafe {
151            let context = Box::new(Context::new());
152            let mc: &'static Mutation<'_> = &*(context.mutation_context() as *const _);
153            let root: Root<'static, R> = f(mc)?;
154            Ok(Arena { context, root })
155        }
156    }
157
158    /// The primary means of interacting with a garbage collected arena. Accepts a callback which
159    /// receives a `&Mutation<'gc>` and a reference to the root, and can return any non garbage
160    /// collected value. The callback may "mutate" any part of the object graph during this call,
161    /// but no garbage collection will take place during this method.
162    #[inline]
163    pub fn mutate<F, T>(&self, f: F) -> T
164    where
165        F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc Root<'gc, R>) -> T,
166    {
167        unsafe {
168            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
169            let root: &'static Root<'_, R> = &*(&self.root as *const _);
170            f(mc, root)
171        }
172    }
173
174    /// An alternative version of [`Arena::mutate`] which allows mutating the root set, at the
175    /// cost of an extra write barrier.
176    #[inline]
177    pub fn mutate_root<F, T>(&mut self, f: F) -> T
178    where
179        F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc mut Root<'gc, R>) -> T,
180    {
181        self.context.root_barrier();
182        unsafe {
183            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
184            let root: &'static mut Root<'_, R> = &mut *(&mut self.root as *mut _);
185            f(mc, root)
186        }
187    }
188
189    #[inline]
190    pub fn map_root<R2: for<'a> Rootable<'a>>(
191        self,
192        f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Root<'gc, R2>,
193    ) -> Arena<R2> {
194        self.context.root_barrier();
195        let new_root: Root<'static, R2> = unsafe {
196            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
197            f(mc, self.root)
198        };
199        Arena {
200            context: self.context,
201            root: new_root,
202        }
203    }
204
205    #[inline]
206    pub fn try_map_root<R2: for<'a> Rootable<'a>, E>(
207        self,
208        f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Result<Root<'gc, R2>, E>,
209    ) -> Result<Arena<R2>, E> {
210        self.context.root_barrier();
211        let new_root: Root<'static, R2> = unsafe {
212            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
213            f(mc, self.root)?
214        };
215        Ok(Arena {
216            context: self.context,
217            root: new_root,
218        })
219    }
220
221    #[inline]
222    pub fn metrics(&self) -> &Metrics {
223        self.context.metrics()
224    }
225
226    #[inline]
227    pub fn collection_phase(&self) -> CollectionPhase {
228        match self.context.phase() {
229            Phase::Mark => {
230                if self.context.gray_remaining() {
231                    CollectionPhase::Marking
232                } else {
233                    CollectionPhase::Marked
234                }
235            }
236            Phase::Sweep => CollectionPhase::Collecting,
237            Phase::Sleep => CollectionPhase::Sleeping,
238            Phase::Drop => unreachable!(),
239        }
240    }
241
242    /// Run incremental garbage collection until the allocation debt is <= 0.0.
243    ///
244    /// There is no minimum unit of work enforced here, so it may be faster to only call this method
245    /// when the allocation debt is above some threshold.
246    ///
247    /// This method will always return at least once when collection enters the `Sleeping` phase,
248    /// i.e. it will never transition from the `Collecting` phase to the `Marking` phase without
249    /// returning in-between.
250    #[inline]
251    pub fn collect_debt(&mut self) {
252        unsafe {
253            self.context.do_collection(&self.root, 0.0, None);
254        }
255    }
256
257    /// Run only the *marking* part of incremental garbage collection until allocation debt is
258    /// <= 0.0.
259    ///
260    /// This does *not* transition collection past the `Marked` phase. Does nothing if the
261    /// collection phase is `Marked` or `Collecting`, otherwise acts like `Arena::collect_debt`.
262    #[inline]
263    pub fn mark_debt(&mut self) -> Option<MarkedArena<'_, R>> {
264        if matches!(self.context.phase(), Phase::Mark | Phase::Sleep) {
265            unsafe {
266                self.context
267                    .do_collection(&self.root, 0.0, Some(EarlyStop::BeforeSweep));
268            }
269        }
270
271        if self.context.phase() == Phase::Mark && !self.context.gray_remaining() {
272            Some(MarkedArena(self))
273        } else {
274            None
275        }
276    }
277
278    /// Run the current garbage collection cycle to completion, stopping once garbage collection
279    /// has restarted in the sleep phase. If the collector is currently in the sleep phase, this
280    /// restarts the collection and performs a full collection before transitioning back to the
281    /// sleep phase.
282    #[inline]
283    pub fn collect_all(&mut self) {
284        unsafe {
285            self.context
286                .do_collection(&self.root, f64::NEG_INFINITY, None);
287        }
288    }
289
290    /// Runs all of the remaining *marking* part of the current garbage collection cycle.
291    ///
292    /// Similarly to `Arena::mark_debt`, this does not transition collection  past the `Marked`
293    /// phase, and does nothing if the collector is currently in the `Marked` phase or the
294    /// `Collecting` phase.
295    #[inline]
296    pub fn mark_all(&mut self) -> Option<MarkedArena<'_, R>> {
297        if matches!(self.context.phase(), Phase::Mark | Phase::Sleep) {
298            unsafe {
299                self.context.do_collection(
300                    &self.root,
301                    f64::NEG_INFINITY,
302                    Some(EarlyStop::BeforeSweep),
303                );
304            }
305        }
306
307        if self.context.phase() == Phase::Mark && !self.context.gray_remaining() {
308            Some(MarkedArena(self))
309        } else {
310            None
311        }
312    }
313}
314
315pub struct MarkedArena<'a, R: for<'b> Rootable<'b>>(&'a mut Arena<R>);
316
317impl<'a, R: for<'b> Rootable<'b>> MarkedArena<'a, R> {
318    /// Examine the state of a fully marked arena.
319    ///
320    /// Allows you to determine whether `GcWeak` pointers are "dead" (aka, soon-to-be-dropped) and
321    /// potentially resurrect them for this cycle.
322    ///
323    /// Note that the arena is guaranteed to be *fully marked* only at the *beginning* of this
324    /// callback, any mutation that resurrects a pointer or triggers a write barrier can immediately
325    /// invalidate this.
326    #[inline]
327    pub fn finalize<F, T>(self, f: F) -> T
328    where
329        F: for<'gc> FnOnce(&'gc Finalization<'gc>, &'gc Root<'gc, R>) -> T,
330    {
331        unsafe {
332            let mc: &'static Finalization<'_> =
333                &*(self.0.context.finalization_context() as *const _);
334            let root: &'static Root<'_, R> = &*(&self.0.root as *const _);
335            f(mc, root)
336        }
337    }
338
339    /// Immediately transition the arena out of `CollectionPhase::Marked` to
340    /// `CollectionPhase::Collecting`.
341    #[inline]
342    pub fn start_collecting(self) {
343        unsafe {
344            self.0.context.do_collection(
345                &self.0.root,
346                f64::NEG_INFINITY,
347                Some(EarlyStop::AfterSweep),
348            );
349        }
350        assert_eq!(self.0.context.phase(), Phase::Sweep);
351    }
352}
353
354/// Create a temporary arena without a root object and perform the given operation on it. No garbage
355/// collection will be done until the very end of the call, at which point all allocations will
356/// be collected.
357pub fn rootless_arena<F, R>(f: F) -> R
358where
359    F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> R,
360{
361    unsafe {
362        let context = Context::new();
363        f(context.mutation_context())
364    }
365}