use crate::trace::Trace;
use std::cell::{Cell, RefCell};
use std::mem;
use std::ptr::{self, NonNull};
struct GcState {
stats: GcStats,
config: GcConfig,
boxes_start: Option<NonNull<GcBox<dyn Trace>>>,
}
impl Drop for GcState {
fn drop(&mut self) {
if !self.config.leak_on_drop {
collect_garbage(self);
}
}
}
thread_local!(pub static GC_DROPPING: Cell<bool> = Cell::new(false));
struct DropGuard;
impl DropGuard {
fn new() -> DropGuard {
GC_DROPPING.with(|dropping| dropping.set(true));
DropGuard
}
}
impl Drop for DropGuard {
fn drop(&mut self) {
GC_DROPPING.with(|dropping| dropping.set(false));
}
}
pub fn finalizer_safe() -> bool {
GC_DROPPING.with(|dropping| !dropping.get())
}
thread_local!(static GC_STATE: RefCell<GcState> = RefCell::new(GcState {
stats: Default::default(),
config: Default::default(),
boxes_start: None,
}));
pub(crate) struct GcBoxHeader {
roots: Cell<usize>,
next: Option<NonNull<GcBox<dyn Trace>>>,
marked: Cell<bool>,
}
#[repr(C)] pub(crate) struct GcBox<T: Trace + ?Sized + 'static> {
header: GcBoxHeader,
data: T,
}
impl<T: Trace> GcBox<T> {
pub(crate) fn new(value: T) -> NonNull<Self> {
GC_STATE.with(|st| {
let mut st = st.borrow_mut();
if st.stats.bytes_allocated > st.config.threshold {
collect_garbage(&mut *st);
if st.stats.bytes_allocated as f64
> st.config.threshold as f64 * st.config.used_space_ratio
{
st.config.threshold =
(st.stats.bytes_allocated as f64 / st.config.used_space_ratio) as usize
}
}
let gcbox = Box::into_raw(Box::new(GcBox {
header: GcBoxHeader {
roots: Cell::new(1),
marked: Cell::new(false),
next: st.boxes_start.take(),
},
data: value,
}));
st.boxes_start = Some(unsafe { NonNull::new_unchecked(gcbox) });
st.stats.bytes_allocated += mem::size_of::<GcBox<T>>();
unsafe { NonNull::new_unchecked(gcbox) }
})
}
}
impl<T: Trace + ?Sized> GcBox<T> {
pub(crate) fn ptr_eq(this: &GcBox<T>, other: &GcBox<T>) -> bool {
ptr::eq(&this.header, &other.header)
}
pub(crate) unsafe fn trace_inner(&self) {
let marked = self.header.marked.get();
if !marked {
self.header.marked.set(true);
self.data.trace();
}
}
pub(crate) unsafe fn root_inner(&self) {
self.header
.roots
.set(self.header.roots.get().checked_add(1).unwrap());
}
pub(crate) unsafe fn unroot_inner(&self) {
self.header.roots.set(self.header.roots.get() - 1);
}
pub(crate) fn value(&self) -> &T {
&self.data
}
}
fn collect_garbage(st: &mut GcState) {
st.stats.collections_performed += 1;
struct Unmarked {
incoming: *mut Option<NonNull<GcBox<dyn Trace>>>,
this: NonNull<GcBox<dyn Trace>>,
}
unsafe fn mark(head: &mut Option<NonNull<GcBox<dyn Trace>>>) -> Vec<Unmarked> {
let mut mark_head = *head;
while let Some(node) = mark_head {
if (*node.as_ptr()).header.roots.get() > 0 {
(*node.as_ptr()).trace_inner();
}
mark_head = (*node.as_ptr()).header.next;
}
let mut unmarked = Vec::new();
let mut unmark_head = head;
while let Some(node) = *unmark_head {
if (*node.as_ptr()).header.marked.get() {
(*node.as_ptr()).header.marked.set(false);
} else {
unmarked.push(Unmarked {
incoming: unmark_head,
this: node,
});
}
unmark_head = &mut (*node.as_ptr()).header.next;
}
unmarked
}
unsafe fn sweep(finalized: Vec<Unmarked>, bytes_allocated: &mut usize) {
let _guard = DropGuard::new();
for node in finalized.into_iter().rev() {
if (*node.this.as_ptr()).header.marked.get() {
continue;
}
let incoming = node.incoming;
let mut node = Box::from_raw(node.this.as_ptr());
*bytes_allocated -= mem::size_of_val::<GcBox<_>>(&*node);
*incoming = node.header.next.take();
}
}
unsafe {
let unmarked = mark(&mut st.boxes_start);
if unmarked.is_empty() {
return;
}
for node in &unmarked {
Trace::finalize_glue(&(*node.this.as_ptr()).data);
}
mark(&mut st.boxes_start);
sweep(unmarked, &mut st.stats.bytes_allocated);
}
}
pub fn force_collect() {
GC_STATE.with(|st| {
let mut st = st.borrow_mut();
collect_garbage(&mut *st);
});
}
pub struct GcConfig {
pub threshold: usize,
pub used_space_ratio: f64,
pub leak_on_drop: bool,
}
impl Default for GcConfig {
fn default() -> Self {
Self {
used_space_ratio: 0.7,
threshold: 100,
leak_on_drop: false,
}
}
}
#[allow(dead_code)]
pub fn configure(configurer: impl FnOnce(&mut GcConfig)) {
GC_STATE.with(|st| {
let mut st = st.borrow_mut();
configurer(&mut st.config);
})
}
#[derive(Clone)]
pub struct GcStats {
pub bytes_allocated: usize,
pub collections_performed: usize,
}
impl Default for GcStats {
fn default() -> Self {
Self {
bytes_allocated: 0,
collections_performed: 0,
}
}
}
#[allow(dead_code)]
pub fn stats() -> GcStats {
GC_STATE.with(|st| st.borrow().stats.clone())
}