mod cell;
mod collect;
#[cfg(loom)]
mod loom_ext;
#[cfg(all(loom, test))]
mod loom_tests;
#[cfg(all(test, not(loom)))]
mod tests;
use std::{
alloc::{dealloc, handle_alloc_error, Layout},
any::TypeId,
borrow::{Borrow, Cow},
fmt::Debug,
mem::{self, ManuallyDrop, MaybeUninit},
num::NonZeroUsize,
ops::Deref,
ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping, drop_in_place, NonNull},
slice,
};
#[cfg(loom)]
use loom::{
lazy_static,
sync::atomic::{fence, AtomicUsize, Ordering},
};
#[cfg(not(loom))]
use std::sync::atomic::{fence, AtomicUsize, Ordering};
use crate::{
contains_gcs, panic_deref_of_collected_object,
ptr::Nullable,
sync::{
cell::UCell,
collect::{Dfs, PrepareForDestruction},
},
Trace, TraceWith, Visitor,
};
use self::collect::{
collect_all_await, mark_clean, mark_dirty, n_gcs_dropped, n_gcs_existing, notify_created_gc,
notify_dropped_gc,
};
const MAX_STRONG_COUNT: usize = (isize::MAX) as usize;
#[expect(private_bounds)]
pub(crate) trait TraceSync:
for<'a> TraceWith<Dfs<'a>> + for<'a> TraceWith<PrepareForDestruction<'a>> + TraceWith<Rehydrate>
{
}
impl<T> TraceSync for T where
T: ?Sized
+ for<'a> TraceWith<Dfs<'a>>
+ for<'a> TraceWith<PrepareForDestruction<'a>>
+ TraceWith<Rehydrate>
{
}
pub struct Gc<T: Trace + Send + Sync + ?Sized + 'static> {
ptr: UCell<Nullable<GcBox<T>>>,
tag: AtomicUsize,
}
#[cfg(not(loom))]
static CURRENT_TAG: AtomicUsize = AtomicUsize::new(0);
#[cfg(loom)]
lazy_static! {
static ref CURRENT_TAG: AtomicUsize = AtomicUsize::new(0);
}
#[repr(C)]
#[doc(hidden)]
pub struct GcBox<T>
where
T: Trace + Send + Sync + ?Sized,
{
strong: AtomicUsize,
weak: AtomicUsize,
generation: AtomicUsize,
value: T,
}
unsafe impl<T> Send for Gc<T> where T: Trace + Send + Sync + ?Sized {}
unsafe impl<T> Sync for Gc<T> where T: Trace + Send + Sync + ?Sized {}
pub fn collect() {
collect_all_await();
}
#[derive(Debug)]
pub struct CollectInfo {
_private: (),
}
pub type CollectCondition = fn(&CollectInfo) -> bool;
#[must_use]
pub fn default_collect_condition(info: &CollectInfo) -> bool {
info.n_gcs_dropped_since_last_collect() > info.n_gcs_existing()
}
pub use collect::set_collect_condition;
impl<T> Gc<T>
where
T: Trace + Send + Sync + ?Sized,
{
pub fn new(value: T) -> Gc<T>
where
T: Sized,
{
notify_created_gc();
Gc {
ptr: UCell::new(Nullable::new(NonNull::from(Box::leak(Box::new(GcBox {
strong: AtomicUsize::new(1),
weak: AtomicUsize::new(0),
generation: AtomicUsize::new(CURRENT_TAG.load(Ordering::Acquire)),
value,
}))))),
tag: AtomicUsize::new(0),
}
}
pub fn new_cyclic<F: FnOnce(Self) -> T>(data_fn: F) -> Self
where
T: Sized,
{
#[repr(transparent)]
struct Uninitialized<T>(MaybeUninit<T>);
unsafe impl<V: Visitor, T> TraceWith<V> for Uninitialized<T> {
fn accept(&self, _: &mut V) -> Result<(), ()> {
Ok(())
}
}
struct CleanUp<T: Trace + Send + Sync + 'static> {
initialized: bool,
ptr: NonNull<GcBox<T>>,
}
impl<T: Trace + Send + Sync + 'static> Drop for CleanUp<T> {
fn drop(&mut self) {
if self.initialized {
unsafe { mark_dirty(self.ptr) };
} else {
unsafe {
dealloc(
self.ptr.as_ptr().cast::<u8>(),
Layout::for_value(self.ptr.as_ref()),
);
}
}
}
}
notify_created_gc();
let mut gcbox = NonNull::from(Box::leak(Box::new(GcBox {
strong: AtomicUsize::new(1),
weak: AtomicUsize::new(0),
generation: AtomicUsize::new(CURRENT_TAG.load(Ordering::Acquire)),
value: Uninitialized(MaybeUninit::<T>::uninit()),
})));
let mut cleanup = CleanUp {
ptr: gcbox,
initialized: false,
};
let nilgc = Gc {
tag: AtomicUsize::new(0),
ptr: UCell::new(Nullable::new(gcbox.cast::<GcBox<T>>()).as_null()),
};
assert!(Gc::is_dead(&nilgc));
unsafe {
gcbox.as_mut().value = Uninitialized(MaybeUninit::new(data_fn(nilgc)));
}
cleanup.initialized = true;
let gcbox = gcbox.cast::<GcBox<T>>();
let res = unsafe {
gcbox.as_ref().value.accept(&mut Rehydrate {
ptr: Nullable::new(gcbox.cast()),
type_id: TypeId::of::<T>(),
})
};
assert!(
res.is_ok(),
"visitor must be able to access all Gc fields of structure when rehydrating dead Gcs"
);
let gc = Gc {
ptr: UCell::new(Nullable::new(gcbox)),
tag: AtomicUsize::new(CURRENT_TAG.load(Ordering::Acquire)),
};
let _ = ManuallyDrop::new(cleanup);
gc
}
pub fn try_deref(gc: &Gc<T>) -> Option<&T> {
unsafe { (!gc.ptr.get().is_null()).then(|| &**gc) }
}
pub fn try_clone(gc: &Gc<T>) -> Option<Gc<T>> {
unsafe { (!gc.ptr.get().is_null()).then(|| gc.clone()) }
}
pub fn as_ptr(gc: &Gc<T>) -> *const T {
unsafe {
let ptr = NonNull::as_ptr(gc.ptr.get().unwrap());
addr_of_mut!((*ptr).value)
}
}
pub fn ptr_eq(this: &Gc<T>, other: &Gc<T>) -> bool {
unsafe { this.ptr.get() }.as_option() == unsafe { other.ptr.get() }.as_option()
}
pub fn ref_count(gc: &Self) -> NonZeroUsize {
let box_ptr = unsafe { gc.ptr.get() }.expect(
"Attempt to dereference Gc to already-collected object. \
This means a Gc escaped from a Drop implementation, likely implying a bug in your code.",
);
let box_ref = unsafe { box_ptr.as_ref() };
NonZeroUsize::new(box_ref.strong.load(Ordering::Relaxed))
.expect("strong count to a GcBox may never be zero while a Gc to it exists")
}
#[inline]
pub fn is_dead(gc: &Self) -> bool {
unsafe { gc.ptr.get() }.is_null()
}
#[inline]
#[must_use]
fn into_ptr(this: Self) -> (*const GcBox<T>, usize) {
let this = ManuallyDrop::new(this);
let tag = &raw const this.tag;
let ptr = unsafe { this.ptr.get().as_ptr() };
let tag = unsafe { tag.read() }.into_inner();
(ptr, tag)
}
#[inline]
#[must_use]
unsafe fn from_ptr(ptr: *const GcBox<T>, tag: usize) -> Self {
Self {
ptr: UCell::new(Nullable::from_ptr(ptr.cast_mut())),
tag: AtomicUsize::new(tag),
}
}
unsafe fn kill(&self) {
self.ptr.set(self.ptr.get().as_null());
}
#[inline]
#[must_use]
#[doc(hidden)]
pub fn __private_into_ptr(this: Self) -> (*const GcBox<T>, usize) {
Self::into_ptr(this)
}
#[inline]
#[must_use]
#[doc(hidden)]
pub unsafe fn __private_from_ptr(ptr: *const GcBox<T>, tag: usize) -> Self {
Self::from_ptr(ptr, tag)
}
}
pub(super) struct Rehydrate {
ptr: Nullable<GcBox<()>>,
type_id: TypeId,
}
impl Visitor for Rehydrate {
fn visit_sync<T>(&mut self, gc: &Gc<T>)
where
T: Trace + Send + Sync + ?Sized,
{
if Gc::is_dead(gc) && TypeId::of::<T>() == self.type_id {
unsafe {
let cell_ptr = (&raw const gc.ptr).cast::<UCell<Nullable<GcBox<()>>>>();
(*cell_ptr).set(self.ptr);
let box_ref = &*self.ptr.as_ptr();
let old_strong = box_ref.strong.fetch_add(1, Ordering::Relaxed);
if old_strong > MAX_STRONG_COUNT {
std::process::abort();
}
box_ref
.generation
.store(CURRENT_TAG.load(Ordering::Acquire), Ordering::Release);
notify_created_gc();
}
}
}
fn visit_unsync<T>(&mut self, _: &crate::unsync::Gc<T>)
where
T: Trace + ?Sized,
{
}
}
impl<T: Trace + Send + Sync + Clone> Gc<T> {
#[inline]
pub fn make_mut(this: &mut Self) -> &mut T {
if Gc::is_dead(this) {
panic_deref_of_collected_object();
}
let box_ref = unsafe { this.ptr.get().unwrap_unchecked().as_ref() };
let strong = box_ref.strong.load(Ordering::Acquire);
let weak = box_ref.weak.load(Ordering::Acquire);
if strong != 1 || weak != 0 {
*this = Gc::new(box_ref.value.clone());
}
unsafe { &mut (*this.ptr.get().as_ptr()).value }
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __sync_coerce_gc {
($gc:expr) => {{
let (ptr, tag): (*const _, usize) = $crate::sync::Gc::__private_into_ptr($gc);
unsafe { $crate::sync::Gc::__private_from_ptr(ptr, tag) }
}};
}
#[doc(inline)]
pub use crate::__sync_coerce_gc as coerce_gc;
impl<T> Clone for Gc<T>
where
T: Trace + Send + Sync + ?Sized,
{
fn clone(&self) -> Gc<T> {
if Gc::is_dead(self) {
return unsafe { ptr::read(self) };
}
let box_ref = unsafe { self.ptr.get().unwrap().as_ref() };
let old_strong = box_ref.strong.fetch_add(1, Ordering::Acquire);
if old_strong > MAX_STRONG_COUNT {
std::process::abort();
}
box_ref
.generation
.store(CURRENT_TAG.load(Ordering::Acquire), Ordering::Release);
notify_created_gc();
Gc {
ptr: UCell::new(unsafe { self.ptr.get() }),
tag: AtomicUsize::new(CURRENT_TAG.load(Ordering::Acquire)),
}
}
}
impl<T> Drop for Gc<T>
where
T: Trace + Send + Sync + ?Sized,
{
fn drop(&mut self) {
let Some(mut ptr) = unsafe { self.ptr.get() }.as_option() else {
return;
};
let box_ref = unsafe { ptr.as_ref() };
box_ref.weak.fetch_add(1, Ordering::AcqRel); box_ref
.generation
.store(CURRENT_TAG.load(Ordering::Relaxed), Ordering::Release);
match box_ref.strong.fetch_sub(1, Ordering::AcqRel) {
0 => unreachable!("strong cannot reach zero while a Gc to it exists"),
1 => {
mark_clean(box_ref);
if box_ref.weak.fetch_sub(1, Ordering::Release) == 1 {
let layout = Layout::for_value(box_ref);
fence(Ordering::Acquire);
unsafe {
drop_in_place(ptr.as_mut());
dealloc(ptr.as_ptr().cast(), layout);
}
}
}
_ => {
if contains_gcs(&box_ref.value).unwrap_or(true) {
unsafe { mark_dirty(ptr) };
}
box_ref.weak.fetch_sub(1, Ordering::Release);
}
}
notify_dropped_gc();
}
}
impl CollectInfo {
#[must_use]
pub fn n_gcs_dropped_since_last_collect(&self) -> usize {
n_gcs_dropped()
}
#[must_use]
pub fn n_gcs_existing(&self) -> usize {
n_gcs_existing()
}
}
impl<T: Trace + Send + Sync + ?Sized> Gc<T> {
unsafe fn allocate_for_layout(
value_layout: Layout,
mem_to_gc_box: impl FnOnce(*mut u8) -> *mut GcBox<T>,
) -> *mut GcBox<T> {
let layout = Layout::new::<GcBox<()>>()
.extend(value_layout)
.unwrap()
.0
.pad_to_align();
Self::allocate_for_layout_of_box(layout, mem_to_gc_box)
}
unsafe fn allocate_for_layout_of_box(
layout: Layout,
mem_to_gc_box: impl FnOnce(*mut u8) -> *mut GcBox<T>,
) -> *mut GcBox<T> {
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
handle_alloc_error(layout);
}
let inner = mem_to_gc_box(ptr);
unsafe {
(&raw mut (*inner).strong).write(AtomicUsize::new(1));
(&raw mut (*inner).weak).write(AtomicUsize::new(0));
(&raw mut (*inner).generation).write(AtomicUsize::new(0));
}
inner
}
}
impl<T: Trace + Send + Sync> Gc<[T]> {
fn allocate_for_slice(len: usize) -> *mut GcBox<[T]> {
unsafe {
Self::allocate_for_layout(Layout::array::<T>(len).unwrap(), |mem| {
ptr::slice_from_raw_parts_mut(mem.cast::<T>(), len) as *mut GcBox<[T]>
})
}
}
}
unsafe impl<V: Visitor, T: Trace + Send + Sync + ?Sized> TraceWith<V> for Gc<T> {
fn accept(&self, visitor: &mut V) -> Result<(), ()> {
visitor.visit_sync(self);
Ok(())
}
}
impl<T: Trace + Send + Sync + ?Sized> Deref for Gc<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
let box_ref = unsafe {
self.ptr.get().expect(
"Attempting to dereference Gc to already-deallocated object.\
This is caused by accessing a Gc during a Drop implementation, likely implying a bug in your code."
).as_ref()
};
let current_tag = CURRENT_TAG.load(Ordering::Acquire);
self.tag.store(current_tag, Ordering::Release);
box_ref.generation.store(current_tag, Ordering::Release);
&box_ref.value
}
}
impl<T> PartialEq<Gc<T>> for Gc<T>
where
T: Trace + Send + Sync + ?Sized + PartialEq,
{
fn eq(&self, other: &Gc<T>) -> bool {
self.as_ref() == other.as_ref()
}
}
impl<T> Eq for Gc<T> where T: Trace + Send + Sync + ?Sized + PartialEq {}
impl<T: Trace + Send + Sync + ?Sized> AsRef<T> for Gc<T> {
fn as_ref(&self) -> &T {
self
}
}
impl<T: Trace + Send + Sync + ?Sized> Borrow<T> for Gc<T> {
fn borrow(&self) -> &T {
self
}
}
impl<T: Trace + Send + Sync + Default> Default for Gc<T> {
fn default() -> Self {
Gc::new(T::default())
}
}
impl<T: Trace + Send + Sync + ?Sized> std::fmt::Pointer for Gc<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Pointer::fmt(&addr_of!(**self), f)
}
}
#[cfg(not(loom))]
#[cfg(feature = "coerce-unsized")]
impl<T, U> std::ops::CoerceUnsized<Gc<U>> for Gc<T>
where
T: std::marker::Unsize<U> + Trace + Send + Sync + ?Sized,
U: Trace + Send + Sync + ?Sized,
{
}
impl<T: Trace + Send + Sync + ?Sized> Debug for Gc<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Gc({:?}, {})",
self.ptr,
self.tag.load(Ordering::Acquire)
)
}
}
impl<T: Trace + Send + Sync> From<T> for Gc<T> {
fn from(value: T) -> Self {
Gc::new(value)
}
}
impl<T: Trace + Send + Sync, const N: usize> From<[T; N]> for Gc<[T]> {
#[inline]
fn from(v: [T; N]) -> Gc<[T]> {
coerce_gc!(Gc::<[T; N]>::from(v))
}
}
impl<T: Trace + Send + Sync + Clone> From<&[T]> for Gc<[T]> {
#[inline]
fn from(slice: &[T]) -> Gc<[T]> {
struct Guard<T> {
mem: *mut u8,
layout: Layout,
elems: *mut T,
n_elems: usize,
}
impl<T> Drop for Guard<T> {
fn drop(&mut self) {
unsafe {
let slice = slice::from_raw_parts_mut(self.elems, self.n_elems);
ptr::drop_in_place(slice);
dealloc(self.mem, self.layout);
}
}
}
unsafe {
let value_layout = Layout::array::<T>(slice.len()).unwrap();
let layout = Layout::new::<GcBox<()>>()
.extend(value_layout)
.unwrap()
.0
.pad_to_align();
let ptr = Self::allocate_for_layout_of_box(layout, |mem| {
ptr::slice_from_raw_parts_mut(mem.cast::<T>(), slice.len()) as *mut GcBox<[T]>
});
let elems = (&raw mut (*ptr).value).cast::<T>();
let mut guard = Guard {
mem: ptr.cast::<u8>(),
layout,
elems,
n_elems: 0,
};
for (i, item) in slice.iter().enumerate() {
ptr::write(elems.add(i), item.clone());
guard.n_elems += 1;
}
mem::forget(guard);
notify_created_gc();
Self {
ptr: UCell::new(Nullable::from_ptr(ptr)),
tag: AtomicUsize::new(0),
}
}
}
}
impl<T: Trace + Send + Sync + Clone> From<&mut [T]> for Gc<[T]> {
#[inline]
fn from(value: &mut [T]) -> Self {
Gc::from(&*value)
}
}
impl From<&str> for Gc<str> {
#[inline]
fn from(v: &str) -> Self {
let bytes = Gc::<[u8]>::from(v.as_bytes());
let (ptr, tag) = Gc::into_ptr(bytes);
unsafe { Gc::from_ptr(ptr as *const GcBox<str>, tag) }
}
}
impl From<&mut str> for Gc<str> {
#[inline]
fn from(v: &mut str) -> Self {
Gc::from(&*v)
}
}
impl From<Gc<str>> for Gc<[u8]> {
#[inline]
fn from(value: Gc<str>) -> Self {
let (ptr, tag) = Gc::into_ptr(value);
unsafe { Gc::from_ptr(ptr as *const GcBox<[u8]>, tag) }
}
}
impl From<String> for Gc<str> {
#[inline]
fn from(value: String) -> Self {
Self::from(&value[..])
}
}
impl<T: Trace + Send + Sync> From<Box<T>> for Gc<T> {
#[inline]
fn from(src: Box<T>) -> Self {
unsafe {
let layout = Layout::for_value(&*src);
let gc_ptr = Gc::allocate_for_layout(layout, <*mut u8>::cast::<GcBox<T>>);
ptr::copy_nonoverlapping(
(&raw const *src).cast::<u8>(),
(&raw mut (*gc_ptr).value).cast::<u8>(),
layout.size(),
);
let bptr = Box::into_raw(src);
let src = Box::from_raw(bptr.cast::<mem::ManuallyDrop<T>>());
drop(src);
notify_created_gc();
Self::from_ptr(gc_ptr, 0)
}
}
}
impl<T: Trace + Send + Sync> From<Vec<T>> for Gc<[T]> {
#[inline]
fn from(vec: Vec<T>) -> Self {
let mut vec = ManuallyDrop::new(vec);
let vec_cap = vec.capacity();
let vec_len = vec.len();
let vec_ptr = vec.as_mut_ptr();
let gc_ptr = Self::allocate_for_slice(vec_len);
unsafe {
let dst_ptr = (&raw mut (*gc_ptr).value).cast::<T>();
ptr::copy_nonoverlapping(vec_ptr, dst_ptr, vec_len);
let _ = Vec::from_raw_parts(vec_ptr, 0, vec_cap);
notify_created_gc();
Self::from_ptr(gc_ptr, 0)
}
}
}
impl<'a, B: Trace + Send + Sync> From<Cow<'a, B>> for Gc<B>
where
B: ToOwned + ?Sized,
Gc<B>: From<&'a B> + From<B::Owned>,
{
#[inline]
fn from(cow: Cow<'a, B>) -> Gc<B> {
match cow {
Cow::Borrowed(s) => Gc::from(s),
Cow::Owned(s) => Gc::from(s),
}
}
}
impl<T> FromIterator<T> for Gc<[T]>
where
T: Trace + Send + Sync,
{
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut t_vec = iter.into_iter().collect::<Vec<_>>();
let n = t_vec.len();
let box_ptr = Gc::allocate_for_slice(n);
let gc = unsafe {
copy_nonoverlapping(t_vec.as_ptr(), (*box_ptr).value.as_mut_ptr(), n);
t_vec.set_len(0); Gc::from_ptr(box_ptr, CURRENT_TAG.load(Ordering::Acquire))
};
notify_created_gc();
gc
}
}