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}