use crate::cc::GcHeader;
use crate::debug;
use crate::Cc;
use crate::Trace;
use std::cell::RefCell;
use std::ops::Deref;
use std::pin::Pin;
pub fn collect_thread_cycles() -> usize {
debug::log(|| ("collect", "collect_thread_cycles"));
GC_LIST.with(|list| {
let list: &GcHeader = { &list.borrow() };
collect_list(list)
})
}
pub fn count_thread_tracked() -> usize {
GC_LIST.with(|list| {
let list: &GcHeader = { &list.borrow() };
let mut count = 0;
visit_list(list, |_| count += 1);
count
})
}
thread_local!(pub(crate) static GC_LIST: RefCell<Pin<Box<GcHeader>>> = RefCell::new(new_gc_list()));
fn new_gc_list() -> Pin<Box<GcHeader>> {
let pinned = Box::pin(GcHeader::empty());
let header: &GcHeader = pinned.deref();
header.prev.set(header);
header.next.set(header);
pinned
}
fn collect_list(list: &GcHeader) -> usize {
update_refs(list);
subtract_refs(list);
release_unreachable(list)
}
fn visit_list<'a>(list: &'a GcHeader, mut func: impl FnMut(&'a GcHeader)) {
let mut ptr = list.next.get();
while ptr != list {
let header: &GcHeader = unsafe { &*ptr };
ptr = header.next.get();
func(header);
}
}
const PREV_MASK_COLLECTING: usize = 1;
const PREV_SHIFT: u32 = 1;
fn update_refs(list: &GcHeader) {
visit_list(list, |header| {
let ref_count = header.value().gc_ref_count();
let shifted = (ref_count << PREV_SHIFT) | PREV_MASK_COLLECTING;
header.prev.set(shifted as _);
});
}
fn subtract_refs(list: &GcHeader) {
let mut tracer = |header: &GcHeader| {
if is_collecting(header) {
debug_assert!(!is_unreachable(header));
edit_gc_ref_count(header, -1);
}
};
visit_list(list, |header| {
header.value().gc_traverse(&mut tracer);
});
}
fn mark_reachable(list: &GcHeader) {
fn revive(header: &GcHeader) {
if is_collecting(header) {
unset_collecting(header);
if is_unreachable(header) {
edit_gc_ref_count(header, 1); }
header.value().gc_traverse(&mut revive); }
}
visit_list(list, |header| {
if is_collecting(header) && !is_unreachable(header) {
unset_collecting(header);
header.value().gc_traverse(&mut revive)
}
});
}
fn release_unreachable(list: &GcHeader) -> usize {
mark_reachable(list);
let mut count = 0;
visit_list(list, |header| {
if is_unreachable(header) {
count += 1;
}
});
debug::log(|| ("collect", format!("{} unreachable objects", count)));
let mut to_drop: Vec<Cc<dyn Trace>> = Vec::with_capacity(count);
visit_list(list, |header| {
if is_unreachable(header) {
to_drop.push(header.value().gc_clone());
}
});
restore_prev(list);
for value in to_drop.iter() {
value.inner().drop_t();
}
for value in to_drop.iter() {
let ref_count = value.ref_count();
assert_eq!(
ref_count, 1,
concat!(
"bug: unexpected ref-count after dropping cycles\n",
"This usually indicates a buggy Trace or Drop implementation."
)
);
}
count
}
fn restore_prev(list: &GcHeader) {
let mut prev = list;
visit_list(list, |header| {
header.prev.set(prev);
prev = header;
});
}
fn is_unreachable(header: &GcHeader) -> bool {
let prev = header.prev.get() as usize;
is_collecting(header) && (prev >> PREV_SHIFT) == 0
}
fn is_collecting(header: &GcHeader) -> bool {
let prev = header.prev.get() as usize;
(prev & PREV_MASK_COLLECTING) != 0
}
fn unset_collecting(header: &GcHeader) {
let prev = header.prev.get() as usize;
let new_prev = (prev & PREV_MASK_COLLECTING) ^ prev;
header.prev.set(new_prev as _);
}
fn edit_gc_ref_count(header: &GcHeader, delta: isize) {
let prev = header.prev.get() as isize;
let new_prev = prev + (1 << PREV_SHIFT) * delta;
header.prev.set(new_prev as _);
}