Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ mod serde;
mod trace;

#[cfg(feature = "derive")]
pub use gc_derive::{Finalize, Trace};
pub use gc_derive::{EmptyTrace, Finalize, Trace};

// We re-export the Trace method, as well as some useful internal methods for
// managing collections or configuring the garbage collector.
pub use crate::gc::{finalizer_safe, force_collect};
pub use crate::trace::{Finalize, Trace};
pub use crate::trace::{EmptyTrace, Finalize, Trace};

#[cfg(feature = "unstable-config")]
pub use crate::gc::{configure, GcConfig};
Expand Down
86 changes: 82 additions & 4 deletions gc/src/trace.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::{Cow, ToOwned};
use std::cell::{Cell, OnceCell, RefCell};
use std::collections::hash_map::{DefaultHasher, RandomState};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
use std::hash::BuildHasherDefault;
Expand All @@ -11,6 +12,7 @@ use std::num::{
};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::{Arc, Mutex, OnceLock, RwLock};
use std::sync::atomic::{
AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32,
AtomicU64, AtomicU8, AtomicUsize,
Expand Down Expand Up @@ -105,10 +107,60 @@ macro_rules! custom_trace {
};
}

impl<T: ?Sized> Finalize for &'static T {}
unsafe impl<T: ?Sized> Trace for &'static T {
/// A marker trait for types that don't require tracing.
///
/// # Safety
/// TODO: Safety conditions
pub unsafe trait EmptyTrace {}

// TODO: The README needs to be updated to explain when `Rc` and the other types here can be managed by GC.
impl<T: EmptyTrace + ?Sized> Finalize for Rc<T> {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl<T: EmptyTrace + ?Sized> Finalize for Rc<T> {}
impl<T: ?Sized> Finalize for Rc<T> {}

since Finalize itself doesn’t recursively finalize inner members (we have Trace::finalize_glue for that).

Same for the rest.

unsafe impl<T: EmptyTrace + ?Sized> Trace for Rc<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace + ?Sized> EmptyTrace for Rc<T> {}

impl<T: EmptyTrace + ?Sized> Finalize for Arc<T> {}
unsafe impl<T: EmptyTrace + ?Sized> Trace for Arc<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace + ?Sized> EmptyTrace for Arc<T> {}

impl<T: EmptyTrace + ?Sized> Finalize for RefCell<T> {}
unsafe impl<T: EmptyTrace + ?Sized> Trace for RefCell<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace + ?Sized> EmptyTrace for RefCell<T> {}

impl<T: EmptyTrace + ?Sized> Finalize for Cell<T> {}
unsafe impl<T: EmptyTrace + ?Sized> Trace for Cell<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace + ?Sized> EmptyTrace for Cell<T> {}

impl<T: EmptyTrace> Finalize for OnceCell<T> {}
unsafe impl<T: EmptyTrace> Trace for OnceCell<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace> EmptyTrace for OnceCell<T> {}

impl<T: EmptyTrace + ?Sized> Finalize for Mutex<T> {}
unsafe impl<T: EmptyTrace + ?Sized> Trace for Mutex<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace + ?Sized> EmptyTrace for Mutex<T> {}

impl<T: EmptyTrace + ?Sized> Finalize for RwLock<T> {}
unsafe impl<T: EmptyTrace + ?Sized> Trace for RwLock<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace + ?Sized> EmptyTrace for RwLock<T> {}

impl<T: EmptyTrace> Finalize for OnceLock<T> {}
unsafe impl<T: EmptyTrace> Trace for OnceLock<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace> EmptyTrace for OnceLock<T> {}
Comment on lines +129 to +163
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these other types have the sharing problem that Rc<T> and Arc<T> have. It should be possible to give them full Trace implementations with only a T: Trace constraint.

(There are some subtleties here—for example, we need to trace a Mutex with try_lock instead of lock in case safe code has leaked a MutexGuard. But I think they’re all solvable.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the internal mutability not a problem too? I thought that was the reason why GcCell is required.


macro_rules! simple_empty_finalize_trace {
($($T:ty),*) => {
Expand All @@ -117,6 +169,8 @@ macro_rules! simple_empty_finalize_trace {
impl Finalize for $T {}
#[allow(deprecated)]
unsafe impl Trace for $T { unsafe_empty_trace!(); }
#[allow(deprecated)]
unsafe impl EmptyTrace for $T {}
)*
}
}
Expand All @@ -141,7 +195,6 @@ simple_empty_finalize_trace![
char,
String,
str,
Rc<str>,
Path,
PathBuf,
NonZeroIsize,
Expand Down Expand Up @@ -172,6 +225,13 @@ simple_empty_finalize_trace![
RandomState
];

// We don't care about non-static references because they can never be owned by a `Gc`.
impl<T: ?Sized> Finalize for &'static T {}
unsafe impl<T: ?Sized> Trace for &'static T {
unsafe_empty_trace!();
}
unsafe impl<T: ?Sized> EmptyTrace for &'static T {}

impl<T, const N: usize> Finalize for [T; N] {}
unsafe impl<T: Trace, const N: usize> Trace for [T; N] {
custom_trace!(this, {
Expand All @@ -180,11 +240,13 @@ unsafe impl<T: Trace, const N: usize> Trace for [T; N] {
}
});
}
unsafe impl<T: EmptyTrace, const N: usize> EmptyTrace for [T; N] {}

macro_rules! fn_finalize_trace_one {
($ty:ty $(,$args:ident)*) => {
impl<Ret $(,$args)*> Finalize for $ty {}
unsafe impl<Ret $(,$args)*> Trace for $ty { unsafe_empty_trace!(); }
unsafe impl<Ret $(,$args)*> EmptyTrace for $ty {}
}
}
macro_rules! fn_finalize_trace_group {
Expand Down Expand Up @@ -215,6 +277,7 @@ macro_rules! tuple_finalize_trace {
$(mark($args);)*
});
}
unsafe impl<$($args: $crate::EmptyTrace),*> EmptyTrace for ($($args,)*) {}
}
}

