1use std::{num::NonZeroU8, ops::DerefMut};
2
3use {core::ptr::NonNull, std::cell::RefCell, std::marker::PhantomData};
4
5use smallvec::SmallVec;
6
7use crate::{
8 heap::GcHeap,
9 helpers::GcError,
10 node::{GcHead, GcNode, GcRef},
11 partition::GcPartitionId,
12};
13
14#[derive(Debug)]
15pub struct GcScopeState<'heap> {
16 heap: NonNull<GcHeap>,
17 partition_id: GcPartitionId,
18 depth: NonZeroU8,
19 cache: RefCell<SmallVec<[NonNull<GcHead>; 8]>>,
20 promote: RefCell<Option<(NonNull<GcHead>, bool)>>,
21 _marker: PhantomData<&'heap mut GcHeap>,
22}
23
24impl<'heap> Drop for GcScopeState<'heap> {
25 fn drop(&mut self) {
26 self.flush();
27 }
28}
29
30impl<'heap> GcScopeState<'heap> {
31 pub fn new(heap: &'heap mut GcHeap, partition_id: GcPartitionId) -> Self {
32 debug_assert!(!partition_id.is_null());
33 let depth = heap.scope_stack.len() + 1;
34
35 Self {
36 heap: NonNull::from_ref(heap),
37 partition_id,
38 depth: NonZeroU8::new(depth as u8).unwrap(),
39 cache: RefCell::new(SmallVec::new()),
40 promote: RefCell::new(None),
41 _marker: PhantomData,
42 }
43 }
44
45 #[inline(always)]
46 pub fn partition_id(&self) -> GcPartitionId {
47 self.partition_id
48 }
49
50 #[inline(always)]
51 pub fn heap(&self) -> &GcHeap {
52 unsafe { self.heap.as_ref() }
53 }
54
55 #[inline(always)]
56 pub fn heap_mut(&mut self) -> &mut GcHeap {
57 unsafe { self.heap.as_mut() }
58 }
59
60 #[inline(always)]
61 pub fn depth(&self) -> u8 {
62 self.depth.get()
63 }
64
65 pub fn count(&self) -> usize {
66 self.cache.borrow().len()
67 }
68
69 pub fn parent(&self) -> Option<(&GcScopeState<'_>, u8)> {
71 let d = self.depth();
72 if d > 1 {
73 let parent_index = d - 2;
74
75 self.heap().scope_stack.get(parent_index as usize).map(|s| {
76 (
77 unsafe { std::mem::transmute::<&GcScopeState<'static>, &GcScopeState<'_>>(s) },
78 d - 1,
79 )
80 })
81 } else {
82 None
83 }
84 }
85
86 pub fn alloc_root<T: GcNode>(&self, payload: T) -> Result<GcRef<T>, (GcError, T)> {
87 unsafe { (*self.heap.as_ptr()).alloc_root_raw(self.partition_id, payload) }
88 }
89
90 pub fn alloc_local<T: GcNode>(&self, payload: T) -> Result<GcRef<T>, (GcError, T)> {
92 let r = unsafe { (*self.heap.as_ptr()).alloc_raw(self.partition_id, payload)? };
93 self.add_node(r.head_ptr);
94 Ok(r)
95 }
96
97 fn add_node(&self, mut node: NonNull<GcHead>) {
98 #[cfg(debug_assertions)]
99 unsafe {
100 debug_assert!(!node.as_ref().is_root_or_local());
101 node.as_mut().dbg_scope_depth = self.depth();
102 }
103
104 unsafe {
105 node.as_mut().insert_flag(crate::node::GcNodeFlag::LOCAL);
106
107 if let Some(par) = (*self.heap.as_ptr()).partition_mut(self.partition_id)
108 && par.is_marking()
109 {
110 par.add_gray_node(node);
111 }
112 }
113
114 self.cache.borrow_mut().push(node);
115 }
116
117 pub fn add_non_local(&self, node: NonNull<GcHead>) -> bool {
119 if unsafe { node.as_ref().is_root_or_local() } {
120 false
121 } else {
122 self.add_node(node);
123 true
124 }
125 }
126
127 pub fn get_promote(&self) -> Option<NonNull<GcHead>> {
128 self.promote.borrow().map(|(p, _)| p)
129 }
130
131 pub fn set_promote(&self, node: Option<NonNull<GcHead>>) {
135 debug_assert_eq!(
136 self.depth(),
137 self.heap().scope_max_depth(),
138 "only inner-most scope can set_promote"
139 );
140
141 if self.depth() == 1 {
143 debug_assert!(
144 self.promote.borrow().is_none(),
145 "top scope don't promote node"
146 );
147 return;
148 }
149
150 if let Some((mut p, was_non_local)) = self.promote.borrow_mut().take()
152 && was_non_local
153 {
154 unsafe {
155 p.as_mut().remove_flag(crate::node::GcNodeFlag::LOCAL);
156
157 let mut lst = self.cache.borrow_mut();
158 if let Some(i) = lst.iter().position(|&n| n == p) {
159 lst.swap_remove(i);
160 }
161 }
162 }
163
164 if let Some(n) = node {
165 let h = unsafe { n.as_ref() };
166 if h.is_root() {
167 return; }
169
170 let is_non_local = if h.is_local() {
171 if !self
172 .cache
173 .borrow()
174 .iter()
175 .any(|p| std::ptr::eq(n.as_ptr(), p.as_ptr()))
176 {
177 return;
179 }
180 false
181 } else {
182 self.add_node(n);
183 true
184 };
185
186 *self.promote.borrow_mut() = Some((n, is_non_local));
187 }
188 }
189
190 pub fn flush(&self) {
193 let promote: Option<NonNull<GcHead>> = self.promote.borrow_mut().take().map(|(p, _)| p);
194
195 if let Some(mut p) = promote {
196 let (up, _lev) = self.parent().unwrap();
197 up.cache.borrow_mut().push(p);
199
200 #[cfg(debug_assertions)]
201 unsafe {
202 p.as_mut().dbg_scope_depth = _lev as _;
203 }
204 }
205
206 let lst = std::mem::take(self.cache.borrow_mut().deref_mut());
207 for mut n in lst.into_iter().filter(|&p| promote.is_none_or(|x| x != p)) {
208 unsafe {
209 n.as_mut().remove_flag(crate::node::GcNodeFlag::LOCAL);
210
211 #[cfg(debug_assertions)]
212 {
213 n.as_mut().dbg_scope_depth = 0;
214 }
215 }
216 }
217 }
218
219 pub fn contains(&self, node: NonNull<GcHead>) -> bool {
220 self.cache.borrow().contains(&node)
221 }
222
223 pub(super) unsafe fn abort(&self) {
229 self.promote.borrow_mut().take();
230
231 let nodes = std::mem::take(self.cache.borrow_mut().deref_mut());
232 for mut n in nodes {
233 unsafe {
234 n.as_mut().remove_flag(crate::node::GcNodeFlag::LOCAL);
235 }
236 }
237 }
238}
239
240pub struct GcScope<'heap> {
241 heap: NonNull<GcHeap>,
242 index: u8,
243 _marker: PhantomData<&'heap mut GcHeap>,
244}
245
246impl<'heap> GcScope<'heap> {
247 fn heap_raw(&self) -> &GcHeap {
248 unsafe { self.heap.as_ref() }
249 }
250
251 fn heap_raw_mut(&mut self) -> &mut GcHeap {
252 unsafe { self.heap.as_mut() }
253 }
254
255 fn inner_static(&self) -> &GcScopeState<'static> {
256 let heap = self.heap_raw();
257 &heap.scope_stack[self.index as usize]
258 }
259
260 fn inner_static_mut(&mut self) -> &mut GcScopeState<'static> {
261 let index = self.index as usize;
262 let heap = self.heap_raw_mut();
263 &mut heap.scope_stack[index]
264 }
265
266 fn inner(&self) -> &GcScopeState<'heap> {
267 unsafe {
268 std::mem::transmute::<&GcScopeState<'static>, &GcScopeState<'heap>>(self.inner_static())
269 }
270 }
271
272 fn inner_mut(&mut self) -> &mut GcScopeState<'heap> {
273 unsafe {
274 std::mem::transmute::<&mut GcScopeState<'static>, &mut GcScopeState<'heap>>(
275 self.inner_static_mut(),
276 )
277 }
278 }
279
280 pub fn new(heap: &'heap mut GcHeap, partition_id: GcPartitionId) -> Self {
281 heap.new_scope(partition_id)
282 }
283
284 pub fn new_scope<'child>(&'child mut self) -> GcScope<'child>
285 where
286 'heap: 'child,
287 {
288 let partition_id = self.partition_id();
289 self.heap_raw_mut().new_scope(partition_id)
290 }
291
292 pub fn with_new_scope<R>(&self, f: impl FnOnce(GcScope<'_>) -> R) -> R {
293 let partition_id = self.partition_id();
294 let heap = unsafe { &mut *self.heap.as_ptr() };
295 let scope = heap.new_scope(partition_id);
296 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(scope)));
297
298 match result {
299 Ok(r) => r,
300 Err(e) => std::panic::resume_unwind(e),
301 }
302 }
303
304 #[inline(always)]
305 pub fn partition_id(&self) -> GcPartitionId {
306 self.inner().partition_id()
307 }
308
309 #[inline(always)]
310 pub fn heap(&self) -> &GcHeap {
311 self.inner().heap()
312 }
313
314 #[inline(always)]
315 pub fn heap_mut(&mut self) -> &mut GcHeap {
316 self.inner_mut().heap_mut()
317 }
318
319 #[inline(always)]
320 pub fn depth(&self) -> u8 {
321 self.inner().depth()
322 }
323
324 #[inline(always)]
325 pub fn count(&self) -> usize {
326 self.inner().count()
327 }
328
329 pub fn alloc_root<T: GcNode>(&self, payload: T) -> Result<GcRef<T>, (GcError, T)> {
330 self.inner().alloc_root(payload)
331 }
332
333 pub fn alloc_local<T: GcNode>(&self, payload: T) -> Result<GcRef<T>, (GcError, T)> {
334 self.inner().alloc_local(payload)
335 }
336
337 pub fn add_non_local(&self, node: NonNull<GcHead>) -> bool {
338 self.inner().add_non_local(node)
339 }
340
341 pub fn get_promote(&self) -> Option<NonNull<GcHead>> {
342 self.inner().get_promote()
343 }
344
345 pub fn set_promote(&self, node: Option<NonNull<GcHead>>) {
346 self.inner().set_promote(node)
347 }
348
349 pub fn flush(&self) {
350 self.inner().flush()
351 }
352
353 pub fn contains(&self, node: NonNull<GcHead>) -> bool {
354 self.inner().contains(node)
355 }
356}
357
358impl<'heap> std::ops::Deref for GcScope<'heap> {
359 type Target = GcScopeState<'heap>;
360
361 fn deref(&self) -> &Self::Target {
362 self.inner()
363 }
364}
365
366impl<'heap> std::ops::DerefMut for GcScope<'heap> {
367 fn deref_mut(&mut self) -> &mut Self::Target {
368 self.inner_mut()
369 }
370}
371
372impl<'heap> Drop for GcScope<'heap> {
373 fn drop(&mut self) {
374 unsafe {
375 let heap = self.heap.as_mut();
376 debug_assert_eq!(heap.scope_stack.len() as u8 - 1, self.index);
377 if let Some(ctx) = heap.pop_gc_scope() {
378 drop(ctx);
379 }
380 }
381 }
382}
383
384impl GcHeap {
385 #[inline(always)]
386 pub fn scope_max_depth(&self) -> u8 {
387 self.scope_stack.len() as _
388 }
389
390 pub fn scope(&self, depth: u8) -> Option<&GcScopeState<'_>> {
392 if depth == 0 {
393 return None;
394 }
395
396 self.scope_stack.get(depth as usize - 1)
397 }
398
399 pub(crate) fn push_gc_scope(&mut self, partition_id: GcPartitionId) -> &GcScopeState<'_> {
400 let ctx = GcScopeState::new(self, partition_id);
401 let static_ctx =
404 unsafe { std::mem::transmute::<GcScopeState<'_>, GcScopeState<'static>>(ctx) };
405 self.scope_stack.push(static_ctx);
406
407 self.scope_stack.last().unwrap()
408 }
409
410 #[inline(always)]
411 pub(crate) fn pop_gc_scope(&mut self) -> Option<GcScopeState<'_>> {
412 self.scope_stack.pop()
413 }
414
415 #[inline]
416 pub fn current_scope(&self) -> Option<&GcScopeState<'_>> {
417 let s = self.scope_stack.last();
418 unsafe {
421 std::mem::transmute::<Option<&GcScopeState<'static>>, Option<&GcScopeState<'_>>>(s)
422 }
423 }
424
425 #[inline]
426 pub fn with_current_scope<R>(&mut self, f: impl FnOnce(&mut GcScopeState) -> R) -> Option<R> {
427 self.scope_stack.last_mut().map(f)
428 }
429
430 #[inline]
431 pub fn new_scope<'s>(&'s mut self, partition_id: GcPartitionId) -> GcScope<'s> {
432 self.push_gc_scope(partition_id);
433 let index = self.scope_stack.len() as u8 - 1;
434 GcScope {
435 heap: NonNull::from(self),
436 index,
437 _marker: PhantomData,
438 }
439 }
440
441 #[inline]
442 pub fn with_new_scope<R>(
443 &mut self,
444 partition_id: GcPartitionId,
445 f: impl FnOnce(GcScope<'_>) -> R,
446 ) -> R {
447 let scope = self.new_scope(partition_id);
448 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(scope)));
449
450 match result {
451 Ok(r) => r,
452 Err(e) => std::panic::resume_unwind(e),
453 }
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use crate::{GcTraceCtx, trace::GcTrace};
460
461 use super::*;
462
463 #[derive(Debug)]
464 struct Node {
465 next: Option<GcRef<Node>>,
466 value: i32,
467 }
468
469 impl GcTrace for Node {
470 fn trace(&self, tr: &mut GcTraceCtx) {
471 if let Some(next) = self.next {
472 tr.add(next);
473 }
474 }
475 }
476
477 crate::gc_type_register! {
478 Node, drop_pass = 0;
479 }
480
481 #[test]
482 fn test_gc_context_local_flag_set_and_cleared_on_commit() {
483 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
484 let partition_id = heap.create_partition();
485
486 let head;
487
488 {
489 let ctx = GcScope::new(&mut heap, partition_id);
490 let node: GcRef<Node> = ctx
491 .alloc_local(Node {
492 next: None,
493 value: 1,
494 })
495 .unwrap();
496
497 head = node.head_ptr;
498
499 unsafe {
500 assert!(head.as_ref().is_local());
501 }
502
503 ctx.flush();
504
505 unsafe {
506 assert!(!head.as_ref().is_local());
507 }
508 }
509
510 unsafe {
511 assert!(!head.as_ref().is_local());
512 }
513 }
514
515 #[test]
516 fn test_gc_context_alloc_protects_and_unprotects_on_drop() {
517 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
518 let partition_id = heap.create_partition();
519
520 let head;
521 {
522 let mut ctx = GcScope::new(&mut heap, partition_id);
523 let node: GcRef<Node> = ctx
524 .alloc_local(Node {
525 next: None,
526 value: 1,
527 })
528 .unwrap();
529
530 head = node.head_ptr;
531
532 unsafe {
533 assert!(head.as_ref().is_local());
534 }
535
536 while !ctx.heap_mut().mark(partition_id, 64) {}
537 let removed = ctx
538 .heap_mut()
539 .sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
540 assert_eq!(removed, 0);
541 ctx.flush();
542 }
543
544 unsafe {
545 assert!(!head.as_ref().is_local());
546 }
547
548 while !heap.mark(partition_id, 64) {}
549 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
550 assert!(removed_after > 0);
551 }
552
553 #[test]
554 fn test_gc_context_add_sets_local_and_clears_on_commit() {
555 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
556 let partition_id = heap.create_partition();
557
558 let node: GcRef<Node> = unsafe {
559 heap.alloc_raw(
560 partition_id,
561 Node {
562 next: None,
563 value: 1,
564 },
565 )
566 }
567 .unwrap();
568
569 let head = node.head_ptr;
570
571 unsafe {
572 assert!(!head.as_ref().is_local());
573 }
574
575 {
576 let mut ctx = GcScope::new(&mut heap, partition_id);
577 let added = ctx.add_non_local(head);
578 assert!(added);
579
580 unsafe {
581 assert!(head.as_ref().is_local());
582 }
583
584 ctx.flush();
585
586 unsafe {
587 assert!(!head.as_ref().is_local());
588 }
589 }
590
591 unsafe {
592 assert!(!head.as_ref().is_local());
593 }
594
595 while !heap.mark(partition_id, 64) {}
596 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
597 assert!(removed_after > 0);
598 }
599
600 #[test]
601 fn test_gc_context_add_on_local_node_returns_false() {
602 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
603 let partition_id = heap.create_partition();
604
605 let ctx = GcScope::new(&mut heap, partition_id);
606 let node: GcRef<Node> = ctx
607 .alloc_local(Node {
608 next: None,
609 value: 1,
610 })
611 .unwrap();
612
613 let head = node.head_ptr;
614
615 unsafe {
616 assert!(head.as_ref().is_local());
617 }
618 let added = ctx.add_non_local(head);
619 assert!(!added);
620
621 unsafe {
622 assert!(head.as_ref().is_local());
623 }
624
625 ctx.flush();
626
627 unsafe {
628 assert!(!head.as_ref().is_local());
629 }
630 }
631
632 #[test]
633 fn test_gc_context_alloc_root_creates_root_without_protection() {
634 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
635 let partition_id = heap.create_partition();
636
637 let head;
638
639 {
640 let ctx = GcScope::new(&mut heap, partition_id);
641 let node: GcRef<Node> = ctx
642 .alloc_root(Node {
643 next: None,
644 value: 1,
645 })
646 .unwrap();
647
648 head = node.head_ptr;
649
650 unsafe {
651 assert!(node.is_root());
652 assert!(head.as_ref().is_root());
653 assert!(!head.as_ref().is_local());
654 }
655 }
656
657 while !heap.mark(partition_id, 64) {}
658 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
659 assert_eq!(removed_after, 0);
660 }
661
662 #[test]
663 fn test_gc_context_alloc_multiple_nodes() {
664 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
665 let partition_id = heap.create_partition();
666
667 let head1;
668 let head2;
669
670 {
671 let mut ctx = GcScope::new(&mut heap, partition_id);
672 let n1: GcRef<Node> = ctx
673 .alloc_local(Node {
674 next: None,
675 value: 1,
676 })
677 .unwrap();
678 let n2: GcRef<Node> = ctx
679 .alloc_local(Node {
680 next: None,
681 value: 2,
682 })
683 .unwrap();
684
685 head1 = n1.head_ptr;
686 head2 = n2.head_ptr;
687
688 unsafe {
689 assert!(head1.as_ref().is_local());
690 assert!(head2.as_ref().is_local());
691 }
692
693 while !ctx.heap_mut().mark(partition_id, 64) {}
694 let removed = ctx
695 .heap_mut()
696 .sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
697 assert_eq!(removed, 0);
698 ctx.flush();
699 }
700
701 unsafe {
702 assert!(!head1.as_ref().is_local());
703 assert!(!head2.as_ref().is_local());
704 }
705
706 while !heap.mark(partition_id, 64) {}
707 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
708 assert!(removed_after >= 2);
709 }
710
711 #[test]
712 fn test_gc_context_reset_unprotects_and_clears_cache() {
713 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
714 let partition_id = heap.create_partition();
715
716 let head;
717
718 {
719 let mut ctx = GcScope::new(&mut heap, partition_id);
720 let node: GcRef<Node> = ctx
721 .alloc_local(Node {
722 next: None,
723 value: 1,
724 })
725 .unwrap();
726
727 head = node.head_ptr;
728
729 unsafe {
730 assert!(head.as_ref().is_local());
731 }
732
733 ctx.flush();
734
735 unsafe {
736 assert!(!head.as_ref().is_local());
737 }
738
739 while !ctx.heap_mut().mark(partition_id, 64) {}
740 let removed_before = ctx
741 .heap_mut()
742 .sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
743 assert!(removed_before > 0);
744 }
745 }
746
747 #[test]
748 fn test_gc_context_level_for_heap_scopes() {
749 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
750 let partition_id = heap.create_partition();
751
752 assert_eq!(heap.scope_max_depth(), 0);
753
754 heap.push_gc_scope(partition_id);
755 heap.push_gc_scope(partition_id);
756 heap.push_gc_scope(partition_id);
757
758 assert_eq!(heap.scope_max_depth(), 3);
759
760 for level in 1..=3 {
761 let ctx = heap.scope(level).unwrap();
762 assert_eq!(ctx.depth(), level);
763 }
764
765 heap.pop_gc_scope();
766 assert_eq!(heap.scope_max_depth(), 2);
767 for level in 1..=2 {
768 let ctx = heap.scope(level).unwrap();
769 assert_eq!(ctx.depth(), level);
770 }
771
772 heap.pop_gc_scope();
773 assert_eq!(heap.scope_max_depth(), 1);
774 let ctx = heap.scope(1).unwrap();
775 assert_eq!(ctx.depth(), 1);
776 }
777
778 #[test]
779 fn test_gc_context_parent_mut_returns_parent_and_level() {
780 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
781 let partition_id = heap.create_partition();
782
783 heap.push_gc_scope(partition_id);
784 heap.push_gc_scope(partition_id);
785 heap.push_gc_scope(partition_id);
786
787 heap.with_current_scope(|ctx| {
788 assert_eq!(ctx.depth(), 3);
789 let (parent, parent_level) = ctx.parent().unwrap();
790 assert_eq!(parent_level, 2);
791 assert_eq!(parent.depth(), 2);
792 });
793
794 heap.pop_gc_scope();
795
796 heap.with_current_scope(|ctx| {
797 assert_eq!(ctx.depth(), 2);
798 let (parent, parent_level) = ctx.parent().unwrap();
799 assert_eq!(parent_level, 1);
800 assert_eq!(parent.depth(), 1);
801 });
802
803 heap.pop_gc_scope();
804
805 heap.with_current_scope(|ctx| {
806 assert_eq!(ctx.depth(), 1);
807 assert!(ctx.parent().is_none());
808 });
809 }
810
811 #[test]
812 fn test_set_promote_moves_node_to_parent_scope_and_keeps_protection() {
813 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
814 let partition_id = heap.create_partition();
815
816 heap.push_gc_scope(partition_id);
817 heap.push_gc_scope(partition_id);
818
819 let promoted_head;
820 let other_head;
821
822 {
823 let ctx = heap.scope_stack.last_mut().unwrap();
824 let promoted: GcRef<Node> = ctx
825 .alloc_local(Node {
826 next: None,
827 value: 1,
828 })
829 .unwrap();
830 let other: GcRef<Node> = ctx
831 .alloc_local(Node {
832 next: None,
833 value: 2,
834 })
835 .unwrap();
836
837 promoted_head = promoted.head_ptr;
838 other_head = other.head_ptr;
839
840 unsafe {
841 assert!(promoted_head.as_ref().is_local());
842 assert!(other_head.as_ref().is_local());
843 }
844
845 ctx.set_promote(Some(promoted_head));
846 }
847
848 heap.with_current_scope(|ctx| ctx.flush());
849
850 unsafe {
851 assert!(promoted_head.as_ref().is_local());
852 assert!(!other_head.as_ref().is_local());
853 }
854
855 {
856 let parent_ctx = heap.scope_stack.first().unwrap();
857 assert!(parent_ctx.contains(promoted_head));
858 }
859
860 {
861 let child_ctx = heap.scope_stack.last().unwrap();
862 assert!(!child_ctx.contains(promoted_head));
863 }
864
865 {
866 let parent_ctx = heap.scope_stack.first_mut().unwrap();
867 parent_ctx.flush();
868 }
869
870 unsafe {
871 assert!(!promoted_head.as_ref().is_local());
872 }
873 }
874
875 #[test]
876 fn test_set_promote_in_top_level_scope_is_noop() {
877 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
878 let partition_id = heap.create_partition();
879
880 heap.push_gc_scope(partition_id);
881
882 let head;
883
884 {
885 let ctx = heap.scope_stack.last_mut().unwrap();
886 let node: GcRef<Node> = ctx
887 .alloc_local(Node {
888 next: None,
889 value: 1,
890 })
891 .unwrap();
892
893 head = node.head_ptr;
894
895 unsafe {
896 assert!(head.as_ref().is_local());
897 }
898
899 ctx.set_promote(Some(head));
900 ctx.flush();
901 }
902
903 unsafe {
904 assert!(!head.as_ref().is_local());
905 }
906 }
907
908 #[test]
909 fn test_set_promote_reset_on_non_local_restores_state() {
910 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
911 let partition_id = heap.create_partition();
912
913 let node: GcRef<Node> = unsafe {
914 heap.alloc_raw(
915 partition_id,
916 Node {
917 next: None,
918 value: 1,
919 },
920 )
921 }
922 .unwrap();
923
924 let head = node.head_ptr;
925
926 unsafe {
927 assert!(!head.as_ref().is_local());
928 }
929
930 heap.push_gc_scope(partition_id);
931 heap.push_gc_scope(partition_id);
932
933 {
934 let ctx = heap.scope_stack.last_mut().unwrap();
935 ctx.set_promote(Some(head));
936
937 unsafe {
938 assert!(head.as_ref().is_local());
939 }
940
941 ctx.set_promote(None);
942 ctx.flush();
943 }
944
945 {
946 let parent_ctx = heap.scope_stack.first().unwrap();
947 assert!(!parent_ctx.contains(head));
948 }
949
950 unsafe {
951 assert!(!head.as_ref().is_local());
952 }
953 }
954}