sim_lib_gc_tracing/
heap.rs1use sim_lib_mutation::{
2 ArenaError, HardCappedRetainPolicy, ManagedArena, ManagedHandle, ManagedObject, RootedHandle,
3 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 root(&mut self, handle: ManagedHandle) -> Result<RootedHandle, ArenaError> {
60 self.arena.root(handle)
61 }
62
63 pub fn release_root(&mut self, rooted: RootedHandle) -> Result<ManagedHandle, ArenaError> {
65 self.arena.release_root(rooted)
66 }
67
68 pub fn live_len(&self) -> usize {
70 self.arena.len()
71 }
72
73 pub const fn policy(&self) -> ManagedHeapPolicy {
75 self.policy
76 }
77
78 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 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 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}