Expand Down Expand Up @@ -249,6 +312,7 @@ unsafe impl<T: Trace + ?Sized> Trace for Box<T> {
mark(&**this);
});
}
unsafe impl<T: EmptyTrace + ?Sized> EmptyTrace for Box<T> {}

impl<T> Finalize for [T] {}
unsafe impl<T: Trace> Trace for [T] {
Expand All @@ -258,6 +322,7 @@ unsafe impl<T: Trace> Trace for [T] {
}
});
}
unsafe impl<T: EmptyTrace> EmptyTrace for [T] {}

impl<T> Finalize for Vec<T> {}
unsafe impl<T: Trace> Trace for Vec<T> {
Expand All @@ -267,6 +332,7 @@ unsafe impl<T: Trace> Trace for Vec<T> {
}
});
}
unsafe impl<T: EmptyTrace> EmptyTrace for Vec<T> {}

impl<T> Finalize for Option<T> {}
unsafe impl<T: Trace> Trace for Option<T> {
Expand All @@ -276,6 +342,7 @@ unsafe impl<T: Trace> Trace for Option<T> {
}
});
}
unsafe impl<T: EmptyTrace> EmptyTrace for Option<T> {}

impl<T, E> Finalize for Result<T, E> {}
unsafe impl<T: Trace, E: Trace> Trace for Result<T, E> {
Expand All @@ -286,6 +353,7 @@ unsafe impl<T: Trace, E: Trace> Trace for Result<T, E> {
}
});
}
unsafe impl<T: EmptyTrace, E: Trace> EmptyTrace for Result<T, E> {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unsafe impl<T: EmptyTrace, E: Trace> EmptyTrace for Result<T, E> {}
unsafe impl<T: EmptyTrace, E: EmptyTrace> EmptyTrace for Result<T, E> {}


impl<T> Finalize for BinaryHeap<T> {}
unsafe impl<T: Trace> Trace for BinaryHeap<T> {
Expand All @@ -295,6 +363,7 @@ unsafe impl<T: Trace> Trace for BinaryHeap<T> {
}
});
}
unsafe impl<T: EmptyTrace> EmptyTrace for BinaryHeap<T> {}

impl<K, V> Finalize for BTreeMap<K, V> {}
unsafe impl<K: Trace, V: Trace> Trace for BTreeMap<K, V> {
Expand All @@ -305,6 +374,7 @@ unsafe impl<K: Trace, V: Trace> Trace for BTreeMap<K, V> {
}
});
}
unsafe impl<K: EmptyTrace, V: EmptyTrace> EmptyTrace for BTreeMap<K, V> {}

impl<T> Finalize for BTreeSet<T> {}
unsafe impl<T: Trace> Trace for BTreeSet<T> {
Expand All @@ -314,6 +384,7 @@ unsafe impl<T: Trace> Trace for BTreeSet<T> {
}
});
}
unsafe impl<T: EmptyTrace> EmptyTrace for BTreeSet<T> {}

impl<K, V, S> Finalize for HashMap<K, V, S> {}
unsafe impl<K: Trace, V: Trace, S: Trace> Trace for HashMap<K, V, S> {
Expand All @@ -325,6 +396,7 @@ unsafe impl<K: Trace, V: Trace, S: Trace> Trace for HashMap<K, V, S> {
}
});
}
unsafe impl<K: EmptyTrace, V: EmptyTrace> EmptyTrace for HashMap<K, V> {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unsafe impl<K: EmptyTrace, V: EmptyTrace> EmptyTrace for HashMap<K, V> {}
unsafe impl<K: EmptyTrace, V: EmptyTrace, S: EmptyTrace> EmptyTrace for HashMap<K, V, S> {}


impl<T, S> Finalize for HashSet<T, S> {}
unsafe impl<T: Trace, S: Trace> Trace for HashSet<T, S> {
Expand All @@ -335,6 +407,7 @@ unsafe impl<T: Trace, S: Trace> Trace for HashSet<T, S> {
}
});
}
unsafe impl<T: EmptyTrace, S: EmptyTrace> EmptyTrace for HashSet<T, S> {}

