gc-arena 0.6.0

safe, incrementally garbage collected arenas
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
use alloc::boxed::Box;
use core::marker::PhantomData;

use crate::{
    Collect,
    context::{Context, Finalization, Mutation, Phase, RunUntil, Stop},
    metrics::Metrics,
};

/// A trait that produces a [`Collect`]-able type for the given lifetime. This is used to produce
/// the root [`Collect`] instance in an [`Arena`].
///
/// In order to use an implementation of this trait in an [`Arena`], it must implement
/// `Rootable<'a>` for *any* possible `'a`. This is necessary so that the `Root` types can be
/// branded by the unique, invariant lifetimes that makes an `Arena` sound.
pub trait Rootable<'a> {
    type Root: ?Sized + 'a;
}

/// A marker type used by the `Rootable!` macro instead of a bare trait object.
///
/// Prevents having to include extra ?Sized bounds on every `for<'a> Rootable<'a>`.
#[doc(hidden)]
pub struct __DynRootable<T: ?Sized>(PhantomData<T>);

impl<'a, T: ?Sized + Rootable<'a>> Rootable<'a> for __DynRootable<T> {
    type Root = <T as Rootable<'a>>::Root;
}

/// A convenience macro for quickly creating a type that implements `Rootable`.
///
/// The macro takes a single argument, which should be a generic type with elided lifetimes.
/// When used as a root object, every instance of the elided lifetime will be replaced with
/// the branding lifetime.
///
/// ```
/// # use gc_arena::{Arena, Collect, Gc, Rootable};
/// #
/// # fn main() {
/// #[derive(Collect)]
/// #[collect(no_drop)]
/// struct MyRoot<'gc> {
///     ptr: Gc<'gc, i32>,
/// }
///
/// type MyArena = Arena<Rootable![MyRoot<'_>]>;
///
/// // If desired, the branding lifetime can also be explicitly named:
/// type MyArena2 = Arena<Rootable!['gc => MyRoot<'gc>]>;
/// # }
/// ```
///
/// The macro can also be used to create implementations of `Rootable` that use other generic
/// parameters, though in complex cases it may be better to implement `Rootable` directly.
///
/// ```
/// # use gc_arena::{Arena, Collect, Gc, Rootable};
/// #
/// # fn main() {
/// #[derive(Collect)]
/// #[collect(no_drop)]
/// struct MyGenericRoot<'gc, T> {
///     ptr: Gc<'gc, T>,
/// }
///
/// type MyGenericArena<T> = Arena<Rootable![MyGenericRoot<'_, T>]>;
/// # }
/// ```
#[macro_export]
macro_rules! Rootable {
    ($gc:lifetime => $root:ty) => {
        // Instead of generating an impl of `Rootable`, we use a trait object. Thus, we avoid the
        // need to generate a new type for each invocation of this macro.
        $crate::__DynRootable::<dyn for<$gc> $crate::Rootable<$gc, Root = $root>>
    };
    ($root:ty) => {
        $crate::Rootable!['__gc => $crate::__unelide_lifetimes!('__gc; $root)]
    };
}

/// A helper type alias for a `Rootable::Root` for a specific lifetime.
pub type Root<'a, R> = <R as Rootable<'a>>::Root;

#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum CollectionPhase {
    /// The arena is done with a collection cycle and is waiting to be restarted.
    Sleeping,
    /// The arena is currently tracing objects from the root to determine reachability.
    Marking,
    /// The arena has finished tracing, all reachable objects are marked. This may transition
    /// back to `Marking` if write barriers occur.
    Marked,
    /// The arena has determined a set of unreachable objects and has started freeing them. At this
    /// point, marking is no longer taking place so the root may have reachable, unmarked pointers.
    Sweeping,
}

