Skip to main content

gc_lite/
scope.rs

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    /// get parent scope, and its level.
70    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    /// alloc a local node in scope.
91    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    // if node is neither root, nor local, then add it to `self` scope
118    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    /// Set a node to be promoted.
132    ///
133    /// Promote means when the scope is dropped, the node will be added to upper scope.
134    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        // Note: scope depth==1 indicates the top scope, which don't promote any node
142        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        // check current promote node
151        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; // root node don't need to be promoted
168            }
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                    // node is already in some other scope, can't promote by this
178                    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    /// clear and unprotect cached nodes.
191    /// this behaves like to drop current scope, and start a new scope.
192    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            // put to upper scope cache
198            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    /// # Safety
224    ///
225    /// 仅供 `GcHeap::drop` 在销毁事务栈时调用,用于跳过对
226    /// `nodes` 中节点的保护计数更新与根集合维护逻辑。
227    /// 调用方必须保证这些节点即将被整体释放,不再通过 GC 访问。
228    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    /// get scope by depth (peek, without modifying stack)
391    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        // SAFETY: It is safe because the GcHeap owns the GcScope, and we ensure that
402        // the GcScope does not outlive the GcHeap.
403        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        // SAFETY: It is safe because the GcHeap owns the GcScope, and we ensure that
419        // the GcScope does not outlive the GcHeap.
420        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}