#![feature(cell_update)]
#![feature(maybe_uninit_extra)]
#![feature(box_syntax)]
#![feature(allocator_api)]
#![forbid(unsafe_op_in_unsafe_fn)]
#![warn(clippy::missing_const_for_fn)]
#![no_std]
#[cfg(feature = "trace")]
extern crate std;
#[cfg(feature = "trace")]
use std::println;
extern crate alloc;
mod trace_impls;
use alloc::{
alloc::{Allocator, Global},
boxed::Box,
};
use core::{
alloc::Layout,
cell::Cell,
fmt,
fmt::Formatter,
marker::{PhantomData, Unpin},
mem::MaybeUninit,
ops::Deref,
pin::Pin,
ptr,
ptr::NonNull,
};
#[cfg(feature = "derive")]
pub use mjb_gc_derive::Trace;
pub struct Gc<T: Trace> {
ptr: NonNull<GcInner<T>>,
_phantom: PhantomData<GcInner<T>>,
}
impl<T: Trace> Gc<T> {
fn inner(&self) -> &GcInner<T> {
unsafe { self.ptr.as_ref() }
}
fn as_inner_ptr(&self) -> *const GcInner<T> {
self.ptr.as_ptr()
}
fn as_inner_ptr_mut(&mut self) -> *mut GcInner<T> {
self.ptr.as_ptr()
}
unsafe fn as_value_mut(&mut self) -> &mut MaybeUninit<T> {
unsafe { &mut (*self.as_inner_ptr_mut()).value }
}
fn from_inner(ptr: NonNull<GcInner<T>>) -> Self {
Self {
ptr,
_phantom: PhantomData,
}
}
#[track_caller]
fn assert_undropped(&self) {
if self.inner().dropped.get() {
panic!("Gc was already dropped");
}
}
}
impl<T: Trace> Gc<T> {
#[must_use]
pub fn new(value: T) -> Self {
Self::from_inner(
Box::leak(box GcInner {
ref_count: Cell::new(1),
internal_ref_count: Cell::new(0),
done: Cell::new(false),
dropped: Cell::new(false),
value: MaybeUninit::new(value),
})
.into(),
)
}
#[must_use]
pub fn new_cyclic<F>(data_fn: F) -> Self
where
F: FnOnce(Self) -> T,
{
let mut gc = Self::from_inner(
Box::leak(box GcInner {
ref_count: Cell::new(1),
internal_ref_count: Cell::new(0),
done: Cell::new(false),
dropped: Cell::new(true),
value: MaybeUninit::<T>::uninit(),
})
.into(),
);
let data = data_fn(gc.clone());
unsafe { gc.as_value_mut().write(data) };
gc.inner().dropped.set(false);
gc
}
#[must_use]
pub fn pin(value: T) -> Pin<Self> {
unsafe { Pin::new_unchecked(Self::new(value)) }
}
#[must_use]
pub fn ptr_eq(&self, other: &Self) -> bool {
ptr::eq(self.inner(), other.inner())
}
#[must_use]
pub fn as_ptr(&self) -> *const T {
self.assert_undropped();
unsafe { (*self.as_inner_ptr()).value.as_ptr() }
}
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut T {
self.assert_undropped();
unsafe { (*self.as_inner_ptr_mut()).value.as_mut_ptr() }
}
pub fn try_unwrap(this: Self) -> Result<T, Self> {
if Self::is_unique(&this) {
this.assert_undropped();
let inner = this.inner();
inner.dropped.set(true);
Ok(unsafe { inner.value.assume_init_read() })
} else {
Err(this)
}
}
#[must_use]
pub fn ref_count(this: &Self) -> usize {
this.inner().ref_count.get()
}
#[must_use]
pub fn is_unique(this: &Self) -> bool {
this.inner().ref_count.get() == 1
}
#[must_use]
pub fn get_mut(this: &mut Self) -> Option<&mut T> {
if Self::is_unique(this) {
this.assert_undropped();
Some(unsafe { Self::get_unchecked_mut(this) })
} else {
None
}
}
#[must_use]
pub unsafe fn get_unchecked_mut(this: &mut Self) -> &mut T {
unsafe { this.as_value_mut().assume_init_mut() }
}
}
impl<T: Trace + Clone> Gc<T> {
#[must_use]
pub fn make_mut(this: &mut Self) -> &mut T {
this.assert_undropped();
if !Self::is_unique(this) {
*this = Self::new(T::clone(this));
}
unsafe { Self::get_unchecked_mut(this) }
}
}
impl<T: Trace> Deref for Gc<T> {
type Target = T;
fn deref(&self) -> &T {
self.assert_undropped();
unsafe { self.inner().value.assume_init_ref() }
}
}
impl<T: Trace> Clone for Gc<T> {
fn clone(&self) -> Self {
self.inner()
.ref_count
.update(|v| v.checked_add(1).expect("Gc ref-count overflow"));
Self::from_inner(self.ptr)
}
fn clone_from(&mut self, other: &Self) {
if !ptr::eq(self.inner(), other.inner()) {
*self = other.clone();
}
}
}
impl<T: Trace + fmt::Debug> fmt::Debug for Gc<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<T: Trace + fmt::Display> fmt::Display for Gc<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl<T: Trace + PartialEq> PartialEq for Gc<T> {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
impl<T: Trace + Eq> Eq for Gc<T> {}
impl<T: Trace> Unpin for Gc<T> {}
struct GcInner<T: Trace> {
ref_count: Cell<usize>,
done: Cell<bool>,
internal_ref_count: Cell<usize>,
dropped: Cell<bool>,
value: MaybeUninit<T>,
}
impl<T: Trace> GcInner<T> {
fn value(&self) -> Option<&T> {
if !self.dropped.get() {
Some(unsafe { self.value.assume_init_ref() })
} else {
None
}
}
}
impl<T: Trace> fmt::Debug for GcInner<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("GcInner")
.field("ref_count", &self.ref_count.get())
.field("done", &self.done.get())
.field("internal_ref_count", &self.internal_ref_count.get())
.field("dropped", &self.dropped.get())
.finish_non_exhaustive()
}
}
impl<T: Trace> Drop for Gc<T> {
fn drop(&mut self) {
let inner = self.inner();
inner.ref_count.update(|v| v - 1);
if inner.ref_count.get() == 0 {
if !inner.dropped.get() {
unsafe { self.as_value_mut().assume_init_drop() };
}
unsafe { ptr::drop_in_place(self.as_inner_ptr_mut()) };
unsafe { Global.deallocate(self.ptr.cast(), Layout::new::<GcInner<T>>()) };
} else if let Some(v_ref) = inner.value() {
if !T::could_contain_cycles() {
return;
}
#[cfg(feature = "trace")]
println!("\n>>>> untrace");
inner.internal_ref_count.set(0); inner.done.set(true);
unsafe { v_ref.untrace() };
inner.done.set(false);
unsafe { v_ref.set_undone() };
#[cfg(feature = "trace")]
println!("\n>>>> trace");
inner.done.set(true);
unsafe { v_ref.trace() };
inner.done.set(false);
unsafe { v_ref.set_undone() };
if inner.internal_ref_count.get() == inner.ref_count.get() {
#[cfg(feature = "trace")]
println!("\n>>>> counts_match");
inner.done.set(true);
let matched = v_ref.counts_match();
#[cfg(feature = "trace")]
if matched {
println!("\n>>>> all_matched");
}
inner.done.set(false);
unsafe { v_ref.set_undone() };
if matched {
inner.dropped.set(true);
unsafe { self.as_value_mut().assume_init_drop() };
}
}
}
}
}
pub unsafe trait Trace {
unsafe fn untrace(&self);
unsafe fn trace(&self);
unsafe fn set_undone(&self);
fn counts_match(&self) -> bool;
fn could_contain_cycles() -> bool {
true
}
}
unsafe impl<T: Trace> Trace for Gc<T> {
unsafe fn untrace(&self) {
let inner = self.inner();
inner.internal_ref_count.set(0);
if !inner.done.get() {
#[cfg(feature = "trace")]
println!("not done yet");
inner.done.set(true);
if let Some(v) = inner.value() {
unsafe { v.untrace() };
}
}
}
unsafe fn trace(&self) {
let inner = self.inner();
inner.internal_ref_count.update(|v| v + 1);
if inner.internal_ref_count.get() > inner.ref_count.get() {
unreachable!("more references than ref_count");
}
if !inner.done.get() {
#[cfg(feature = "trace")]
println!("not done yet");
inner.done.set(true);
if let Some(v) = inner.value() {
unsafe { v.trace() };
}
}
}
unsafe fn set_undone(&self) {
let inner = self.inner();
if inner.done.get() {
#[cfg(feature = "trace")]
println!("not done yet");
inner.done.set(false);
if let Some(v) = inner.value() {
unsafe { v.set_undone() };
}
}
}
fn counts_match(&self) -> bool {
let inner = self.inner();
#[cfg(feature = "trace")]
println!(
" int_rc => {}, rc => {}",
inner.internal_ref_count.get(),
inner.ref_count.get()
);
let mut v = inner.internal_ref_count.get() == inner.ref_count.get();
if !inner.done.get() {
#[cfg(feature = "trace")]
println!("not done yet");
inner.done.set(true);
if let Some(value) = inner.value() {
v &= value.counts_match();
}
}
v
}
fn could_contain_cycles() -> bool {
T::could_contain_cycles()
}
}
#[macro_export]
macro_rules! unsafe_empty_trace {
() => {
$crate::unsafe_field_trace! {}
};
}
#[macro_export]
macro_rules! unsafe_field_trace {
($($f:tt: $T:ty),*) => {
unsafe fn untrace(&self) {
$(
unsafe { <$T as $crate::Trace>::untrace(&self.$f) };
)*
}
unsafe fn trace(&self) {
$(
unsafe { $crate::Trace::trace(&self.$f) };
)*
}
unsafe fn set_undone(&self) {
$(
unsafe { $crate::Trace::set_undone(&self.$f) };
)*
}
fn counts_match(&self) -> bool {
true $(& $crate::Trace::counts_match(&self.$f))*
}
fn could_contain_cycles() -> bool {
false $(|| <$T as $crate::Trace>::could_contain_cycles())*
}
};
($($f:tt),*) => {
unsafe fn untrace(&self) {
$(
unsafe { $crate::Trace::untrace(&self.$f) };
)*
}
unsafe fn trace(&self) {
$(
unsafe { $crate::Trace::trace(&self.$f) };
)*
}
unsafe fn set_undone(&self) {
$(
unsafe { $crate::Trace::set_undone(&self.$f) };
)*
}
fn counts_match(&self) -> bool {
true $(& $crate::Trace::counts_match(&self.$f))*
}
fn could_contain_cycles() -> bool {
true
}
};
($($f:tt: $T:ty,)*) => { $crate::unsafe_field_trace! {$($f: $T),*} };
($($f:tt,)*) => { $crate::unsafe_field_trace! {$($f),*} };
}
#[cfg(test)]
mod tests;