use crate::collect;
use crate::collect::AbstractObjectSpace;
use crate::collect::ObjectSpace;
use crate::debug;
use crate::ref_count::RefCount;
use crate::trace::Trace;
use crate::trace::Tracer;
use std::cell::UnsafeCell;
use std::mem;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ops::DerefMut;
use std::panic::UnwindSafe;
use std::ptr::NonNull;
#[repr(C)]
pub(crate) struct RawCcBox<T: ?Sized, O: AbstractObjectSpace> {
pub(crate) ref_count: O::RefCount,
#[cfg(test)]
pub(crate) name: String,
value: UnsafeCell<ManuallyDrop<T>>,
}
#[repr(C)]
pub struct RawCcBoxWithGcHeader<T: ?Sized, O: AbstractObjectSpace> {
header: O::Header,
cc_box: RawCcBox<T, O>,
}
pub type Cc<T> = RawCc<T, ObjectSpace>;
pub struct RawCc<T: ?Sized, O: AbstractObjectSpace>(NonNull<RawCcBox<T, O>>);
impl<T: UnwindSafe + ?Sized> UnwindSafe for RawCcBox<T, ObjectSpace> {}
impl<T: UnwindSafe + ?Sized> UnwindSafe for Cc<T> {}
pub trait CcDyn {
fn gc_ref_count(&self) -> usize;
fn gc_traverse(&self, tracer: &mut Tracer);
fn gc_clone(&self) -> Box<dyn GcClone>;
#[cfg(feature = "debug")]
fn gc_debug_name(&self) -> String {
"?".to_string()
}
}
pub trait GcClone {
fn gc_drop_t(&self);
fn gc_ref_count(&self) -> usize;
}
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) -> Box<dyn GcClone> {
panic!("bug: CcDummy::gc_clone should never be called");
}
}
impl<T: Trace> Cc<T> {
pub fn new(value: T) -> Cc<T> {
collect::THREAD_OBJECT_SPACE.with(|space| Self::new_in_space(value, space))
}
}
impl<T: Trace, O: AbstractObjectSpace> RawCc<T, O> {
pub(crate) fn new_in_space(value: T, space: &O) -> Self {
let is_tracked = T::is_type_tracked();
let cc_box = RawCcBox {
ref_count: space.new_ref_count(is_tracked),
value: UnsafeCell::new(ManuallyDrop::new(value)),
#[cfg(test)]
name: debug::NEXT_DEBUG_NAME.with(|n| n.get().to_string()),
};
let ccbox_ptr: *mut RawCcBox<T, O> = if is_tracked {
let header = space.empty_header();
let cc_box_with_header = RawCcBoxWithGcHeader { header, cc_box };
let mut boxed = Box::new(cc_box_with_header);
space.insert(&mut boxed.header, &boxed.cc_box);
debug_assert_eq!(
mem::size_of::<O::Header>() + mem::size_of::<RawCcBox<T, O>>(),
mem::size_of::<RawCcBoxWithGcHeader<T, O>>()
);
let ptr: *mut RawCcBox<T, O> = &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)"));
}
debug_assert_eq!(result.ref_count(), 1);
result
}
pub fn into_dyn(self) -> RawCc<dyn Trace, O> {
#[cfg(feature = "nightly")]
{
self
}
#[cfg(not(feature = "nightly"))]
unsafe {
let mut fat_ptr: [usize; 2] = mem::transmute(self.inner().deref() as &dyn Trace);
let self_ptr: usize = mem::transmute(self);
fat_ptr[0] = self_ptr;
mem::transmute(fat_ptr)
}
}
}
impl<T: Trace + Clone> Cc<T> {
pub fn update_with(&mut self, mut update_func: impl FnMut(&mut T)) {
let need_clone = self.ref_count() > 1;
if need_clone {
let mut value = <Cc<T>>::deref(self).clone();
update_func(&mut value);
*self = Cc::new(value);
} else {
let value_ptr: *mut ManuallyDrop<T> = self.inner().value.get();
let value_mut: &mut T = unsafe { &mut *value_ptr }.deref_mut();
update_func(value_mut);
}
}
}
impl<T: ?Sized, O: AbstractObjectSpace> RawCcBox<T, O> {
#[inline]
fn header_ptr(&self) -> *const () {
self.header() as *const _ as _
}
#[inline]
fn header(&self) -> &O::Header {
debug_assert!(self.is_tracked());
unsafe { cast_ref(self, -(mem::size_of::<O::Header>() as isize)) }
}
#[inline]
fn is_tracked(&self) -> bool {
self.ref_count.is_tracked()
}
#[inline]
fn is_dropped(&self) -> bool {
self.ref_count.is_dropped()
}
#[inline]
fn inc_ref(&self) -> usize {
self.ref_count.inc_ref()
}
#[inline]
fn dec_ref(&self) -> usize {
self.ref_count.dec_ref()
}
#[inline]
fn ref_count(&self) -> usize {
self.ref_count.ref_count()
}
#[inline]
fn set_dropped(&self) -> bool {
self.ref_count.set_dropped()
}
#[inline]
pub(crate) fn drop_t(&self) {
let already_dropped = self.set_dropped();
if !already_dropped {
debug::log(|| (self.debug_name(), "drop (T)"));
unsafe { ManuallyDrop::drop(&mut *(self.value.get())) };
}
}
pub(crate) fn trace_t(&self, tracer: &mut Tracer) {
if !self.is_tracked() {
return;
}
debug::log(|| (self.debug_name(), "trace"));
tracer(self.header_ptr());
}
pub(crate) fn debug_name(&self) -> String {
#[cfg(test)]
{
self.name.clone()
}
#[cfg(not(test))]
{
#[allow(unused_mut)]
let mut result = format!("{} at {:p}", std::any::type_name::<T>(), &self.value);
#[cfg(all(feature = "debug", feature = "nightly"))]
{
if !self.is_dropped() && crate::debug::GC_DROPPING.with(|t| !t.get()) {
let debug = self.deref().optional_debug();
if !debug.is_empty() {
result += &format!(" {}", debug);
}
}
}
return result;
}
}
}
#[cfg(all(feature = "debug", feature = "nightly"))]
pub(crate) trait OptionalDebug {
fn optional_debug(&self) -> String;
}
#[cfg(all(feature = "debug", feature = "nightly"))]
impl<T: ?Sized> OptionalDebug for T {
default fn optional_debug(&self) -> String {
"".to_string()
}
}
#[cfg(all(feature = "debug", feature = "nightly"))]
impl<T: std::fmt::Debug + ?Sized> OptionalDebug for T {
fn optional_debug(&self) -> String {
format!("{:?}", self)
}
}
impl<T: ?Sized, O: AbstractObjectSpace> RawCc<T, O> {
#[inline]
pub(crate) fn inner(&self) -> &RawCcBox<T, O> {
unsafe { self.0.as_ref() }
}
#[inline]
pub fn trace(&self, tracer: &mut Tracer) {
self.inner().trace_t(tracer);
}
#[inline]
fn inc_ref(&self) -> usize {
self.inner().inc_ref()
}
#[inline]
fn dec_ref(&self) -> usize {
self.inner().dec_ref()
}
#[inline]
pub(crate) fn ref_count(&self) -> usize {
self.inner().ref_count()
}
pub(crate) fn debug_name(&self) -> String {
self.inner().debug_name()
}
}
impl<T, O: AbstractObjectSpace> Clone for RawCc<T, O> {
#[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, O: AbstractObjectSpace> Deref for RawCcBox<T, O> {
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, O: AbstractObjectSpace>(cc_box: &mut RawCcBox<T, O>) {
let cc_box: Box<RawCcBox<T, O>> = unsafe { Box::from_raw(cc_box) };
let is_tracked = cc_box.is_tracked();
if is_tracked {
let gc_box: Box<RawCcBoxWithGcHeader<T, O>> = unsafe { cast_box(cc_box) };
O::remove(&gc_box.header);
gc_box.cc_box.drop_t();
debug::log(|| (gc_box.cc_box.debug_name(), "drop (CcBoxWithGcHeader)"));
drop(gc_box);
} else {
cc_box.drop_t();
debug::log(|| (cc_box.debug_name(), "drop (CcBox)"));
drop(cc_box);
}
}
impl<T: ?Sized, O: AbstractObjectSpace> Drop for RawCc<T, O> {
fn drop(&mut self) {
let ptr: *mut RawCcBox<T, O> = self.0.as_ptr();
let _locked = self.inner().ref_count.locked();
let old_ref_count = self.dec_ref();
debug::log(|| (self.debug_name(), format!("drop ({})", self.ref_count())));
debug_assert!(old_ref_count >= 1);
if old_ref_count == 1 {
drop_ccbox(unsafe { &mut *ptr });
}
}
}
impl<T: Trace, O: AbstractObjectSpace> CcDyn for RawCcBox<T, O> {
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) -> Box<dyn GcClone> {
self.ref_count.inc_ref();
debug::log(|| {
let msg = format!("gc_clone ({})", self.ref_count());
(self.debug_name(), msg)
});
let ptr: NonNull<RawCcBox<T, O>> = unsafe { mem::transmute(self) };
let cc = RawCc::<T, O>(ptr);
Box::new(cc)
}
#[cfg(feature = "debug")]
fn gc_debug_name(&self) -> String {
self.debug_name()
}
}
impl<T: Trace, O: AbstractObjectSpace> GcClone for RawCc<T, O> {
fn gc_ref_count(&self) -> usize {
self.ref_count()
}
fn gc_drop_t(&self) {
self.inner().drop_t()
}
}
impl<T: Trace> Trace for Cc<T> {
fn trace(&self, tracer: &mut Tracer) {
Cc::<T>::trace(self, tracer)
}
#[inline]
fn is_type_tracked() -> bool {
T::is_type_tracked()
}
}
impl Trace for Cc<dyn Trace> {
fn trace(&self, tracer: &mut Tracer) {
Cc::<dyn Trace>::trace(self, tracer)
}
#[inline]
fn is_type_tracked() -> bool {
true
}
}
#[cfg(feature = "nightly")]
impl<T: ?Sized + std::marker::Unsize<U>, U: ?Sized, O: AbstractObjectSpace>
std::ops::CoerceUnsized<RawCc<U, O>> for RawCc<T, O>
{
}
#[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, O: AbstractObjectSpace>(
value: Box<RawCcBox<T, O>>,
) -> Box<RawCcBoxWithGcHeader<T, O>> {
let mut ptr: *const RawCcBox<T, O> = Box::into_raw(value);
let pptr: *mut *const RawCcBox<T, O> = &mut ptr;
let pptr: *mut *const O::Header = pptr as _;
*pptr = (*pptr).offset(-1);
let ptr: *mut RawCcBoxWithGcHeader<T, O> = mem::transmute(ptr);
Box::from_raw(ptr)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collect::Linked;
#[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().header().value();
assert_eq!(v4.gc_ref_count(), 2);
}
#[cfg(feature = "nightly")]
#[test]
fn test_unsize_coerce() {
let _v: Cc<dyn Trace> = Cc::new(vec![1u8, 2, 3]);
}
}