aiscript_arena/arena.rs
1use alloc::boxed::Box;
2use core::{f64, marker::PhantomData};
3
4use crate::{
5 Collect,
6 context::{Context, EarlyStop, Finalization, Mutation, Phase},
7 metrics::Metrics,
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> {
17 type Root: ?Sized + '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 aiscript_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 aiscript_arena::{Arena, Collect, Gc, Rootable};
58/// #
59/// # fn main() {
60/// #[derive(Collect)]
61/// #[collect(no_drop)]
62/// struct MyGenericRoot<'gc, T> {
63/// ptr: Gc<'gc, 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, Ord, PartialOrd)]
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 freeing them. At this
94 /// point, marking is no longer taking place so the root may have reachable, unmarked pointers.
95 Sweeping,
96}
97
98/// A generic, garbage collected arena.
99///
100/// Garbage collected arenas allow for isolated sets of garbage collected objects with zero-overhead
101/// garbage collected pointers. It provides incremental mark and sweep garbage collection which
102/// must be manually triggered outside the `mutate` method, and works best when units of work inside
103/// `mutate` can be kept relatively small. It is designed primarily to be a garbage collector for
104/// scripting language runtimes.
105///
106/// The arena API is able to provide extremely cheap Gc pointers because it is based around
107/// "generativity". During construction and access, the root type is branded by a unique, invariant
108/// lifetime `'gc` which ensures that `Gc` pointers must be contained inside the root object
109/// hierarchy and cannot escape the arena callbacks or be smuggled inside another arena. This way,
110/// the arena can be sure that during mutation, all `Gc` pointers come from the arena we expect
111/// them to come from, and that they're all either reachable from root or have been allocated during
112/// the current `mutate` call. When not inside the `mutate` callback, the arena knows that all `Gc`
113/// pointers must be either reachable from root or they are unreachable and safe to collect. In
114/// this way, incremental garbage collection can be achieved (assuming "sufficiently small" calls
115/// to `mutate`) that is both extremely safe and zero overhead vs what you would write in C with raw
116/// pointers and manually ensuring that invariants are held.
117pub struct Arena<R>
118where
119 R: for<'a> Rootable<'a>,
120{
121 context: Box<Context>,
122 root: Root<'static, R>,
123}
124
125impl<R> Arena<R>
126where
127 R: for<'a> Rootable<'a>,
128 for<'a> Root<'a, R>: Sized,
129{
130 /// Create a new arena with the given garbage collector tuning parameters. You must provide a
131 /// closure that accepts a `&Mutation<'gc>` and returns the appropriate root.
132 pub fn new<F>(f: F) -> Arena<R>
133 where
134 F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Root<'gc, R>,
135 {
136 unsafe {
137 let context = Box::new(Context::new());
138 // Note - we cast the `&Mutation` to a `'static` lifetime here,
139 // instead of transmuting the root type returned by `f`. Transmuting the root
140 // type is allowed in nightly versions of rust
141 // (see https://github.com/rust-lang/rust/pull/101520#issuecomment-1252016235)
142 // but is not yet stable. Casting the `&Mutation` is completely invisible
143 // to the callback `f` (since it needs to handle an arbitrary lifetime),
144 // and lets us stay compatible with older versions of Rust
145 let mc: &'static Mutation<'_> = &*(context.mutation_context() as *const _);
146 let root: Root<'static, R> = f(mc);
147 Arena { context, root }
148 }
149 }
150
151 /// Similar to `new`, but allows for constructor that can fail.
152 pub fn try_new<F, E>(f: F) -> Result<Arena<R>, E>
153 where
154 F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Result<Root<'gc, R>, E>,
155 {
156 unsafe {
157 let context = Box::new(Context::new());
158 let mc: &'static Mutation<'_> = &*(context.mutation_context() as *const _);
159 let root: Root<'static, R> = f(mc)?;
160 Ok(Arena { context, root })
161 }
162 }
163
164 #[inline]
165 pub fn map_root<R2>(
166 self,
167 f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Root<'gc, R2>,
168 ) -> Arena<R2>
169 where
170 R2: for<'a> Rootable<'a>,
171 for<'a> Root<'a, R2>: Sized,
172 {
173 self.context.root_barrier();
174 let new_root: Root<'static, R2> = unsafe {
175 let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
176 f(mc, self.root)
177 };
178 Arena {
179 context: self.context,
180 root: new_root,
181 }
182 }
183
184 #[inline]
185 pub fn try_map_root<R2, E>(
186 self,
187 f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Result<Root<'gc, R2>, E>,
188 ) -> Result<Arena<R2>, E>
189 where
190 R2: for<'a> Rootable<'a>,
191 for<'a> Root<'a, R2>: Sized,
192 {
193 self.context.root_barrier();
194 let new_root: Root<'static, R2> = unsafe {
195 let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
196 f(mc, self.root)?
197 };
198 Ok(Arena {
199 context: self.context,
200 root: new_root,
201 })
202 }
203}
204
205impl<R> Arena<R>
206where
207 R: for<'a> Rootable<'a>,
208{
209 /// The primary means of interacting with a garbage collected arena. Accepts a callback which
210 /// receives a `&Mutation<'gc>` and a reference to the root, and can return any non garbage
211 /// collected value. The callback may "mutate" any part of the object graph during this call,
212 /// but no garbage collection will take place during this method.
213 #[inline]
214 pub fn mutate<F, T>(&self, f: F) -> T
215 where
216 F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc Root<'gc, R>) -> T,
217 {
218 unsafe {
219 let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
220 let root: &'static Root<'_, R> = &*(&self.root as *const _);
221 f(mc, root)
222 }
223 }
224
225 /// An alternative version of [`Arena::mutate`] which allows mutating the root set, at the
226 /// cost of an extra write barrier.
227 #[inline]
228 pub fn mutate_root<F, T>(&mut self, f: F) -> T
229 where
230 F: for<'gc> FnOnce(&'gc Mutation<'gc>, &'gc mut Root<'gc, R>) -> T,
231 {
232 self.context.root_barrier();
233 unsafe {
234 let mc: &'static Mutation<'_> = &*(self.context.mutation_context() as *const _);
235 let root: &'static mut Root<'_, R> = &mut *(&mut self.root as *mut _);
236 f(mc, root)
237 }
238 }
239
240 #[inline]
241 pub fn metrics(&self) -> &Metrics {
242 self.context.metrics()
243 }
244
245 #[inline]
246 pub fn collection_phase(&self) -> CollectionPhase {
247 match self.context.phase() {
248 Phase::Mark => {
249 if self.context.gray_remaining() {
250 CollectionPhase::Marking
251 } else {
252 CollectionPhase::Marked
253 }
254 }
255 Phase::Sweep => CollectionPhase::Sweeping,
256 Phase::Sleep => CollectionPhase::Sleeping,
257 Phase::Drop => unreachable!(),
258 }
259 }
260}
261
262impl<R> Arena<R>
263where
264 R: for<'a> Rootable<'a>,
265 for<'a> Root<'a, R>: Collect,
266{
267 /// Run incremental garbage collection until the allocation debt is <= 0.0.
268 ///
269 /// There is no minimum unit of work enforced here, so it may be faster to only call this method
270 /// when the allocation debt is above some threshold.
271 ///
272 /// This method will always return at least once when collection enters the `Sleeping` phase,
273 /// i.e. it will never transition from the `Sweeping` phase to the `Marking` phase without
274 /// returning in-between.
275 #[inline]
276 pub fn collect_debt(&mut self) {
277 unsafe {
278 self.context.do_collection(&self.root, 0.0, None);
279 }
280 }
281
282 /// Run only the *marking* part of incremental garbage collection until allocation debt is
283 /// <= 0.0.
284 ///
285 /// This does *not* transition collection past the `Marked` phase. Does nothing if the
286 /// collection phase is `Marked` or `Sweeping`, otherwise acts like `Arena::collect_debt`.
287 #[inline]
288 pub fn mark_debt(&mut self) -> Option<MarkedArena<'_, R>> {
289 if matches!(self.context.phase(), Phase::Mark | Phase::Sleep) {
290 unsafe {
291 self.context
292 .do_collection(&self.root, 0.0, Some(EarlyStop::BeforeSweep));
293 }
294 }
295
296 if self.context.phase() == Phase::Mark && !self.context.gray_remaining() {
297 Some(MarkedArena(self))
298 } else {
299 None
300 }
301 }
302
303 /// Run the current garbage collection cycle to completion, stopping once garbage collection
304 /// has restarted in the sleep phase. If the collector is currently in the sleep phase, this
305 /// restarts the collection and performs a full collection before transitioning back to the
306 /// sleep phase.
307 #[inline]
308 pub fn collect_all(&mut self) {
309 unsafe {
310 self.context
311 .do_collection(&self.root, f64::NEG_INFINITY, None);
312 }
313 }
314
315 /// Runs all of the remaining *marking* part of the current garbage collection cycle.
316 ///
317 /// Similarly to `Arena::mark_debt`, this does not transition collection past the `Marked`
318 /// phase, and does nothing if the collector is currently in the `Marked` phase or the
319 /// `Sweeping` phase.
320 #[inline]
321 pub fn mark_all(&mut self) -> Option<MarkedArena<'_, R>> {
322 if matches!(self.context.phase(), Phase::Mark | Phase::Sleep) {
323 unsafe {
324 self.context.do_collection(
325 &self.root,
326 f64::NEG_INFINITY,
327 Some(EarlyStop::BeforeSweep),
328 );
329 }
330 }
331
332 if self.context.phase() == Phase::Mark && !self.context.gray_remaining() {
333 Some(MarkedArena(self))
334 } else {
335 None
336 }
337 }
338}
339
340pub struct MarkedArena<'a, R: for<'b> Rootable<'b>>(&'a mut Arena<R>);
341
342impl<R> MarkedArena<'_, R>
343where
344 R: for<'b> Rootable<'b>,
345 for<'b> Root<'b, R>: Collect,
346{
347 /// Examine the state of a fully marked arena.
348 ///
349 /// Allows you to determine whether `GcWeak` pointers are "dead" (aka, soon-to-be-dropped) and
350 /// potentially resurrect them for this cycle.
351 ///
352 /// Note that the arena is guaranteed to be *fully marked* only at the *beginning* of this
353 /// callback, any mutation that resurrects a pointer or triggers a write barrier can immediately
354 /// invalidate this.
355 #[inline]
356 pub fn finalize<F, T>(self, f: F) -> T
357 where
358 F: for<'gc> FnOnce(&'gc Finalization<'gc>, &'gc Root<'gc, R>) -> T,
359 {
360 unsafe {
361 let mc: &'static Finalization<'_> =
362 &*(self.0.context.finalization_context() as *const _);
363 let root: &'static Root<'_, R> = &*(&self.0.root as *const _);
364 f(mc, root)
365 }
366 }
367
368 /// Immediately transition the arena out of [`CollectionPhase::Marked`] to
369 /// [`CollectionPhase::Sweeping`].
370 #[inline]
371 pub fn start_sweeping(self) {
372 unsafe {
373 self.0.context.do_collection(
374 &self.0.root,
375 f64::NEG_INFINITY,
376 Some(EarlyStop::AfterSweep),
377 );
378 }
379 assert_eq!(self.0.context.phase(), Phase::Sweep);
380 }
381}
382
383/// Create a temporary arena without a root object and perform the given operation on it.
384///
385/// No garbage collection will be done until the very end of the call, at which point all
386/// allocations will be collected.
387///
388/// This is a convenience function that makes it a little easier to quickly test code that uses
389/// `gc-arena`, it is not very useful on its own.
390pub fn rootless_mutate<F, R>(f: F) -> R
391where
392 F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> R,
393{
394 unsafe {
395 let context = Context::new();
396 f(context.mutation_context())
397 }
398}