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