/// A generic, garbage collected arena.
///
/// Garbage collected arenas allow for isolated sets of garbage collected objects with zero-overhead
/// garbage collected pointers. It provides incremental mark and sweep garbage collection which
/// must be manually triggered outside the `mutate` method, and works best when units of work inside
/// `mutate` can be kept relatively small. It is designed primarily to be a garbage collector for
/// scripting language runtimes.
///
/// The arena API is able to provide extremely cheap Gc pointers because it is based around
/// "generativity". During construction and access, the root type is branded by a unique, invariant
/// lifetime `'gc` which ensures that `Gc` pointers must be contained inside the root object
/// hierarchy and cannot escape the arena callbacks or be smuggled inside another arena. This way,
/// the arena can be sure that during mutation, all `Gc` pointers come from the arena we expect
/// them to come from, and that they're all either reachable from root or have been allocated during
/// the current `mutate` call. When not inside the `mutate` callback, the arena knows that all `Gc`
/// pointers must be either reachable from root or they are unreachable and safe to collect. In
/// this way, incremental garbage collection can be achieved (assuming "sufficiently small" calls
/// to `mutate`) that is both extremely safe and zero overhead vs what you would write in C with raw
/// pointers and manually ensuring that invariants are held.
pub struct Arena<R>
where
    R: for<'a> Rootable<'a>,
{
    context: Box<Context>,
    root: Root<'static, R>,
}

impl<R> Arena<R>
where
    R: for<'a> Rootable<'a>,
    for<'a> Root<'a, R>: Sized,
{
    /// Create a new arena with the given garbage collector tuning parameters. You must provide a
    /// closure that accepts a `&Mutation<'gc>` and returns the appropriate root.
    pub fn new<F>(f: F) -> Arena<R>
    where
        F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Root<'gc, R>,
    {
        unsafe {
            let context = Box::new(Context::new());
            // Note - we cast the `&Mutation` to a `'static` lifetime here,
            // instead of transmuting the root type returned by `f`. Transmuting the root
            // type is allowed in nightly versions of rust
            // (see https://github.com/rust-lang/rust/pull/101520#issuecomment-1252016235)
            // but is not yet stable. Casting the `&Mutation` is completely invisible
            // to the callback `f` (since it needs to handle an arbitrary lifetime),
            // and lets us stay compatible with older versions of Rust
            let mc: &'static Mutation<'_> = &*(context.mutation_context() as *const _);
            let root: Root<'static, R> = f(mc);
            Arena { context, root }
        }
    }

    /// Similar to `new`, but allows for constructor that can fail.
    pub fn try_new<F, E>(f: F) -> Result<Arena<R>, E>
    where
        F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Result<Root<'gc, R>, E>,
    {
        unsafe {
            let context = Box::new(Context::new());
            let mc: &'static Mutation<'_> = &*(context.mutation_context() as *const _);
            let root: Root<'static, R> = f(mc)?;
            Ok(Arena { context, root })
        }
    }

    #[inline]
    pub fn map_root<R2>(
        mut self,
        f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Root<'gc, R2>,
    ) -> Arena<R2>
    where
        R2: for<'a> Rootable<'a>,
        for<'a> Root<'a, R2>: Sized,
    {
        self.context.root_barrier();
        let new_root: Root<'static, R2> = unsafe {
            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
            f(mc, self.root)
        };
        Arena {
            context: self.context,
            root: new_root,
        }
    }

    #[inline]
    pub fn try_map_root<R2, E>(
        mut self,
        f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Result<Root<'gc, R2>, E>,
    ) -> Result<Arena<R2>, E>
    where
        R2: for<'a> Rootable<'a>,
        for<'a> Root<'a, R2>: Sized,
    {
        self.context.root_barrier();
        let new_root: Root<'static, R2> = unsafe {
            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
            f(mc, self.root)?
        };
        Ok(Arena {
            context: self.context,
            root: new_root,
        })
    }
}

