use sim_lib_mutation::{
ArenaError, HardCappedRetainPolicy, ManagedArena, ManagedHandle, ManagedObject, RootedHandle,
TeardownReceipt,
};
use crate::{CollectionError, CollectionLimits, CollectionReceipt, collect};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ManagedHeapPolicy {
Tracing(CollectionLimits),
Retain,
}
pub struct ManagedHeap<T: ManagedObject> {
arena: ManagedArena<T>,
policy: ManagedHeapPolicy,
}
impl<T: ManagedObject> ManagedHeap<T> {
pub fn tracing(cap: usize, limits: CollectionLimits) -> Result<Self, ArenaError> {
Ok(Self {
arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
policy: ManagedHeapPolicy::Tracing(limits),
})
}
pub fn retaining(cap: usize) -> Result<Self, ArenaError> {
Ok(Self {
arena: ManagedArena::new(HardCappedRetainPolicy::new(cap)?),
policy: ManagedHeapPolicy::Retain,
})
}
pub fn allocate(&mut self, value: T) -> Result<ManagedHandle, ArenaError> {
self.arena.allocate(value)
}
pub fn get(&self, handle: ManagedHandle) -> Result<&T, ArenaError> {
self.arena.get(handle)
}
pub fn get_mut(&mut self, handle: ManagedHandle) -> Result<&mut T, ArenaError> {
self.arena.get_mut(handle)
}
pub fn root(&mut self, handle: ManagedHandle) -> Result<RootedHandle, ArenaError> {
self.arena.root(handle)
}
pub fn release_root(&mut self, rooted: RootedHandle) -> Result<ManagedHandle, ArenaError> {
self.arena.release_root(rooted)
}
pub fn live_len(&self) -> usize {
self.arena.len()
}
pub const fn policy(&self) -> ManagedHeapPolicy {
self.policy
}
pub const fn cycle_leak_gap(&self) -> Option<&'static str> {
match self.policy {
ManagedHeapPolicy::Tracing(_) => None,
ManagedHeapPolicy::Retain => {
Some("unreachable strong cycles are retained until heap teardown")
}
}
}
pub fn collect(&mut self) -> Result<Option<CollectionReceipt>, CollectionError> {
match self.policy {
ManagedHeapPolicy::Tracing(limits) => collect(&mut self.arena, limits).map(Some),
ManagedHeapPolicy::Retain => Ok(None),
}
}
pub fn teardown(&mut self) -> TeardownReceipt {
self.arena.teardown()
}
}
#[cfg(test)]
mod tests {
use sim_lib_mutation::{EdgeId, EdgeVisitor, ManagedId};
use super::*;
#[derive(Default)]
struct Node(Vec<ManagedId>);
impl ManagedObject for Node {
fn trace_edges(&self, visitor: &mut dyn EdgeVisitor) {
for (edge, target) in self.0.iter().copied().enumerate() {
visitor.strong(EdgeId(edge as u32), target);
}
}
fn clear_weak_edge(&mut self, _: EdgeId, _: ManagedId) -> bool {
false
}
}
fn limits() -> CollectionLimits {
CollectionLimits {
objects: 8,
edges: 8,
stack: 8,
work: 32,
clears: 8,
finalizers: 0,
}
}
#[test]
fn tracing_and_retaining_policies_match_guest_heap_behavior() {
let tracing = ManagedHeap::<Node>::tracing(8, limits()).unwrap();
assert_eq!(tracing.policy(), ManagedHeapPolicy::Tracing(limits()));
assert_eq!(tracing.cycle_leak_gap(), None);
let mut retaining = ManagedHeap::<Node>::retaining(8).unwrap();
retaining.allocate(Node::default()).unwrap();
assert_eq!(retaining.policy(), ManagedHeapPolicy::Retain);
assert!(retaining.cycle_leak_gap().unwrap().contains("cycles"));
assert_eq!(retaining.collect().unwrap(), None);
assert_eq!(retaining.live_len(), 1);
}
#[test]
fn tracing_reclaims_cycles_and_checked_access_rejects_stale_handles() {
let mut heap = ManagedHeap::tracing(8, limits()).unwrap();
let first = heap.allocate(Node::default()).unwrap();
let second = heap.allocate(Node::default()).unwrap();
heap.get_mut(first).unwrap().0.push(second.id());
heap.get_mut(second).unwrap().0.push(first.id());
assert_eq!(
heap.collect().unwrap().unwrap().swept,
[first.id(), second.id()]
);
assert!(matches!(heap.get(first), Err(ArenaError::StaleHandle(id)) if id == first.id()));
assert_eq!(heap.live_len(), 0);
}
#[test]
fn roots_survive_collection_and_teardown_reports_all_state() {
let mut heap = ManagedHeap::tracing(8, limits()).unwrap();
let handle = heap.allocate(Node::default()).unwrap();
let rooted = heap.root(handle).unwrap();
assert!(heap.collect().unwrap().unwrap().swept.is_empty());
let receipt = heap.teardown();
assert_eq!(receipt.objects, [handle.id()]);
assert_eq!(receipt.roots, [rooted.root_id()]);
assert_eq!(heap.live_len(), 0);
}
}