-
Notifications
You must be signed in to change notification settings - Fork 59
Expand file tree
/
Copy patharena.rs
More file actions
349 lines (324 loc) · 13.3 KB
/
arena.rs
File metadata and controls
349 lines (324 loc) · 13.3 KB
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
use alloc::boxed::Box;
use core::{f64, marker::PhantomData};
use crate::{
context::{Context, Finalization, Mutation, Phase},
metrics::Metrics,
Collect,
};
/// 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>: 'static {
type Root: Collect + '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 explicitely 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, StaticCollect};
/// #
/// # fn main() {
/// #[derive(Collect)]
/// #[collect(no_drop)]
/// struct MyGenericRoot<'gc, T: 'static> {
/// ptr: Gc<'gc, StaticCollect<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)]
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 collecting them.
/// At this point, marking is no longer taking place so the root may have reachable, unmarked
/// pointers
Collecting,
}
/// 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: for<'a> Rootable<'a>> {
context: Box<Context>,
root: Root<'static, R>,
}
impl<R: for<'a> Rootable<'a>> Arena<R> {
/// 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 })
}
}
/// 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 map_root<R2: for<'a> Rootable<'a>>(
self,
f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Root<'gc, R2>,
) -> Arena<R2> {
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: for<'a> Rootable<'a>, E>(
self,
f: impl for<'gc> FnOnce(&'gc Mutation<'gc>, Root<'gc, R>) -> Result<Root<'gc, R2>, E>,
) -> Result<Arena<R2>, E> {
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,
})
}
#[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::Collecting,
Phase::Sleep => CollectionPhase::Sleeping,
Phase::Drop => unreachable!(),
}
}
/// Run incremental garbage collection until the allocation debt is <= 0.0.
///
/// 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 threshold.
///
/// This method will always return at least once when collection enters the `Sleeping` phase,
/// i.e. it will never transition from the `Collecting` phase to the `Marking` phase without
/// returning in-between.
#[inline]
pub fn collect_debt(&mut self) {
unsafe {
self.context.do_collection(&self.root, 0.0, false);
}
}
/// Run only the *marking* part of incremental garbage collection until allocation debt is
/// <= 0.0.
///
/// This does *not* transition collection past the `Marked` phase. Does nothing if the
/// collection phase is `Marked` or `Collecting`, otherwise acts like `Arena::collect_debt`.
#[inline]
pub fn mark_debt(&mut self) -> Option<MarkedArena<'_, R>> {
if matches!(self.context.phase(), Phase::Mark | Phase::Sleep) {
unsafe {
self.context.do_collection(&self.root, 0.0, true);
}
}
debug_assert!(self.context.phase() == Phase::Mark);
if !self.context.gray_remaining() {
Some(MarkedArena(self))
} else {
None
}
}
/// Run the current garbage collection cycle to completion, stopping once garbage collection
/// has restarted in the sleep phase. If the collector is currently in the sleep phase, this
/// restarts the collection and performs a full collection before transitioning back to the
/// sleep phase.
#[inline]
pub fn collect_all(&mut self) {
unsafe {
self.context
.do_collection(&self.root, f64::NEG_INFINITY, false);
}
}
/// 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 `Marked`
/// phase, and does nothing if the collector is currently in the `Marked` phase or the
/// `Collecting` phase.
#[inline]
pub fn mark_all(&mut self) -> Option<MarkedArena<'_, R>> {
if matches!(self.context.phase(), Phase::Mark | Phase::Sleep) {
unsafe {
self.context
.do_collection(&self.root, f64::NEG_INFINITY, true);
}
}
debug_assert!(self.context.phase() == Phase::Mark);
if !self.context.gray_remaining() {
Some(MarkedArena(self))
} else {
None
}
}
}
pub struct MarkedArena<'a, R: for<'b> Rootable<'b>>(&'a Arena<R>);
impl<'a, R: for<'b> Rootable<'b>> MarkedArena<'a, R> {
/// 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)
}
}
}
/// 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.
pub fn rootless_arena<F, R>(f: F) -> R
where
F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> R,
{
unsafe {
let context = Context::new();
f(context.mutation_context())
}
}