impl<R> Arena<R>
where
    R: for<'a> Rootable<'a>,
{
    /// The primary means of interacting with a garbage collected arena. Accepts a callback which
    /// receives a `&Mutation<'gc>` and a reference to the root, and can return any non garbage
    /// collected value. The callback may "mutate" any part of the object graph during this call,
    /// but no garbage collection will take place during this method.
    #[inline]
    pub fn mutate<F, T>(&self, f: F) -> T
    where
        F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc Root<'gc, R>) -> T,
    {
        unsafe {
            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
            let root: &'static Root<'_, R> = &*(&self.root as *const _);
            f(mc, root)
        }
    }

    /// An alternative version of [`Arena::mutate`] which allows mutating the root set, at the
    /// cost of an extra write barrier.
    #[inline]
    pub fn mutate_root<F, T>(&mut self, f: F) -> T
    where
        F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc mut Root<'gc, R>) -> T,
    {
        self.context.root_barrier();
        unsafe {
            let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
            let root: &'static mut Root<'_, R> = &mut *(&mut self.root as *mut _);
            f(mc, root)
        }
    }

    #[inline]
    pub fn metrics(&self) -> &Metrics {
        self.context.metrics()
    }

    #[inline]
    pub fn collection_phase(&self) -> CollectionPhase {
        match self.context.phase() {
            Phase::Mark => {
                if self.context.gray_remaining() {
                    CollectionPhase::Marking
                } else {
                    CollectionPhase::Marked
                }
            }
            Phase::Sweep => CollectionPhase::Sweeping,
            Phase::Sleep => CollectionPhase::Sleeping,
            Phase::Drop => unreachable!(),
        }
    }
}

impl<R> Arena<R>
where
    R: for<'a> Rootable<'a>,
    for<'a> Root<'a, R>: Collect<'a>,
{
    /// Run incremental garbage collection until the allocation debt is zero.
    ///
    /// This will run through ALL phases of the collection cycle until the debt is zero, including
    /// implicitly finishing the current cycle and starting a new one (transitioning from
    /// [`CollectionPhase::Sweeping`] to [`CollectionPhase::Sleeping`]). Since this method runs
    /// until debt is zero with no guaranteed return at any specific transition, you may need to use
    /// other methods like [`Arena::mark_debt`] and [`Arena::cycle_debt`] if you need to keep close
    /// track of the current collection phase.
    ///
    /// There is no minimum unit of work enforced here, so it may be faster to only call this method
    /// when the allocation debt is above some minimum threshold.
    #[inline]
    pub fn collect_debt(&mut self) {
        unsafe {
            self.context
                .do_collection(&self.root, RunUntil::PayDebt, Stop::Full);
        }
    }

    /// Run only the *marking* part of incremental garbage collection until allocation debt is zero.
    ///
    /// This does *not* transition collection past the [`CollectionPhase::Marked`]
    /// phase. Does nothing if the collection phase is [`CollectionPhase::Marked`] or
    /// [`CollectionPhase::Sweeping`], otherwise acts like [`Arena::collect_debt`].
    ///
    /// If this method stops because the arena is now fully marked (the collection phase is
    /// [`CollectionPhase::Marked`]), then a [`MarkedArena`] object will be returned to allow
    /// you to examine the state of the fully marked arena.
    #[inline]
    pub fn mark_debt(&mut self) -> Option<MarkedArena<'_, R>> {
        unsafe {
            self.context
                .do_collection(&self.root, RunUntil::PayDebt, Stop::FullyMarked);
        }

        if self.context.phase() == Phase::Mark && !self.context.gray_remaining() {
            Some(MarkedArena(self))
        } else {
            None
        }
    }

    /// Runs ALL of the remaining *marking* part of the current garbage collection cycle.
    ///
    /// Similarly to [`Arena::mark_debt`], this does not transition collection past the
    /// [`CollectionPhase::Marked`] phase, and does nothing if the collector is currently in the
    /// [`CollectionPhase::Marked`] phase or the [`CollectionPhase::Sweeping`] phase.
    ///
    /// This method will always fully mark the arena and return a [`MarkedArena`] object as long as
    /// the current phase is not [`CollectionPhase::Sweeping`].
    #[inline]
    pub fn finish_marking(&mut self) -> Option<MarkedArena<'_, R>> {
        unsafe {
            self.context
                .do_collection(&self.root, RunUntil::Stop, Stop::FullyMarked);
        }

        if self.context.phase() == Phase::Mark && !self.context.gray_remaining() {
            Some(MarkedArena(self))
        } else {
            None
        }
    }

    /// Run the *current* collection cycle until the allocation debt is zero.
    ///
    /// This is nearly identical to the [`Arena::collect_debt`] method, except it
    /// *always* returns immediately when a cycle is finished (when phase transitions
    /// to [`CollectionPhase::Sleeping`]), and will never transition directly from
    /// [`CollectionPhase::Sweeping`] to [`CollectionPhase::Marking`] within a single call, even if
    /// there is enough outstanding debt to do so.
    ///
    /// This mostly only important when the user of an `Arena` needs to closely track collection
    /// phases, otherwise [`Arena::collect_debt`] simpler to use.
    #[inline]
    pub fn cycle_debt(&mut self) {
        unsafe {
            self.context
                .do_collection(&self.root, RunUntil::PayDebt, Stop::FinishCycle);
        }
    }

    /// Run the current garbage collection cycle to completion, stopping once garbage collection
    /// has entered the [`CollectionPhase::Sleeping`] phase. If the collector is currently sleeping,
    /// then this restarts the collector and performs a full collection before transitioning back to
    /// the sleep phase.
    #[inline]
    pub fn finish_cycle(&mut self) {
        unsafe {
            self.context
                .do_collection(&self.root, RunUntil::Stop, Stop::FinishCycle);
        }
    }
}

