use crate::Cc;
use crate::Trace;
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 std::cell::Cell;
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::mem;
use std::ptr::without_provenance;
pub struct ObjectSpace {
pub(crate) list: RefCell<OwnedGcHeader>,
_phantom: PhantomData<Cc<()>>,
}
pub trait AbstractObjectSpace: 'static + Sized {
type RefCount: RefCount;
type Header;
fn insert(&self, header: *const 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: *const Self::Header, value: &dyn CcDyn) {
let list = self.list.borrow();
let prev: &GcHeader = list.inner();
debug_assert!(unsafe { (*header).next.get() }.is_null());
let next = prev.next.get();
unsafe { (*header).prev.set(prev) };
unsafe { (*header).next.set(next) };
unsafe {
(*next).prev.set(header);
let fat_ptr: [*mut (); 2] = mem::transmute(value);
(*header).ccdyn_vptr.set(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 = unmask_ptr(header.next.get());
let prev = unmask_ptr(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 = self.list.borrow();
let list: &GcHeader = list.inner();
let mut count = 0;
visit_list(list, |_| count += 1);
count
}
pub fn collect_cycles(&self) -> usize {
let list = self.list.borrow();
let list: &GcHeader = list.inner();
collect_list(list, ())
}
pub fn create<T: Trace>(&self, value: T) -> Cc<T> {
Cc::new_in_space(value, self)
}
pub fn leak(&self) {
*self.list.borrow_mut() = new_gc_list();
}
}
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_ptr(this: *const Self) -> *const dyn CcDyn;
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: Cell<*const ()>,
pub(crate) _marker: UnsafeCell<()>,
}
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)
}
fn value_ptr(this: *const Self) -> *const dyn CcDyn {
unsafe {
let fat_ptr: (*const (), *const ()) = (this.offset(1) as _, (*this).ccdyn_vptr.get());
mem::transmute(fat_ptr)
}
}
#[inline]
fn value(&self) -> &dyn CcDyn {
unsafe { mem::transmute(Self::value_ptr(self)) }
}
}
impl GcHeader {
pub(crate) fn empty() -> Self {
Self {
next: Cell::new(std::ptr::null()),
prev: Cell::new(std::ptr::null()),
ccdyn_vptr: Cell::new(CcDummy::ccdyn_vptr()),
_marker: UnsafeCell::new(()),
}
}
}
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 fn with_thread_object_space<R>(handler: impl FnOnce(&ObjectSpace) -> R) -> R {
THREAD_OBJECT_SPACE.with(handler)
}
pub(crate) struct OwnedGcHeader {
raw: *mut GcHeader,
}
impl OwnedGcHeader {
fn inner(&self) -> &GcHeader {
unsafe { &*self.raw }
}
}
impl Drop for OwnedGcHeader {
fn drop(&mut self) {
drop(unsafe { Box::from_raw(self.raw) });
}
}
pub(crate) fn new_gc_list() -> OwnedGcHeader {
let header = Box::into_raw(Box::new(GcHeader::empty()));
unsafe { (*header).prev.set(header) };
unsafe { (*header).next.set(header) };
OwnedGcHeader { raw: header }
}
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 PTR_MASK: usize = usize::MAX & !(0b11);
const PREV_MASK_COLLECTING: usize = 1;
const PREV_MASK_VISITED: usize = 2;
const PREV_SHIFT: u32 = 2;
#[inline]
fn unmask_ptr<T>(ptr: *const T) -> *const T {
ptr.map_addr(|ptr| ptr & PTR_MASK)
}
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(without_provenance(shifted));
} 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);
#[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().addr();
is_collecting(header) && (prev >> PREV_SHIFT) == 0
}
pub(crate) fn is_collecting<L: Linked>(header: &L) -> bool {
let prev = header.prev().addr();
(prev & PREV_MASK_COLLECTING) != 0
}
fn set_visited<L: Linked>(header: &L) -> bool {
let prev = header.prev();
let visited = (prev.addr() & PREV_MASK_VISITED) != 0;
debug_assert!(
!visited,
"bug: double visit: {} (is Trace impl correct?)",
debug_name(header)
);
let new_prev = prev.map_addr(|prev| prev | PREV_MASK_VISITED);
header.set_prev(new_prev);
visited
}
fn unset_collecting<L: Linked>(header: &L) {
let prev = header.prev();
let new_prev = prev.map_addr(|prev| (prev & PREV_MASK_COLLECTING) ^ prev);
header.set_prev(new_prev);
}
fn edit_gc_ref_count<L: Linked>(header: &L, delta: isize) {
let prev = header.prev() as isize;
let new_prev = prev + (1 << PREV_SHIFT) * delta;
header.set_prev(without_provenance(new_prev as usize));
}
#[allow(unused_variables)]
fn debug_name<L: Linked>(header: &L) -> String {
#[cfg(feature = "debug")]
{
return header.value().gc_debug_name();
}
#[cfg(not(feature = "debug"))]
{
"(enable gcmodule \"debug\" feature for debugging)".to_string()
}
}