Skip to main content

sim_lib_gc_tracing/
heap.rs

1use sim_lib_mutation::{
2    ArenaError, HardCappedRetainPolicy, ManagedArena, ManagedHandle, ManagedObject, RootedHandle,
3    TeardownReceipt,
4};
5
6use crate::{CollectionError, CollectionLimits, CollectionReceipt, collect};
7
8/// Explicit reclamation policy for a managed heap.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum ManagedHeapPolicy {
11    /// Reclaim unreachable objects with the bounded tracing collector.
12    Tracing(CollectionLimits),
13    /// Retain every object until explicit heap teardown.
14    Retain,
15}
16
17/// A dependency-correct managed heap composed from an arena and reclamation policy.
18///
19/// The wrapper is generic over the guest payload and lives beside the collector,
20/// so guest runtimes do not need to duplicate policy or lifecycle behavior.
21pub struct ManagedHeap<T: ManagedObject> {
22    arena: ManagedArena<T>,
23    policy: ManagedHeapPolicy,
24}
25
26impl<T: ManagedObject> ManagedHeap<T> {
27    /// Creates a heap which uses bounded tracing collection.
28    pub fn tracing(cap: usize, limits: CollectionLimits) -> Result<Self, ArenaError> {
29        Ok(Self {
30            arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
31            policy: ManagedHeapPolicy::Tracing(limits),
32        })
33    }
34
35    /// Creates a heap which retains objects until explicit teardown.
36    pub fn retaining(cap: usize) -> Result<Self, ArenaError> {
37        Ok(Self {
38            arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
39            policy: ManagedHeapPolicy::Retain,
40        })
41    }
42
43    /// Allocates a value after checking the arena capacity and identity space.
44    pub fn allocate(&mut self, value: T) -> Result<ManagedHandle, ArenaError> {
45        self.arena.allocate(value)
46    }
47
48    /// Returns a shared value reference after validating its handle.
49    pub fn get(&self, handle: ManagedHandle) -> Result<&T, ArenaError> {
50        self.arena.get(handle)
51    }
52
53    /// Returns a mutable value reference after validating its handle.
54    pub fn get_mut(&mut self, handle: ManagedHandle) -> Result<&mut T, ArenaError> {
55        self.arena.get_mut(handle)
56    }
57
58    /// Registers a validated handle as a tracing root.
59    pub fn root(&mut self, handle: ManagedHandle) -> Result<RootedHandle, ArenaError> {
60        self.arena.root(handle)
61    }
62
63    /// Releases one matching root registration.
64    pub fn release_root(&mut self, rooted: RootedHandle) -> Result<ManagedHandle, ArenaError> {
65        self.arena.release_root(rooted)
66    }
67
68    /// Returns the number of live managed allocations.
69    pub fn live_len(&self) -> usize {
70        self.arena.len()
71    }
72
73    /// Returns the selected reclamation policy.
74    pub const fn policy(&self) -> ManagedHeapPolicy {
75        self.policy
76    }
77
78    /// Describes the cycle-reclamation gap when retention is selected.
79    pub const fn cycle_leak_gap(&self) -> Option<&'static str> {
80        match self.policy {
81            ManagedHeapPolicy::Tracing(_) => None,
82            ManagedHeapPolicy::Retain => {
83                Some("unreachable strong cycles are retained until heap teardown")
84            }
85        }
86    }
87
88    /// Runs the configured reclamation policy at a safepoint.
89    ///
90    /// Retaining heaps return `None` without mutating the arena.
91    pub fn collect(&mut self) -> Result<Option<CollectionReceipt>, CollectionError> {
92        match self.policy {
93            ManagedHeapPolicy::Tracing(limits) => collect(&mut self.arena, limits).map(Some),
94            ManagedHeapPolicy::Retain => Ok(None),
95        }
96    }
97
98    /// Removes every object and root, returning deterministic teardown evidence.
99    pub fn teardown(&mut self) -> TeardownReceipt {
100        self.arena.teardown()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use sim_lib_mutation::{EdgeId, EdgeVisitor, ManagedId};
107
108    use super::*;
109
110    #[derive(Default)]
111    struct Node(Vec<ManagedId>);
112
113    impl ManagedObject for Node {
114        fn trace_edges(&self, visitor: &mut dyn EdgeVisitor) {
115            for (edge, target) in self.0.iter().copied().enumerate() {
116                visitor.strong(EdgeId(edge as u32), target);
117            }
118        }
119
120        fn clear_weak_edge(&mut self, _: EdgeId, _: ManagedId) -> bool {
121            false
122        }
123    }
124
125    fn limits() -> CollectionLimits {
126        CollectionLimits {
127            objects: 8,
128            edges: 8,
129            stack: 8,
130            work: 32,
131            clears: 8,
132            finalizers: 0,
133        }
134    }
135
136    #[test]
137    fn tracing_and_retaining_policies_match_guest_heap_behavior() {
138        let tracing = ManagedHeap::<Node>::tracing(8, limits()).unwrap();
139        assert_eq!(tracing.policy(), ManagedHeapPolicy::Tracing(limits()));
140        assert_eq!(tracing.cycle_leak_gap(), None);
141
142        let mut retaining = ManagedHeap::<Node>::retaining(8).unwrap();
143        retaining.allocate(Node::default()).unwrap();
144        assert_eq!(retaining.policy(), ManagedHeapPolicy::Retain);
145        assert!(retaining.cycle_leak_gap().unwrap().contains("cycles"));
146        assert_eq!(retaining.collect().unwrap(), None);
147        assert_eq!(retaining.live_len(), 1);
148    }
149
150    #[test]
151    fn tracing_reclaims_cycles_and_checked_access_rejects_stale_handles() {
152        let mut heap = ManagedHeap::tracing(8, limits()).unwrap();
153        let first = heap.allocate(Node::default()).unwrap();
154        let second = heap.allocate(Node::default()).unwrap();
155        heap.get_mut(first).unwrap().0.push(second.id());
156        heap.get_mut(second).unwrap().0.push(first.id());
157
158        assert_eq!(
159            heap.collect().unwrap().unwrap().swept,
160            [first.id(), second.id()]
161        );
162        assert!(matches!(heap.get(first), Err(ArenaError::StaleHandle(id)) if id == first.id()));
163        assert_eq!(heap.live_len(), 0);
164    }
165
166    #[test]
167    fn roots_survive_collection_and_teardown_reports_all_state() {
168        let mut heap = ManagedHeap::tracing(8, limits()).unwrap();
169        let handle = heap.allocate(Node::default()).unwrap();
170        let rooted = heap.root(handle).unwrap();
171        assert!(heap.collect().unwrap().unwrap().swept.is_empty());
172
173        let receipt = heap.teardown();
174        assert_eq!(receipt.objects, [handle.id()]);
175        assert_eq!(receipt.roots, [rooted.root_id()]);
176        assert_eq!(heap.live_len(), 0);
177    }
178}