diff --git a/Cargo.toml b/Cargo.toml index 0693855..971abd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ readme = "README.md" # Providing support for these important libraries, # gives zerogc 'batteries included' support. indexmap = { version = "1.6", optional = true } +# Used for macros +zerogc-derive = { path = "libs/derive", version = "0.2.0-alpha.1" } [workspace] members = ["libs/simple", "libs/derive", "libs/context"] diff --git a/libs/derive/Cargo.toml b/libs/derive/Cargo.toml index 0432158..0ae4c9a 100644 --- a/libs/derive/Cargo.toml +++ b/libs/derive/Cargo.toml @@ -16,6 +16,6 @@ zerogc = { version = "0.2.0-alpha.1", path = "../.." } [dependencies] # Proc macros -syn = "1.0.55" +syn = { version = "1.0.55", features = ["full", "extra-traits"] } quote = "1.0.8" proc-macro2 = "1" diff --git a/libs/derive/src/lib.rs b/libs/derive/src/lib.rs index 537003d..c47101c 100644 --- a/libs/derive/src/lib.rs +++ b/libs/derive/src/lib.rs @@ -1,5 +1,6 @@ #![feature( proc_macro_tracked_env, // Used for `DEBUG_DERIVE` + proc_macro_span, // Used for source file ids )] extern crate proc_macro; @@ -12,6 +13,26 @@ use std::collections::HashSet; use std::fmt::Display; use std::io::Write; +mod macros; + +/// Magic const that expands to either `::zerogc` or `crate::` +/// depending on whether we are currently bootstraping (compiling `zerogc` itself) +/// +/// This is equivalant to `$crate` for regular macros +pub(crate) fn zerogc_crate() -> TokenStream { + if is_bootstraping() { + quote!(crate) + } else { + quote!(::zerogc) + } +} + +/// If we are currently compiling the base crate `zerogc` itself +pub(crate) fn is_bootstraping() -> bool { + ::proc_macro::tracked_env::var("CARGO_CRATE_NAME") + .expect("Expected `CARGO_CRATE_NAME`") == "zerogc" +} + struct MutableFieldOpts { public: bool } @@ -149,6 +170,20 @@ impl Default for TypeAttrs { } } } +fn span_file_loc(span: Span) -> String { + /* + * Source file identifiers in the form `:` + */ + let internal = span.unwrap(); + let sf = internal.source_file(); + let path = sf.path(); + let file_name = if sf.is_real() { path.file_name() } else { None } + .map(std::ffi::OsStr::to_string_lossy) + .map(String::from) + .unwrap_or_else(|| String::from("")); + let lineno = internal.start().line; + format!("{}:{}", file_name, lineno) +} impl Parse for TypeAttrs { fn parse(raw_input: ParseStream) -> Result { let input; @@ -334,6 +369,22 @@ impl Parse for TypeAttrs { } } +#[proc_macro] +pub fn unsafe_gc_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let parsed = parse_macro_input!(input as macros::MacroInput); + let res = parsed.expand_output() + .unwrap_or_else(|e| e.to_compile_error()); + let span_loc = span_file_loc(Span::call_site()); + debug_derive( + "unsafe_gc_impl!", + &span_loc, + &format_args!("unsafe_gc_impl! @ {}", span_loc), + &res + ); + res.into() +} + + #[proc_macro_derive(Trace, attributes(zerogc))] pub fn derive_trace(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -341,7 +392,7 @@ pub fn derive_trace(input: proc_macro::TokenStream) -> proc_macro::TokenStream { .unwrap_or_else(|e| e.to_compile_error())); debug_derive( "derive(Trace)", - &input.ident, + &input.ident.to_string(), &format_args!("#[derive(Trace) for {}", input.ident), &res ); @@ -377,6 +428,7 @@ fn impl_derive_trace(input: &DeriveInput) -> Result { } fn trace_fields(fields: &Fields, access_ref: &mut dyn FnMut(Member) -> TokenStream) -> TokenStream { + let zerogc_crate = zerogc_crate(); // TODO: Detect if we're a unit struct and implement `NullTrace` let mut result = Vec::new(); for (index, field) in fields.iter().enumerate() { @@ -384,7 +436,7 @@ fn trace_fields(fields: &Fields, access_ref: &mut dyn FnMut(Member) -> TokenStre Some(ref ident) => Member::Named(ident.clone()), None => Member::Unnamed(Index::from(index)) }); - result.push(quote!(::zerogc::Trace::visit(#val, &mut *visitor)?)); + result.push(quote!(#zerogc_crate::Trace::visit(#val, &mut *visitor)?)); } quote!(#(#result;)*) } @@ -393,6 +445,7 @@ fn trace_fields(fields: &Fields, access_ref: &mut dyn FnMut(Member) -> TokenStre /// /// 1. Implement setters for `GcCell` fields using a write barrier fn impl_extras(target: &DeriveInput, info: &GcTypeInfo) -> Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let mut extra_items = Vec::new(); let gc_lifetime = info.config.gc_lifetime(); @@ -449,13 +502,13 @@ fn impl_extras(target: &DeriveInput, info: &GcTypeInfo) -> Result GcCell::as_ptr(&(*self.value()).#original_name)); - let barrier = quote_spanned!(field.span() => ::zerogc::GcDirectBarrier::write_barrier(&value, &self, offset)); + let field_as_ptr = quote_spanned!(field.span() => #zerogc_crate::cell::GcCell::as_ptr(&(*self.value()).#original_name)); + let barrier = quote_spanned!(field.span() => #zerogc_crate::GcDirectBarrier::write_barrier(&value, &self, offset)); extra_items.push(quote! { #[inline] // TODO: Implement `GcDirectBarrier` ourselves - #mutator_vis fn #mutator_name(self: ::zerogc::Gc<#gc_lifetime, Self, Id>, value: #value_ref_type) - where Id: ::zerogc::CollectorId, - #value_ref_type: ::zerogc::GcDirectBarrier<#gc_lifetime, ::zerogc::Gc<#gc_lifetime, Self, Id>> { + #mutator_vis fn #mutator_name(self: #zerogc_crate::Gc<#gc_lifetime, Self, Id>, value: #value_ref_type) + where Id: #zerogc_crate::CollectorId, + #value_ref_type: #zerogc_crate::GcDirectBarrier<#gc_lifetime, #zerogc_crate::Gc<#gc_lifetime, Self, Id>> { unsafe { let target_ptr = #field_as_ptr; let offset = target_ptr as usize - self.as_raw_ptr() as usize; @@ -502,13 +555,14 @@ fn impl_extras(target: &DeriveInput, info: &GcTypeInfo) -> Result Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let mut generics: Generics = target.generics.clone(); for param in &mut generics.params { match param { GenericParam::Type(ref mut type_param) => { // Require all params are NullTrace - type_param.bounds.push(parse_quote!(::zerogc::NullTrace)); + type_param.bounds.push(parse_quote!(#zerogc_crate::NullTrace)); }, GenericParam::Lifetime(ref mut l) => { if l.lifetime == info.config.gc_lifetime() { @@ -535,7 +589,7 @@ fn impl_erase_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result id.clone(), None => { - impl_generics.params.push(GenericParam::Type(parse_quote!(Id: ::zerogc::CollectorId))); + impl_generics.params.push(GenericParam::Type(parse_quote!(Id: #zerogc_crate::CollectorId))); parse_quote!(Id) } }; @@ -543,13 +597,13 @@ fn impl_erase_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result + unsafe impl #impl_generics #zerogc_crate::GcErase<'min, #collector_id> for #name #ty_generics #where_clause { // We can pass-through because we are NullTrace type Erased = Self; @@ -557,6 +611,7 @@ fn impl_erase_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let mut generics: Generics = target.generics.clone(); let mut rewritten_params = Vec::new(); @@ -570,10 +625,10 @@ fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result { let original_bounds = type_param.bounds.iter().cloned().collect::>(); - type_param.bounds.push(parse_quote!(::zerogc::GcErase<'min, #collector_id>)); + type_param.bounds.push(parse_quote!(#zerogc_crate::GcErase<'min, #collector_id>)); type_param.bounds.push(parse_quote!('min)); let param_name = &type_param.ident; - let rewritten_type: Type = parse_quote!(<#param_name as ::zerogc::GcErase<'min, #collector_id>>::Erased); + let rewritten_type: Type = parse_quote!(<#param_name as #zerogc_crate::GcErase<'min, #collector_id>>::Erased); rewritten_restrictions.push(WherePredicate::Type(PredicateType { lifetimes: None, bounded_ty: rewritten_type.clone(), @@ -621,17 +676,17 @@ fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result <#field_type as ::zerogc::GcErase<'min, #collector_id>>::assert_erase();) + quote_spanned!(span => <#field_type as #zerogc_crate::GcErase<'min, #collector_id>>::assert_erase();) }).collect::>(); Ok(quote! { - unsafe impl #impl_generics ::zerogc::GcErase<'min, #collector_id> + unsafe impl #impl_generics #zerogc_crate::GcErase<'min, #collector_id> for #name #ty_generics #where_clause { type Erased = #name::<#(#rewritten_params),*>; @@ -644,13 +699,14 @@ fn impl_erase(target: &DeriveInput, info: &GcTypeInfo) -> Result Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let mut generics: Generics = target.generics.clone(); for param in &mut generics.params { match param { GenericParam::Type(ref mut type_param) => { // Require all params are NullTrace - type_param.bounds.push(parse_quote!(::zerogc::NullTrace)); + type_param.bounds.push(parse_quote!(#zerogc_crate::NullTrace)); }, GenericParam::Lifetime(ref mut l) => { if l.lifetime == info.config.gc_lifetime() { @@ -677,7 +733,7 @@ fn impl_rebrand_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result id.clone(), None => { - impl_generics.params.push(GenericParam::Type(parse_quote!(Id: ::zerogc::CollectorId))); + impl_generics.params.push(GenericParam::Type(parse_quote!(Id: #zerogc_crate::CollectorId))); parse_quote!(Id) } }; @@ -685,13 +741,13 @@ fn impl_rebrand_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result + unsafe impl #impl_generics #zerogc_crate::GcRebrand<'new_gc, #collector_id> for #name #ty_generics #where_clause { // We can pass-through because we are NullTrace type Branded = Self; @@ -699,6 +755,7 @@ fn impl_rebrand_nop(target: &DeriveInput, info: &GcTypeInfo) -> Result Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let mut generics: Generics = target.generics.clone(); let mut rewritten_params = Vec::new(); @@ -712,9 +769,9 @@ fn impl_rebrand(target: &DeriveInput, info: &GcTypeInfo) -> Result { let original_bounds = type_param.bounds.iter().cloned().collect::>(); - type_param.bounds.push(parse_quote!(::zerogc::GcRebrand<'new_gc, #collector_id>)); + type_param.bounds.push(parse_quote!(#zerogc_crate::GcRebrand<'new_gc, #collector_id>)); let param_name = &type_param.ident; - let rewritten_type: Type = parse_quote!(<#param_name as ::zerogc::GcRebrand<'new_gc, #collector_id>>::Branded); + let rewritten_type: Type = parse_quote!(<#param_name as #zerogc_crate::GcRebrand<'new_gc, #collector_id>>::Branded); rewritten_restrictions.push(WherePredicate::Type(PredicateType { lifetimes: None, bounded_ty: rewritten_type.clone(), @@ -762,17 +819,17 @@ fn impl_rebrand(target: &DeriveInput, info: &GcTypeInfo) -> Result <#field_type as ::zerogc::GcRebrand<'new_gc, #collector_id>>::assert_rebrand();) + quote_spanned!(span => <#field_type as #zerogc_crate::GcRebrand<'new_gc, #collector_id>>::assert_rebrand();) }).collect::>(); impl_generics.make_where_clause().predicates.extend(rewritten_restrictions); let (_, ty_generics, _) = generics.split_for_impl(); let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); Ok(quote! { - unsafe impl #impl_generics ::zerogc::GcRebrand<'new_gc, #collector_id> + unsafe impl #impl_generics #zerogc_crate::GcRebrand<'new_gc, #collector_id> for #name #ty_generics #where_clause { type Branded = #name::<#(#rewritten_params),*>; @@ -783,6 +840,7 @@ fn impl_rebrand(target: &DeriveInput, info: &GcTypeInfo) -> Result Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let generics = add_trait_bounds_except( &target.generics, parse_quote!(zerogc::Trace), @@ -851,15 +909,15 @@ fn impl_trace(target: &DeriveInput, info: &GcTypeInfo) -> Result::NEEDS_TRACE)*; + unsafe impl #impl_generics #zerogc_crate::Trace for #name #ty_generics #where_clause { + const NEEDS_TRACE: bool = false #(|| <#field_types as #zerogc_crate::Trace>::NEEDS_TRACE)*; /* * The inline annotation adds this function's MIR to the metadata. * Without it cross-crate inlining is impossible (without LTO). */ #[inline] - fn visit(&mut self, #[allow(unused)] visitor: &mut V) -> Result<(), V::Err> { + fn visit(&mut self, #[allow(unused)] visitor: &mut Visitor) -> Result<(), Visitor::Err> { #trace_impl Ok(()) } @@ -867,10 +925,11 @@ fn impl_trace(target: &DeriveInput, info: &GcTypeInfo) -> Result Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let collector_id = &info.config.collector_id; let generics = add_trait_bounds_except( - &target.generics, parse_quote!(zerogc::GcSafe), + &target.generics, parse_quote!(#zerogc_crate::GcSafe), &info.config.ignore_params, Some(&mut |other: &Ident| { if let Some(ref collector_id) = *collector_id { @@ -903,7 +962,7 @@ fn impl_gc_safe(target: &DeriveInput, info: &GcTypeInfo) -> Result::NEEDS_DROP)*) + quote!(false #(|| <#field_types as #zerogc_crate::GcSafe>::NEEDS_DROP)*) }; let fake_drop_impl = if info.config.is_copy { /* @@ -926,12 +985,12 @@ fn impl_gc_safe(target: &DeriveInput, info: &GcTypeInfo) -> Result()) + quote!(#zerogc_crate::assert_copy::()) } else { - quote!(#(<#field_types as GcSafe>::assert_gc_safe();)*) + quote!(#(<#field_types as #zerogc_crate::GcSafe>::assert_gc_safe();)*) }; Ok(quote! { - unsafe impl #impl_generics ::zerogc::GcSafe + unsafe impl #impl_generics #zerogc_crate::GcSafe for #name #ty_generics #where_clause { const NEEDS_DROP: bool = #does_need_drop; @@ -945,9 +1004,10 @@ fn impl_gc_safe(target: &DeriveInput, info: &GcTypeInfo) -> Result Result { + let zerogc_crate = zerogc_crate(); let name = &target.ident; let generics = add_trait_bounds_except( - &target.generics, parse_quote!(zerogc::Trace), + &target.generics, parse_quote!(#zerogc_crate::Trace), &info.config.ignore_params, None )?; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); @@ -974,30 +1034,30 @@ fn impl_nop_trace(target: &DeriveInput, info: &GcTypeInfo) -> Result assert!( - !<#t as Trace>::NEEDS_TRACE, + !<#t as #zerogc_crate::Trace>::NEEDS_TRACE, "Can't #[derive(NullTrace) with {}", stringify!(#t) ); } }).collect::>(); Ok(quote! { - unsafe impl #impl_generics ::zerogc::Trace for #name #ty_generics #where_clause { + unsafe impl #impl_generics #zerogc_crate::Trace for #name #ty_generics #where_clause { const NEEDS_TRACE: bool = false; #[inline] // Should be const-folded away - fn visit(&mut self, #[allow(unused)] visitor: &mut V) -> Result<(), V::Err> { + fn visit(&mut self, #[allow(unused)] visitor: &mut Visitor) -> Result<(), Visitor::Err> { #(#trace_assertions)* Ok(()) } } - unsafe impl #impl_generics ::zerogc::TraceImmutable for #name #ty_generics #where_clause { + unsafe impl #impl_generics #zerogc_crate::TraceImmutable for #name #ty_generics #where_clause { #[inline] // Should be const-folded away - fn visit_immutable(&self, #[allow(unused)] visitor: &mut V) -> Result<(), V::Err> { + fn visit_immutable(&self, #[allow(unused)] visitor: &mut Visitor) -> Result<(), Visitor::Err> { #(#trace_assertions)* Ok(()) } } - unsafe impl #impl_generics ::zerogc::NullTrace for #name #ty_generics #where_clause {} + unsafe impl #impl_generics #zerogc_crate::NullTrace for #name #ty_generics #where_clause {} }) } @@ -1066,22 +1126,22 @@ fn debug_derive(key: &str, target: &dyn ToString, message: &dyn Display, value: let target = target.to_string(); // TODO: Use proc_macro::tracked_env::var match ::proc_macro::tracked_env::var("DEBUG_DERIVE") { - Ok(ref var) if var == "*" => {} + Ok(ref var) if var == "*" || var == "1" || var.is_empty() => {} + Ok(ref var) if var == "0" => { return /* disabled */ } Ok(var) => { + let target_parts = std::iter::once(key) + .chain(target.split(":")).collect::>(); for pattern in var.split_terminator(",") { - let parts = pattern.split(":").collect::>(); - let (desired_key, desired_target) = match *parts { - [desired_key, desired_target] => (desired_key, Some(desired_target)), - [desired_key] => (desired_key, None), - _ => { - panic!("Invalid pattern for debug derive: {}", pattern) + let pattern_parts = pattern.split(":").collect::>(); + if pattern_parts.len() > target_parts.len() { continue } + for (&pattern_part, &target_part) in pattern_parts.iter() + .chain(std::iter::repeat(&"*")).zip(&target_parts) { + if pattern_part == "*" { + continue // Wildcard matches anything: Keep checking + } + if pattern_part != target_part { + return // Pattern mismatch } - }; - if desired_key != key && desired_key != "*" { return } - if let Some(desired_target) = desired_target { - if desired_target != target && desired_target != "*" { - return - } } } // Fallthrough -> enable this debug diff --git a/libs/derive/src/macros.rs b/libs/derive/src/macros.rs new file mode 100644 index 0000000..7a20b3e --- /dev/null +++ b/libs/derive/src/macros.rs @@ -0,0 +1,886 @@ +//! Procedural macros for implementing `GcType` + +/* + * The main macro here is `unsafe_impl_gc` + */ + +use std::collections::HashSet; + +use proc_macro2::{Ident, TokenStream, TokenTree}; +use syn::{ + GenericParam, WhereClause, Type, Expr, Error, Token, braced, bracketed, + Generics, TypeParamBound, WherePredicate, PredicateType, parse_quote, + GenericArgument +}; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; + +use quote::{quote, quote_spanned}; +use super::zerogc_crate; + +#[derive(Debug)] +struct GenericParamInput(Vec); +impl Parse for GenericParamInput { + fn parse(input: ParseStream) -> syn::Result { + let inner; + bracketed!(inner in input); + let res = inner + .parse_terminated::<_, Token![,]>(GenericParam::parse)?; + Ok(GenericParamInput(res.into_iter().collect())) + } +} + +fn empty_clause() -> WhereClause { + WhereClause { + predicates: Default::default(), + where_token: Default::default() + } +} + +#[derive(Debug)] +pub struct MacroInput { + /// The target type we are implementing + /// + /// This has unconstrained use of the parameters defined in `params` + target_type: Type, + /// The generic parameters (both types and lifetimes) that we want to + /// declare for each implementation + /// + /// This must not conflict with our internal names ;) + params: Vec, + /// Custom bounds provided for each + /// + /// All of these bounds are optional. + /// This option can be omitted, + /// giving the equivalent of `bounds = {}` + bounds: CustomBounds, + /// The standard arguments to the macro + options: StandardOptions, + visit: VisitImpl +} +impl MacroInput { + fn basic_generics(&self) -> Generics { + let mut generics = Generics::default(); + generics.params.extend(self.params.iter().cloned()); + generics + } + pub fn expand_output(&self) -> Result { + let zerogc_crate = zerogc_crate(); + let target_type = &self.target_type; + let trace_impl = self.expand_trace_impl(true)? + .expect("Trace impl required"); + let trace_immutable_impl = self.expand_trace_impl(false)? + .unwrap_or_default(); + let gcsafe_impl = self.expand_gcsafe_impl(); + let null_trace_clause = match self.options.null_trace { + TraitRequirements::Always => Some(empty_clause()), + TraitRequirements::Where(ref clause) => Some(clause.clone()), + TraitRequirements::Never => None + }; + let null_trace_impl = if let Some(null_trace_clause) = null_trace_clause { + let mut generics = self.basic_generics(); + generics.make_where_clause().predicates.extend(null_trace_clause.predicates.clone()); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + quote! { + unsafe impl #impl_generics #zerogc_crate::NullTrace for #target_type + #where_clause {} + } + } else { + quote!() + }; + let rebrand_impl = self.expand_brand_impl(true)?; + let erase_impl = self.expand_brand_impl(false)?; + Ok(quote! { + #trace_impl + #trace_immutable_impl + #null_trace_impl + #gcsafe_impl + #rebrand_impl + #erase_impl + }) + } + fn expand_trace_impl(&self, mutable: bool) -> Result, Error> { + let zerogc_crate = zerogc_crate(); + let target_type = &self.target_type; + let mut generics = self.basic_generics(); + let clause = if mutable { + self.bounds.trace_where_clause(&self.params) + } else { + match self.bounds.trace_immutable_clause(&self.params) { + Some(clause) => clause, + None => return Ok(None), // They are requesting that we dont implement + } + }; + generics.make_where_clause().predicates + .extend(clause.predicates); + let visit_impl = self.visit.expand_impl(mutable)?; + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let trait_name = if mutable { quote!(#zerogc_crate::Trace) } else { quote!(#zerogc_crate::TraceImmutable) }; + let visit_method_name = if mutable { quote!(visit) } else { quote!(visit_immutable) }; + let needs_trace_const = if mutable { + let expr = &self.options.needs_trace; + Some(quote!(const NEEDS_TRACE: bool = { + // Import the trait so we can access `T::NEEDS_TRACE` + use #zerogc_crate::Trace; + #expr + };)) + } else { + None + }; + let mutability = if mutable { + quote!(mut) + } else { + quote!() + }; + Ok(Some(quote! { + unsafe impl #impl_generics #trait_name for #target_type #where_clause { + #needs_trace_const + #[inline] // TODO: Should this be unconditional? + fn #visit_method_name(&#mutability self, visitor: &mut Visitor) -> Result<(), Visitor::Err> { + #visit_impl + } + } + })) + } + fn expand_gcsafe_impl(&self) -> Option { + let zerogc_crate = zerogc_crate(); + let target_type = &self.target_type; + let mut generics = self.basic_generics(); + generics.make_where_clause().predicates + .extend(match self.bounds.gcsafe_clause(&self.params) { + Some(clause) => clause.predicates, + None => return None // They are requesting we dont implement + }); + let needs_drop = &self.options.needs_drop; + let (impl_generics, _, where_clause) = generics.split_for_impl(); + Some(quote! { + unsafe impl #impl_generics #zerogc_crate::GcSafe for #target_type #where_clause { + const NEEDS_DROP: bool = { + // Import the trait so we can access `T::NEEDS_DROP` + use #zerogc_crate::GcSafe; + #needs_drop + }; + } + }) + } + fn expand_brand_impl(&self, rebrand: bool /* true => rebrand, false => erase */) -> Result, Error> { + let zerogc_crate = zerogc_crate(); + let requirements = if rebrand { self.bounds.rebrand.clone() } else { self.bounds.erase.clone() }; + if let Some(TraitRequirements::Never) = requirements { + // They are requesting that we dont implement + return Ok(None); + } + let target_type = &self.target_type; + let mut generics = self.basic_generics(); + let default_bounds: Vec = match requirements { + Some(TraitRequirements::Where(ref explicit_requirements)) => { + generics.make_where_clause().predicates + .extend(explicit_requirements.predicates.iter().cloned()); + // they have explicit requirements -> no default bounds + vec![] + } + Some(TraitRequirements::Always) => { + vec![] // always should implement + }, + Some(TraitRequirements::Never) => unreachable!(), + None => { + if rebrand { + vec![parse_quote!(#zerogc_crate::GcRebrand<'new_gc, Id>)] + } else { + vec![parse_quote!(#zerogc_crate::GcErase<'min, Id>)] + } + } + }; + // generate default bounds + for param in &self.params { + if default_bounds.is_empty() { + // no defaults to generate + break + } + match param { + GenericParam::Type(ref tp) => { + let type_name = &tp.ident; + let mut bounds = tp.bounds.clone(); + bounds.extend(default_bounds.iter().cloned()); + generics.make_where_clause() + .predicates.push(WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: if rebrand { + self.options.branded_type.clone().unwrap_or_else(|| { + parse_quote!(<#type_name as #zerogc_crate::GcRebrand<'new_gc, Id>>::Branded) + }) + } else { + self.options.erased_type.clone().unwrap_or_else(|| { + parse_quote!(<#type_name as #zerogc_crate::GcErase<'min, Id>>::Erased) + }) + }, + colon_token: Default::default(), + bounds: bounds.clone(), + })); + generics.make_where_clause() + .predicates.push(WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: parse_quote!(#type_name), + colon_token: Default::default(), + bounds + })) + } + _ => {} + } + } + /* + * If we don't have explicit specification, + * extend the with the trace clauses + * + * TODO: Do we need to apply to the `Branded`/`Erased` types + */ + generics.make_where_clause().predicates + .extend(self.bounds.trace_where_clause(&self.params).predicates); + generics.params.push(parse_quote!(Id: #zerogc_crate::CollectorId)); + if rebrand { + generics.params.push(parse_quote!('new_gc)); + } else { + generics.params.push(parse_quote!('min)); + } + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let target_trait = if rebrand { + quote!(#zerogc_crate::GcRebrand<'new_gc, Id>) + } else { + quote!(#zerogc_crate::GcErase<'min, Id>) + }; + fn rewrite_brand_trait( + target: &Type, trait_name: &str, target_params: &HashSet, + target_trait: TokenStream, associated_type: Ident + ) -> Result { + rewrite_type(target, trait_name, &mut |target_type| { + let ident = match target_type { + Type::Path(ref tp) if tp.qself.is_none() => { + match tp.path.get_ident() { + Some(ident) => ident, + None => return None + } + }, + _ => return None + }; + if target_params.contains(&ident) { + Some(parse_quote!(<#ident as #target_trait>::#associated_type)) + } else { + None + } + }) + } + let target_params = self.params.iter().filter_map(|param| match param { + GenericParam::Type(ref tp) => Some(tp.ident.clone()), + _ => None + }).collect::>(); + let associated_type = if rebrand { + let branded = self.options.branded_type.clone().map_or_else(|| { + rewrite_brand_trait( + &self.target_type, "GcRebrand", + &target_params, + parse_quote!(#zerogc_crate::GcRebrand<'new_gc, Id>), + parse_quote!(Branded) + ) + }, Ok)?; + quote!(type Branded = #branded;) + } else { + let erased = Ok(self.options.erased_type.clone()).transpose().unwrap_or_else(|| { + rewrite_brand_trait( + &self.target_type, "GcErase", + &target_params, + parse_quote!(#zerogc_crate::GcErase<'min, Id>), + parse_quote!(Erased) + ) + })?; + quote!(type Erased = #erased;) + }; + Ok(Some(quote! { + unsafe impl #impl_generics #target_trait for #target_type #where_clause { + #associated_type + } + })) + } +} + +macro_rules! __full_field_ty { + ($field_type:ty, opt) => (Option<$field_type>); + ($field_type:ty,) => ($field_type); +} +macro_rules! __unwrap_field_ty { + ($opt_name:literal, $span:expr, $val:ident, $field_ty:ty, opt) => ($val); + ($opt_name:literal, $span:expr, $val:ident, $field_ty:ty,) => (match $val { + Some(inner) => inner, + None => return Err(Error::new( + $span, concat!("Missing required option: ", $opt_name) + )) + }); +} + +macro_rules! parse_field_opts { + ($parser:ident, { + $($opt_name:literal [$field_type:ty] $($suffix:ident)? => $field_name:ident;)* + }) => (parse_field_opts!($parser, complete = true, { + $($opt_name [$field_type] $($suffix)? => $field_name;)* + })); + ($parser:ident, complete = $complete:literal, { + $($opt_name:literal [$field_type:ty] $($suffix:ident)? => $field_name:ident;)* + }) => {{ + assert!($complete, "Incomplte is unsupported!"); // NOTE: How would we parse an unknown type? + struct ParsedFields { + $($field_name: __full_field_ty!($field_type, $($suffix)?)),* + } + $(let mut $field_name = Option::<$field_type>::None;)* + #[allow(unused)] + let start_span = $parser.span(); + while !$parser.is_empty() { + let ident = $parser.parse::() + .map_err(|e| Error::new(e.span(), "Expected an option name"))?; + let s = ident.to_string(); + match &*s { + $($opt_name => { + if $field_name.is_some() { + return Err(Error::new( + ident.span(), + concat!("Duplicate values specified for option: ", $opt_name), + )) + } + $parser.parse::]>()?; + $field_name = Some($parser.parse::<$field_type>()?); + if $parser.peek(Token![,]) { + $parser.parse::()?; + } + },)* + _ => { + return Err(Error::new( + ident.span(), + format!("Unknown option name: {}", ident) + )) + } + } + } + ParsedFields { + $($field_name: __unwrap_field_ty!($opt_name, start_span, $field_name, $field_type, $($suffix)?)),* + } + }}; +} +impl Parse for MacroInput { + fn parse(input: ParseStream) -> syn::Result { + let res = parse_field_opts!(input, { + "target" [Type] => target_type; + "params" [GenericParamInput] => params; + "bounds" [CustomBounds] opt => bounds; + // StandardOptions + "null_trace" [TraitRequirements] => null_trace; + "branded_type" [Type] opt => branded_type; + "erased_type" [Type] opt => erased_type; + "NEEDS_TRACE" [Expr] => needs_trace; + "NEEDS_DROP" [Expr] => needs_drop; + "visit" [VisitClosure] opt => visit_closure; + "trace_mut" [VisitClosure] opt => trace_mut_closure; + "trace_immutable" [VisitClosure] opt => trace_immutable_closure; + }); + let bounds = res.bounds.unwrap_or_default(); + if let Some(TraitRequirements::Never) = bounds.trace { + return Err(Error::new( + res.target_type.span(), "Bounds on `Trace` can't be never" + )) + } + let visit_impl = if let Some(visit_closure) = res.visit_closure { + if let Some(closure) = res.trace_immutable_closure.as_ref() + .or(res.trace_mut_closure.as_ref()) { + return Err(Error::new( + closure.body.span(), + "Cannot specify specific closure (trace_mut/trace_immutable) in addition to `visit`" + )) + } + VisitImpl::Generic { generic_impl: visit_closure.body } + } else { + let trace_closure = res.trace_mut_closure.ok_or_else(|| { + Error::new( + input.span(), + "Either a `visit` or a `trace_mut` impl is required for Trace types" + ) + })?; + let trace_immut_closure = match bounds.trace_immutable { + Some(TraitRequirements::Never) => { + if let Some(closure) = res.trace_immutable_closure { + return Err(Error::new( + closure.body.span(), + "Specified a `trace_immutable` implementation even though TraceImmutable is never implemented" + )) + } else { + None + } + }, + _ => { + let target_span = res.target_type.span(); + // we maybe implement `TraceImmutable` some of the time + Some(res.trace_immutable_closure.ok_or_else(|| { + Error::new( + target_span, + "Requires a `trace_immutable` implementation" + ) + })?) + } + }; + VisitImpl::Specific { + mutable: ::syn::parse2(trace_closure.body)?, + immutable: trace_immut_closure + .map(|closure| ::syn::parse2::(closure.body)) + .transpose()? + } + }; + Ok(MacroInput { + target_type: res.target_type, + params: res.params.0, + bounds, + options: StandardOptions { + null_trace: res.null_trace, + branded_type: res.branded_type, + erased_type: res.erased_type, + needs_trace: res.needs_trace, + needs_drop: res.needs_drop + }, + visit: visit_impl + }) + } +} + +#[derive(Debug)] +pub struct VisitClosure { + body: TokenStream, + brace: ::syn::token::Brace +} +impl Parse for VisitClosure { + fn parse(input: ParseStream) -> syn::Result { + input.parse::()?; + if !input.peek(Token![self]) { + return Err(Error::new( + input.span(), + "Expected first argument to closure to be `self`" + )); + } + input.parse::()?; + input.parse::()?; + let visitor_name = input.parse::()?; + if visitor_name != "visitor" { + return Err(Error::new( + visitor_name.span(), + "Expected second argument to closure to be `visitor`" + )); + } + input.parse::()?; + if !input.peek(syn::token::Brace) { + return Err(input.error("Expected visitor closure to be braced")); + } + let body; + let brace = braced!(body in input); + let body = body.parse::()?; + Ok(VisitClosure { body: quote!({ #body }), brace }) + } +} + +/// Extra bounds +#[derive(Default, Debug)] +pub struct CustomBounds { + /// Additional bounds on the `Trace` implementation + trace: Option, + /// Additional bounds on the `TraceImmutable` implementation + /// + /// If unspecified, this will default to the same as those + /// specified for `Trace` + trace_immutable: Option, + /// Additional bounds on the `GcSafe` implementation + /// + /// If unspecified, this will default to the same as those + /// specified for `Trace` + gcsafe: Option, + /// The requirements to implement `GcRebrand` + rebrand: Option, + /// The requirements to implement `GcErase` + erase: Option, +} +impl CustomBounds { + fn trace_where_clause(&self, generic_params: &[GenericParam]) -> WhereClause { + let zerogc_crate = zerogc_crate(); + create_clause_with_default( + &self.trace, generic_params, + vec![parse_quote!(#zerogc_crate::Trace)] + ).unwrap_or_else(|| unreachable!("Trace must always be implemented")) + } + fn trace_immutable_clause(&self, generic_params: &[GenericParam]) -> Option { + let zerogc_crate = zerogc_crate(); + create_clause_with_default( + &self.trace_immutable, generic_params, + vec![parse_quote!(#zerogc_crate::TraceImmutable)] + ) + } + fn gcsafe_clause(&self, generic_params: &[GenericParam]) -> Option { + let zerogc_crate = zerogc_crate(); + let mut res = create_clause_with_default( + &self.gcsafe, generic_params, + vec![parse_quote!(#zerogc_crate::GcSafe)] + ); + if self.gcsafe.is_none() { + // Extend with the trae bounds + res.get_or_insert_with(empty_clause).predicates.extend( + self.trace_where_clause(generic_params).predicates + ) + } + res + } +} +fn create_clause_with_default( + target: &Option, generic_params: &[GenericParam], + default_bounds: Vec +) -> Option { + Some(match *target { + Some(TraitRequirements::Never) => return None, // do not implement + Some(TraitRequirements::Where(ref explicit)) => explicit.clone(), + Some(TraitRequirements::Always) => { + // Absolutely no conditions on implementation + empty_clause() + } + None => { + let mut where_clause = empty_clause(); + // Infer bounds for all params + for param in generic_params { + if let GenericParam::Type(ref t) = param { + let ident = &t.ident; + where_clause.predicates.push(WherePredicate::Type(PredicateType { + bounded_ty: parse_quote!(#ident), + colon_token: Default::default(), + bounds: default_bounds.iter().cloned().collect(), + lifetimes: None + })) + } + } + where_clause + } + }) +} +impl Parse for CustomBounds { + fn parse(input: ParseStream) -> syn::Result { + let inner; + braced!(inner in input); + let res = parse_field_opts!(inner, { + "Trace" [TraitRequirements] opt => trace; + "TraceImmutable" [TraitRequirements] opt => trace_immutable; + "GcSafe" [TraitRequirements] opt => gcsafe; + "GcRebrand" [TraitRequirements] opt => rebrand; + "GcErase" [TraitRequirements] opt => erase; + }); + Ok(CustomBounds { + trace: res.trace, + trace_immutable: res.trace_immutable, + gcsafe: res.gcsafe, + rebrand: res.rebrand, + erase: res.erase, + }) + } +} + +/// The standard options for the macro +/// +/// Options are required unless wrapped in an `Option` +/// (or explicitly marked optional) +#[derive(Debug)] +pub struct StandardOptions { + /// Requirements on implementing the NullTrace + /// + /// This is unsafe, and completely unchecked. + null_trace: TraitRequirements, + /// The associated type implemented as `GcRebrand::Branded` + branded_type: Option, + /// The associated type implemented as `GcErase::Erased` + erased_type: Option, + /// A (constant) expression determining whether the array needs to be traced + needs_trace: Expr, + /// A (constant) expression determining whether the type should be dropped + needs_drop: Expr, +} + +/// The visit implementation. +/// +/// The target object is always accessible through `self`. +/// Other variables depend on the implementation. +#[derive(Debug)] +pub enum VisitImpl { + /// A generic implementation, whose code is shared across + /// both mutable/immutable implementations. + /// + /// This requires auto-replacement of certain magic variables, + /// which vary depending on whether we're generating a mutable + /// or immutable visitor. + /// + /// There are two variables accessible to the implementation: `self` and `visitor` + /// + /// | Magic Variable | for Trace | for TraceImmutable | + /// | -------------- | ---------- | ------------------ | + /// | #mutability | `` (empty) | `mut` | + /// | #as_ref | `as_ref` | `as_mut` | + /// | #iter | `iter` | `iter_mut` | + /// | #visit_func | `visit` | `visit_immutable` | + /// | #b | `&` | `&mut` | + /// | ## (escape) | `#` | `#` | + /// + /// Example visitor for `Vec`: + /// ````no_test + /// for item in self.#iter() { + /// #visit(item); + /// } + /// Ok(()) + /// ```` + Generic { + generic_impl: TokenStream + }, + /// Specialized implementations which are different for + /// both `Trace` and `TraceImmutable` + Specific { + mutable: Expr, + immutable: Option + } +} +enum MagicVarType { + Mutability, + AsRef, + Iter, + VisitFunc, + B +} +impl MagicVarType { + fn parse_ident(ident: &Ident) -> Result { + let s = ident.to_string(); + Ok(match &*s { + "mutability" => MagicVarType::Mutability, + "as_ref" => MagicVarType::AsRef, + "iter" => MagicVarType::Iter, + "visit_func" => MagicVarType::VisitFunc, + "b" => MagicVarType::B, + _ => return Err( + Error::new(ident.span(), + "Invalid magic variable name" + )) + }) + } +} +impl VisitImpl { + fn expand_impl(&self, mutable: bool) -> Result { + match *self { + VisitImpl::Generic { ref generic_impl } => { + let tokens = replace_magic_tokens(generic_impl.clone(), &mut |ident| { + let res = match MagicVarType::parse_ident(ident)? { + MagicVarType::Mutability => { + if mutable { + quote!(mut) + } else { + quote!() + } + } + MagicVarType::AsRef => { + if mutable { + quote!(as_mut) + } else { + quote!(as_ref) + } + } + MagicVarType::Iter => { + if mutable { + quote!(iter_mut) + } else { + quote!(iter) + } + } + MagicVarType::VisitFunc => { + if mutable { + quote!(visit) + } else { + quote!(visit_immutable) + } + } + MagicVarType::B => { + if mutable { + quote!(&mut) + } else { + quote!(&) + } + } + }; + let span = ident.span(); // Reuse the span of the *input* + Ok(quote_spanned!(span => #res)) + })?; + Ok(match ::syn::parse2::(tokens.clone()) { + Ok(res) => res, + Err(cause) => { + let mut err = Error::new( + generic_impl.span(), + format!( + "Unable to perform 'magic' variable substitution on closure: {}", + tokens + ) + ); + err.combine(cause); + return Err(err) + } + }) + } + VisitImpl::Specific { mutable: ref mutable_impl, ref immutable } => { + Ok(if mutable { + mutable_impl.clone() + } else { + immutable.clone().unwrap() + }) + } + } + } +} +fn replace_magic_tokens(input: TokenStream, func: &mut dyn FnMut(&Ident) -> Result) -> Result { + use quote::TokenStreamExt; + let mut res = TokenStream::new(); + let mut iter = input.into_iter(); + while let Some(item) = iter.next() { + match item { + TokenTree::Group(ref group) => { + let old_span = group.span(); + let delim = group.delimiter(); + let new_stream = replace_magic_tokens(group.stream(), &mut *func)?; + let mut new_group = ::proc_macro2::Group::new(delim, new_stream); + new_group.set_span(old_span); // The overall span must be preserved + res.append(TokenTree::Group(new_group)) + } + TokenTree::Punct(ref p) if p.as_char() == '#' => { + match iter.next() { + None => return Err(Error::new( + p.span(), "Unexpected EOF after magic token `#`" + )), + Some(TokenTree::Punct(ref p2)) if p2.as_char() == '#' => { + // Pass through p2 + res.append(TokenTree::Punct(p2.clone())); + } + Some(TokenTree::Ident(ref ident)) => { + res.extend(func(ident)?); + }, + Some(ref other) => { + return Err(Error::new( + p.span(), format!( + "Invalid token after magic token `#`: {}", + other + ) + )) + } + } + } + TokenTree::Punct(_) | TokenTree::Ident(_) | TokenTree::Literal(_)=> { + // pass through + res.append(item); + } + } + } + Ok(res) +} + +/// The requirements to implement a trait +/// +/// In addition to a where clause, you can specify `always` for unconditional +/// implementation and `never` to forbid generated implementations +#[derive(Clone, Debug)] +pub enum TraitRequirements { + /// The trait should never be implemented + Never, + /// The trait should only be implemented if + /// the specified where clause is satisfied + Where(WhereClause), + /// The trait should always be implemented + Always +} + +impl Parse for TraitRequirements { + fn parse(input: ParseStream) -> syn::Result { + if input.peek(syn::Ident) { + let ident = input.parse::()?; + if ident == "always" { + Ok(TraitRequirements::Always) + } else if ident == "never" { + Ok(TraitRequirements::Never) + } else { + return Err(Error::new( + ident.span(), + "Invalid identifier for `TraitRequirement`" + )) + } + } else if input.peek(syn::token::Brace) { + let inner; + braced!(inner in input); + Ok(TraitRequirements::Where(inner.parse::()?)) + } else { + return Err(input.error("Invalid `TraitRequirement`")) + } + } +} + + + +fn rewrite_type(target: &Type, target_type_name: &str, rewriter: &mut dyn FnMut(&Type) -> Option) -> Result { + if let Some(explicitly_rewritten) = rewriter(target) { + return Ok(explicitly_rewritten) + } + let mut target = target.clone(); + match target { + Type::Paren(ref mut inner) => { + *inner.elem = rewrite_type(&inner.elem, target_type_name, rewriter)? + }, + Type::Group(ref mut inner) => { + *inner.elem = rewrite_type(&inner.elem, target_type_name, rewriter)? + }, + Type::Reference(ref mut target) => { + // TODO: Lifetime safety? + // Rewrite reference target + *target.elem = rewrite_type(&target.elem, target_type_name, rewriter)? + } + Type::Path(::syn::TypePath { ref mut qself, ref mut path }) => { + *qself = qself.clone().map::, _>(|mut qself| { + qself.ty = Box::new(rewrite_type( + &*qself.ty, target_type_name, + &mut *rewriter + )?); + Ok(qself) + }).transpose()?; + path.segments = path.segments.iter().cloned().map(|mut segment| { + // old_segment.ident is ignored... + match segment.arguments { + ::syn::PathArguments::None => {}, // Nothing to do here + ::syn::PathArguments::AngleBracketed(ref mut generic_args) => { + for arg in &mut generic_args.args { + match arg { + GenericArgument::Lifetime(_) | GenericArgument::Const(_) => {}, + GenericArgument::Type(ref mut generic_type) => { + *generic_type = rewrite_type(generic_type, target_type_name, &mut *rewriter)?; + } + GenericArgument::Constraint(_) | GenericArgument::Binding(_) => { + return Err(Error::new( + arg.span(), format!( + "Unable to handle generic arg while rewriting as a {}", + target_type_name + ) + )) + } + } + } + } + ::syn::PathArguments::Parenthesized(ref mut paran_args) => { + return Err(Error::new( + paran_args.span(), + "TODO: Rewriting paranthesized (fn-style) args" + )); + } + } + Ok(segment) + }).collect::>()?; + } + _ => return Err(Error::new(target.span(), format!( + "Unable to rewrite type as a `{}`: {}", + target_type_name, quote!(#target) + ))) + } + Ok(target) +} diff --git a/src/lib.rs b/src/lib.rs index e1f78e2..04e5166 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,8 @@ use core::marker::PhantomData; use core::hash::{Hash, Hasher}; use core::fmt::{self, Debug, Formatter}; +use zerogc_derive::unsafe_gc_impl; + #[macro_use] mod manually_traced; pub mod cell; @@ -767,26 +769,24 @@ impl DerefMut for AssumeNotTraced { &mut self.0 } } - -unsafe impl Trace for AssumeNotTraced { - const NEEDS_TRACE: bool = false; - #[inline(always)] // This method does nothing and is always a win to inline - fn visit(&mut self, _visitor: &mut V) -> Result<(), V::Err> { - Ok(()) - } -} -unsafe impl TraceImmutable for AssumeNotTraced { - #[inline(always)] - fn visit_immutable(&self, _visitor: &mut V) -> Result<(), V::Err> { - Ok(()) - } -} -unsafe impl NullTrace for AssumeNotTraced {} -/// No tracing implies GcSafe -unsafe impl GcSafe for AssumeNotTraced { - const NEEDS_DROP: bool = core::mem::needs_drop::(); +unsafe_gc_impl! { + target => AssumeNotTraced, + params => [T], + bounds => { + // Unconditionally implement all traits + Trace => always, + TraceImmutable => always, + GcSafe => always, + GcRebrand => { where T: 'new_gc }, + GcErase => { where T: 'min } + }, + null_trace => always, + branded_type => AssumeNotTraced, + erased_type => AssumeNotTraced, + NEEDS_TRACE => false, + NEEDS_DROP => core::mem::needs_drop::(), + visit => |self, visitor| { /* nop */ Ok(()) } } -unsafe_gc_brand!(AssumeNotTraced, T); /// Changes all references to garbage collected /// objects to match a specific lifetime. diff --git a/src/manually_traced/core.rs b/src/manually_traced/core.rs index 34078cd..8b6222b 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -7,12 +7,31 @@ use core::num::Wrapping; use crate::prelude::*; -use crate::{GcDirectBarrier, CollectorId}; +use crate::GcDirectBarrier; + +use zerogc_derive::unsafe_gc_impl; macro_rules! trace_tuple { - { $($param:ident)* } => { - unsafe impl<$($param),*> Trace for ($($param,)*) - where $($param: Trace),* { + { $single_param:ident } => { + trace_tuple_impl!(); + trace_tuple_impl!($single_param); + }; + { $first_param:ident, $($param:ident),* } => { + trace_tuple! { $($param),* } + trace_tuple_impl!( $first_param, $($param),*); + }; +} + +macro_rules! trace_tuple_impl { + { $($param:ident),* } => { + unsafe_gc_impl! { + target => ( $($param,)* ), + params => [$($param),*], + bounds => { + GcRebrand => { where $($param: GcRebrand<'new_gc, Id>),* }, + GcErase => { where $($param: GcErase<'min, Id>),* }, + } + null_trace => { where $($param: NullTrace,)* i32: Sized }, /* * HACK: Macros don't allow using `||` as separator, * so we use it as a terminator, causing there to be an illegal trailing `||`. @@ -20,38 +39,44 @@ macro_rules! trace_tuple { * since `a || false` is always `a`. * This also correctly handles the empty unit tuple by making it false */ - const NEEDS_TRACE: bool = $($param::NEEDS_TRACE || )* false; - #[inline] - fn visit(&mut self, #[allow(unused)] visitor: &mut Visit) -> Result<(), Visit::Err> { - #[allow(non_snake_case)] - let ($(ref mut $param,)*) = *self; - $(visitor.visit::<$param>($param)?;)* + NEEDS_TRACE => $($param::NEEDS_TRACE || )* false, + NEEDS_DROP => $($param::NEEDS_DROP || )* false, + branded_type => ( $(<$param as GcRebrand<'new_gc, Id>>::Branded,)* ), + erased_type => ( $(<$param as GcErase<'min, Id>>::Erased,)* ), + visit => |self, visitor| { + ##[allow(non_snake_case)] + let ($(ref #mutability $param,)*) = *self; + $(visitor.#visit_func($param)?;)* Ok(()) } } - unsafe impl<$($param),*> TraceImmutable for ($($param,)*) - where $($param: TraceImmutable),* { + unsafe impl<'gc, OwningRef, $($param),*> $crate::GcDirectBarrier<'gc, OwningRef> for ($($param,)*) + where $($param: $crate::GcDirectBarrier<'gc, OwningRef>),* { #[inline] - fn visit_immutable(&self, #[allow(unused)] visitor: &mut V) -> Result<(), V::Err> { + unsafe fn write_barrier( + &self, #[allow(unused)] owner: &OwningRef, + #[allow(unused)] start_offset: usize + ) { + /* + * We are implementing gc **direct** write. + * This is safe because all the tuple's values + * are stored inline. We calculate the pointer offset + * using arithmetic. + */ #[allow(non_snake_case)] let ($(ref $param,)*) = *self; - $(visitor.visit_immutable::<$param>($param)?;)* - Ok(()) + $({ + let field_offset = ($param as *const $param as usize) + - (self as *const Self as usize); + $param.write_barrier(owner, field_offset + start_offset); + };)* } } - unsafe impl<$($param: NullTrace),*> NullTrace for ($($param,)*) {} - unsafe impl<'new_gc, Id, $($param),*> $crate::GcRebrand<'new_gc, Id> for ($($param,)*) - where Id: $crate::CollectorId, $($param: $crate::GcRebrand<'new_gc, Id>,)* - $(<$param as $crate::GcRebrand<'new_gc, Id>>::Branded: Sized,)* { - type Branded = ($(<$param as $crate::GcRebrand<'new_gc, Id>>::Branded,)*); - } - unsafe impl<'a, Id, $($param),*> $crate::GcErase<'a, Id> for ($($param,)*) - where Id: $crate::CollectorId, $($param: $crate::GcErase<'a, Id>,)* - $(<$param as $crate::GcErase<'a, Id>>::Erased: Sized,)* { - type Erased = ($(<$param as $crate::GcErase<'a, Id>>::Erased,)*); - } - unsafe impl<$($param: GcSafe),*> GcSafe for ($($param,)*) { - const NEEDS_DROP: bool = false $(|| <$param as GcSafe>::NEEDS_DROP)*; + }; + { $first_param:ident, $($param:ident)* ; gc_impl => { $($impls:tt)* }} => { + trace_tuple!($first_param:ident, $($param)*); + unsafe_gc_impl! { + $($impls)* } unsafe impl<'gc, OwningRef, $($param),*> $crate::GcDirectBarrier<'gc, OwningRef> for ($($param,)*) where $($param: $crate::GcDirectBarrier<'gc, OwningRef>),* { @@ -96,45 +121,21 @@ unsafe_trace_primitive!(char); // TODO: Get proper support for unsized types (issue #15) unsafe_trace_primitive!(&'static str); -trace_tuple! {} -trace_tuple! { A } -trace_tuple! { A B } -trace_tuple! { A B C } -trace_tuple! { A B C D } -trace_tuple! { A B C D E } -trace_tuple! { A B C D E F } -trace_tuple! { A B C D E F G } -trace_tuple! { A B C D E F G H } -trace_tuple! { A B C D E F G H I } +trace_tuple! { A, B, C, D, E, F, G, H, I } macro_rules! trace_array { ($size:tt) => { - unsafe impl Trace for [T; $size] { - const NEEDS_TRACE: bool = T::NEEDS_TRACE; - #[inline] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { - visitor.visit::<[T]>(self as &mut [T]) - } - } - unsafe impl $crate::TraceImmutable for [T; $size] { - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), V::Err> { - visitor.visit_immutable::<[T]>(self as &[T]) - } - } - unsafe impl $crate::NullTrace for [T; $size] {} - unsafe impl GcSafe for [T; $size] { - const NEEDS_DROP: bool = core::mem::needs_drop::(); - } - unsafe impl<'new_gc, Id, T> $crate::GcRebrand<'new_gc, Id> for [T; $size] - where Id: CollectorId, T: GcRebrand<'new_gc, Id>, - >::Branded: Sized { - type Branded = [>::Branded; $size]; - } - unsafe impl<'a, Id, T> $crate::GcErase<'a, Id> for [T; $size] - where Id: CollectorId, T: GcErase<'a, Id>, - >::Erased: Sized { - type Erased = [>::Erased; $size]; + unsafe_gc_impl! { + target => [T; $size], + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => T::NEEDS_DROP, + branded_type => [>::Branded; $size], + erased_type => [>::Erased; $size], + visit => |self, visitor| { + visitor.#visit_func(#b*self as #b [T]) + }, } }; { $($size:tt),* } => ($(trace_array!($size);)*) @@ -144,125 +145,108 @@ trace_array! { 24, 32, 48, 64, 100, 128, 256, 512, 1024, 2048, 4096 } -/// Implements tracing for references. -/// -/// The underlying data must support `TraceImmutable` since we -/// only have an immutable reference. -unsafe impl<'a, T: TraceImmutable> Trace for &'a T { - const NEEDS_TRACE: bool = T::NEEDS_TRACE; - #[inline(always)] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { - visitor.visit_immutable::(*self) - } -} -unsafe impl<'a, T: TraceImmutable> TraceImmutable for &'a T { - #[inline(always)] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), V::Err> { - visitor.visit_immutable::(*self) - } -} -unsafe impl<'a, T: NullTrace> NullTrace for &'a T {} -unsafe impl<'a, T: GcSafe + TraceImmutable> GcSafe for &'a T { - const NEEDS_DROP: bool = false; // References are safe :) -} -/// TODO: Right now we require `NullTrace` -/// -/// This is unfortunately required by our bounds, since we don't know -/// that `T::Branded` lives for &'a making `&'a T::Branded` invalid -/// as far as the compiler is concerned. -/// -/// Therefore the only solution is to preserve `&'a T` as-is, -/// which is only safe if `T: NullTrace` -unsafe impl<'a, 'new_gc, Id, T> GcRebrand<'new_gc, Id> for &'a T - where Id: CollectorId, T: NullTrace, 'a: 'new_gc { - type Branded = &'a T; -} -/// See impl of `GcRebrand` for why we require `T: NullTrace` -unsafe impl<'a, Id, T> GcErase<'a, Id> for &'a T - where Id: CollectorId, T: NullTrace { - type Erased = &'a T; -} - -/// Implements tracing for mutable references. -unsafe impl<'a, T: Trace> Trace for &'a mut T { - const NEEDS_TRACE: bool = T::NEEDS_TRACE; - #[inline(always)] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { - visitor.visit::(*self) - } -} -unsafe impl<'a, T: TraceImmutable> TraceImmutable for &'a mut T { - #[inline(always)] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), V::Err> { +/* + * Implements tracing for references. + * + * The underlying data must support `TraceImmutable` since we + * only have an immutable reference. + */ +unsafe_gc_impl! { + target => &'a T, + params => ['a, T: 'a], + bounds => { + Trace => { where T: TraceImmutable }, + TraceImmutable => { where T: TraceImmutable }, + /* + * TODO: Right now we require `NullTrace` + * + * This is unfortunately required by our bounds, since we don't know + * that `T::Branded` lives for &'a making `&'a T::Branded` invalid + * as far as the compiler is concerned. + * + * Therefore the only solution is to preserve `&'a T` as-is, + * which is only safe if `T: NullTrace` + */ + GcRebrand => { where T: NullTrace, 'a: 'new_gc }, + GcErase => { where T: NullTrace, 'a: 'min } + }, + branded_type => &'a T, + erased_type => &'a T, + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => false, // We never need to be dropped + visit => |self, visitor| { visitor.visit_immutable::(&**self) } } -unsafe impl<'a, T: NullTrace> NullTrace for &'a mut T {} -unsafe impl<'a, T: GcSafe> GcSafe for &'a mut T { - const NEEDS_DROP: bool = false; // References are Copy -} -/// TODO: We currently require NullTrace for `T` -unsafe impl<'a, 'new_gc, Id, T> GcRebrand<'new_gc, Id> for &'a mut T - where Id: CollectorId, T: NullTrace, 'a: 'new_gc { - type Branded = &'a mut T; -} -/// TODO: We currently require NullTrace for `T` -unsafe impl<'a, Id, T> GcErase<'a, Id> for &'a mut T - where Id: CollectorId, T: NullTrace { - type Erased = &'a mut T; -} -/// Implements tracing for slices, by tracing all the objects they refer to. -unsafe impl Trace for [T] { - const NEEDS_TRACE: bool = T::NEEDS_TRACE ; - #[inline] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { - if !T::NEEDS_TRACE { return Ok(()) }; - for value in self { - visitor.visit(value)?; - } - Ok(()) - } -} -unsafe impl TraceImmutable for [T] { - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), ::Err> { - if !T::NEEDS_TRACE { return Ok(()) }; - for value in self { - visitor.visit_immutable(value)?; - } - Ok(()) +/* + * Implements tracing for mutable references. + * + * See also: Implementation for `&'a T` + */ +unsafe_gc_impl! { + target => &'a mut T, + params => ['a, T: 'a], + bounds => { + /* + * TODO: Right now we require `NullTrace` + * + * This is the same reasoning as the requirements for `&'a T`. + * See their comments for details..... + */ + GcRebrand => { where T: NullTrace, 'a: 'new_gc }, + GcErase => { where T: NullTrace, 'a: 'min } + }, + branded_type => &'a mut T, + erased_type => &'a mut T, + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => false, // Although not `Copy`, mut references don't need to be dropped + visit => |self, visitor| { + visitor.#visit_func::(#b **self) } } -unsafe impl NullTrace for [T] {} -unsafe impl GcSafe for [T] { - const NEEDS_DROP: bool = core::mem::needs_drop::(); -} -unsafe impl Trace for Option { - const NEEDS_TRACE: bool = T::NEEDS_TRACE; - #[inline] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { - match *self { - None => Ok(()), - Some(ref mut value) => visitor.visit(value), + +/* + * Implements tracing for slices, by tracing all the objects they refer to. + * + * NOTE: We cannot currently implement `GcRebrand` + `GcErase` + * because we are unsized :(( + */ +unsafe_gc_impl! { + target => [T], + params => [T], + bounds => { + GcRebrand => never, + GcErase => never + }, + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => ::core::mem::needs_drop::(), + visit => |self, visitor| { + for val in self.#iter() { + visitor.#visit_func(val)?; } + Ok(()) } } -unsafe impl TraceImmutable for Option { - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), ::Err> { + +unsafe_gc_impl! { + target => Option, + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => T::NEEDS_DROP, + visit => |self, visitor| { match *self { None => Ok(()), - Some(ref value) => visitor.visit_immutable(value), + Some(ref #mutability value) => visitor.#visit_func::(value), } - } -} -unsafe impl NullTrace for Option {} -unsafe impl GcSafe for Option { - const NEEDS_DROP: bool = T::NEEDS_DROP; + }, } unsafe impl<'gc, OwningRef, V> GcDirectBarrier<'gc, OwningRef> for Option where V: GcDirectBarrier<'gc, OwningRef> { @@ -270,7 +254,10 @@ unsafe impl<'gc, OwningRef, V> GcDirectBarrier<'gc, OwningRef> for Option unsafe fn write_barrier(&self, owner: &OwningRef, start_offset: usize) { // Implementing direct write is safe because we store our value inline match *self { - None => { /* Nothing to trigger the barrier for :) */ }, + None => { + /* Nothing to trigger the barrier for :) */ + // TODO: Is this unreachable code? + }, Some(ref value) => { /* * We must manually compute the offset @@ -284,13 +271,42 @@ unsafe impl<'gc, OwningRef, V> GcDirectBarrier<'gc, OwningRef> for Option } } } -unsafe_gc_brand!(Option, T); -// We can trace `Wrapping` by simply tracing its interior -unsafe_trace_deref!(Wrapping, T; immut = false; |wrapping| &mut wrapping.0); -unsafe impl TraceImmutable for Wrapping { - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), V::Err> { - visitor.visit_immutable(&self.0) +unsafe_gc_impl! { + target => Wrapping, + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => T::NEEDS_DROP, + visit => |self, visitor| { + // We can trace `Wrapping` by simply tracing its interior + visitor.#visit_func(#b self.0) + } +} + +#[cfg(test)] +mod test { + use crate::dummy_impl::{DummyCollectorId, Gc}; + use zerogc_derive::Trace; + use crate::prelude::*; + #[test] + fn test_null_trace() { + assert!(! as Trace>::NEEDS_TRACE); + assert!(! as Trace>::NEEDS_TRACE) + } + #[derive(Trace)] + #[zerogc(collector_id(DummyCollectorId))] + struct Rec<'gc> { + inner: Gc<'gc, Rec<'gc>>, + inner_tuple: (Gc<'gc, Rec<'gc>>, Gc<'gc, Option>), + inner_opt: Option>>, + inner_opt_tuple: Option<(Gc<'gc, Rec<'gc>>, Gc<'gc, char>)>, + } + #[test] + fn test_trace<'gc>() { + assert!(> as Trace>::NEEDS_TRACE); + assert!(, char)> as Trace>::NEEDS_TRACE); + assert!( as Trace>::NEEDS_TRACE); + assert!(> as Trace>::NEEDS_TRACE); } } \ No newline at end of file diff --git a/src/manually_traced/indexmap.rs b/src/manually_traced/indexmap.rs index 3993aae..2eae9c4 100644 --- a/src/manually_traced/indexmap.rs +++ b/src/manually_traced/indexmap.rs @@ -1,93 +1,35 @@ use indexmap::{IndexMap, IndexSet}; use crate::prelude::*; -use std::hash::Hash; -use crate::CollectorId; -// Maps - -unsafe impl TraceImmutable for IndexMap - where K: TraceImmutable + Eq + Hash, V: TraceImmutable { - fn visit_immutable(&self, visitor: &mut Visit) -> Result<(), Visit::Err> { - if !Self::NEEDS_TRACE { return Ok(()); }; - for (key, value) in self.iter() { - visitor.visit_immutable(key)?; - visitor.visit_immutable(value)?; - } - Ok(()) - } -} -unsafe impl NullTrace for IndexMap - where K: NullTrace + Eq + Hash, V: NullTrace {} -unsafe impl Trace for IndexMap - where K: TraceImmutable + Eq + Hash, V: Trace { - const NEEDS_TRACE: bool = K::NEEDS_TRACE || V::NEEDS_TRACE; - - fn visit(&mut self, visitor: &mut Visit) -> Result<(), Visit::Err> { - if !Self::NEEDS_TRACE { return Ok(()); }; - for (key, value) in self.iter_mut() { - visitor.visit_immutable(key)?; - visitor.visit(value)?; +use zerogc_derive::unsafe_gc_impl; + +unsafe_gc_impl! { + target => IndexMap, + params => [K: TraceImmutable, V], + null_trace => { where K: NullTrace, V: NullTrace }, + NEEDS_TRACE => K::NEEDS_TRACE || V::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + for (key, value) in self.#iter() { + visitor.visit_immutable::(key)?; + visitor.#visit_func::(value)?; } Ok(()) } } -unsafe impl GcSafe for IndexMap where - K: GcSafe + TraceImmutable + Eq + Hash, V: GcSafe { - const NEEDS_DROP: bool = true; // IndexMap has internal memory -} -unsafe impl<'new_gc, Id, K, V> GcRebrand<'new_gc, Id> for IndexMap - where Id: CollectorId, K: TraceImmutable + Eq + Hash + GcRebrand<'new_gc, Id>, - V: GcRebrand<'new_gc, Id>, - >::Branded: TraceImmutable + Eq + Hash + Sized, - >::Branded: Sized { - type Branded = IndexMap< - >::Branded, - >::Branded - >; -} -unsafe impl<'a, Id, K, V> GcErase<'a, Id> for IndexMap - where Id: CollectorId, K: TraceImmutable + Eq + Hash + GcErase<'a, Id>, - V: GcErase<'a, Id>, - >::Erased: TraceImmutable + Eq + Hash + Sized, - >::Erased: Sized { - type Erased = IndexMap< - >::Erased, - >::Erased - >; -} - -// Sets - -unsafe impl TraceImmutable for IndexSet - where T: TraceImmutable + Eq + Hash { - fn visit_immutable(&self, visitor: &mut Visit) -> Result<(), Visit::Err> { - if !Self::NEEDS_TRACE { return Ok(()); }; - for element in self.iter() { - visitor.visit_immutable(element)?; - } - Ok(()) - } -} -unsafe impl Trace for IndexSet - where V: TraceImmutable + Eq + Hash { - const NEEDS_TRACE: bool = V::NEEDS_TRACE; - fn visit(&mut self, visitor: &mut Visit) -> Result<(), Visit::Err> { - for value in self.iter() { - visitor.visit_immutable(value)?; +unsafe_gc_impl! { + target => IndexSet, + params => [T: TraceImmutable], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + for val in self.iter() { + visitor.visit_immutable::(val)?; } Ok(()) } } -unsafe impl<'new_gc, Id, V> GcRebrand<'new_gc, Id> for IndexSet - where Id: CollectorId, V: GcRebrand<'new_gc, Id> + TraceImmutable + Eq + Hash, - >::Branded: TraceImmutable + Eq + Hash, { - type Branded = IndexSet<>::Branded>; -} -unsafe impl<'a, Id, V> GcErase<'a, Id> for IndexSet - where Id: CollectorId, V: GcErase<'a, Id> + TraceImmutable + Eq + Hash, - >::Erased: TraceImmutable + Eq + Hash, { - type Erased = IndexSet<>::Erased>; -} \ No newline at end of file diff --git a/src/manually_traced/mod.rs b/src/manually_traced/mod.rs index 2159869..cc1e6bc 100644 --- a/src/manually_traced/mod.rs +++ b/src/manually_traced/mod.rs @@ -54,19 +54,19 @@ #[macro_export] macro_rules! unsafe_trace_lock { ($target:ident, target = $target_type:ident; |$get_mut:ident| $get_mut_expr:expr, |$lock:ident| $acquire_guard:expr) => { - unsafe_gc_brand!($target, $target_type); - unsafe impl<$target_type: Trace> Trace for $target<$target_type> { - const NEEDS_TRACE: bool = T::NEEDS_TRACE; - #[inline] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { + unsafe_impl_gc!( + target => $target<$target_type>, + params = [$target_type], + null_trace => { where $target_type: NullTrace }, + NEEDS_TRACE => true, + NEEDS_DROP => $target_type::NEEDS_DROP /* if our inner type needs a drop */ + || core::mem::needs_drop::<$target<()>>; // Or we have unconditional drop (std-mutex) + trace_mut => |self, visitor| { let $get_mut = self; let value: &mut $target_type = $get_mut_expr; - visitor.visit(value) - } - } - unsafe impl<$target_type: Trace> $crate::TraceImmutable for $target<$target_type> { - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), V::Err> { + visitor.visit::<$target_type>(value) + }, + trace_immutable => |self, visitor| { if !Self::NEEDS_TRACE { return Ok(()) }; // We can immutably visit a lock by acquiring it let $lock = self; @@ -75,207 +75,10 @@ macro_rules! unsafe_trace_lock { let guard_value = &mut *guard; visitor.visit(guard_value) } - } - unsafe impl<$target_type: $crate::NullTrace> $crate::NullTrace for $target<$target_type> {} - unsafe impl<$target_type: $crate::GcSafe> $crate::GcSafe for $target<$target_type> {} + ); }; } -/// Unsafely implement `GarbageCollected` for the specified type, -/// by converting the specified wrapper type in order to trace the underlying objects. -/// -/// In addition to smart pointers, it can be used for newtype structs like `Wrapping` or `NonZero`, -/// as long as the types can properly traced by just reading their underlying value. -/// However, it's slightly easier to invoke if you implement `Deref`, -/// since the expression is inferred based on the target type. -/// However, you will always need to explicitly indicate the dereferencing output type, -/// in order to be eplicit about the intended operation. -/// -/// This macro is usually only useful for unsafe wrappers like `NonZero` and smart pointers like `Rc` and `Box` who use raw pointers internally, -/// since raw pointers can't be automatically traced without additional type information. -/// Otherwise, it's best to use an automatically derived implementation since that's always safe. -/// However, using this macro is always better than a manual implementation, since it makes your intent clearer. -/// -/// ## Usage -/// ````no_test -/// // Easy to use for wrappers that `Deref` to their type parameter -/// unsafe_trace_deref!(Box, target = T); -/// unsafe_trace_deref!(Rc, target = T); -/// unsafe_trace_deref!(Arc, target = T); -/// // The `Deref::Output` type can be declared separately from the type paremters -/// unsafe_trace_deref!(Vec, target = { [T] }, T); -/// /* -/// * You can use an arbitrary expression to acquire the underlying value, -/// * and the type doesn't need to implement `Deref` at all -/// */ -/// unsafe_trace_deref!(Cell, T; |cell| cell.get()); -/// unsafe_trace_deref!(Wrapping, T; |wrapping| &wrapping.0); -/// -/// // wrappers shouldn't need tracing if their innards don't -/// assert!(! as Trace>::NEEDS_TRACE); -/// assert!(! as Trace>::NEEDS_TRACE); -/// // Box needs to be dropped -/// assert!( as GcSafe>::NEEDS_DROP); -/// // but Cell doesn't need to be dropped -/// assert!( as GcSafe>::NEEDS_DROP); -/// -/// // if the inside needs tracing, the outside does -/// assert!(> as Trace>::NEEDS_TRACE); -/// assert!(> as Trace>::NEEDS_TRACE); -/// ```` -/// -/// ## Safety -/// Always prefer automatically derived implementations where possible, -/// since they're just as fast and can never cause undefined behavior. -/// This is basically an _unsafe automatically derived_ implementation, -/// to be used only when a safe automatically derived implementation isn't possible (like with `Vec`). -/// -/// Undefined behavior if there could be garbage collected objects that are not reachable via iteration, -/// since the macro only traces objects it can iterate over, -/// and the garbage collector will free objects that haven't been traced. -/// This usually isn't the case with collections and would be somewhat rare, -/// but it's still a possibility that causes the macro to be unsafe. -/// -/// This delegates to `unsafe_gc_brand` to provide the [GcRebrand] and [GcErase] implementation, -/// so that could also trigger undefined behavior. -#[macro_export] -macro_rules! unsafe_trace_deref { - ($target:ident, target = $target_type:ident) => { - unsafe_trace_deref!($target, target = $target_type; $target_type); - }; - ($target:ident, target = $target_type:ident; $($param:ident),*) => { - unsafe_trace_deref!($target, target = { $target_type }; $($param),*); - }; - ($target:ident, target = { $target_type:ty }; $($param:ident),*) => { - unsafe impl<$($param: TraceImmutable),*> $crate::TraceImmutable for $target<$($param),*> { - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), V::Err> { - let extracted: &$target_type = &**self; - visitor.visit_immutable(extracted) - } - } - unsafe_trace_deref!($target, $($param),*; immut = false; |value| { - // I wish we had type ascription -_- - let dereferenced: &mut $target_type = &mut **value; - dereferenced - }); - }; - ($target:ident, $($param:ident),*; immut = required; |$value:ident| $extract:expr) => { - unsafe_gc_brand!($target, immut = required; $($param),*); - unsafe impl<$($param),*> Trace for $target<$($param),*> - where $($param: TraceImmutable),* { - - const NEEDS_TRACE: bool = $($param::NEEDS_TRACE || )* false; - #[inline] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { - let extracted = { - let $value = self; - $extract - }; - visitor.visit_immutable(extracted) - } - } - unsafe impl<$($param),*> TraceImmutable for $target<$($param),*> - where $($param: TraceImmutable),* { - - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), V::Err> { - let extracted = { - let $value = self; - $extract - }; - visitor.visit_immutable(extracted) - } - } - unsafe impl<$($param: NullTrace),*> NullTrace for $target<$($param),*> {} - /// We trust ourselves to not do anything bad as long as our paramaters don't - unsafe impl<$($param),*> GcSafe for $target<$($param),*> - where $($param: GcSafe + TraceImmutable),* { - const NEEDS_DROP: bool = core::mem::needs_drop::(); - } - }; - ($target:ident, $($param:ident),*; immut = false; |$value:ident| $extract:expr) => { - unsafe_gc_brand!($target, $($param),*); - unsafe impl<$($param),*> Trace for $target<$($param),*> - where $($param: Trace),* { - - const NEEDS_TRACE: bool = $($param::NEEDS_TRACE || )* false; - #[inline] - fn visit(&mut self, visitor: &mut V) -> Result<(), V::Err> { - let extracted = { - let $value = self; - $extract - }; - visitor.visit(extracted) - } - } - unsafe impl<$($param: NullTrace),*> NullTrace for $target<$($param),*> {} - /// We trust ourselves to not do anything bad as long as our paramaters don't - unsafe impl<$($param),*> GcSafe for $target<$($param),*> - where $($param: GcSafe),* { - const NEEDS_DROP: bool = core::mem::needs_drop::(); - } - }; -} - - -/// Unsafely implement `ImmutableTrace` for the specified iterable type, -/// by iterating over the type to trace all objects. -/// -/// You still have to implement the regular `Trace` and `GcSafe` traits by hand. -/// -/// This macro is only useful for unsafe collections like `Vec` and `HashMap` who use raw pointers internally, -/// since raw pointers can't have automatically derived tracing implementations. -/// Otherwise, it's best to use an automatically derived implementation since that's always safe. -/// In order to prevent ambiguity, this always requires the type of the element being traced. -/// -/// ## Usage -/// ````no_test -/// unsafe_trace_iterable!(Vec, element = T); -/// unsafe_trace_iterable!(HashMap, element = { (&K, &V) }; K, V); -/// unsafe_trace_iterable!(HashSet, element = T); -/// -/// assert!(! as Trace>::NEEDS_TRACE); -/// assert!(> as Trace>::NEEDS_TRACE); -/// assert!( as GcSafe>::NEEDS_DROP); -/// ```` -/// -/// ## Safety -/// Always prefer automatically derived implementations where possible, -/// since they're just as fast and can never cause undefined behavior. -/// This is basically an _unsafe automatically derived_ implementation, -/// to be used only when a safe automatically derived implementation isn't possible (like with `Vec`). -/// -/// Undefined behavior if there could be garbage collected objects that are not reachable via iteration, -/// since the macro only traces objects it can iterate over, -/// and the garbage collector will free objects that haven't been traced. -/// This usually isn't the case with collections and would be somewhat rare, -/// but it's still a possibility that causes the macro to be unsafe. -/// -/// -/// This delegates to `unsafe_gc_brand!` to provide the [GcRebrand] and [GcErase] implementation, -/// so that could also trigger undefined behavior. -#[macro_export] -macro_rules! unsafe_immutable_trace_iterable { - ($target:ident, element = $element_type:ident) => { - unsafe_trace_iterable!($target, element = &$element_type; $element_type); - }; - ($target:ident<$($param:ident),*>; element = { $element_type:ty }) => { - unsafe impl<$($param),*> TraceImmutable for $target<$($param),*> - where $($param: TraceImmutable),* { - fn visit_immutable(&self, visitor: &mut Visit) -> Result<(), Visit::Err> { - if !Self::NEEDS_TRACE { return Ok(()) }; - let iter = IntoIterator::into_iter(self); - for element in iter { - let element: $element_type = element; - visitor.visit_immutable(&element)?; - } - Ok(()) - } - } - unsafe impl<$($param: $crate::NullTrace),*> NullTrace for $target<$($param),*> {} - }; -} /// Unsafely implement `GarbageCollected` for the specified type, @@ -302,94 +105,29 @@ macro_rules! unsafe_immutable_trace_iterable { #[macro_export] macro_rules! unsafe_trace_primitive { ($target:ty) => { - unsafe_gc_brand!($target); - unsafe impl Trace for $target { - const NEEDS_TRACE: bool = false; - #[inline(always)] // This method does nothing and is always a win to inline - fn visit(&mut self, _visitor: &mut V) -> Result<(), V::Err> { - Ok(()) - } - } - unsafe impl $crate::TraceImmutable for $target { - #[inline(always)] - fn visit_immutable(&self, _visitor: &mut V) -> Result<(), V::Err> { - Ok(()) - } - } - unsafe impl $crate::NullTrace for $target {} - /// No drop/custom behavior -> GcSafe - unsafe impl GcSafe for $target { - const NEEDS_DROP: bool = core::mem::needs_drop::<$target>(); + unsafe_gc_impl! { + target => $target, + params => [], + null_trace => always, + NEEDS_TRACE => false, + NEEDS_DROP => core::mem::needs_drop::<$target>(), + visit => |self, visitor| { /* nop */ Ok(()) } } unsafe impl<'gc, OwningRef> $crate::GcDirectBarrier<'gc, OwningRef> for $target { #[inline(always)] unsafe fn write_barrier( &self, _owner: &OwningRef, _field_offset: usize, ) { + /* + * TODO: We don't have any GC fields, + * so what does it mean to have a write barrier? + */ /* NOP */ } } }; } - -/// Unsafely assume that the generic implementation of [GcRebrand] and [GcErase] is valid, -/// if and only if it's valid for the generic lifetime and type parameters. -/// -/// Always _prefer automatically derived implementations where possible_, -/// since they can never cause undefined behavior. -/// This macro is only necessary if you have raw pointers internally, -/// which can't have automatically derived safe implementations. -/// This is basically an _unsafe automatically derived_ implementation, -/// to be used only when a safe automatically derived implementation isn't possible (like with `Vec`). -/// -/// This macro takes a varying number of parameters referring to the type's generic parameters, -/// which are all properly bounded and required to implement [GcRebrand] and [GcErase] correctly. -/// -/// This macro can only cause undefined behavior if there are garbage collected pointers -/// that aren't included in the type parameter. -/// For example including `Gc` would be completely undefined behavior, -/// since we'd blindly erase its lifetime. -/// -/// However, generally this macro provides the correct implementation -/// for straighrtforward wrapper/collection types. -/// Currently the only exception is when you have garbage collected lifetimes like `Gc`. -#[macro_export] -macro_rules! unsafe_gc_brand { - ($target:tt) => { - unsafe impl<'new_gc, Id: $crate::CollectorId> $crate::GcRebrand<'new_gc, Id> for $target { - type Branded = Self; - } - unsafe impl<'a, Id: $crate::CollectorId> $crate::GcErase<'a, Id> for $target { - type Erased = Self; - } - }; - ($target:ident, $($param:ident),+) => { - unsafe impl<'new_gc, Id, $($param),*> $crate::GcRebrand<'new_gc, Id> for $target<$($param),*> - where Id: $crate::CollectorId, $($param: $crate::GcRebrand<'new_gc, Id>,)* - $(<$param as $crate::GcRebrand<'new_gc, Id>>::Branded: Trace,)* { - type Branded = $target<$(<$param as $crate::GcRebrand<'new_gc, Id>>::Branded),*>; - } - unsafe impl<'a, Id, $($param),*> $crate::GcErase<'a, Id> for $target<$($param),*> - where Id: $crate::CollectorId, $($param: $crate::GcErase<'a, Id>,)* - $(<$param as $crate::GcErase<'a, Id>>::Erased: Trace,)* { - type Erased = $target<$(<$param as $crate::GcErase<'a, Id>>::Erased),*>; - } - }; - ($target:tt, immut = required; $($param:ident),+) => { - unsafe impl<'new_gc, Id, $($param),*> $crate::GcRebrand<'new_gc, Id> for $target<$($param),*> - where Id: $crate::CollectorId, $($param: $crate::GcRebrand<'new_gc, Id> + TraceImmutable,)* - $(<$param as $crate::GcRebrand<'new_gc, Id>>::Branded: TraceImmutable,)* { - type Branded = $target<$(<$param as $crate::GcRebrand<'new_gc, Id>>::Branded),*>; - } - unsafe impl<'a, Id, $($param),*> $crate::GcErase<'a, Id> for $target<$($param),*> - where Id: $crate::CollectorId, $($param: $crate::GcErase<'a, Id> + TraceImmutable,)* - $(<$param as $crate::GcErase<'a, Id>>::Erased: TraceImmutable,)* { - type Erased = $target<$(<$param as $crate::GcErase<'a, Id>>::Erased),*>; - } - } -} - mod core; #[cfg(any(feature = "alloc", feature = "std"))] mod stdalloc; diff --git a/src/manually_traced/stdalloc.rs b/src/manually_traced/stdalloc.rs index 78be9d9..c654f9d 100644 --- a/src/manually_traced/stdalloc.rs +++ b/src/manually_traced/stdalloc.rs @@ -10,11 +10,51 @@ use alloc::string::String; use crate::prelude::*; -// NOTE: Delegate to slice to avoid code duplication -unsafe_trace_deref!(Vec, target = { [T] }; T); -unsafe_trace_deref!(Box, target = T); +use zerogc_derive::unsafe_gc_impl; + +unsafe_gc_impl! { + target => Vec, + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + // Delegate to slice + visitor.#visit_func::<[T]>(#b**self as #b [T]) + } +} +unsafe_gc_impl! { + target => Box, + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + visitor.#visit_func::(#b **self) + } +} // We can only trace `Rc` and `Arc` if the inner type implements `TraceImmutable` -unsafe_trace_deref!(Rc, T; immut = required; |rc| &**rc); -unsafe_trace_deref!(Arc, T; immut = required; |arc| &**arc); +unsafe_gc_impl! { + target => Rc, + params => [T: TraceImmutable], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + // We must always visit immutable, since we have shared references + visitor.visit_immutable::(&**self) + } +} +unsafe_gc_impl! { + target => Arc, + params => [T: TraceImmutable], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + // We must always visit immutable, since we have shared references + visitor.visit_immutable::(&**self) + } +} // String is a primitive with no internal references unsafe_trace_primitive!(String); diff --git a/src/manually_traced/stdlib.rs b/src/manually_traced/stdlib.rs index 3f9df70..f5c08e5 100644 --- a/src/manually_traced/stdlib.rs +++ b/src/manually_traced/stdlib.rs @@ -3,57 +3,39 @@ //! Types that are in `libcore` and are `#![no_std]` should go in the core module, //! but anything that requires the rest of the stdlib (including collections and allocations), //! should go in this module. -use crate::prelude::*; - use std::collections::{HashMap, HashSet}; -use crate::CollectorId; -unsafe_immutable_trace_iterable!(HashMap; element = { (&K, &V) }); -unsafe impl Trace for HashMap { - const NEEDS_TRACE: bool = K::NEEDS_TRACE || V::NEEDS_TRACE; +use zerogc_derive::unsafe_gc_impl; + +use crate::prelude::*; + - fn visit(&mut self, visitor: &mut Visit) -> Result<(), Visit::Err> { - if !Self::NEEDS_TRACE { return Ok(()); }; - for (key, value) in self.iter_mut() { - visitor.visit_immutable(key)?; - visitor.visit(value)?; +unsafe_gc_impl! { + target => HashMap, + params => [K: TraceImmutable, V], + null_trace => { where K: NullTrace, V: NullTrace }, + NEEDS_TRACE => K::NEEDS_TRACE || V::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + for (key, value) in self.#iter() { + visitor.visit_immutable::(key)?; + visitor.#visit_func::(value)?; } Ok(()) } } -unsafe impl GcSafe for HashMap { - const NEEDS_DROP: bool = true; // HashMap has internal memory -} -unsafe impl<'new_gc, Id, K, V> GcRebrand<'new_gc, Id> for HashMap - where Id: CollectorId, K: TraceImmutable + GcRebrand<'new_gc, Id>, - V: GcRebrand<'new_gc, Id>, - >::Branded: TraceImmutable + Sized, - >::Branded: Sized { - type Branded = HashMap< - >::Branded, - >::Branded - >; -} -unsafe impl<'a, Id, K, V> GcErase<'a, Id> for HashMap - where Id: CollectorId, K: TraceImmutable + GcErase<'a, Id>, - V: GcErase<'a, Id>, - >::Erased: TraceImmutable + Sized, - >::Erased: Sized { - type Erased = HashMap< - >::Erased, - >::Erased - >; -} -unsafe_immutable_trace_iterable!(HashSet; element = { &V }); -unsafe impl Trace for HashSet { - const NEEDS_TRACE: bool = V::NEEDS_TRACE; - fn visit(&mut self, visitor: &mut Visit) -> Result<(), Visit::Err> { - if !Self::NEEDS_TRACE { return Ok(()); }; - for value in self.iter() { - visitor.visit_immutable(value)?; + +unsafe_gc_impl! { + target => HashSet, + params => [T: TraceImmutable], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |self, visitor| { + for val in self.iter() { + visitor.visit_immutable::(val)?; } Ok(()) } } -unsafe_gc_brand!(HashSet, immut = required; V); diff --git a/src/prelude.rs b/src/prelude.rs index b66207b..ce8c861 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -23,6 +23,8 @@ pub use crate::{ }; // Hack traits pub use crate::{GcBindHandle}; +// TODO: Should this trait be auto-imported??? +pub use crate::CollectorId; // Utils pub use crate::AssumeNotTraced; pub use crate::cell::GcCell; \ No newline at end of file