Skip to main content

shape_vm/
memory.rs

1//! Memory management for Shape VM
2//!
3//! Without `gc` feature: stub using Arc reference counting (no-op GC).
4//! With `gc` feature: delegates to shape-gc's GcHeap for real collection.
5
6use std::cell::RefCell;
7use std::time::{Duration, Instant};
8
9/// Garbage collection configuration
10#[derive(Debug, Clone)]
11pub struct GCConfig {
12    pub initial_heap_size: usize,
13    pub max_heap_size: usize,
14    pub collection_threshold: f64,
15    pub generational: bool,
16    pub incremental: bool,
17    pub max_increment_time: u64,
18    pub enable_stats: bool,
19}
20
21impl Default for GCConfig {
22    fn default() -> Self {
23        Self {
24            initial_heap_size: 1024 * 1024,
25            max_heap_size: 64 * 1024 * 1024,
26            collection_threshold: 0.75,
27            generational: true,
28            incremental: true,
29            max_increment_time: 1000,
30            enable_stats: false,
31        }
32    }
33}
34
35/// Unique identifier for managed objects (legacy, kept for API compatibility)
36pub type ObjectId = u64;
37
38/// Garbage collection statistics
39#[derive(Debug, Default, Clone)]
40pub struct GCStats {
41    pub collections: u64,
42    pub objects_collected: u64,
43    pub bytes_collected: u64,
44    pub total_collection_time: Duration,
45    pub avg_collection_time: Duration,
46    pub peak_heap_size: usize,
47    pub current_heap_size: usize,
48    pub last_collection: Option<Instant>,
49}
50
51/// Shape VM Garbage Collector
52///
53/// Without `gc` feature: stub (all operations are no-ops, Arc handles memory).
54/// With `gc` feature: tracks stats from GcHeap collections.
55pub struct GarbageCollector {
56    config: GCConfig,
57    stats: RefCell<GCStats>,
58}
59
60impl GarbageCollector {
61    pub fn new(config: GCConfig) -> Self {
62        Self {
63            config,
64            stats: RefCell::new(GCStats::default()),
65        }
66    }
67
68    pub fn config(&self) -> &GCConfig {
69        &self.config
70    }
71
72    pub fn add_root(&self, _obj_id: ObjectId) {}
73    pub fn remove_root(&self, _obj_id: ObjectId) {}
74    pub fn collect(&self) -> GCResult {
75        GCResult::empty()
76    }
77    /// Incremental collection step (no-op in this stub).
78    ///
79    /// When the `gc` feature is enabled, incremental marking is driven by
80    /// `gc_heap.collect_incremental()` in `gc_integration.rs` -- this stub
81    /// is not called in that path. It exists for API compatibility when
82    /// the `gc` feature is disabled (Arc refcounting handles memory).
83    pub fn collect_incremental(&self) {}
84    pub fn force_collect(&self) -> GCResult {
85        GCResult::empty()
86    }
87    pub fn heap_size(&self) -> usize {
88        0
89    }
90    pub fn object_count(&self) -> usize {
91        0
92    }
93    pub fn stats(&self) -> GCStats {
94        self.stats.borrow().clone()
95    }
96    pub fn contains_object(&self, _obj_id: ObjectId) -> bool {
97        false
98    }
99
100    /// Record a collection in the stats (used by GC integration).
101    pub fn record_collection(&self, result: &GCResult) {
102        let mut stats = self.stats.borrow_mut();
103        stats.collections += 1;
104        stats.objects_collected += result.objects_collected;
105        stats.bytes_collected += result.bytes_collected;
106        stats.total_collection_time += result.duration;
107        if stats.collections > 0 {
108            stats.avg_collection_time = stats.total_collection_time / stats.collections as u32;
109        }
110        stats.last_collection = Some(Instant::now());
111    }
112}
113
114/// Write barrier for GC-tracked heap writes (raw u64 bits).
115///
116/// Called when a heap pointer in an existing slot is overwritten.
117/// `old` is the NaN-boxed bits being replaced; `new` is the incoming bits.
118///
119/// Without `gc` feature: no-op (compiles away entirely).
120/// With `gc` feature: enqueues the old reference into the SATB buffer
121/// and marks the new reference gray if an incremental marking cycle is active.
122#[inline(always)]
123pub fn write_barrier_slot(_old: u64, _new: u64) {
124    #[cfg(feature = "gc_barrier_debug")]
125    {
126        BARRIER_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
127    }
128    #[cfg(feature = "gc")]
129    {
130        // Will wire to shape_gc::barrier::SatbBuffer::enqueue() here.
131        // 1. `_old` may become unreachable (SATB enqueue)
132        // 2. `_new` has a new reference (mark gray)
133    }
134}
135
136// `write_barrier_vw(old: &ValueWord, new: &ValueWord)` was deleted with the
137// `ValueWord` carrier (ADR-006 §2.7.7 — `ValueWord` removed; the parallel
138// `Vec<NativeKind>` track is the post-strict-typing dispatch surface). The
139// raw-bits entry point `write_barrier_slot(old: u64, new: u64)` below is
140// the only barrier surface; callers that previously took `&ValueWord`
141// arguments must thread raw bits via `stack_read_kinded_raw` or the
142// equivalent kinded-slot API and call `write_barrier_slot` directly.
143
144/// Write barrier counter for debug coverage assertions.
145///
146/// Incremented by every `write_barrier_slot` call when the `gc_barrier_debug`
147/// feature is enabled. Tests can compare this against a heap-write counter
148/// to verify that no write site is missing a barrier.
149#[cfg(feature = "gc_barrier_debug")]
150pub static BARRIER_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
151
152/// Heap-write counter for debug coverage assertions.
153///
154/// Incremented at every heap-write site when the `gc_barrier_debug` feature
155/// is enabled. At the end of execution, `BARRIER_COUNT >= HEAP_WRITE_COUNT`
156/// must hold, guaranteeing full barrier coverage.
157#[cfg(feature = "gc_barrier_debug")]
158pub static HEAP_WRITE_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
159
160/// Record a heap write for barrier coverage tracking.
161///
162/// Call this at every heap write site under `gc_barrier_debug`. The
163/// corresponding barrier call increments `BARRIER_COUNT`. After execution,
164/// `assert_barrier_coverage()` checks that every write was barriered.
165#[inline(always)]
166pub fn record_heap_write() {
167    #[cfg(feature = "gc_barrier_debug")]
168    {
169        HEAP_WRITE_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
170    }
171}
172
173/// Assert that every heap write was accompanied by a write barrier.
174///
175/// Panics if `BARRIER_COUNT < HEAP_WRITE_COUNT`, indicating a missing barrier.
176/// Only active under `gc_barrier_debug` feature; no-op otherwise.
177#[cfg(feature = "gc_barrier_debug")]
178pub fn assert_barrier_coverage() {
179    let barriers = BARRIER_COUNT.load(std::sync::atomic::Ordering::Relaxed);
180    let writes = HEAP_WRITE_COUNT.load(std::sync::atomic::Ordering::Relaxed);
181    assert!(
182        barriers >= writes,
183        "Write barrier coverage gap: {} heap writes but only {} barriers",
184        writes,
185        barriers
186    );
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn write_barrier_slot_does_not_panic() {
195        // Verify the barrier can be called with arbitrary bits without panicking.
196        write_barrier_slot(0, 0);
197        write_barrier_slot(u64::MAX, 0);
198        write_barrier_slot(0, u64::MAX);
199        write_barrier_slot(0xFFF8_0000_0000_0000, 0xFFF8_0000_0000_0001);
200    }
201
202    // `write_barrier_vw_does_not_panic` was deleted alongside the
203    // `write_barrier_vw` entry point (post-`ValueWord` carrier; barrier
204    // surface is now raw-bits only — `write_barrier_slot`).
205
206    #[test]
207    fn record_heap_write_does_not_panic() {
208        // Even without gc_barrier_debug, the function should be a safe no-op.
209        record_heap_write();
210    }
211}
212
213/// Result of a garbage collection
214#[derive(Debug, Clone)]
215pub struct GCResult {
216    pub objects_collected: u64,
217    pub bytes_collected: u64,
218    pub duration: Duration,
219}
220
221impl GCResult {
222    pub fn new(objects_collected: u64, bytes_collected: u64, duration: Duration) -> Self {
223        Self {
224            objects_collected,
225            bytes_collected,
226            duration,
227        }
228    }
229
230    pub fn empty() -> Self {
231        Self {
232            objects_collected: 0,
233            bytes_collected: 0,
234            duration: Duration::ZERO,
235        }
236    }
237}