sim_lib_gc_tracing/
heap.rs1use sim_lib_mutation::{
2 ArenaError, HardCappedRetainPolicy, ManagedArena, ManagedHandle, ManagedId, ManagedObject,
3 RootedHandle, TeardownReceipt,
4};
5
6use crate::{CollectionError, CollectionLimits, CollectionReceipt, collect};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum ManagedHeapPolicy {
11 Tracing(CollectionLimits),
13 Retain,
15}
16
17pub struct ManagedHeap<T: ManagedObject> {
22 arena: ManagedArena<T>,
23 policy: ManagedHeapPolicy,
24}
25
26impl<T: ManagedObject> ManagedHeap<T> {
27 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 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 pub fn allocate(&mut self, value: T) -> Result<ManagedHandle, ArenaError> {
45 self.arena.allocate(value)
46 }
47
48 pub fn get(&self, handle: ManagedHandle) -> Result<&T, ArenaError> {
50 self.arena.get(handle)
51 }
52
53 pub fn get_mut(&mut self, handle: ManagedHandle) -> Result<&mut T, ArenaError> {
55 self.arena.get_mut(handle)
56 }
57
58 pub fn handle(&self, id: ManagedId) -> Result<ManagedHandle, ArenaError> {
60 self.arena.handle(id)
61 }
62
63 pub fn root(&mut self, handle: ManagedHandle) -> Result<RootedHandle, ArenaError> {
65 self.arena.root(handle)
66 }
67
68 pub fn release_root(&mut self, rooted: RootedHandle) -> Result<ManagedHandle, ArenaError> {
70 self.arena.release_root(rooted)
71 }
72
73 pub fn live_len(&self) -> usize {
75 self.arena.len()
76 }
77
78 pub const fn policy(&self) -> ManagedHeapPolicy {
80 self.policy
81 }
82
83 pub const fn cycle_leak_gap(&self) -> Option<&'static str> {
85 match self.policy {
86 ManagedHeapPolicy::Tracing(_) => None,
87 ManagedHeapPolicy::Retain => {
88 Some("unreachable strong cycles are retained until heap teardown")
89 }
90 }
91 }
92
93 pub fn collect(&mut self) -> Result<Option<CollectionReceipt>, CollectionError> {
97 match self.policy {
98 ManagedHeapPolicy::Tracing(limits) => collect(&mut self.arena, limits).map(Some),
99 ManagedHeapPolicy::Retain => Ok(None),
100 }
101 }
102
103 pub fn teardown(&mut self) -> TeardownReceipt {
105 self.arena.teardown()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use sim_lib_mutation::{EdgeId, EdgeVisitor, ManagedId};
112
113 use super::*;
114
115 #[derive(Default)]
116 struct Node(Vec<ManagedId>);
117
118 impl ManagedObject for Node {
119 fn trace_edges(&self, visitor: &mut dyn EdgeVisitor) {
120 for (edge, target) in self.0.iter().copied().enumerate() {
121 visitor.strong(EdgeId(edge as u32), target);
122 }
123 }
124
125 fn clear_weak_edge(&mut self, _: EdgeId, _: ManagedId) -> bool {
126 false
127 }
128 }
129
130 fn limits() -> CollectionLimits {
131 CollectionLimits {
132 objects: 8,
133 edges: 8,
134 stack: 8,
135 work: 32,
136 clears: 8,
137 finalizers: 0,
138 }
139 }
140
141 #[test]
142 fn tracing_and_retaining_policies_match_guest_heap_behavior() {
143 let tracing = ManagedHeap::<Node>::tracing(8, limits()).unwrap();
144 assert_eq!(tracing.policy(), ManagedHeapPolicy::Tracing(limits()));
145 assert_eq!(tracing.cycle_leak_gap(), None);
146
147 let mut retaining = ManagedHeap::<Node>::retaining(8).unwrap();
148 retaining.allocate(Node::default()).unwrap();
149 assert_eq!(retaining.policy(), ManagedHeapPolicy::Retain);
150 assert!(retaining.cycle_leak_gap().unwrap().contains("cycles"));
151 assert_eq!(retaining.collect().unwrap(), None);
152 assert_eq!(retaining.live_len(), 1);
153 }
154
155 #[test]
156 fn tracing_reclaims_cycles_and_checked_access_rejects_stale_handles() {
157 let mut heap = ManagedHeap::tracing(8, limits()).unwrap();
158 let first = heap.allocate(Node::default()).unwrap();
159 let second = heap.allocate(Node::default()).unwrap();
160 assert_eq!(heap.handle(first.id()).unwrap(), first);
161 heap.get_mut(first).unwrap().0.push(second.id());
162 heap.get_mut(second).unwrap().0.push(first.id());
163
164 assert_eq!(
165 heap.collect().unwrap().unwrap().swept,
166 [first.id(), second.id()]
167 );
168 assert!(matches!(heap.get(first), Err(ArenaError::StaleHandle(id)) if id == first.id()));
169 assert!(
170 matches!(heap.handle(first.id()), Err(ArenaError::StaleHandle(id)) if id == first.id())
171 );
172 assert_eq!(heap.live_len(), 0);
173 }
174
175 #[test]
176 fn roots_survive_collection_and_teardown_reports_all_state() {
177 let mut heap = ManagedHeap::tracing(8, limits()).unwrap();
178 let handle = heap.allocate(Node::default()).unwrap();
179 let rooted = heap.root(handle).unwrap();
180 assert!(heap.collect().unwrap().unwrap().swept.is_empty());
181
182 let receipt = heap.teardown();
183 assert_eq!(receipt.objects, [handle.id()]);
184 assert_eq!(receipt.roots, [rooted.root_id()]);
185 assert_eq!(heap.live_len(), 0);
186 }
187}