1use parking_lot::{Mutex, RwLock};
8use runmat_builtins::Value;
11use std::collections::HashSet;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::Arc;
14use std::time::Instant;
15use thiserror::Error;
16
17pub mod allocator;
18pub mod barriers;
19pub mod collector;
20pub mod config;
21pub mod gc_ptr;
22pub use runmat_gc_api::GcPtr;
23pub mod generations;
24pub mod roots;
25pub mod stats;
26
27use std::collections::HashMap;
29
30pub trait GcFinalizer: Send + Sync {
34 fn finalize(&self);
35}
36
37static FINALIZERS: once_cell::sync::Lazy<
38 parking_lot::Mutex<HashMap<usize, std::sync::Arc<dyn GcFinalizer>>>,
39> = once_cell::sync::Lazy::new(|| parking_lot::Mutex::new(HashMap::new()));
40
41pub fn gc_register_finalizer(ptr: GcPtr<Value>, f: std::sync::Arc<dyn GcFinalizer>) -> Result<()> {
43 let addr = unsafe { ptr.as_raw() } as usize;
44 if addr == 0 {
45 return Ok(());
46 }
47 FINALIZERS.lock().insert(addr, f);
48 Ok(())
49}
50
51pub fn gc_unregister_finalizer(ptr: GcPtr<Value>) -> Result<()> {
53 let addr = unsafe { ptr.as_raw() } as usize;
54 if addr == 0 {
55 return Ok(());
56 }
57 FINALIZERS.lock().remove(&addr);
58 Ok(())
59}
60
61pub(crate) fn gc_run_finalizer_for_addr(addr: usize) {
63 if let Some(f) = FINALIZERS.lock().remove(&addr) {
64 f.finalize();
66 }
67}
68
69#[cfg(feature = "pointer-compression")]
70pub mod compression;
71
72pub use allocator::{AllocatorStats, GenerationalAllocator, SizeClass};
73use barriers::WriteBarrierManager;
74pub use barriers::{CardTable, WriteBarrier};
75pub use collector::MarkSweepCollector;
76pub use config::{GcConfig, GcConfigBuilder};
77pub use generations::{Generation, GenerationStats, GenerationalHeap, GenerationalHeapStats};
79pub use roots::{GcRoot, GlobalRoot, RootId, RootScanner, StackRoot, VariableArrayRoot};
80pub use stats::{CollectionEvent, CollectionType, GcStats};
81
82#[cfg(feature = "pointer-compression")]
83pub use compression::*;
84
85#[derive(Error, Debug)]
87pub enum GcError {
88 #[error("Out of memory: {0}")]
89 OutOfMemory(String),
90
91 #[error("Invalid GC pointer: {0}")]
92 InvalidPointer(String),
93
94 #[error("Collection failed: {0}")]
95 CollectionFailed(String),
96
97 #[error("Root registration failed: {0}")]
98 RootRegistrationFailed(String),
99
100 #[error("Configuration error: {0}")]
101 ConfigError(String),
102
103 #[error("Thread synchronization error: {0}")]
104 SyncError(String),
105}
106
107pub type Result<T> = std::result::Result<T, GcError>;
108
109pub struct HighPerformanceGC {
113 config: Arc<RwLock<GcConfig>>,
115
116 allocator: Mutex<GenerationalAllocator>,
118
119 collector: Mutex<MarkSweepCollector>,
121
122 root_ptrs: Arc<Mutex<HashSet<usize>>>,
124
125 collection_in_progress: AtomicBool,
127
128 stats: Arc<GcStats>,
130}
131
132impl HighPerformanceGC {
133 pub fn new() -> Result<Self> {
134 Self::with_config(GcConfig::default())
135 }
136
137 pub fn with_config(config: GcConfig) -> Result<Self> {
138 let allocator = GenerationalAllocator::new(&config);
139 let collector = MarkSweepCollector::new(&config);
140
141 Ok(Self {
142 config: Arc::new(RwLock::new(config)),
143 allocator: Mutex::new(allocator),
144 collector: Mutex::new(collector),
145 root_ptrs: Arc::new(Mutex::new(HashSet::new())),
146 collection_in_progress: AtomicBool::new(false),
147 stats: Arc::new(GcStats::new()),
148 })
149 }
150
151 pub fn allocate(&self, value: Value) -> Result<GcPtr<Value>> {
153 if self.should_collect() {
155 let _ = self.collect_minor();
156 }
157
158 let gpu_handle: Option<runmat_accelerate_api::GpuTensorHandle> =
160 if let Value::GpuTensor(h) = &value {
161 Some(h.clone())
162 } else {
163 None
164 };
165
166 let mut allocator = self.allocator.lock();
167 let ptr = allocator.allocate(value, &self.stats)?;
168 let usage = allocator.young_generation_usage();
169 let alloc_count = allocator.young_allocations_count();
170 let cfg = self.config.read().clone();
171 drop(allocator);
172
173 if let Some(handle) = gpu_handle {
175 struct GpuTensorFinalizer {
176 handle: runmat_accelerate_api::GpuTensorHandle,
177 }
178 impl GcFinalizer for GpuTensorFinalizer {
179 fn finalize(&self) {
180 if let Some(p) = runmat_accelerate_api::provider() {
181 let _ = p.free(&self.handle);
182 runmat_accelerate_api::clear_handle_logical(&self.handle);
183 }
184 }
185 }
186 let fin = std::sync::Arc::new(GpuTensorFinalizer { handle });
187 let _ = gc_register_finalizer(ptr.clone(), fin);
188 }
189
190 if usage > cfg.minor_gc_threshold
194 || (cfg.minor_gc_threshold <= 0.35 && alloc_count > 0 && alloc_count.is_multiple_of(32))
195 {
196 let _ = self.collect_minor();
197 }
198 Ok(ptr)
199 }
200
201 fn should_collect(&self) -> bool {
205 if self.collection_in_progress.load(Ordering::Acquire) {
206 return false;
207 }
208
209 let threshold = self.config.read().minor_gc_threshold;
210 let allocator = self.allocator.lock();
211 let utilization = allocator.young_generation_usage();
212 utilization > threshold
213 }
214
215 pub fn collect_minor(&self) -> Result<usize> {
217 if self
218 .collection_in_progress
219 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
220 .is_err()
221 {
222 return Ok(0);
223 }
224
225 let start_time = Instant::now();
226
227 let mut combined_roots: Vec<GcPtr<Value>> = Vec::new();
229 {
230 let roots = self.root_ptrs.lock();
231 combined_roots.extend(
232 roots
233 .iter()
234 .map(|&addr| unsafe { GcPtr::from_raw(addr as *const Value) }),
235 );
236 }
237 if let Ok(mut ext) = ROOT_SCANNER.scan_roots() {
239 combined_roots.append(&mut ext);
240 }
241 combined_roots.extend(gc_barrier_minor_roots());
242
243 let mut allocator = self.allocator.lock();
245 let mut collector = self.collector.lock();
246 let collected_count =
247 collector.collect_young_generation(&mut allocator, &combined_roots, &self.stats)?;
248
249 WRITE_BARRIERS.clear_after_minor_gc();
251
252 let duration = start_time.elapsed();
253 self.stats
254 .record_minor_collection(collected_count, duration);
255
256 self.collection_in_progress.store(false, Ordering::Release);
257
258 log::info!("Minor GC: collected {collected_count} objects in {duration:?}");
259
260 Ok(collected_count)
261 }
262
263 pub fn collect_major(&self) -> Result<usize> {
274 if self
275 .collection_in_progress
276 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
277 .is_err()
278 {
279 return Ok(0);
280 }
281
282 let start_time = Instant::now();
283
284 let mut combined_roots: Vec<GcPtr<Value>> = Vec::new();
286 {
287 let roots = self.root_ptrs.lock();
288 combined_roots.extend(
289 roots
290 .iter()
291 .map(|&addr| unsafe { GcPtr::from_raw(addr as *const Value) }),
292 );
293 }
294 if let Ok(mut ext) = ROOT_SCANNER.scan_roots() {
295 combined_roots.append(&mut ext);
296 }
297 combined_roots.extend(gc_barrier_minor_roots());
298
299 let mut allocator = self.allocator.lock();
300 let mut collector = self.collector.lock();
301 let collected_count =
302 collector.collect_all_generations(&mut allocator, &combined_roots, &self.stats)?;
303
304 WRITE_BARRIERS.clear_after_major_gc();
306 allocator.clear_promotion_state();
307
308 let duration = start_time.elapsed();
309 self.stats
310 .record_major_collection(collected_count, duration);
311
312 self.collection_in_progress.store(false, Ordering::Release);
313
314 log::info!("Major GC: collected {collected_count} objects in {duration:?}");
315
316 Ok(collected_count)
317 }
318
319 pub fn add_root(&self, root: GcPtr<Value>) -> Result<()> {
321 let value_ptr = unsafe { root.as_raw() } as usize;
322 if value_ptr == 0 {
323 return Ok(());
324 }
325 self.root_ptrs.lock().insert(value_ptr);
326 Ok(())
327 }
328
329 pub fn remove_root(&self, root: GcPtr<Value>) -> Result<()> {
331 let value_ptr = unsafe { root.as_raw() } as usize;
332 if value_ptr == 0 {
333 return Ok(());
334 }
335 self.root_ptrs.lock().remove(&value_ptr);
336 Ok(())
337 }
338
339 pub fn stats(&self) -> GcStats {
343 self.stats.as_ref().clone()
344 }
345
346 pub fn configure(&self, config: GcConfig) -> Result<()> {
348 *self.config.write() = config.clone();
349 {
350 let mut allocator = self.allocator.lock();
352 *allocator = GenerationalAllocator::new(&config);
353 }
354 {
355 let mut collector = self.collector.lock();
356 collector.reconfigure(&config)?;
357 }
358 Ok(())
359 }
360
361 pub fn get_config(&self) -> GcConfig {
363 self.config.read().clone()
364 }
365}
366
367unsafe impl Send for HighPerformanceGC {}
369unsafe impl Sync for HighPerformanceGC {}
370
371static GC: once_cell::sync::Lazy<Arc<HighPerformanceGC>> =
373 once_cell::sync::Lazy::new(|| Arc::new(HighPerformanceGC::new().expect("Failed to create GC")));
374static ROOT_SCANNER: once_cell::sync::Lazy<Arc<RootScanner>> =
375 once_cell::sync::Lazy::new(|| Arc::new(RootScanner::new()));
376
377static WRITE_BARRIERS: once_cell::sync::Lazy<Arc<WriteBarrierManager>> =
379 once_cell::sync::Lazy::new(|| Arc::new(WriteBarrierManager::new(true, false)));
380
381pub fn gc_deref(ptr: GcPtr<Value>) -> Value {
383 (*ptr).clone()
384}
385
386pub fn gc_allocate(value: Value) -> Result<GcPtr<Value>> {
388 GC.allocate(value)
389}
390
391pub fn gc_collect_minor() -> Result<usize> {
392 GC.collect_minor()
394}
395
396pub fn gc_collect_major() -> Result<usize> {
397 GC.collect_major()
398}
399
400pub fn gc_add_root(root: GcPtr<Value>) -> Result<()> {
401 GC.add_root(root)
402}
403
404pub fn gc_remove_root(root: GcPtr<Value>) -> Result<()> {
405 GC.remove_root(root)
406}
407
408pub fn gc_stats() -> GcStats {
409 GC.stats()
410}
411
412pub fn gc_configure(config: GcConfig) -> Result<()> {
413 GC.configure(config)
414}
415
416pub fn gc_get_config() -> GcConfig {
417 GC.get_config()
418}
419
420pub fn gc_register_root(root: Box<dyn GcRoot>) -> Result<RootId> {
422 ROOT_SCANNER.register_root(root)
423}
424
425pub fn gc_unregister_root(root_id: RootId) -> Result<()> {
426 ROOT_SCANNER.unregister_root(root_id)
427}
428
429pub fn gc_record_write(old: &Value, new: &Value) {
431 let old_ptr = old as *const Value as *const u8;
433 let young_ptr = new as *const Value as *const u8;
434 let alloc = GC.allocator.lock();
436 let old_gen = alloc.logical_generation(old_ptr).unwrap_or(0);
437 let new_gen = alloc.logical_generation(young_ptr).unwrap_or(0);
438 drop(alloc);
439 if old_gen > new_gen {
440 WRITE_BARRIERS.record_reference(old_ptr, young_ptr);
441 }
442}
443
444pub fn gc_barrier_minor_roots() -> Vec<GcPtr<Value>> {
446 WRITE_BARRIERS
447 .get_minor_gc_roots()
448 .into_iter()
449 .map(|p| unsafe { GcPtr::from_raw(p as *const Value) })
450 .collect()
451}
452
453#[cfg(any(test, feature = "debug-gc"))]
455pub fn gc_force_collect() -> Result<usize> {
456 gc_collect_major()
457}
458
459pub fn gc_reset_for_test() -> Result<()> {
461 GC.stats.reset();
463
464 {
466 let config = GcConfig::default();
467 *GC.config.write() = config.clone();
468 let mut alloc = GC.allocator.lock();
469 *alloc = GenerationalAllocator::new(&config);
470 let mut coll = GC.collector.lock();
471 *coll = MarkSweepCollector::new(&config);
472 }
473
474 {
475 let mut roots = GC.root_ptrs.lock();
476 roots.clear();
477 }
478
479 GC.collection_in_progress.store(false, Ordering::Relaxed);
481
482 gc_configure(GcConfig::default())?;
484
485 Ok(())
486}
487
488pub fn gc_test_context<F, R>(test_fn: F) -> R
490where
491 F: FnOnce() -> R,
492{
493 static TEST_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
495
496 let _guard = match TEST_MUTEX.lock() {
498 Ok(guard) => guard,
499 Err(poisoned) => {
500 poisoned.into_inner()
502 }
503 };
504
505 gc_reset_for_test().expect("GC reset should succeed");
507
508 let result = test_fn();
510
511 let _ = gc_reset_for_test();
513
514 result
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 #[test]
522 fn test_basic_allocation() {
523 gc_test_context(|| {
524 let value = Value::Num(42.0);
525 let ptr = gc_allocate(value).expect("allocation failed");
526 assert_eq!(*ptr, Value::Num(42.0));
527 });
528 }
529
530 #[test]
531 fn test_collection() {
532 gc_test_context(|| {
533 let mut _ptrs = Vec::new();
534 for i in 0..100 {
535 let ptr = gc_allocate(Value::Num(i as f64)).expect("allocation failed");
536 _ptrs.push(ptr);
537 }
538
539 let _collected = gc_collect_minor().expect("collection failed");
540 drop(_ptrs);
541 });
542 }
543
544 #[test]
545 fn test_thread_safety() {
546 use std::thread;
547
548 gc_test_context(|| {
549 let handles: Vec<_> = (0..4)
550 .map(|i| {
551 thread::spawn(move || {
552 let mut ptrs = Vec::new();
553 for j in 0..100 {
554 let value = Value::Num((i * 100 + j) as f64);
555 let ptr = gc_allocate(value).expect("allocation failed");
556 ptrs.push(ptr);
557 }
558 ptrs
559 })
560 })
561 .collect();
562
563 for handle in handles {
564 let _ptrs = handle.join().expect("thread failed");
565 }
566
567 let _ = gc_collect_major();
568 });
569 }
570
571 #[test]
572 fn test_root_protection() {
573 gc_test_context(|| {
574 let protected = gc_allocate(Value::Num(42.0)).expect("allocation failed");
575 gc_add_root(protected.clone()).expect("root registration failed");
576
577 for i in 0..60 {
578 let _ = gc_allocate(Value::String(format!("garbage_{i}")));
579 }
580
581 let _ = gc_collect_minor().expect("collection failed");
582 assert_eq!(*protected, Value::Num(42.0));
583
584 gc_remove_root(protected).expect("root removal failed");
585 });
586 }
587
588 #[test]
589 fn test_gc_allocation_and_roots() {
590 gc_test_context(|| {
591 let v = gc_allocate(Value::Num(7.0)).expect("allocation failed");
592 assert_eq!(*v, Value::Num(7.0));
593
594 gc_add_root(v.clone()).expect("root add failed");
595 let _ = gc_collect_minor().expect("collection failed");
596 assert_eq!(*v, Value::Num(7.0));
597
598 gc_remove_root(v).expect("root remove failed");
599 });
600 }
601}