1use core::{cell::RefCell, fmt, mem};
2
3use alloc::{
4 rc::{Rc, Weak},
5 vec::Vec,
6};
7
8use crate::{Collect, Gc, Mutation, Rootable, arena::Root, metrics::Metrics};
9
10#[derive(Copy, Clone)]
26pub struct DynamicRootSet<'gc>(Gc<'gc, Inner<'gc>>);
27
28unsafe impl Collect for DynamicRootSet<'_> {
29 fn trace(&self, cc: &crate::Collection) {
30 self.0.trace(cc);
31 }
32}
33
34impl<'gc> DynamicRootSet<'gc> {
35 pub fn new(mc: &Mutation<'gc>) -> Self {
37 DynamicRootSet(Gc::new(
38 mc,
39 Inner {
40 slots: Rc::new(RefCell::new(Slots::new(mc.metrics().clone()))),
41 },
42 ))
43 }
44
45 pub fn stash<R: for<'a> Rootable<'a>>(
50 &self,
51 mc: &Mutation<'gc>,
52 root: Gc<'gc, Root<'gc, R>>,
53 ) -> DynamicRoot<R> {
54 mc.backward_barrier(Gc::erase(self.0), Some(Gc::erase(root)));
56
57 let mut slots = self.0.slots.borrow_mut();
58 let index = slots.add(unsafe { Gc::cast(root) });
59
60 let ptr =
61 unsafe { mem::transmute::<Gc<'gc, Root<'gc, R>>, Gc<'static, Root<'static, R>>>(root) };
62 let slots = unsafe {
63 mem::transmute::<Weak<RefCell<Slots<'gc>>>, Weak<RefCell<Slots<'static>>>>(
64 Rc::downgrade(&self.0.slots),
65 )
66 };
67
68 DynamicRoot { ptr, slots, index }
69 }
70
71 #[inline]
78 pub fn fetch<R: for<'r> Rootable<'r>>(&self, root: &DynamicRoot<R>) -> Gc<'gc, Root<'gc, R>> {
79 if self.contains(root) {
80 unsafe {
81 mem::transmute::<Gc<'static, Root<'static, R>>, Gc<'gc, Root<'gc, R>>>(root.ptr)
82 }
83 } else {
84 panic!("mismatched root set")
85 }
86 }
87
88 #[inline]
91 pub fn try_fetch<R: for<'r> Rootable<'r>>(
92 &self,
93 root: &DynamicRoot<R>,
94 ) -> Result<Gc<'gc, Root<'gc, R>>, MismatchedRootSet> {
95 if self.contains(root) {
96 Ok(unsafe {
97 mem::transmute::<Gc<'static, Root<'static, R>>, Gc<'gc, Root<'gc, R>>>(root.ptr)
98 })
99 } else {
100 Err(MismatchedRootSet(()))
101 }
102 }
103
104 #[inline]
106 pub fn contains<R: for<'r> Rootable<'r>>(&self, root: &DynamicRoot<R>) -> bool {
107 let ours = unsafe {
113 mem::transmute::<*const RefCell<Slots<'gc>>, *const RefCell<Slots<'static>>>(
114 Rc::as_ptr(&self.0.slots),
115 )
116 };
117 let theirs = Weak::as_ptr(&root.slots);
118 ours == theirs
119 }
120}
121
122pub struct DynamicRoot<R: for<'gc> Rootable<'gc>> {
125 ptr: Gc<'static, Root<'static, R>>,
126 slots: Weak<RefCell<Slots<'static>>>,
127 index: Index,
128}
129
130impl<R: for<'gc> Rootable<'gc>> Drop for DynamicRoot<R> {
131 fn drop(&mut self) {
132 if let Some(slots) = self.slots.upgrade() {
133 slots.borrow_mut().dec(self.index);
134 }
135 }
136}
137
138impl<R: for<'gc> Rootable<'gc>> Clone for DynamicRoot<R> {
139 fn clone(&self) -> Self {
140 if let Some(slots) = self.slots.upgrade() {
141 slots.borrow_mut().inc(self.index);
142 }
143
144 Self {
145 ptr: self.ptr,
146 slots: self.slots.clone(),
147 index: self.index,
148 }
149 }
150}
151
152impl<R: for<'gc> Rootable<'gc>> DynamicRoot<R> {
153 #[inline]
170 pub fn as_ptr<'gc>(&self) -> *const Root<'gc, R> {
171 unsafe { mem::transmute::<&Root<'static, R>, &Root<'gc, R>>(&self.ptr) as *const _ }
172 }
173}
174
175#[derive(Debug)]
177pub struct MismatchedRootSet(());
178
179impl fmt::Display for MismatchedRootSet {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 f.write_str("mismatched root set")
182 }
183}
184
185#[cfg(feature = "std")]
186impl std::error::Error for MismatchedRootSet {}
187
188struct Inner<'gc> {
189 slots: Rc<RefCell<Slots<'gc>>>,
190}
191
192unsafe impl Collect for Inner<'_> {
193 fn trace(&self, cc: &crate::Collection) {
194 let slots = self.slots.borrow();
195 slots.trace(cc);
196 }
197}
198
199type Index = usize;
200
201const NULL_INDEX: Index = usize::MAX;
206
207enum Slot<'gc> {
208 Vacant { next_free: Index },
209 Occupied { root: Gc<'gc, ()>, ref_count: usize },
210}
211
212unsafe impl Collect for Slot<'_> {
213 fn trace(&self, cc: &crate::Collection) {
214 match self {
215 Slot::Vacant { .. } => {}
216 Slot::Occupied { root, .. } => root.trace(cc),
217 }
218 }
219}
220
221struct Slots<'gc> {
222 metrics: Metrics,
223 slots: Vec<Slot<'gc>>,
224 next_free: Index,
225}
226
227impl Drop for Slots<'_> {
228 fn drop(&mut self) {
229 self.metrics
230 .mark_external_deallocation(self.slots.capacity() * mem::size_of::<Slot>());
231 }
232}
233
234unsafe impl Collect for Slots<'_> {
235 fn trace(&self, cc: &crate::Collection) {
236 self.slots.trace(cc);
237 }
238}
239
240impl<'gc> Slots<'gc> {
241 fn new(metrics: Metrics) -> Self {
242 Self {
243 metrics,
244 slots: Vec::new(),
245 next_free: NULL_INDEX,
246 }
247 }
248
249 fn add(&mut self, p: Gc<'gc, ()>) -> Index {
250 if self.next_free != NULL_INDEX {
254 let idx = self.next_free;
255 let slot = &mut self.slots[idx];
256 match *slot {
257 Slot::Vacant { next_free } => {
258 self.next_free = next_free;
259 }
260 Slot::Occupied { .. } => panic!("free slot linked list corrupted"),
261 }
262 *slot = Slot::Occupied {
263 root: p,
264 ref_count: 0,
265 };
266 idx
267 } else {
268 let idx = self.slots.len();
269
270 let old_capacity = self.slots.capacity();
271 self.slots.push(Slot::Occupied {
272 root: p,
273 ref_count: 0,
274 });
275 let new_capacity = self.slots.capacity();
276
277 debug_assert!(new_capacity >= old_capacity);
278 if new_capacity > old_capacity {
279 self.metrics.mark_external_allocation(
280 (new_capacity - old_capacity) * mem::size_of::<Slot>(),
281 );
282 }
283
284 idx
285 }
286 }
287
288 fn inc(&mut self, idx: Index) {
289 match &mut self.slots[idx] {
290 Slot::Occupied { ref_count, .. } => {
291 *ref_count = ref_count
292 .checked_add(1)
293 .expect("DynamicRoot refcount overflow!");
294 }
295 Slot::Vacant { .. } => panic!("taken slot has been improperly freed"),
296 }
297 }
298
299 fn dec(&mut self, idx: Index) {
300 let slot = &mut self.slots[idx];
301 match slot {
302 Slot::Occupied { ref_count, .. } => {
303 if *ref_count == 0 {
304 *slot = Slot::Vacant {
305 next_free: self.next_free,
306 };
307 self.next_free = idx;
308 } else {
309 *ref_count -= 1;
310 }
311 }
312 Slot::Vacant { .. } => panic!("taken slot has been improperly freed"),
313 }
314 }
315}