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}