use crate::cc::CcDummy;
use crate::cc::CcDyn;
use crate::cc::GcClone;
use crate::debug;
use crate::ref_count::RefCount;
use crate::ref_count::SingleThreadRefCount;
use crate::Cc;
use crate::Trace;
use std::cell::Cell;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::pin::Pin;
pub struct ObjectSpace {
pub(crate) list: RefCell<Pin<Box<GcHeader>>>,
_phantom: PhantomData<Cc<()>>,
}
pub trait AbstractObjectSpace: 'static + Sized {
type RefCount: RefCount;
type Header;
fn insert(&self, header: &mut Self::Header, value: &dyn CcDyn);
fn remove(header: &Self::Header);
fn new_ref_count(&self, tracked: bool) -> Self::RefCount;
fn empty_header(&self) -> Self::Header;
}
impl AbstractObjectSpace for ObjectSpace {
type RefCount = SingleThreadRefCount;
type Header = GcHeader;
fn insert(&self, header: &mut Self::Header, value: &dyn CcDyn) {
let prev: &GcHeader = &self.list.borrow();
debug_assert!(header.next.get().is_null());
let next = prev.next.get();
header.prev.set(prev.deref());
header.next.set(next);
unsafe {
(&*next).prev.set(header);
let fat_ptr: [*mut (); 2] = mem::transmute(value);
header.ccdyn_vptr = fat_ptr[1];
}
prev.next.set(header);
}
#[inline]
fn remove(header: &Self::Header) {
let header: &GcHeader = &header;
debug_assert!(!header.next.get().is_null());
debug_assert!(!header.prev.get().is_null());
let next = header.next.get();
let prev = header.prev.get();
unsafe {
(*prev).next.set(next);
(*next).prev.set(prev);
}
header.next.set(std::ptr::null_mut());
}
#[inline]
fn new_ref_count(&self, tracked: bool) -> Self::RefCount {
SingleThreadRefCount::new(tracked)
}
#[inline]
fn empty_header(&self) -> Self::Header {
GcHeader::empty()
}
}
impl Default for ObjectSpace {
fn default() -> Self {
let header = new_gc_list();
Self {
list: RefCell::new(header),
_phantom: PhantomData,
}
}
}
impl ObjectSpace {
pub fn count_tracked(&self) -> usize {
let list: &GcHeader = &self.list.borrow();
let mut count = 0;
visit_list(list, |_| count += 1);
count
}
pub fn collect_cycles(&self) -> usize {
let list: &GcHeader = &self.list.borrow();
collect_list(list, ())
}
pub fn create<T: Trace>(&self, value: T) -> Cc<T> {
Cc::new_in_space(value, self)
}
}
impl Drop for ObjectSpace {
fn drop(&mut self) {
self.collect_cycles();
}
}
pub trait Linked {
fn next(&self) -> *const Self;
fn prev(&self) -> *const Self;
fn set_prev(&self, other: *const Self);
fn value(&self) -> &dyn CcDyn;
}
#[repr(C)]
pub struct GcHeader {
pub(crate) next: Cell<*const GcHeader>,
pub(crate) prev: Cell<*const GcHeader>,
pub(crate) ccdyn_vptr: *const (),
}
impl Linked for GcHeader {
#[inline]
fn next(&self) -> *const Self {
self.next.get()
}
#[inline]
fn prev(&self) -> *const Self {
self.prev.get()
}
#[inline]
fn set_prev(&self, other: *const Self) {
self.prev.set(other)
}
#[inline]
fn value(&self) -> &dyn CcDyn {
unsafe {
let fat_ptr: (*const (), *const ()) =
((self as *const Self).offset(1) as _, self.ccdyn_vptr);
mem::transmute(fat_ptr)
}
}
}
impl GcHeader {
pub(crate) fn empty() -> Self {
Self {
next: Cell::new(std::ptr::null()),
prev: Cell::new(std::ptr::null()),
ccdyn_vptr: CcDummy::ccdyn_vptr(),
}
}
}
pub fn collect_thread_cycles() -> usize {
debug::log(|| ("collect", "collect_thread_cycles"));
THREAD_OBJECT_SPACE.with(|list| list.collect_cycles())
}
pub fn count_thread_tracked() -> usize {
THREAD_OBJECT_SPACE.with(|list| list.count_tracked())
}
thread_local!(pub(crate) static THREAD_OBJECT_SPACE: ObjectSpace = ObjectSpace::default());
pub(crate) 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
}
pub(crate) fn collect_list<L: Linked, K>(list: &L, lock: K) -> usize {
update_refs(list);
subtract_refs(list);
release_unreachable(list, lock)
}
pub(crate) fn visit_list<'a, L: Linked>(list: &'a L, mut func: impl FnMut(&'a L)) {
let mut ptr = list.next();
while ptr as *const _ != list as *const _ {
let header: &L = unsafe { &*ptr };
ptr = header.next();
func(header);
}
}
const PREV_MASK_COLLECTING: usize = 1;
const PREV_MASK_VISITED: usize = 2;
const PREV_SHIFT: u32 = 2;
fn update_refs<L: Linked>(list: &L) {
visit_list(list, |header| {
let ref_count = header.value().gc_ref_count();
if ref_count > 0 {
let shifted = (ref_count << PREV_SHIFT) | PREV_MASK_COLLECTING;
header.set_prev(shifted as _);
} else {
debug_assert!(header.prev() as usize & PREV_MASK_COLLECTING == 0);
}
});
}
fn subtract_refs<L: Linked>(list: &L) {
let mut tracer = |header: *const ()| {
let header = unsafe { &*(header as *const L) };
if is_collecting(header) {
debug_assert!(
!is_unreachable(header),
"bug: object {} becomes unreachable while trying to dec_ref (is Trace impl correct?)",
debug_name(header)
);
edit_gc_ref_count(header, -1);
}
};
visit_list(list, |header| {
set_visited(header);
header.value().gc_traverse(&mut tracer);
});
}
fn mark_reachable<L: Linked>(list: &L) {
fn revive<L: Linked>(header: *const ()) {
let header = unsafe { &*(header as *const L) };
if is_collecting(header) {
unset_collecting(header);
if is_unreachable(header) {
edit_gc_ref_count(header, 1); }
header.value().gc_traverse(&mut revive::<L>); }
}
visit_list(list, |header| {
if is_collecting(header) && !is_unreachable(header) {
unset_collecting(header);
header.value().gc_traverse(&mut revive::<L>)
}
});
}
fn release_unreachable<L: Linked, K>(list: &L, lock: K) -> 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<Box<dyn GcClone>> = Vec::with_capacity(count);
visit_list(list, |header| {
if is_unreachable(header) {
to_drop.push(header.value().gc_clone());
}
});
restore_prev(list);
drop(lock);
drop(list);
#[cfg(feature = "debug")]
{
crate::debug::GC_DROPPING.with(|d| d.set(true));
}
for value in to_drop.iter() {
value.gc_drop_t();
}
for value in to_drop.iter() {
let ref_count = value.gc_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."
)
);
}
#[cfg(feature = "debug")]
{
crate::debug::GC_DROPPING.with(|d| d.set(false));
}
count
}
fn restore_prev<L: Linked>(list: &L) {
let mut prev = list;
visit_list(list, |header| {
header.set_prev(prev);
prev = header;
});
}
fn is_unreachable<L: Linked>(header: &L) -> bool {
let prev = header.prev() as *const L as usize;
is_collecting(header) && (prev >> PREV_SHIFT) == 0
}
pub(crate) fn is_collecting<L: Linked>(header: &L) -> bool {
let prev = header.prev() as *const L as usize;
(prev & PREV_MASK_COLLECTING) != 0
}
fn set_visited<L: Linked>(header: &L) -> bool {
let prev = header.prev() as *const L as usize;
let visited = (prev & PREV_MASK_VISITED) != 0;
debug_assert!(
!visited,
"bug: double visit: {} (is Trace impl correct?)",
debug_name(header)
);
let new_prev = prev | PREV_MASK_VISITED;
header.set_prev(new_prev as _);
visited
}
fn unset_collecting<L: Linked>(header: &L) {
let prev = header.prev() as *const L as usize;
let new_prev = (prev & PREV_MASK_COLLECTING) ^ prev;
header.set_prev(new_prev as _);
}
fn edit_gc_ref_count<L: Linked>(header: &L, delta: isize) {
let prev = header.prev() as *const L as isize;
let new_prev = prev + (1 << PREV_SHIFT) * delta;
header.set_prev(new_prev as _);
}
#[allow(unused_variables)]
fn debug_name<L: Linked>(header: &L) -> String {
#[cfg(feature = "debug")]
{
return header.value().gc_debug_name();
}
#[cfg(not(feature = "debug"))]
{
return "(enable gcmodule \"debug\" feature for debugging)".to_string();
}
}