use std::alloc::Layout;
use std::cell::RefCell;
use crate::gc::gc_ptr::{GcObject, GcPtr};
use crate::gc::heap::Heap;
use crate::gc::immix::{ImmixSpace, Tlab};
use crate::objects::heap_object::HeapObject;
thread_local! {
static GC_HEAP: RefCell<Heap> = RefCell::new(Heap::new());
static GC_STATS: RefCell<GcRuntimeStats> = RefCell::new(GcRuntimeStats::new());
static IMMIX_TLAB: RefCell<Tlab> = RefCell::new(Tlab::new());
static IMMIX_SPACE: RefCell<ImmixSpace> = RefCell::new(ImmixSpace::with_defaults());
}
#[derive(Debug, Clone)]
pub struct GcRuntimeStats {
pub bytes_allocated: usize,
pub collections: u64,
pub gc_threshold: usize,
}
impl GcRuntimeStats {
const DEFAULT_THRESHOLD: usize = 1024 * 1024;
fn new() -> Self {
Self {
bytes_allocated: 0,
collections: 0,
gc_threshold: Self::DEFAULT_THRESHOLD,
}
}
}
pub fn gc_alloc<T: GcObject>() -> Option<GcPtr<T>> {
let layout = Layout::new::<T>();
GC_HEAP.with(|heap| {
let ptr = heap.borrow_mut().allocate(layout);
if ptr.is_null() {
return None;
}
GC_STATS.with(|stats| {
let mut s = stats.borrow_mut();
s.bytes_allocated += layout.size();
});
Some(unsafe { GcPtr::from_heap_object_ptr(ptr) })
})
}
pub fn gc_alloc_raw(layout: Layout) -> *mut HeapObject {
GC_HEAP.with(|heap| {
let ptr = heap.borrow_mut().allocate(layout);
if !ptr.is_null() {
GC_STATS.with(|stats| {
let mut s = stats.borrow_mut();
s.bytes_allocated += layout.size();
});
}
ptr
})
}
pub fn gc_collect() {
GC_HEAP.with(|heap| {
heap.borrow_mut().collect();
});
GC_STATS.with(|stats| {
let mut s = stats.borrow_mut();
s.bytes_allocated = 0;
s.collections += 1;
});
}
pub fn gc_safepoint() {
let should_collect = GC_STATS.with(|stats| {
let s = stats.borrow();
s.bytes_allocated >= s.gc_threshold
});
if should_collect {
gc_collect();
}
}
pub fn gc_stats() -> GcRuntimeStats {
GC_STATS.with(|stats| stats.borrow().clone())
}
pub fn gc_set_threshold(threshold: usize) {
GC_STATS.with(|stats| {
stats.borrow_mut().gc_threshold = threshold;
});
}
pub fn with_heap<R>(f: impl FnOnce(&mut Heap) -> R) -> R {
GC_HEAP.with(|heap| f(&mut heap.borrow_mut()))
}
pub fn gc_alloc_immix(layout: Layout) -> Option<*mut u8> {
IMMIX_TLAB.with(|tlab| {
IMMIX_SPACE.with(|space| {
let ptr = tlab
.borrow_mut()
.allocate(layout, &mut space.borrow_mut())?;
GC_STATS.with(|stats| {
let mut s = stats.borrow_mut();
s.bytes_allocated += layout.size();
});
Some(ptr)
})
})
}
pub fn with_immix_space<R>(f: impl FnOnce(&mut ImmixSpace) -> R) -> R {
IMMIX_SPACE.with(|space| f(&mut space.borrow_mut()))
}
pub fn flush_immix_tlab() {
IMMIX_TLAB.with(|tlab| {
IMMIX_SPACE.with(|space| {
tlab.borrow_mut().flush(&mut space.borrow_mut());
});
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gc_stats_initial() {
let stats = GcRuntimeStats::new();
assert_eq!(stats.bytes_allocated, 0);
assert_eq!(stats.collections, 0);
assert_eq!(stats.gc_threshold, GcRuntimeStats::DEFAULT_THRESHOLD);
}
#[test]
fn test_gc_alloc_raw_returns_non_null() {
let layout = Layout::from_size_align(64, 8).unwrap();
let ptr = gc_alloc_raw(layout);
assert!(!ptr.is_null());
}
#[test]
fn test_gc_collect_resets_allocated_bytes() {
let layout = Layout::from_size_align(128, 8).unwrap();
let _ = gc_alloc_raw(layout);
gc_collect();
let stats = gc_stats();
assert_eq!(stats.bytes_allocated, 0);
assert!(stats.collections >= 1);
}
#[test]
fn test_gc_safepoint_does_not_collect_below_threshold() {
let initial = gc_stats().collections;
gc_safepoint();
assert_eq!(gc_stats().collections, initial);
}
#[test]
fn test_gc_set_threshold() {
gc_set_threshold(512);
assert_eq!(gc_stats().gc_threshold, 512);
gc_set_threshold(GcRuntimeStats::DEFAULT_THRESHOLD);
}
}