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