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, 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    /// 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    #[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    /// alloc a local node in scope.
92    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    // if node is neither root, nor local, then add it to `self` scope
119    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    /// Remove a LOCAL node from this scope's cache and clear its LOCAL flag.
129    ///
130    /// This is the inverse of `add_node`: it undoes the LOCAL protection
131    /// that was granted when the node was allocated in this scope.
132    /// After this call, the node is no longer protected by this scope
133    /// and will be traced by GC solely through other GC references.
134    ///
135    /// Returns `true` if the node was found and removed from this scope's cache.
136    /// Returns `false` if the node was not in this scope's cache (e.g., it was
137    /// already promoted, or belongs to a different scope).
138    ///
139    /// # Safety
140    ///
141    /// The caller must ensure the node is still alive and valid.
142    /// This method does NOT check whether the node is the current promote node;
143    /// the caller (e.g., `ScopedContext::throw`) is responsible for ensuring
144    /// the node is not the promote target.
145    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    /// Set a node to be promoted.
167    ///
168    /// Promote means when the scope is dropped, the node will be added to upper scope.
169    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        // Note: scope depth==1 indicates the top scope, which don't promote any node
186        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        // check current promote node
195        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; // root node don't need to be promoted
212            }
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                    // node is already in some other scope, can't promote by this
222                    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    /// clear and unprotect locals nodes in this scope; promote the result node to upper scope.
235    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            // put to upper scope cache
241            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    /// # Safety
267    ///
268    /// 仅供 `GcHeap::drop` 在销毁事务栈时调用,用于跳过对
269    /// `nodes` 中节点的保护计数更新与根集合维护逻辑。
270    /// 调用方必须保证这些节点即将被整体释放,不再通过 GC 访问。
271    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    /// get scope by depth (peek, without modifying stack)
380    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        // SAFETY: It is safe because the GcHeap owns the GcScope, and we ensure that
391        // the GcScope does not outlive the GcHeap.
392        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        // SAFETY: It is safe because the GcHeap owns the GcScope, and we ensure that
408        // the GcScope does not outlive the GcHeap.
409        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}