1use {
2 core::ptr::NonNull,
3 std::{cell::UnsafeCell, marker::PhantomData, num::NonZeroU32},
4};
5
6#[cfg(debug_assertions)]
7use std::cell::Cell;
8
9use crate::{
10 heap::GcHeap,
11 helpers::GcError,
12 node::{Gc, GcHead, GcNode, GcNodeFlag},
13 partition::GcPartitionId,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[repr(transparent)]
18pub struct GcScopeStackId(u16);
19
20impl std::fmt::Display for GcScopeStackId {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(f, "{}", self.0)
23 }
24}
25
26pub(crate) struct ScopeStack {
27 pub(crate) list: Vec<GcScopeState<'static>>,
29 pub(crate) partition: Option<GcPartitionId>,
31}
32
33impl ScopeStack {
34 pub(super) fn new(partition: Option<GcPartitionId>) -> Self {
35 Self {
36 list: Vec::with_capacity(8),
37 partition,
38 }
39 }
40}
41
42#[derive(Debug)]
44pub struct GcScopeState<'s> {
45 heap: NonNull<GcHeap>,
46 partition_id: GcPartitionId,
47 stack_id: GcScopeStackId,
48 depth: NonZeroU32,
49 cache: UnsafeCell<Option<Vec<NonNull<GcHead>>>>,
50 #[cfg(debug_assertions)]
51 borrow_flag: Cell<bool>,
52 _marker: PhantomData<&'s ()>,
53}
54
55impl<'s> Drop for GcScopeState<'s> {
56 fn drop(&mut self) {
57 self.clear();
58 }
59}
60
61impl<'s> GcScopeState<'s> {
62 pub fn new(
63 heap: &'s mut GcHeap,
64 stack_id: GcScopeStackId,
65 partition_id: GcPartitionId,
66 ) -> Self {
67 debug_assert!(
68 (stack_id.0 as usize) < heap.scope_stacks.len(),
69 "GcScopeState stack_id {} out of bounds (max {})",
70 stack_id.0,
71 heap.scope_stacks.len(),
72 );
73
74 let depth = heap.scope_max_depth(stack_id) + 1;
75 Self {
76 heap: NonNull::from_ref(heap),
77 stack_id,
78 partition_id,
79 depth: NonZeroU32::new(depth).unwrap(),
80 cache: UnsafeCell::new(None),
81 #[cfg(debug_assertions)]
82 borrow_flag: Cell::new(false),
83 _marker: PhantomData,
84 }
85 }
86
87 #[inline(always)]
88 pub fn partition_id(&self) -> GcPartitionId {
89 self.partition_id
90 }
91
92 #[inline(always)]
93 pub fn stack_id(&self) -> GcScopeStackId {
94 self.stack_id
95 }
96
97 #[inline(always)]
98 pub fn heap(&self) -> &GcHeap {
99 unsafe { self.heap.as_ref() }
100 }
101
102 #[inline(always)]
103 pub fn heap_mut(&mut self) -> &mut GcHeap {
104 unsafe { self.heap.as_mut() }
105 }
106
107 #[inline(always)]
108 pub fn depth(&self) -> u32 {
109 self.depth.get()
110 }
111
112 #[inline(always)]
113 pub fn count(&self) -> usize {
114 unsafe { (*self.cache.get()).as_ref().map_or(0, |c| c.len()) }
115 }
116
117 pub fn parent(&self) -> Option<(&GcScopeState<'_>, u32)> {
119 let d = self.depth();
120 if d > 1 {
121 let parent_index = d - 2;
122 self.heap().scope_stacks[self.stack_id.0 as usize]
123 .list
124 .get(parent_index as usize)
125 .map(|s| (s, d - 1))
126 } else {
127 None
128 }
129 }
130
131 pub fn alloc_local<T: GcNode>(&self, payload: T) -> Result<Gc<'s, T>, (GcError, T)> {
133 let r = unsafe { (*self.heap.as_ptr()).alloc_raw(self.partition_id, payload)? };
134 self.add_node(r.head_ptr);
135 Ok(unsafe { Gc::from_raw(r) })
140 }
141
142 fn add_node(&self, mut node: NonNull<GcHead>) {
143 unsafe {
144 debug_assert!(
145 !node.as_ref().is_root_or_local(),
146 "add_node: node is already root or local",
147 );
148
149 node.as_mut().insert_flag(GcNodeFlag::LOCAL);
150
151 if let Some(par) = (*self.heap.as_ptr()).partition_mut(self.partition_id)
152 && par.is_marking()
153 {
154 par.add_gray_node(node);
155 }
156 }
157
158 #[cfg(debug_assertions)]
159 debug_assert!(!self.borrow_flag.get(), "GcScopeState: reentrant borrow");
160 #[cfg(debug_assertions)]
161 self.borrow_flag.set(true);
162
163 unsafe {
164 let cache = &mut *self.cache.get();
165 if cache.is_none() {
166 *cache = Some(Vec::with_capacity(1));
167 }
168 cache.as_mut().unwrap().push(node);
169 }
170
171 #[cfg(debug_assertions)]
172 self.borrow_flag.set(false);
173 }
174
175 pub fn add_non_local(&self, node: NonNull<GcHead>) -> bool {
177 if unsafe { node.as_ref().is_root_or_local() } {
178 false
179 } else {
180 self.add_node(node);
181 true
182 }
183 }
184
185 pub fn remove_node(&self, mut node: NonNull<GcHead>) -> bool {
203 #[cfg(debug_assertions)]
204 unsafe {
205 node.as_ref().debug_assert_node_valid_simple();
206 }
207 let cache_opt = unsafe { &mut *self.cache.get() };
208 if let Some(cache) = cache_opt.as_mut()
209 && let Some(pos) = cache.iter().position(|&n| n == node)
210 {
211 cache.swap_remove(pos);
212 unsafe {
213 node.as_mut().remove_flag(GcNodeFlag::LOCAL);
214 }
215 true
216 } else {
217 false
218 }
219 }
220
221 pub fn contains(&self, node: NonNull<GcHead>) -> bool {
222 unsafe {
223 (*self.cache.get())
224 .as_ref()
225 .map_or(false, |c| c.contains(&node))
226 }
227 }
228
229 pub fn promote_node_to(&self, node: NonNull<GcHead>, target: &GcScopeState<'_>) -> bool {
240 let cache_opt = unsafe { &mut *self.cache.get() };
241
242 if let Some(cache) = cache_opt.as_mut()
243 && let Some(pos) = cache.iter().position(|&n| n == node)
244 {
245 debug_assert!(
246 unsafe { node.as_ref().is_local() },
247 "promote_node_to: node is not LOCAL",
248 );
249 cache.swap_remove(pos);
250 let target_cache = unsafe { &mut *target.cache.get() };
251 if target_cache.is_none() {
252 *target_cache = Some(Vec::with_capacity(1));
253 }
254 target_cache.as_mut().unwrap().push(node);
255 true
256 } else {
257 false
269 }
270 }
271
272 pub fn clear(&self) {
274 let cache_opt = unsafe { &mut *self.cache.get() };
275 if let Some(lst) = cache_opt.take() {
276 for mut n in lst {
277 unsafe {
278 n.as_mut().remove_flag(crate::node::GcNodeFlag::LOCAL);
279 }
280 }
281 }
282 }
283}
284
285#[derive(Debug)]
290pub struct GcScope<'s> {
291 heap: NonNull<GcHeap>,
292 stack_id: GcScopeStackId,
293 index: u32,
294 _marker: PhantomData<&'s ()>,
295}
296
297impl<'s> Drop for GcScope<'s> {
298 fn drop(&mut self) {
299 unsafe {
300 let heap = self.heap.as_mut();
301 let stack = &heap.scope_stacks[self.stack_id.0 as usize];
302
303 debug_assert!(
304 !stack.list.is_empty(),
305 "GcScope dropped but scope stack {} is empty",
306 self.stack_id.0,
307 );
308 debug_assert_eq!(
309 stack.list.len() as u32 - 1,
310 self.index as u32,
311 "GcScope dropped out of LIFO order: scope stack {} has {} entries, expected top index {}",
312 self.stack_id.0,
313 stack.list.len(),
314 self.index,
315 );
316
317 heap.pop_scope(self.stack_id);
318 }
319 }
320}
321
322impl<'s> std::ops::Deref for GcScope<'s> {
323 type Target = GcScopeState<'s>;
324
325 #[inline(always)]
326 fn deref(&self) -> &Self::Target {
327 unsafe {
328 let stack = &self.heap.as_ref().scope_stacks[self.stack_id.0 as usize];
329 debug_assert!(
330 (self.index as usize) < stack.list.len(),
331 "GcScope index {} out of bounds for stack {} (len {})",
332 self.index,
333 self.stack_id.0,
334 stack.list.len(),
335 );
336
337 std::mem::transmute::<&GcScopeState<'_>, &GcScopeState<'s>>(
338 stack.list.get(self.index as usize).unwrap_unchecked(),
339 )
340 }
341 }
342}
343
344impl<'s> std::ops::DerefMut for GcScope<'s> {
345 #[inline(always)]
346 fn deref_mut(&mut self) -> &mut Self::Target {
347 unsafe {
348 let stack = &mut self.heap.as_mut().scope_stacks[self.stack_id.0 as usize];
349 debug_assert!(
350 (self.index as usize) < stack.list.len(),
351 "GcScope index {} out of bounds for stack {} (len {})",
352 self.index,
353 self.stack_id.0,
354 stack.list.len(),
355 );
356
357 std::mem::transmute::<&mut GcScopeState<'_>, &mut GcScopeState<'s>>(
358 stack.list.get_mut(self.index as usize).unwrap_unchecked(),
359 )
360 }
361 }
362}
363
364impl GcHeap {
365 pub fn acquire_scope_stack(&mut self, par: GcPartitionId) -> GcScopeStackId {
367 for (id, stack) in self.scope_stacks.iter_mut().enumerate() {
368 if stack.partition.is_none() {
369 debug_assert!(
370 stack.list.is_empty(),
371 "acquire_scope_stack: idle stack {} has non-empty scope list",
372 id,
373 );
374 stack.partition = Some(par);
375 return GcScopeStackId(id as u16);
376 }
377 }
378
379 let id = u16::try_from(self.scope_stacks.len()).expect("too many scope stacks");
380 self.scope_stacks.push(ScopeStack::new(Some(par)));
381 GcScopeStackId(id)
382 }
383
384 pub fn release_scope_stack(&mut self, stack_id: GcScopeStackId) {
385 let stack = &mut self.scope_stacks[stack_id.0 as usize];
386 debug_assert!(
387 stack.list.is_empty(),
388 "release_scope_stack: stack {} has {} scopes still active",
389 stack_id.0,
390 stack.list.len(),
391 );
392 stack.partition.take();
393 }
394
395 #[inline(always)]
397 pub fn scope_max_depth(&self, stack_id: GcScopeStackId) -> u32 {
398 self.scope_stacks[stack_id.0 as usize].list.len() as _
399 }
400
401 pub fn scope(&self, stack_id: GcScopeStackId, depth: u32) -> Option<&GcScopeState<'_>> {
404 if depth > 0 {
405 self.scope_stacks[stack_id.0 as usize]
406 .list
407 .get(depth as usize - 1)
408 } else {
409 None
410 }
411 }
412
413 #[inline(always)]
419 pub unsafe fn scope_unchecked(
420 &self,
421 stack_id: GcScopeStackId,
422 index: u32,
423 ) -> &GcScopeState<'_> {
424 debug_assert!(
425 (index as usize) < self.scope_stacks[stack_id.0 as usize].list.len(),
426 "scope_unchecked: index {index} out of bounds for stack {} (len {})",
427 stack_id.0,
428 self.scope_stacks[stack_id.0 as usize].list.len(),
429 );
430 unsafe {
432 std::mem::transmute::<&GcScopeState<'static>, &GcScopeState<'_>>(
433 self.scope_stacks[stack_id.0 as usize]
434 .list
435 .get_unchecked(index as usize),
436 )
437 }
438 }
439
440 #[inline(always)]
442 fn pop_scope(&mut self, stack_id: GcScopeStackId) -> Option<GcScopeState<'_>> {
443 self.scope_stacks[stack_id.0 as usize].list.pop()
444 }
445
446 pub fn new_scope<'s>(&'s mut self, stack_id: GcScopeStackId) -> GcScope<'s> {
454 let heap = NonNull::from_ref(self);
455 let stack = &mut self.scope_stacks[stack_id.0 as usize];
456 debug_assert!(
457 stack.partition.is_some(),
458 "scope stack {stack_id:?} is not acquired"
459 );
460
461 let list_len = stack.list.len();
462 if list_len >= u32::MAX as usize {
463 panic!("scope stack overflow: depth {} exceeds u32::MAX", list_len);
464 }
465 let depth = list_len as u32 + 1;
466 let state = GcScopeState {
467 heap,
468 stack_id,
469 partition_id: stack.partition.unwrap(),
470 depth: NonZeroU32::new(depth).unwrap(),
471 cache: UnsafeCell::new(None),
472 #[cfg(debug_assertions)]
473 borrow_flag: Cell::new(false),
474 _marker: PhantomData,
475 };
476
477 stack.list.push(unsafe {
478 std::mem::transmute::<GcScopeState<'_>, GcScopeState<'static>>(state)
481 });
482
483 GcScope {
484 heap,
485 stack_id,
486 index: (depth - 1) as u32,
487 _marker: PhantomData,
488 }
489 }
490
491 #[inline]
493 pub fn current_scope(&self, stack_id: GcScopeStackId) -> Option<&GcScopeState<'_>> {
494 let s = self.scope_stacks[stack_id.0 as usize].list.last();
495 unsafe {
496 std::mem::transmute::<Option<&GcScopeState<'static>>, Option<&GcScopeState<'_>>>(s)
499 }
500 }
501
502 #[inline]
504 pub fn with_current_scope_of<R>(
505 &mut self,
506 stack_id: GcScopeStackId,
507 f: impl FnOnce(&mut GcScopeState) -> R,
508 ) -> Option<R> {
509 self.scope_stacks[stack_id.0 as usize]
510 .list
511 .last_mut()
512 .map(f)
513 }
514
515 #[inline]
516 pub fn with_new_scope<R>(
517 &mut self,
518 stack_id: GcScopeStackId,
519 f: impl FnOnce(GcScope<'_>) -> R,
520 ) -> R {
521 let scope = self.new_scope(stack_id);
522 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(scope)));
523
524 match result {
525 Ok(r) => r,
526 Err(e) => std::panic::resume_unwind(e),
527 }
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use crate::{GcRef, GcTraceCtx, trace::GcTrace};
534
535 use super::*;
536
537 #[derive(Debug)]
538 struct Node {
539 next: Option<GcRef<Node>>,
540 value: i32,
541 }
542
543 impl GcTrace for Node {
544 fn trace(&self, tr: &mut GcTraceCtx) {
545 if let Some(next) = self.next {
546 tr.add(next);
547 }
548 }
549 }
550
551 crate::gc_type_register! {
552 Node, drop_pass = 0;
553 }
554
555 #[test]
556 fn test_gc_context_local_flag_set_and_cleared_on_commit() {
557 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
558 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
559 let stack_id = heap.acquire_scope_stack(partition_id);
560
561 let head;
562
563 {
564 let ctx = heap.new_scope(stack_id);
565 let node = ctx
566 .alloc_local(Node {
567 next: None,
568 value: 1,
569 })
570 .unwrap();
571
572 head = node.as_raw().node_ptr();
573
574 unsafe {
575 assert!(head.as_ref().is_local());
576 }
577
578 ctx.clear();
579
580 unsafe {
581 assert!(!head.as_ref().is_local());
582 }
583 }
584
585 unsafe {
586 assert!(!head.as_ref().is_local());
587 }
588 }
589
590 #[test]
591 fn test_gc_context_alloc_protects_and_unprotects_on_drop() {
592 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
593 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
594 let stack_id = heap.acquire_scope_stack(partition_id);
595
596 let head;
597 {
598 let mut ctx = heap.new_scope(stack_id);
599 let node = ctx
600 .alloc_local(Node {
601 next: None,
602 value: 1,
603 })
604 .unwrap();
605
606 head = node.as_raw().node_ptr();
607
608 unsafe {
609 assert!(head.as_ref().is_local());
610 }
611
612 while !ctx.heap_mut().mark(partition_id, 64) {}
613 let removed = ctx
614 .heap_mut()
615 .sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
616 assert_eq!(removed, 0);
617 ctx.clear();
618 }
619
620 unsafe {
621 assert!(!head.as_ref().is_local());
622 }
623
624 while !heap.mark(partition_id, 64) {}
625 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
626 assert!(removed_after > 0);
627 }
628
629 #[test]
630 fn test_gc_context_add_sets_local_and_clears_on_commit() {
631 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
632 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
633 let stack_id = heap.acquire_scope_stack(partition_id);
634
635 let node: GcRef<Node> = unsafe {
636 heap.alloc_raw(
637 partition_id,
638 Node {
639 next: None,
640 value: 1,
641 },
642 )
643 }
644 .unwrap();
645
646 let head = node.head_ptr;
647
648 unsafe {
649 assert!(!head.as_ref().is_local());
650 }
651
652 {
653 let ctx = heap.new_scope(stack_id);
654 let added = ctx.add_non_local(head);
655 assert!(added);
656
657 unsafe {
658 assert!(head.as_ref().is_local());
659 }
660
661 ctx.clear();
662
663 unsafe {
664 assert!(!head.as_ref().is_local());
665 }
666 }
667
668 unsafe {
669 assert!(!head.as_ref().is_local());
670 }
671
672 while !heap.mark(partition_id, 64) {}
673 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
674 assert!(removed_after > 0);
675 }
676
677 #[test]
678 fn test_gc_context_add_on_local_node_returns_false() {
679 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
680 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
681 let stack_id = heap.acquire_scope_stack(partition_id);
682
683 let ctx = heap.new_scope(stack_id);
684 let node = ctx
685 .alloc_local(Node {
686 next: None,
687 value: 1,
688 })
689 .unwrap();
690
691 let head = node.as_raw().node_ptr();
692
693 unsafe {
694 assert!(head.as_ref().is_local());
695 }
696 let added = ctx.add_non_local(head);
697 assert!(!added);
698
699 unsafe {
700 assert!(head.as_ref().is_local());
701 }
702
703 ctx.clear();
704
705 unsafe {
706 assert!(!head.as_ref().is_local());
707 }
708 }
709
710 #[test]
711 fn test_root_node_not_collected_by_sweep() {
712 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
713 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
714
715 let head;
716
717 unsafe {
718 let node = heap
719 .alloc_root_raw(
720 partition_id,
721 Node {
722 next: None,
723 value: 1,
724 },
725 )
726 .unwrap();
727
728 head = node.head_ptr;
729
730 assert!(node.is_root());
731 assert!(head.as_ref().is_root());
732 assert!(!head.as_ref().is_local());
733 }
734
735 while !heap.mark(partition_id, 64) {}
736 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
737 assert_eq!(removed_after, 0);
738 }
739
740 #[test]
741 fn test_gc_context_alloc_multiple_nodes() {
742 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
743 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
744 let stack_id = heap.acquire_scope_stack(partition_id);
745
746 let head1;
747 let head2;
748
749 {
750 let mut ctx = heap.new_scope(stack_id);
751 let n1 = ctx
752 .alloc_local(Node {
753 next: None,
754 value: 1,
755 })
756 .unwrap();
757 let n2 = ctx
758 .alloc_local(Node {
759 next: None,
760 value: 2,
761 })
762 .unwrap();
763
764 head1 = n1.as_raw().node_ptr();
765 head2 = n2.as_raw().node_ptr();
766
767 unsafe {
768 assert!(head1.as_ref().is_local());
769 assert!(head2.as_ref().is_local());
770 }
771
772 while !ctx.heap_mut().mark(partition_id, 64) {}
773 let removed = ctx
774 .heap_mut()
775 .sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
776 assert_eq!(removed, 0);
777 ctx.clear();
778 }
779
780 unsafe {
781 assert!(!head1.as_ref().is_local());
782 assert!(!head2.as_ref().is_local());
783 }
784
785 while !heap.mark(partition_id, 64) {}
786 let removed_after = heap.sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
787 assert!(removed_after >= 2);
788 }
789
790 #[test]
791 fn test_gc_context_reset_unprotects_and_clears_cache() {
792 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
793 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
794 let stack_id = heap.acquire_scope_stack(partition_id);
795
796 let head;
797
798 {
799 let mut ctx = heap.new_scope(stack_id);
800 let node = ctx
801 .alloc_local(Node {
802 next: None,
803 value: 1,
804 })
805 .unwrap();
806
807 head = node.as_raw().node_ptr();
808
809 unsafe {
810 assert!(head.as_ref().is_local());
811 }
812
813 ctx.clear();
814
815 unsafe {
816 assert!(!head.as_ref().is_local());
817 }
818
819 while !ctx.heap_mut().mark(partition_id, 64) {}
820 let removed_before = ctx
821 .heap_mut()
822 .sweep(partition_id, GcHeap::DUMMY_DISPOSE_CALLBACK);
823 assert!(removed_before > 0);
824 }
825 }
826
827 #[test]
828 fn test_gc_context_level_for_heap_scopes() {
829 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
830 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
831 let stack_id = heap.acquire_scope_stack(partition_id);
832
833 assert_eq!(heap.scope_max_depth(stack_id), 0);
834
835 let heap_ptr = &mut heap as *mut GcHeap;
836 unsafe {
837 let s1 = (&mut *heap_ptr).new_scope(stack_id);
838 let s2 = (&mut *heap_ptr).new_scope(stack_id);
839 let s3 = (&mut *heap_ptr).new_scope(stack_id);
840
841 assert_eq!(s3.depth(), 3);
842 assert_eq!(s2.depth(), 2);
843 assert_eq!(s1.depth(), 1);
844
845 let ctx3 = (*heap_ptr).scope(stack_id, 3).unwrap();
846 assert_eq!(ctx3.depth(), 3);
847 let ctx2 = (*heap_ptr).scope(stack_id, 2).unwrap();
848 assert_eq!(ctx2.depth(), 2);
849 let ctx1 = (*heap_ptr).scope(stack_id, 1).unwrap();
850 assert_eq!(ctx1.depth(), 1);
851 }
852 }
853
854 #[test]
855 fn test_gc_context_parent_mut_returns_parent_and_level() {
856 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
857 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
858 let stack_id = heap.acquire_scope_stack(partition_id);
859
860 let heap_ptr = &mut heap as *mut GcHeap;
861 unsafe {
862 let scope1 = (&mut *heap_ptr).new_scope(stack_id);
863 let scope2 = (&mut *heap_ptr).new_scope(stack_id);
864 let scope3 = (&mut *heap_ptr).new_scope(stack_id);
865
866 assert_eq!(scope3.depth(), 3);
867 let (parent, parent_level) = scope3.parent().unwrap();
868 assert_eq!(parent_level, 2);
869 assert_eq!(parent.depth(), 2);
870
871 drop(scope3);
872
873 assert_eq!(scope2.depth(), 2);
874 let (parent, parent_level) = scope2.parent().unwrap();
875 assert_eq!(parent_level, 1);
876 assert_eq!(parent.depth(), 1);
877
878 drop(scope2);
879
880 assert_eq!(scope1.depth(), 1);
881 assert!(scope1.parent().is_none());
882 }
883 }
884
885 #[test]
886 fn test_multi_scope_stacks_are_independent() {
887 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
888 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
889 let stack1 = heap.acquire_scope_stack(partition_id);
890 let stack2 = heap.acquire_scope_stack(partition_id);
891
892 assert_ne!(stack1, stack2);
893 assert_eq!(heap.scope_max_depth(stack1), 0);
894 assert_eq!(heap.scope_max_depth(stack2), 0);
895
896 let heap_ptr = &mut heap as *mut GcHeap;
897 unsafe {
898 let _s1a = (&mut *heap_ptr).new_scope(stack1);
899 let _s2a = (&mut *heap_ptr).new_scope(stack2);
900 let _s1b = (&mut *heap_ptr).new_scope(stack1);
901
902 assert_eq!((*heap_ptr).scope_max_depth(stack1), 2);
903 assert_eq!((*heap_ptr).scope_max_depth(stack2), 1);
904 assert_eq!((*heap_ptr).current_scope(stack1).unwrap().depth(), 2);
905 assert_eq!((*heap_ptr).current_scope(stack2).unwrap().depth(), 1);
906
907 (*heap_ptr).with_current_scope_of(stack1, |ctx| {
908 let (parent, parent_level) = ctx.parent().unwrap();
909 assert_eq!(parent_level, 1);
910 assert_eq!(parent.depth(), 1);
911 assert_eq!(parent.stack_id(), stack1);
912 });
913 }
914
915 assert_eq!(heap.scope_max_depth(stack1), 0);
916 assert_eq!(heap.scope_max_depth(stack2), 0);
917
918 heap.release_scope_stack(stack1);
919 heap.release_scope_stack(stack2);
920
921 let reused = heap.acquire_scope_stack(partition_id);
922 assert_eq!(reused, stack1);
923 }
924
925 #[test]
926 fn test_gc_scope_drop_is_lifo_per_stack() {
927 let mut heap = GcHeap::new(&GC_TYPE_REGISTRY);
928 let partition_id = heap.create_partition(64 * 1024, 16 * 1024);
929 let stack1 = heap.acquire_scope_stack(partition_id);
930 let stack2 = heap.acquire_scope_stack(partition_id);
931 let heap_ptr = &mut heap as *mut GcHeap;
932
933 unsafe {
934 let scope1 = (&mut *heap_ptr).new_scope(stack1);
935 let scope2 = (&mut *heap_ptr).new_scope(stack2);
936
937 drop(scope1);
938 drop(scope2);
939 }
940
941 assert_eq!(heap.scope_max_depth(stack1), 0);
942 assert_eq!(heap.scope_max_depth(stack2), 0);
943 }
944}