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
use alloc::boxed::Box;
use core::{f64, marker::PhantomData, usize};
use crate::{
context::{Context, Mutation},
Collect,
};
/// Tuning parameters for a given garbage collected [`Arena`].
#[derive(Debug, Clone)]
pub struct ArenaParameters {
pub(crate) pause_factor: f64,
pub(crate) timing_factor: f64,
pub(crate) min_sleep: usize,
}
/// Creates a default ArenaParameters with `pause_factor` set to 0.5, `timing_factor` set to 1.5,
/// and `min_sleep` set to 4096.
impl Default for ArenaParameters {
fn default() -> ArenaParameters {
const PAUSE_FACTOR: f64 = 0.5;
const TIMING_FACTOR: f64 = 1.5;
const MIN_SLEEP: usize = 4096;
ArenaParameters {
pause_factor: PAUSE_FACTOR,
timing_factor: TIMING_FACTOR,
min_sleep: MIN_SLEEP,
}
}
}
impl ArenaParameters {
/// The garbage collector will wait until the live size reaches <current heap size> + <previous
/// retained size> * `pause_multiplier` before beginning a new collection. Must be >= 0.0,
/// setting this to 0.0 causes the collector to never sleep longer than `min_sleep` before
/// beginning a new collection.
pub fn set_pause_factor(mut self, pause_factor: f64) -> ArenaParameters {
assert!(pause_factor >= 0.0);
self.pause_factor = pause_factor;
self
}
/// The garbage collector will try and finish a collection by the time <current heap size> *
/// `timing_factor` additional bytes are allocated. For example, if the collection is started
/// when the arena has 100KB live data, and the timing_multiplier is 1.0, the collector should
/// finish its final phase of this collection after another 100KB has been allocated. Must be >=
/// 0.0, setting this to 0.0 causes the collector to behave like a stop-the-world collector.
pub fn set_timing_factor(mut self, timing_factor: f64) -> ArenaParameters {
assert!(timing_factor >= 0.0);
self.timing_factor = timing_factor;
self
}
/// The minimum allocation amount during sleep before the arena starts collecting again. This is
/// mostly useful when the heap is very small to prevent rapidly restarting collections.
pub fn set_min_sleep(mut self, min_sleep: usize) -> ArenaParameters {
self.min_sleep = min_sleep;
self
}
}
/// 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 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 instances 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;
/// 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>> {
// We rely on the implicit drop order here, `root` *must* be dropped before `context`!
root: Root<'static, R>,
context: Box<Context>,
}
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>(arena_parameters: ArenaParameters, f: F) -> Arena<R>
where
F: for<'gc> FnOnce(&'gc Mutation<'gc>) -> Root<'gc, R>,
{
unsafe {
let context = Box::new(Context::new(arena_parameters));
// 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>(arena_parameters: ArenaParameters, 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(arena_parameters));
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,
})
}
/// Return total currently used memory.
#[inline]
pub fn total_allocated(&self) -> usize {
self.context.total_allocated()
}
#[inline]
pub fn remembered_size(&self) -> usize {
self.context.remembered_size()
}
/// When the garbage collector is not sleeping, all allocated objects cause the arena to
/// accumulate "allocation debt". This debt is then be used to time incremental garbage
/// collection based on the tuning parameters set in `ArenaParameters`. The allocation debt is
/// measured in bytes, but will generally increase at a rate faster than that of allocation so
/// that collection will always complete.
#[inline]
pub fn allocation_debt(&self) -> f64 {
self.context.allocation_debt()
}
/// Run the incremental garbage collector 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.
#[inline]
pub fn collect_debt(&mut self) {
unsafe {
let debt = self.context.allocation_debt();
if debt > 0.0 {
self.context.do_collection(&self.root, debt);
}
}
}
/// Run the current garbage collection cycle to completion, stopping once the garbage collector
/// has entered the sleeping phase. If the garbage collector is currently sleeping, starts a new
/// cycle and runs that cycle to completion.
#[inline]
pub fn collect_all(&mut self) {
self.context.wake();
unsafe {
self.context.do_collection(&self.root, f64::INFINITY);
}
}
}
/// 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(ArenaParameters::default());
f(context.mutation_context())
}
}