use crate::collect;
use crate::debug;
use crate::trace::Trace;
use crate::trace::Tracer;
use std::any::Any;
use std::cell::Cell;
use std::cell::UnsafeCell;
use std::mem;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::panic::UnwindSafe;
use std::ptr::NonNull;
#[repr(C)]
pub struct GcHeader {
pub(crate) next: Cell<*const GcHeader>,
pub(crate) prev: Cell<*const GcHeader>,
pub(crate) ccdyn_vptr: Cell<*mut ()>,
}
#[repr(C)]
pub(crate) struct CcBox<T: ?Sized> {
pub(crate) ref_count: Cell<usize>,
#[cfg(test)]
pub(crate) name: String,
value: UnsafeCell<ManuallyDrop<T>>,
}
#[repr(C)]
pub(crate) struct CcBoxWithGcHeader<T: ?Sized> {
pub(crate) gc_header: GcHeader,
cc_box: CcBox<T>,
}
pub struct Cc<T: ?Sized>(NonNull<CcBox<T>>);
impl<T: UnwindSafe + ?Sized> UnwindSafe for CcBox<T> {}
impl<T: UnwindSafe + ?Sized> UnwindSafe for Cc<T> {}
const REF_COUNT_MASK_TRACKED: usize = 0b1;
const REF_COUNT_MASK_DROPPED: usize = 0b10;
const REF_COUNT_SHIFT: i32 = 2;
pub(crate) trait CcDyn {
fn gc_ref_count(&self) -> usize;
fn gc_traverse(&self, tracer: &mut Tracer);
fn gc_clone(&self) -> Cc<dyn Trace>;
}
pub(crate) struct CcDummy;
impl CcDummy {
pub(crate) fn ccdyn_vptr() -> *mut () {
let mut dummy = CcDummy;
let fat_ptr: [*mut (); 2] = unsafe { mem::transmute(&mut dummy as &mut dyn CcDyn) };
fat_ptr[1]
}
}
impl CcDyn for CcDummy {
fn gc_ref_count(&self) -> usize {
1
}
fn gc_traverse(&self, _tracer: &mut Tracer) {}
fn gc_clone(&self) -> Cc<dyn Trace> {
panic!("bug: CcDummy::gc_clone should never be called");
}
}
impl GcHeader {
fn insert_into_linked_list(&self, prev: &GcHeader, value: &dyn CcDyn) {
debug_assert!(self.next.get().is_null());
let next = prev.next.get();
self.prev.set(prev.deref());
self.next.set(next);
unsafe {
(&*next).prev.set(self);
let fat_ptr: [*mut (); 2] = mem::transmute(value);
self.ccdyn_vptr.set(fat_ptr[1]);
}
prev.next.set(self);
}
fn remove_from_linked_list(&self) {
debug_assert!(!self.next.get().is_null());
debug_assert!(!self.prev.get().is_null());
let next = self.next.get();
let prev = self.prev.get();
unsafe {
(*prev).next.set(next);
(*next).prev.set(prev);
}
self.next.set(std::ptr::null_mut());
}
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()),
}
}
pub(crate) fn value(&self) -> &dyn CcDyn {
unsafe {
let fat_ptr: (*const (), *mut ()) = (
(self as *const GcHeader).offset(1) as _,
self.ccdyn_vptr.get(),
);
mem::transmute(fat_ptr)
}
}
}
impl<T: Trace> Cc<T> {
pub fn new(value: T) -> Cc<T> {
let is_tracked = T::is_type_tracked();
let cc_box = CcBox {
ref_count: Cell::new(
(1 << REF_COUNT_SHIFT)
+ if is_tracked {
REF_COUNT_MASK_TRACKED
} else {
0
},
),
value: UnsafeCell::new(ManuallyDrop::new(value)),
#[cfg(test)]
name: debug::NEXT_DEBUG_NAME.with(|n| n.get().to_string()),
};
let ccbox_ptr: *mut CcBox<T> = if is_tracked {
let gc_header = GcHeader::empty();
let cc_box_with_header = CcBoxWithGcHeader { gc_header, cc_box };
let mut boxed = Box::new(cc_box_with_header);
collect::GC_LIST.with(|ref_head| {
let head = ref_head.borrow();
boxed
.gc_header
.insert_into_linked_list(&head, &boxed.cc_box);
});
debug_assert_eq!(
mem::size_of::<GcHeader>() + mem::size_of::<CcBox<T>>(),
mem::size_of::<CcBoxWithGcHeader<T>>()
);
let ptr: *mut CcBox<T> = &mut boxed.cc_box;
Box::leak(boxed);
ptr
} else {
Box::into_raw(Box::new(cc_box))
};
let non_null = unsafe { NonNull::new_unchecked(ccbox_ptr) };
let result = Self(non_null);
if is_tracked {
debug::log(|| (result.debug_name(), "new (CcBoxWithGcHeader)"));
} else {
debug::log(|| (result.debug_name(), "new (CcBox)"));
}
result
}
pub fn into_dyn(self) -> Cc<dyn Trace> {
#[cfg(feature = "nightly")]
{
self
}
#[cfg(not(feature = "nightly"))]
unsafe {
let mut fat_ptr: [usize; 2] = mem::transmute(self.deref() as &dyn Trace);
let self_ptr: usize = mem::transmute(self);
fat_ptr[0] = self_ptr;
mem::transmute(fat_ptr)
}
}
}
impl<T: ?Sized> CcBox<T> {
#[inline]
fn is_tracked(&self) -> bool {
(self.ref_count.get() & REF_COUNT_MASK_TRACKED) != 0
}
#[inline]
fn is_dropped(&self) -> bool {
(self.ref_count.get() & REF_COUNT_MASK_DROPPED) != 0
}
#[inline]
fn gc_header(&self) -> &GcHeader {
debug_assert!(self.is_tracked());
unsafe { cast_ref(self, -(mem::size_of::<GcHeader>() as isize)) }
}
#[inline]
fn inc_ref(&self) {
let new_count = self.ref_count.get() + (1 << REF_COUNT_SHIFT);
self.ref_count.set(new_count);
}
#[inline]
fn dec_ref(&self) {
let new_count = self.ref_count.get() - (1 << REF_COUNT_SHIFT);
self.ref_count.set(new_count);
}
#[inline]
fn ref_count(&self) -> usize {
self.ref_count.get() >> REF_COUNT_SHIFT
}
#[inline]
fn set_dropped(&self) {
let new_value = self.ref_count.get() | REF_COUNT_MASK_DROPPED;
self.ref_count.set(new_value);
debug_assert!(self.is_dropped());
}
#[inline]
pub(crate) fn drop_t(&self) {
if !self.is_dropped() {
self.set_dropped();
debug::log(|| (self.debug_name(), "drop (T)"));
unsafe { ManuallyDrop::drop(&mut *(self.value.get())) };
}
}
fn trace_t(&self, tracer: &mut Tracer) {
if !self.is_tracked() {
return;
}
debug::log(|| (self.debug_name(), "trace"));
tracer(self.gc_header());
}
pub(crate) fn debug_name(&self) -> &str {
#[cfg(test)]
{
self.name.as_ref()
}
#[cfg(not(test))]
{
unreachable!()
}
}
}
impl<T: ?Sized> Cc<T> {
#[inline]
pub(crate) fn inner(&self) -> &CcBox<T> {
unsafe { self.0.as_ref() }
}
#[inline]
fn inc_ref(&self) {
self.inner().inc_ref()
}
#[inline]
fn dec_ref(&self) {
self.inner().dec_ref()
}
#[inline]
pub(crate) fn ref_count(&self) -> usize {
self.inner().ref_count()
}
pub(crate) fn debug_name(&self) -> &str {
self.inner().debug_name()
}
}
impl<T> Clone for Cc<T> {
#[inline]
fn clone(&self) -> Self {
self.inc_ref();
debug::log(|| (self.debug_name(), format!("clone ({})", self.ref_count())));
Self(self.0)
}
}
impl<T: ?Sized> Deref for Cc<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.inner().deref()
}
}
impl<T: ?Sized> Deref for CcBox<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
debug_assert!(
!self.is_dropped(),
concat!(
"bug: accessing a dropped CcBox detected\n",
"This usually happens after ignoring another panic triggered by the collector."
)
);
unsafe { &*self.value.get() }
}
}
fn drop_ccbox<T: ?Sized>(cc_box: &mut CcBox<T>) {
let cc_box: Box<CcBox<T>> = unsafe { Box::from_raw(cc_box) };
let is_tracked = cc_box.is_tracked();
cc_box.drop_t();
if is_tracked {
debug::log(|| (cc_box.debug_name(), "drop (CcBoxWithGcHeader)"));
let gc_box: Box<CcBoxWithGcHeader<T>> = unsafe { cast_box(cc_box) };
gc_box.gc_header.remove_from_linked_list();
drop(gc_box);
} else {
debug::log(|| (cc_box.debug_name(), "drop (CcBox)"));
drop(cc_box);
}
}
impl<T: ?Sized> Drop for Cc<T> {
fn drop(&mut self) {
self.dec_ref();
debug::log(|| (self.debug_name(), format!("drop ({})", self.ref_count())));
if self.ref_count() == 0 {
drop_ccbox(unsafe { self.0.as_mut() });
}
}
}
impl<T: Trace> CcDyn for CcBox<T> {
fn gc_ref_count(&self) -> usize {
self.ref_count()
}
fn gc_traverse(&self, tracer: &mut Tracer) {
debug::log(|| (self.debug_name(), "gc_traverse"));
T::trace(self.deref(), tracer)
}
fn gc_clone(&self) -> Cc<dyn Trace> {
self.inc_ref();
debug::log(|| {
let msg = format!("gc_clone ({})", self.ref_count());
(self.debug_name(), msg)
});
let ptr: NonNull<CcBox<T>> = unsafe { mem::transmute(self) };
Cc::<T>(ptr).into_dyn()
}
}
impl<T: Trace> Trace for Cc<T> {
fn trace(&self, tracer: &mut Tracer) {
self.inner().trace_t(tracer)
}
#[inline]
fn is_type_tracked() -> bool {
T::is_type_tracked()
}
fn as_any(&self) -> Option<&dyn Any> {
Trace::as_any(self.deref())
}
}
impl Trace for Cc<dyn Trace> {
fn trace(&self, tracer: &mut Tracer) {
self.inner().trace_t(tracer)
}
#[inline]
fn is_type_tracked() -> bool {
true
}
fn as_any(&self) -> Option<&dyn Any> {
Trace::as_any(self.deref())
}
}
impl Cc<dyn Trace> {
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.deref().as_any().and_then(|any| any.downcast_ref())
}
}
#[cfg(feature = "nightly")]
impl<T: ?Sized + std::marker::Unsize<U>, U: ?Sized> std::ops::CoerceUnsized<Cc<U>> for Cc<T> {}
#[inline]
unsafe fn cast_ref<T: ?Sized, R>(value: &T, offset_bytes: isize) -> &R {
let ptr: *const T = value;
let ptr: *const u8 = ptr as _;
let ptr = ptr.offset(offset_bytes);
&*(ptr as *const R)
}
#[inline]
unsafe fn cast_box<T: ?Sized>(value: Box<CcBox<T>>) -> Box<CcBoxWithGcHeader<T>> {
let mut ptr: *const CcBox<T> = Box::into_raw(value);
let pptr: *mut *const CcBox<T> = &mut ptr;
let pptr: *mut *const GcHeader = pptr as _;
*pptr = (*pptr).offset(-1);
let ptr: *mut CcBoxWithGcHeader<T> = mem::transmute(ptr);
Box::from_raw(ptr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gc_header_value() {
let v1: Cc<Box<dyn Trace>> = Cc::new(Box::new(1));
assert_eq!(v1.ref_count(), 1);
let v2 = v1.clone();
assert_eq!(v1.ref_count(), 2);
assert_eq!(v2.ref_count(), 2);
let v3: &dyn CcDyn = v1.inner() as &dyn CcDyn;
assert_eq!(v3.gc_ref_count(), 2);
let v4: &dyn CcDyn = v2.inner().gc_header().value();
assert_eq!(v4.gc_ref_count(), 2);
}
#[test]
fn test_dyn_downcast() {
let v: Cc<dyn Trace> = Cc::new(vec![1u8, 2, 3]).into_dyn();
let downcasted: &Vec<u8> = v.downcast_ref().unwrap();
assert_eq!(downcasted, &vec![1, 2, 3]);
}
#[cfg(feature = "nightly")]
#[test]
fn test_unsize_coerce() {
let _v: Cc<dyn Trace> = Cc::new(vec![1u8, 2, 3]);
}
}