pub struct MarkedArena<'a, R: for<'b> Rootable<'b>>(&'a mut Arena<R>);

impl<'a, R> MarkedArena<'a, R>
where
    R: for<'b> Rootable<'b>,
    for<'b> Root<'b, R>: Collect<'b>,
{
    /// Examine the state of a fully marked arena.
    ///
    /// Allows you to determine whether `GcWeak` pointers are "dead" (aka, soon-to-be-dropped) and
    /// potentially resurrect them for this cycle.
    ///
    /// Note that the arena is guaranteed to be *fully marked* only at the *beginning* of this
    /// callback, any mutation that resurrects a pointer or triggers a write barrier can immediately
    /// invalidate this.
    #[inline]
    pub fn finalize<F, T>(self, f: F) -> T
    where
        F: for<'gc> FnOnce(&'gc Finalization<'gc>, &'gc Root<'gc, R>) -> T,
    {
        unsafe {
            let mc: &'static Finalization<'_> =
                &*(self.0.context.finalization_context() as *const _);
            let root: &'static Root<'_, R> = &*(&self.0.root as *const _);
            f(mc, root)
        }
    }

    /// Immediately transition the arena out of [`CollectionPhase::Marked`] to
    /// [`CollectionPhase::Sweeping`].
    #[inline]
    pub fn start_sweeping(self) {
        unsafe {
            self.0
                .context
                .do_collection(&self.0.root, RunUntil::Stop, Stop::AtSweep);
        }
        assert_eq!(self.0.context.phase(), Phase::Sweep);
    }
}

/// Create a temporary arena without a root object and perform the given operation on it.
///
/// No garbage collection will be done until the very end of the call, at which point all
/// allocations will be collected.
///
/// This is a convenience function that makes it a little easier to quickly test code that uses
/// `gc-arena`, it is not very useful on its own.
pub fn rootless_mutate<F, R>(f: F) -> R
where
    F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> R,
{
    unsafe {
        let context = Context::new();
        f(context.mutation_context())
    }
}