|
1 | | -use core::{f64, usize}; |
| 1 | +use core::{f64, mem::ManuallyDrop, usize}; |
2 | 2 |
|
3 | | -use crate::context::{Context, MutationContext}; |
| 3 | +use crate::{ |
| 4 | + context::{Context, MutationContext}, |
| 5 | + Collect, |
| 6 | +}; |
4 | 7 |
|
5 | 8 | #[derive(Debug, Clone)] |
6 | 9 | pub struct ArenaParameters { |
@@ -94,130 +97,153 @@ impl ArenaParameters { |
94 | 97 | #[macro_export] |
95 | 98 | macro_rules! make_arena { |
96 | 99 | ($arena:ident, $root:ident) => { |
97 | | - make_arena!(@impl pub(self) $arena, $root); |
| 100 | + make_arena!(pub(self) $arena, $root); |
98 | 101 | }; |
99 | 102 |
|
100 | | - ($v:vis $arena:ident, $root:ident) => { |
101 | | - make_arena!(@impl $v $arena, $root); |
| 103 | + ($vis:vis $arena:ident, $root:ident) => { |
| 104 | + // Instead of generating an impl of `RootProvider`, we use a trait object. |
| 105 | + // The projection `<R as RootProvider<'gc>>::Root` is used to obtain the root |
| 106 | + // type with the lifetime `'gc` applied |
| 107 | + // By using a trait object, we avoid the need to generate a new type for each |
| 108 | + // invocation of this macro, which would lead to name conflicts if the macro was |
| 109 | + // used multiple times in the same scope. |
| 110 | + $vis type $arena = $crate::Arena<dyn for<'a> $crate::RootProvider<'a, Root = $root<'a>>>; |
102 | 111 | }; |
| 112 | +} |
103 | 113 |
|
104 | | - (@impl $v:vis $arena:ident, $root:ident) => { |
105 | | - $v struct $arena { |
106 | | - context: $crate::Context, |
107 | | - root: ::core::mem::ManuallyDrop<$root<'static>>, |
108 | | - } |
| 114 | +#[doc(hidden)] |
| 115 | +/// A helper trait for the `make_arena` macro. Writing |
| 116 | +/// <R as RootProvider<'gc>>::Root is similar to writing a GAT `R::Root<'gc>`, |
| 117 | +/// but it works on older versions of Rust. Additionally, GATs currently |
| 118 | +/// prevent a trait from being object-safe, which prevents the trait object |
| 119 | +/// trick we use in `make_arena` from working. |
| 120 | +pub trait RootProvider<'a> { |
| 121 | + type Root: Collect; |
| 122 | +} |
109 | 123 |
|
110 | | - impl $arena { |
111 | | - /// Create a new arena with the given garbage collector tuning parameters. You must |
112 | | - /// provide a closure that accepts a `MutationContext` and returns the appropriate root. |
113 | | - /// The held root type is immutable inside the arena, in order to provide mutation, you |
114 | | - /// must use `GcCell` types inside the root. |
115 | | - #[allow(unused)] |
116 | | - pub fn new<F>(arena_parameters: $crate::ArenaParameters, f: F) -> $arena |
117 | | - where |
118 | | - F: for<'gc> FnOnce($crate::MutationContext<'gc, '_>) -> $root<'gc>, |
119 | | - { |
120 | | - unsafe { |
121 | | - let context = $crate::Context::new(arena_parameters); |
122 | | - let root: $root<'static> = ::std::mem::transmute(f(context.mutation_context())); |
123 | | - $arena { |
124 | | - context: context, |
125 | | - root: ::core::mem::ManuallyDrop::new(root), |
126 | | - } |
127 | | - } |
128 | | - } |
| 124 | +pub struct Arena<R: for<'a> RootProvider<'a> + ?Sized> { |
| 125 | + context: crate::Context, |
| 126 | + root: ManuallyDrop<<R as RootProvider<'static>>::Root>, |
| 127 | +} |
129 | 128 |
|
130 | | - /// Similar to `new`, but allows for constructor that can fail. |
131 | | - #[allow(unused)] |
132 | | - pub fn try_new<F, E>( |
133 | | - arena_parameters: $crate::ArenaParameters, |
134 | | - f: F, |
135 | | - ) -> Result<$arena, E> |
136 | | - where |
137 | | - F: for<'gc> FnOnce($crate::MutationContext<'gc, '_>) -> Result<$root<'gc>, E>, |
138 | | - { |
139 | | - unsafe { |
140 | | - let context = $crate::Context::new(arena_parameters); |
141 | | - let root: $root = f(context.mutation_context())?; |
142 | | - let root: $root<'static> = ::std::mem::transmute(root); |
143 | | - Ok($arena { |
144 | | - context: context, |
145 | | - root: ::core::mem::ManuallyDrop::new(root), |
146 | | - }) |
147 | | - } |
| 129 | +impl<R: for<'a> RootProvider<'a> + ?Sized> Arena<R> { |
| 130 | + /// Create a new arena with the given garbage collector tuning parameters. You must |
| 131 | + /// provide a closure that accepts a `MutationContext` and returns the appropriate root. |
| 132 | + /// The held root type is immutable inside the arena, in order to provide mutation, you |
| 133 | + /// must use `GcCell` types inside the root. |
| 134 | + #[allow(unused)] |
| 135 | + pub fn new<F>(arena_parameters: crate::ArenaParameters, f: F) -> Arena<R> |
| 136 | + where |
| 137 | + F: for<'gc> FnOnce(crate::MutationContext<'gc, '_>) -> <R as RootProvider<'gc>>::Root, |
| 138 | + { |
| 139 | + unsafe { |
| 140 | + let context = crate::Context::new(arena_parameters); |
| 141 | + // Note - we transmute the `MutationContext` to a `'static` lifetime here, |
| 142 | + // instead of transmuting the root type returned by `f`. Transmuting the root |
| 143 | + // type is allowed in nightly versions of rust |
| 144 | + // (see https://github.com/rust-lang/rust/pull/101520#issuecomment-1252016235) |
| 145 | + // but is not yet stable. Transmuting the `MutationContext` is completely invisible |
| 146 | + // to the callback `f` (since it needs to handle an arbitrary lifetime), |
| 147 | + // and lets us stay compatible with older versions of Rust |
| 148 | + let mutation_context: MutationContext<'static, '_> = |
| 149 | + ::core::mem::transmute(context.mutation_context()); |
| 150 | + let root: <R as RootProvider<'static>>::Root = f(mutation_context); |
| 151 | + Arena { |
| 152 | + context: context, |
| 153 | + root: ::core::mem::ManuallyDrop::new(root), |
148 | 154 | } |
| 155 | + } |
| 156 | + } |
149 | 157 |
|
150 | | - /// The primary means of interacting with a garbage collected arena. Accepts a callback |
151 | | - /// which receives a `MutationContext` and a reference to the root, and can return any |
152 | | - /// non garbage collected value. The callback may "mutate" any part of the object graph |
153 | | - /// during this call, but no garbage collection will take place during this method. |
154 | | - #[allow(unused)] |
155 | | - #[inline] |
156 | | - pub fn mutate<F, R>(&self, f: F) -> R |
157 | | - where |
158 | | - F: for<'gc> FnOnce($crate::MutationContext<'gc, '_>, &$root<'gc>) -> R, |
159 | | - { |
160 | | - unsafe { |
161 | | - f( |
162 | | - self.context.mutation_context(), |
163 | | - ::std::mem::transmute::<&$root<'static>, _>(&*self.root), |
164 | | - ) |
165 | | - } |
166 | | - } |
| 158 | + /// Similar to `new`, but allows for constructor that can fail. |
| 159 | + #[allow(unused)] |
| 160 | + pub fn try_new<F, E>(arena_parameters: crate::ArenaParameters, f: F) -> Result<Arena<R>, E> |
| 161 | + where |
| 162 | + F: for<'gc> FnOnce( |
| 163 | + crate::MutationContext<'gc, '_>, |
| 164 | + ) -> Result<<R as RootProvider<'gc>>::Root, E>, |
| 165 | + { |
| 166 | + unsafe { |
| 167 | + let context = crate::Context::new(arena_parameters); |
| 168 | + let mutation_context: MutationContext<'static, '_> = |
| 169 | + ::core::mem::transmute(context.mutation_context()); |
| 170 | + let root: <R as RootProvider<'static>>::Root = f(mutation_context)?; |
| 171 | + Ok(Arena { |
| 172 | + context: context, |
| 173 | + root: ::core::mem::ManuallyDrop::new(root), |
| 174 | + }) |
| 175 | + } |
| 176 | + } |
167 | 177 |
|
168 | | - /// Return total currently used memory |
169 | | - #[allow(unused)] |
170 | | - #[inline] |
171 | | - pub fn total_allocated(&self) -> usize { |
172 | | - self.context.total_allocated() |
173 | | - } |
| 178 | + /// The primary means of interacting with a garbage collected arena. Accepts a callback |
| 179 | + /// which receives a `MutationContext` and a reference to the root, and can return any |
| 180 | + /// non garbage collected value. The callback may "mutate" any part of the object graph |
| 181 | + /// during this call, but no garbage collection will take place during this method. |
| 182 | + #[allow(unused)] |
| 183 | + #[inline] |
| 184 | + pub fn mutate<F, T>(&self, f: F) -> T |
| 185 | + where |
| 186 | + F: for<'gc> FnOnce(crate::MutationContext<'gc, '_>, &<R as RootProvider<'gc>>::Root) -> T, |
| 187 | + { |
| 188 | + unsafe { |
| 189 | + f( |
| 190 | + self.context.mutation_context(), |
| 191 | + ::core::mem::transmute::<&<R as RootProvider<'static>>::Root, _>(&*self.root), |
| 192 | + ) |
| 193 | + } |
| 194 | + } |
174 | 195 |
|
175 | | - /// When the garbage collector is not sleeping, all allocated objects cause the arena to |
176 | | - /// accumulate "allocation debt". This debt is then be used to time incremental garbage |
177 | | - /// collection based on the tuning parameters set in `ArenaParameters`. The allocation |
178 | | - /// debt is measured in bytes, but will generally increase at a rate faster than that of |
179 | | - /// allocation so that collection will always complete. |
180 | | - #[allow(unused)] |
181 | | - #[inline] |
182 | | - pub fn allocation_debt(&self) -> f64 { |
183 | | - self.context.allocation_debt() |
184 | | - } |
| 196 | + /// Return total currently used memory |
| 197 | + #[allow(unused)] |
| 198 | + #[inline] |
| 199 | + pub fn total_allocated(&self) -> usize { |
| 200 | + self.context.total_allocated() |
| 201 | + } |
185 | 202 |
|
186 | | - /// Run the incremental garbage collector until the allocation debt is <= 0.0. There is |
187 | | - /// no minimum unit of work enforced here, so it may be faster to only call this method |
188 | | - /// when the allocation debt is above some threshold. |
189 | | - #[allow(unused)] |
190 | | - #[inline] |
191 | | - pub fn collect_debt(&mut self) { |
192 | | - unsafe { |
193 | | - let debt = self.context.allocation_debt(); |
194 | | - if debt > 0.0 { |
195 | | - self.context.do_collection(&*self.root, debt); |
196 | | - } |
197 | | - } |
198 | | - } |
| 203 | + /// When the garbage collector is not sleeping, all allocated objects cause the arena to |
| 204 | + /// accumulate "allocation debt". This debt is then be used to time incremental garbage |
| 205 | + /// collection based on the tuning parameters set in `ArenaParameters`. The allocation |
| 206 | + /// debt is measured in bytes, but will generally increase at a rate faster than that of |
| 207 | + /// allocation so that collection will always complete. |
| 208 | + #[allow(unused)] |
| 209 | + #[inline] |
| 210 | + pub fn allocation_debt(&self) -> f64 { |
| 211 | + self.context.allocation_debt() |
| 212 | + } |
199 | 213 |
|
200 | | - /// Run the current garbage collection cycle to completion, stopping once the garbage |
201 | | - /// collector has entered the sleeping phase. If the garbage collector is currently |
202 | | - /// sleeping, starts a new cycle and runs that cycle to completion. |
203 | | - #[allow(unused)] |
204 | | - pub fn collect_all(&mut self) { |
205 | | - self.context.wake(); |
206 | | - unsafe { |
207 | | - self.context |
208 | | - .do_collection(&*self.root, ::std::f64::INFINITY); |
209 | | - } |
| 214 | + /// Run the incremental garbage collector until the allocation debt is <= 0.0. There is |
| 215 | + /// no minimum unit of work enforced here, so it may be faster to only call this method |
| 216 | + /// when the allocation debt is above some threshold. |
| 217 | + #[allow(unused)] |
| 218 | + #[inline] |
| 219 | + pub fn collect_debt(&mut self) { |
| 220 | + unsafe { |
| 221 | + let debt = self.context.allocation_debt(); |
| 222 | + if debt > 0.0 { |
| 223 | + self.context.do_collection(&*self.root, debt); |
210 | 224 | } |
211 | 225 | } |
| 226 | + } |
212 | 227 |
|
213 | | - impl Drop for $arena { |
214 | | - fn drop(&mut self) { |
215 | | - unsafe { |
216 | | - ::core::mem::ManuallyDrop::drop(&mut self.root); |
217 | | - } |
218 | | - } |
| 228 | + /// Run the current garbage collection cycle to completion, stopping once the garbage |
| 229 | + /// collector has entered the sleeping phase. If the garbage collector is currently |
| 230 | + /// sleeping, starts a new cycle and runs that cycle to completion. |
| 231 | + #[allow(unused)] |
| 232 | + pub fn collect_all(&mut self) { |
| 233 | + self.context.wake(); |
| 234 | + unsafe { |
| 235 | + self.context |
| 236 | + .do_collection(&*self.root, ::core::f64::INFINITY); |
219 | 237 | } |
220 | | - }; |
| 238 | + } |
| 239 | +} |
| 240 | + |
| 241 | +impl<R: for<'a> RootProvider<'a> + ?Sized> Drop for Arena<R> { |
| 242 | + fn drop(&mut self) { |
| 243 | + unsafe { |
| 244 | + ::core::mem::ManuallyDrop::drop(&mut self.root); |
| 245 | + } |
| 246 | + } |
221 | 247 | } |
222 | 248 |
|
223 | 249 | /// Create a temporary arena without a root object and perform the given operation on it. No |
|
0 commit comments