impl<T> Finalize for LinkedList<T> {}
unsafe impl<T: Trace> Trace for LinkedList<T> {
Expand All @@ -344,11 +417,13 @@ unsafe impl<T: Trace> Trace for LinkedList<T> {
}
});
}
unsafe impl<T: EmptyTrace> EmptyTrace for LinkedList<T> {}

impl<T: ?Sized> Finalize for PhantomData<T> {}
unsafe impl<T: ?Sized> Trace for PhantomData<T> {
unsafe_empty_trace!();
}
unsafe impl<T: EmptyTrace> EmptyTrace for PhantomData<T> {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unsafe impl<T: EmptyTrace> EmptyTrace for PhantomData<T> {}
unsafe impl<T> EmptyTrace for PhantomData<T> {}

By definition, PhantomData<T> does not contain a T.


impl<T> Finalize for VecDeque<T> {}
unsafe impl<T: Trace> Trace for VecDeque<T> {
Expand All @@ -358,8 +433,9 @@ unsafe impl<T: Trace> Trace for VecDeque<T> {
}
});
}
unsafe impl<T: EmptyTrace> EmptyTrace for VecDeque<T> {}

impl<'a, T: ToOwned + ?Sized> Finalize for Cow<'a, T>{}
impl<'a, T: ToOwned + ?Sized> Finalize for Cow<'a, T> {}
unsafe impl<'a, T: ToOwned + ?Sized> Trace for Cow<'a, T>
where
T::Owned: Trace,
Expand All @@ -370,8 +446,10 @@ where
}
});
}
unsafe impl<'a, T: ToOwned + ?Sized> EmptyTrace for Cow<'a, T> where T::Owned: EmptyTrace {}

impl<T> Finalize for BuildHasherDefault<T> {}
unsafe impl<T> Trace for BuildHasherDefault<T> {
unsafe_empty_trace!();
}
unsafe impl<T> EmptyTrace for BuildHasherDefault<T> {}
1 change: 1 addition & 0 deletions gc_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ proc-macro = true
proc-macro2 = "1.0"
quote = "1.0"
synstructure = "0.13"
syn = "2.0.95"
66 changes: 64 additions & 2 deletions gc_derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use quote::quote;
use synstructure::{decl_derive, AddBounds, Structure};
use quote::{format_ident, quote, ToTokens};
use syn::spanned::Spanned;
use syn::{parse_quote_spanned, GenericParam, WherePredicate};
use synstructure::{decl_derive, AddBounds, Structure, VariantInfo};

decl_derive!([Trace, attributes(unsafe_ignore_trace)] => derive_trace);

Expand Down Expand Up @@ -78,3 +80,63 @@ decl_derive!([Finalize] => derive_finalize);
fn derive_finalize(s: Structure<'_>) -> proc_macro2::TokenStream {
s.unbound_impl(quote!(::gc::Finalize), quote!())
}

decl_derive!([EmptyTrace] => derive_empty_trace);

fn derive_empty_trace(s: Structure<'_>) -> proc_macro2::TokenStream {
let s_ast = &s.ast();
let name = &s_ast.ident;
let temp_name = format_ident!("_{name}");
let params = &s_ast.generics.params;
let param_names = params
.iter()
.map(|p| match p {
GenericParam::Lifetime(p) => p.to_token_stream(),
GenericParam::Type(p) => p.ident.to_token_stream(),
GenericParam::Const(p) => p.ident.to_token_stream(),
})
.collect::<Vec<_>>();
let where_predicates = &s_ast
.generics
.where_clause
.iter()
.flat_map(|wc| &wc.predicates)
.collect::<Vec<_>>();

// Require that all bindings implement `EmptyTrace`
let bindings = s.variants().iter().flat_map(VariantInfo::bindings);
let additional_where_predicates: Vec<WherePredicate> = bindings
.map(|bi| {
let ty = &bi.ast().ty;
let span = ty.span();
parse_quote_spanned! { span=> #ty: ::gc::EmptyTrace }
})
.collect();

// If any bindings in `s` refer to `s` itself then trait resolution could run into a cycle through our generated where predicates.
// We solve this with the following hack:
// Locally, we rename `s` and replace it with a temporary type of the same shape.
// That type unconditionally implements `EmptyTrace`, which might, technically, be unsafe but is fine since we never instantiate that type.
// Its only purpose is to stand in for `s` inside the generated predicates in order to break the cycle.
quote! {
const _: () = {
type #temp_name<#params> = #name<#(#param_names),*>;
{
#s_ast

unsafe impl<#params> ::gc::EmptyTrace
for #name<#(#param_names),*>
where
#(#where_predicates),*
{}

unsafe impl<#params> ::gc::EmptyTrace
for #temp_name<#(#param_names),*>
where
#(#where_predicates),*
#(#additional_where_predicates),*
{}
}
};
}
}