diff --git a/gc/src/lib.rs b/gc/src/lib.rs index 8d086cf..43e78f6 100644 --- a/gc/src/lib.rs +++ b/gc/src/lib.rs @@ -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}; diff --git a/gc/src/trace.rs b/gc/src/trace.rs index a68e72f..72fc8b6 100644 --- a/gc/src/trace.rs +++ b/gc/src/trace.rs @@ -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; @@ -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, @@ -105,10 +107,60 @@ macro_rules! custom_trace { }; } -impl Finalize for &'static T {} -unsafe impl 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 Finalize for Rc {} +unsafe impl Trace for Rc { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Rc {} + +impl Finalize for Arc {} +unsafe impl Trace for Arc { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Arc {} + +impl Finalize for RefCell {} +unsafe impl Trace for RefCell { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for RefCell {} + +impl Finalize for Cell {} +unsafe impl Trace for Cell { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Cell {} + +impl Finalize for OnceCell {} +unsafe impl Trace for OnceCell { unsafe_empty_trace!(); } +unsafe impl EmptyTrace for OnceCell {} + +impl Finalize for Mutex {} +unsafe impl Trace for Mutex { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for Mutex {} + +impl Finalize for RwLock {} +unsafe impl Trace for RwLock { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for RwLock {} + +impl Finalize for OnceLock {} +unsafe impl Trace for OnceLock { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for OnceLock {} macro_rules! simple_empty_finalize_trace { ($($T:ty),*) => { @@ -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 {} )* } } @@ -141,7 +195,6 @@ simple_empty_finalize_trace![ char, String, str, - Rc, Path, PathBuf, NonZeroIsize, @@ -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 Finalize for &'static T {} +unsafe impl Trace for &'static T { + unsafe_empty_trace!(); +} +unsafe impl EmptyTrace for &'static T {} + impl Finalize for [T; N] {} unsafe impl Trace for [T; N] { custom_trace!(this, { @@ -180,11 +240,13 @@ unsafe impl Trace for [T; N] { } }); } +unsafe impl EmptyTrace for [T; N] {} macro_rules! fn_finalize_trace_one { ($ty:ty $(,$args:ident)*) => { impl Finalize for $ty {} unsafe impl Trace for $ty { unsafe_empty_trace!(); } + unsafe impl EmptyTrace for $ty {} } } macro_rules! fn_finalize_trace_group { @@ -215,6 +277,7 @@ macro_rules! tuple_finalize_trace { $(mark($args);)* }); } + unsafe impl<$($args: $crate::EmptyTrace),*> EmptyTrace for ($($args,)*) {} } } @@ -249,6 +312,7 @@ unsafe impl Trace for Box { mark(&**this); }); } +unsafe impl EmptyTrace for Box {} impl Finalize for [T] {} unsafe impl Trace for [T] { @@ -258,6 +322,7 @@ unsafe impl Trace for [T] { } }); } +unsafe impl EmptyTrace for [T] {} impl Finalize for Vec {} unsafe impl Trace for Vec { @@ -267,6 +332,7 @@ unsafe impl Trace for Vec { } }); } +unsafe impl EmptyTrace for Vec {} impl Finalize for Option {} unsafe impl Trace for Option { @@ -276,6 +342,7 @@ unsafe impl Trace for Option { } }); } +unsafe impl EmptyTrace for Option {} impl Finalize for Result {} unsafe impl Trace for Result { @@ -286,6 +353,7 @@ unsafe impl Trace for Result { } }); } +unsafe impl EmptyTrace for Result {} impl Finalize for BinaryHeap {} unsafe impl Trace for BinaryHeap { @@ -295,6 +363,7 @@ unsafe impl Trace for BinaryHeap { } }); } +unsafe impl EmptyTrace for BinaryHeap {} impl Finalize for BTreeMap {} unsafe impl Trace for BTreeMap { @@ -305,6 +374,7 @@ unsafe impl Trace for BTreeMap { } }); } +unsafe impl EmptyTrace for BTreeMap {} impl Finalize for BTreeSet {} unsafe impl Trace for BTreeSet { @@ -314,6 +384,7 @@ unsafe impl Trace for BTreeSet { } }); } +unsafe impl EmptyTrace for BTreeSet {} impl Finalize for HashMap {} unsafe impl Trace for HashMap { @@ -325,6 +396,7 @@ unsafe impl Trace for HashMap { } }); } +unsafe impl EmptyTrace for HashMap {} impl Finalize for HashSet {} unsafe impl Trace for HashSet { @@ -335,6 +407,7 @@ unsafe impl Trace for HashSet { } }); } +unsafe impl EmptyTrace for HashSet {} impl Finalize for LinkedList {} unsafe impl Trace for LinkedList { @@ -344,11 +417,13 @@ unsafe impl Trace for LinkedList { } }); } +unsafe impl EmptyTrace for LinkedList {} impl Finalize for PhantomData {} unsafe impl Trace for PhantomData { unsafe_empty_trace!(); } +unsafe impl EmptyTrace for PhantomData {} impl Finalize for VecDeque {} unsafe impl Trace for VecDeque { @@ -358,8 +433,9 @@ unsafe impl Trace for VecDeque { } }); } +unsafe impl EmptyTrace for VecDeque {} -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, @@ -370,8 +446,10 @@ where } }); } +unsafe impl<'a, T: ToOwned + ?Sized> EmptyTrace for Cow<'a, T> where T::Owned: EmptyTrace {} impl Finalize for BuildHasherDefault {} unsafe impl Trace for BuildHasherDefault { unsafe_empty_trace!(); } +unsafe impl EmptyTrace for BuildHasherDefault {} diff --git a/gc_derive/Cargo.toml b/gc_derive/Cargo.toml index 9e6a9a3..5f53788 100644 --- a/gc_derive/Cargo.toml +++ b/gc_derive/Cargo.toml @@ -18,3 +18,4 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" synstructure = "0.13" +syn = "2.0.95" diff --git a/gc_derive/src/lib.rs b/gc_derive/src/lib.rs index 6a31a65..dea15f6 100644 --- a/gc_derive/src/lib.rs +++ b/gc_derive/src/lib.rs @@ -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); @@ -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::>(); + let where_predicates = &s_ast + .generics + .where_clause + .iter() + .flat_map(|wc| &wc.predicates) + .collect::>(); + + // Require that all bindings implement `EmptyTrace` + let bindings = s.variants().iter().flat_map(VariantInfo::bindings); + let additional_where_predicates: Vec = 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),* + {} + } + }; + } +}