From 043053c9bda46640a6e9908689fbfa51630ef3d6 Mon Sep 17 00:00:00 2001 From: Techcable Date: Wed, 20 Jan 2021 23:24:15 -0700 Subject: [PATCH 1/7] [derive] Start to move `manually_traced` macros over to proc_derive Procedural macros are more flexible and will allow us to use a single macro for all our manually traced code. It should also help downstream crates start to utilize this. --- Cargo.toml | 2 + libs/derive/Cargo.toml | 2 +- libs/derive/src/lib.rs | 18 + libs/derive/src/macros.rs | 757 ++++++++++++++++++++++++++++++++ src/manually_traced/core.rs | 169 +++---- src/manually_traced/mod.rs | 164 +------ src/manually_traced/stdalloc.rs | 22 + 7 files changed, 873 insertions(+), 261 deletions(-) create mode 100644 libs/derive/src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index 0693855..b4272cc 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 = { 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..b802c0a 100644 --- a/libs/derive/src/lib.rs +++ b/libs/derive/src/lib.rs @@ -12,6 +12,8 @@ use std::collections::HashSet; use std::fmt::Display; use std::io::Write; +mod macros; + struct MutableFieldOpts { public: bool } @@ -334,6 +336,22 @@ impl Parse for TypeAttrs { } } +#[proc_macro] +pub fn unsafe_gc_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let cloned_impl = input.clone(); + let parsed = parse_macro_input!(input as macros::MacroInput); + let res = parsed.expand_output() + .unwrap_or_else(|e| e.to_compile_error()); + debug_derive( + "unsafe_gc_impl!", + &"", + &format_args!("#[unsafe_gc_impl {{ {} }}", cloned_impl), + &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); diff --git a/libs/derive/src/macros.rs b/libs/derive/src/macros.rs new file mode 100644 index 0000000..3dfd4ff --- /dev/null +++ b/libs/derive/src/macros.rs @@ -0,0 +1,757 @@ +//! Procedural macros for implementing `GcType` + +/* + * The main macro here is `unsafe_impl_gc` + */ + +use proc_macro2::{Ident, TokenStream, TokenTree}; +use syn::{ + GenericParam, WhereClause, Type, Expr, Error, Token, braced, bracketed, + ExprClosure, Generics, TypeParamBound, WherePredicate, PredicateType, + parse_quote +}; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; + +use quote::{quote, quote_spanned}; + +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() + } +} + +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 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, ty_generics, where_clause) = generics.split_for_impl(); + quote! { + unsafe impl #impl_generics NullTrace for #target_type #ty_generics + #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 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, ty_generics, where_clause) = generics.split_for_impl(); + let trait_name = if mutable { quote!(Trace) } else { quote!(TraceImmutable) }; + let visit_method_name = if mutable { quote!(visit) } else { quote!(visit_immutable) }; + Ok(Some(quote! { + unsafe impl #impl_generics #trait_name for #target_type #ty_generics #where_clause { + #[inline] // TODO: Should this be unconditional? + fn #visit_method_name(&mut self, visitor: &mut V) -> Result<(), V::Err> { + #visit_impl + } + } + })) + } + fn expand_gcsafe_impl(&self) -> Option { + 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 (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + Some(quote! { + unsafe impl #impl_generics GcSafe for #target_type #ty_generics #where_clause {} + }) + } + fn expand_brand_impl(&self, rebrand: bool /* true => rebrand, false => erase */) -> Option { + 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 None; + } + let target_type = &self.target_type; + let mut generics = self.basic_generics(); + for param in &self.params { + match param { + GenericParam::Type(ref tp) => { + let type_name = &tp.ident; + generics.make_where_clause() + .predicates.push(WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: if rebrand { + parse_quote!(<#type_name as GcRebrand<'new_gc, Id>>::Branded) + } else { + parse_quote!(<#type_name as GcErase<'new_gc, Id>>::Erased) + }, + colon_token: Default::default(), + bounds: tp.bounds.clone() + })) + } + _ => {} + } + } + generics.make_where_clause().predicates + .extend(create_clause_with_default( + &requirements, &self.params, + if rebrand { + vec![parse_quote!(GcRebrand<'new_gc, Id>)] + } else { + vec![parse_quote!(GcErase<'min, Id>)] + } + ).unwrap_or_else(empty_clause).predicates); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + Some(quote! { + unsafe impl #impl_generics GcSafe for #target_type #ty_generics #where_clause {} + }) + } +} + +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] opt => branded; + "erased" [Type] opt => erased; + "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.0.span(), + "Cannot specifiy specific closure (trace_mut/trace_immutable) in addition to `visit`" + )) + } + VisitImpl::Generic { generic_impl: visit_closure.0 } + } 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.0.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: trace_closure.0, immutable: trace_immut_closure.map(|closure| closure.0) } + }; + Ok(MacroInput { + target_type: res.target_type, + params: res.params.0, + bounds, + options: StandardOptions { + null_trace: res.null_trace, + branded: res.branded, + erased: res.erased, + needs_trace: res.needs_trace, + needs_drop: res.needs_drop + }, + visit: visit_impl + }) + } +} + +pub struct VisitClosure(Expr); +impl Parse for VisitClosure { + fn parse(input: ParseStream) -> syn::Result { + let closure = match input.parse::()? { + Expr::Closure(closure) => closure, + e => return Err(Error::new(e.span(), "Expected a closure")) + }; + let ExprClosure { + ref attrs, + ref asyncness, + ref movability, + ref capture, + or1_token: _, + ref inputs, + or2_token: _, + ref output, + ref body, .. + } = closure; + if !attrs.is_empty() { + return Err(Error::new( + closure.span(), + "Attributes are forbidden on visit closure" + )); + } + if asyncness.is_some() || movability.is_some() || capture.is_some() { + return Err(Error::new( + closure.span(), + "Modifiers are forbidden on visit closure" + )); + } + if let ::syn::ReturnType::Default = output { + return Err(Error::new( + output.span(), + "Explicit return types are forbidden on visit closures" + )) + } + if inputs.len() != 2 { + return Err(Error::new( + inputs.span(), + "Expected exactly two arguments to visit closure" + )) + } + fn check_input(pat: &::syn::Pat, expected_name: &str,) -> Result<(), Error> { + match pat { + ::syn::Pat::Ident( + ::syn::PatIdent { + ref attrs, + ref by_ref, + ref mutability, + ref ident, + ref subpat + } + ) => { + if ident != expected_name { + return Err(Error::new( + ident.span(), format!( + "Expected argument `{}` for closure", + expected_name + ) + )) + } + if !attrs.is_empty() { + return Err(Error::new( + pat.span(), + format!( + "Attributes are forbidden on closure arg `{}`", + expected_name + ) + )) + } + if let Some(by) = by_ref { + return Err(Error::new( + by.span(), + format!( + "Explicit mutability is forbidden on closure arg `{}`", + expected_name + ) + )) + } + if let Some(mutability) = mutability { + return Err(Error::new( + mutability.span(), + format!( + "Explicit mutability is forbidden on closure arg `{}`", + expected_name + ) + )) + } + if let Some((_, subpat)) = subpat { + return Err(Error::new( + subpat.span(), + format!( + "Subpatterns are forbidden on closure arg `{}`", + expected_name + ) + )) + } + assert_eq!(ident, expected_name); + return Ok(()) + }, + _ => {}, + } + Err(Error::new(pat.span(), format!( + "Expected argument {} to closure", + expected_name + ))) + } + check_input(&inputs[0], "self")?; + check_input(&inputs[1], "visitor")?; + Ok(VisitClosure((**body).clone())) + } +} + +/// Extra bounds +#[derive(Default)] +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 { + create_clause_with_default( + &self.trace, generic_params, + vec![parse_quote!(Trace)] + ).unwrap_or_else(|| unreachable!("Trace must always be implemented")) + } + fn trace_immutable_clause(&self, generic_params: &[GenericParam]) -> Option { + create_clause_with_default( + &self.trace_immutable, generic_params, + vec![parse_quote!(TraceImmutable)] + ) + } + fn gcsafe_clause(&self, generic_params: &[GenericParam]) -> Option { + create_clause_with_default( + &self.gcsafe, generic_params, + vec![parse_quote!(GcSafe)] + ) + } + fn rebrand_clause(&self, generic_params: &[GenericParam]) -> Option { + create_clause_with_default( + &self.rebrand, generic_params, + vec![parse_quote!(GcRebrand<'new_gc, Id>)] + ) + } + fn erase_clause(&self, generic_params: &[GenericParam]) -> Option { + create_clause_with_default( + &self.erase, generic_params, + vec![parse_quote!(GcErase<'min, Id>)] + ) + } +} +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 + })) + } + } + let type_idents = generic_params.iter() + .filter_map(|param| match param { + GenericParam::Type(ref t) => { + Some(t.ident.clone()) + }, + _ => None + }).collect::>(); + parse_quote!(where #(#type_idents: Trace)*) + } + }) +} +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) +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: Option, + /// The associated type implemented as `GcErase::Erased` + erased: 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. +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: Expr + }, + /// 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 { + use quote::ToTokens; + match *self { + VisitImpl::Generic { ref generic_impl } => { + ::syn::parse2(replace_magic_tokens(generic_impl.to_token_stream(), &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)) + })?) + } + 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)] +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`")) + } + } +} \ No newline at end of file diff --git a/src/manually_traced/core.rs b/src/manually_traced/core.rs index 34078cd..4df1db1 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -9,10 +9,15 @@ use core::num::Wrapping; use crate::prelude::*; use crate::{GcDirectBarrier, CollectorId}; +use zerogc_derive::unsafe_gc_impl; + macro_rules! trace_tuple { { $($param:ident)* } => { - unsafe impl<$($param),*> Trace for ($($param,)*) - where $($param: Trace),* { + unsafe_gc_impl! { + target => Option, + params => [T], + null_trace => { where T: NullTrace }, + simple_branded_bounds => [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 +25,20 @@ 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> { + NEEDS_TRACE => $($param::NEEDS_TRACE || )* false, + NEEDS_DROP => $($param::NEEDS_DROP || )* false, + trace_immutable => |$visitor, $target| { #[allow(non_snake_case)] let ($(ref mut $param,)*) = *self; - $(visitor.visit::<$param>($param)?;)* + $($visitor.trace($param)?;)* Ok(()) - } - } - unsafe impl<$($param),*> TraceImmutable for ($($param,)*) - where $($param: TraceImmutable),* { - #[inline] - fn visit_immutable(&self, #[allow(unused)] visitor: &mut V) -> Result<(), V::Err> { + }, + trace_immutable => |$visitor, $target| { #[allow(non_snake_case)] let ($(ref $param,)*) = *self; - $(visitor.visit_immutable::<$param>($param)?;)* + $($visitor.trace_immutable($param)?;)* Ok(()) - } - } - 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)*; + }, } unsafe impl<'gc, OwningRef, $($param),*> $crate::GcDirectBarrier<'gc, OwningRef> for ($($param,)*) where $($param: $crate::GcDirectBarrier<'gc, OwningRef>),* { @@ -109,32 +96,15 @@ 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_impl_gc! { + target => [T; $size], + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => T::NEEDS_DROP, + visit => |$visit:expr, $target:ident| { + visit(*$target as [T]); + }, } }; { $($size:tt),* } => ($(trace_array!($size);)*) @@ -148,40 +118,38 @@ trace_array! { /// /// 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_gc! { + target => &'a T, + params => [T], + bounds = { + Trace => { where T: TraceImmmutable }, + 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 } + }, + Branded => &'a T, + Erased => &'a T, + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => false, // We never need to be dropped + trace => |$visitor:ident, $target:ident| { + $visitor.visit_immutable::(*$target) + }, + trace_immutable => |$visitor:ident, $target:ident| { + $visitor.visit_immutable::(*$target) } } -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 { @@ -240,29 +208,18 @@ 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), - } - } -} -unsafe impl TraceImmutable for Option { - #[inline] - fn visit_immutable(&self, visitor: &mut V) -> Result<(), ::Err> { +unsafe_impl_gc! { + target => Option, + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => T::NEEDS_DROP, + visit => |$visit, $mutability, $target:ident| { match *self { None => Ok(()), - Some(ref value) => visitor.visit_immutable(value), + Some(ref $mutability value) => $visit($target), } - } -} -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> { diff --git a/src/manually_traced/mod.rs b/src/manually_traced/mod.rs index 2159869..c2e4e04 100644 --- a/src/manually_traced/mod.rs +++ b/src/manually_traced/mod.rs @@ -5,6 +5,8 @@ //! This is done for all stdlib types and some feature gated external libraries. #![doc(hidden)] // This is unstable +use zerogc_derive::unsafe_impl_gc; + /// Unsafely implement `GarbageCollected` for the specified type, /// by acquiring a 'lock' in order to trace the underlying value. /// @@ -81,142 +83,6 @@ macro_rules! unsafe_trace_lock { }; } -/// 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, @@ -302,24 +168,13 @@ 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_impl_gc! { + target => $target, + params => [], + null_trace => { /* always */ }, + NEEDS_TRACE => false, + NEEDS_DROP => core::mem::needs_drop::<$target>(), + visit => |$visit:expr, $target:ident| { /* nop */ } } unsafe impl<'gc, OwningRef> $crate::GcDirectBarrier<'gc, OwningRef> for $target { #[inline(always)] @@ -355,6 +210,7 @@ macro_rules! unsafe_trace_primitive { /// for straighrtforward wrapper/collection types. /// Currently the only exception is when you have garbage collected lifetimes like `Gc`. #[macro_export] +#[deprecated(note = "Use unsafe_impl_gc")] macro_rules! unsafe_gc_brand { ($target:tt) => { unsafe impl<'new_gc, Id: $crate::CollectorId> $crate::GcRebrand<'new_gc, Id> for $target { diff --git a/src/manually_traced/stdalloc.rs b/src/manually_traced/stdalloc.rs index 78be9d9..b3e80b6 100644 --- a/src/manually_traced/stdalloc.rs +++ b/src/manually_traced/stdalloc.rs @@ -12,6 +12,28 @@ use crate::prelude::*; // NOTE: Delegate to slice to avoid code duplication unsafe_trace_deref!(Vec, target = { [T] }; T); +unsafe_impl_gc! { + target => Vec, + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |$visit:expr, $target:ident| { + // Delegate to slice + visit(**$target as [T]); + } +} +unsafe_impl_gc! { + target => Box, + params => [T], + null_trace => { where T: NullTrace }, + NEEDS_TRACE => T::NEEDS_TRACE, + NEEDS_DROP => true, // Internal memory + visit => |$visit:expr, $target:ident| { + // Delegate to slice + visit(**$target); + } +} unsafe_trace_deref!(Box, target = T); // We can only trace `Rc` and `Arc` if the inner type implements `TraceImmutable` unsafe_trace_deref!(Rc, T; immut = required; |rc| &**rc); From 087c6ffdf2ba795a4939dfb0ebdd7c72263f156d Mon Sep 17 00:00:00 2001 From: Techcable Date: Thu, 21 Jan 2021 17:52:23 -0700 Subject: [PATCH 2/7] Start to implment a `unsafe_gc_impl` procedural macro This is incomplete, but pretty darn awesome The core crate now depends on zerogc-derive and syn-full --- Cargo.toml | 2 +- libs/derive/src/lib.rs | 49 ++-- libs/derive/src/macros.rs | 411 ++++++++++++++++++++------------ src/manually_traced/core.rs | 121 ++++++---- src/manually_traced/mod.rs | 40 ++-- src/manually_traced/stdalloc.rs | 50 +++- src/prelude.rs | 2 + 7 files changed, 422 insertions(+), 253 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b4272cc..971abd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" # gives zerogc 'batteries included' support. indexmap = { version = "1.6", optional = true } # Used for macros -zerogc-derive = { version = "0.2.0-alpha.1" } +zerogc-derive = { path = "libs/derive", version = "0.2.0-alpha.1" } [workspace] members = ["libs/simple", "libs/derive", "libs/context"] diff --git a/libs/derive/src/lib.rs b/libs/derive/src/lib.rs index b802c0a..8df28af 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; @@ -151,6 +152,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; @@ -338,14 +353,14 @@ impl Parse for TypeAttrs { #[proc_macro] pub fn unsafe_gc_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let cloned_impl = input.clone(); 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!", - &"", - &format_args!("#[unsafe_gc_impl {{ {} }}", cloned_impl), + &span_loc, + &format_args!("unsafe_gc_impl! @ {}", span_loc), &res ); res.into() @@ -359,7 +374,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 ); @@ -1084,22 +1099,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 index 3dfd4ff..58d9926 100644 --- a/libs/derive/src/macros.rs +++ b/libs/derive/src/macros.rs @@ -4,17 +4,20 @@ * 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, - ExprClosure, Generics, TypeParamBound, WherePredicate, PredicateType, - parse_quote + Generics, TypeParamBound, WherePredicate, PredicateType, parse_quote, + GenericArgument }; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use quote::{quote, quote_spanned}; +#[derive(Debug)] struct GenericParamInput(Vec); impl Parse for GenericParamInput { fn parse(input: ParseStream) -> syn::Result { @@ -33,6 +36,7 @@ fn empty_clause() -> WhereClause { } } +#[derive(Debug)] pub struct MacroInput { /// The target type we are implementing /// @@ -61,7 +65,7 @@ impl MacroInput { } pub fn expand_output(&self) -> Result { let target_type = &self.target_type; - let trace_impl = self.expand_trace_impl(true) + let trace_impl = self.expand_trace_impl(true)? .expect("Trace impl required"); let trace_immutable_impl = self.expand_trace_impl(false)? .unwrap_or_default(); @@ -74,9 +78,9 @@ impl MacroInput { 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, ty_generics, where_clause) = generics.split_for_impl(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); quote! { - unsafe impl #impl_generics NullTrace for #target_type #ty_generics + unsafe impl #impl_generics NullTrace for #target_type #where_clause {} } } else { @@ -107,13 +111,25 @@ impl MacroInput { generics.make_where_clause().predicates .extend(clause.predicates); let visit_impl = self.visit.expand_impl(mutable)?; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let (impl_generics, _, where_clause) = generics.split_for_impl(); let trait_name = if mutable { quote!(Trace) } else { quote!(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 = #expr;)) + } else { + None + }; + let mutability = if mutable { + quote!(mut) + } else { + quote!() + }; Ok(Some(quote! { - unsafe impl #impl_generics #trait_name for #target_type #ty_generics #where_clause { + unsafe impl #impl_generics #trait_name for #target_type #where_clause { + #needs_trace_const #[inline] // TODO: Should this be unconditional? - fn #visit_method_name(&mut self, visitor: &mut V) -> Result<(), V::Err> { + fn #visit_method_name(&#mutability self, visitor: &mut V) -> Result<(), V::Err> { #visit_impl } } @@ -127,9 +143,12 @@ impl MacroInput { Some(clause) => clause.predicates, None => return None // They are requesting we dont implement }); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let needs_drop = &self.options.needs_drop; + let (impl_generics, _, where_clause) = generics.split_for_impl(); Some(quote! { - unsafe impl #impl_generics GcSafe for #target_type #ty_generics #where_clause {} + unsafe impl #impl_generics GcSafe for #target_type #where_clause { + const NEEDS_DROP: bool = #needs_drop; + } }) } fn expand_brand_impl(&self, rebrand: bool /* true => rebrand, false => erase */) -> Option { @@ -140,7 +159,29 @@ impl MacroInput { } let target_type = &self.target_type; let mut generics = self.basic_generics(); + let default_bounds: Vec = match requirements { + Some(TraitRequirements::Where(_)) => { + // 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!(GcRebrand<'new_gc, Id>)] + } else { + vec![parse_quote!(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; @@ -148,29 +189,148 @@ impl MacroInput { .predicates.push(WherePredicate::Type(PredicateType { lifetimes: None, bounded_ty: if rebrand { - parse_quote!(<#type_name as GcRebrand<'new_gc, Id>>::Branded) + self.options.branded_type.clone().unwrap_or_else(|| { + parse_quote!(<#type_name as GcRebrand<'new_gc, Id>>::Branded) + }) } else { - parse_quote!(<#type_name as GcErase<'new_gc, Id>>::Erased) + self.options.branded_type.clone().unwrap_or_else(|| { + parse_quote!(<#type_name as GcErase<'min, Id>>::Erased) + }) }, colon_token: Default::default(), - bounds: tp.bounds.clone() + bounds: default_bounds.iter().cloned().collect() + })); + generics.make_where_clause() + .predicates.push(WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty: parse_quote!(#type_name), + colon_token: Default::default(), + bounds: default_bounds.iter().cloned().collect() })) } _ => {} } } + /* + * 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(create_clause_with_default( - &requirements, &self.params, - if rebrand { - vec![parse_quote!(GcRebrand<'new_gc, Id>)] + .extend(self.bounds.trace_where_clause(&self.params).predicates); + generics.params.push(parse_quote!(Id: 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!(GcRebrand<'new_gc, Id>) + } else { + quote!(GcErase<'min, Id>) + }; + 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) + } + match target { + ::syn::Type::Path(::syn::TypePath { ref qself, ref path }) => { + let new_qself = qself.clone().map(|mut qself| { + qself.ty = Box::new(rewrite_type( + &*qself.ty, target_type_name, + &mut *rewriter + )?); + Ok(qself) + }).transpose()?; + let new_path = ::syn::Path { + leading_colon: path.leading_colon, + segments: path.segments.iter().cloned().map(|segment| { + // old_segment.ident is ignored... + for arg in &mut segment.arguments { + match arg { + ::syn::PathArguments::None => {}, // Nothing to do here + ::syn::PathArguments::AngleBracketed(ref mut generic_args) => { + for arg in &mut generic_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 + ) + )) + } + } + } + } + } + } + }).collect() + }; + Ok(::syn::Type::Path(::syn::TypePath { qself: new_qself, path: new_path })) + } + _ => return Err(Error::new(target.span(), format!( + "Unable to rewrite type as a `{}`: {}", + target_type_name, target + ))) + } + } + 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 { - vec![parse_quote!(GcErase<'min, Id>)] + None } - ).unwrap_or_else(empty_clause).predicates); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + }) + } + 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 = Ok(self.options.branded_type.clone()).transpose().unwrap_or_else(|| { + rewrite_brand_trait( + &self.target_type, "GcRebrand", + &target_params, + parse_quote!(GcRebrand<'new_gc, Id>), + parse_quote!(Branded) + ) + })?; + quote!(type Branded = #branded;) + } else { + let erased = Ok(self.options.branded_type.clone()).transpose().unwrap_or_else(|| { + rewrite_brand_trait( + &self.target_type, "GcErase", + &target_params, + parse_quote!(GcErase<'min, Id>), + parse_quote!(Erased) + ) + })?; + quote!(type Erased = #erased;) + }; Some(quote! { - unsafe impl #impl_generics GcSafe for #target_type #ty_generics #where_clause {} + unsafe impl #impl_generics #target_trait for #target_type #where_clause { + const + } }) } } @@ -244,8 +404,8 @@ impl Parse for MacroInput { "bounds" [CustomBounds] opt => bounds; // StandardOptions "null_trace" [TraitRequirements] => null_trace; - "branded" [Type] opt => branded; - "erased" [Type] opt => erased; + "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; @@ -262,11 +422,11 @@ impl Parse for MacroInput { if let Some(closure) = res.trace_immutable_closure.as_ref() .or(res.trace_mut_closure.as_ref()) { return Err(Error::new( - closure.0.span(), - "Cannot specifiy specific closure (trace_mut/trace_immutable) in addition to `visit`" + closure.body.span(), + "Cannot specify specific closure (trace_mut/trace_immutable) in addition to `visit`" )) } - VisitImpl::Generic { generic_impl: visit_closure.0 } + VisitImpl::Generic { generic_impl: visit_closure.body } } else { let trace_closure = res.trace_mut_closure.ok_or_else(|| { Error::new( @@ -278,7 +438,7 @@ impl Parse for MacroInput { Some(TraitRequirements::Never) => { if let Some(closure) = res.trace_immutable_closure { return Err(Error::new( - closure.0.span(), + closure.body.span(), "Specified a `trace_immutable` implementation even though TraceImmutable is never implemented" )) } else { @@ -296,7 +456,12 @@ impl Parse for MacroInput { })?) } }; - VisitImpl::Specific { mutable: trace_closure.0, immutable: trace_immut_closure.map(|closure| closure.0) } + 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, @@ -304,8 +469,8 @@ impl Parse for MacroInput { bounds, options: StandardOptions { null_trace: res.null_trace, - branded: res.branded, - erased: res.erased, + branded_type: res.branded_type, + erased_type: res.erased_type, needs_trace: res.needs_trace, needs_drop: res.needs_drop }, @@ -314,121 +479,42 @@ impl Parse for MacroInput { } } -pub struct VisitClosure(Expr); +#[derive(Debug)] +pub struct VisitClosure { + body: TokenStream, + brace: ::syn::token::Brace +} impl Parse for VisitClosure { fn parse(input: ParseStream) -> syn::Result { - let closure = match input.parse::()? { - Expr::Closure(closure) => closure, - e => return Err(Error::new(e.span(), "Expected a closure")) - }; - let ExprClosure { - ref attrs, - ref asyncness, - ref movability, - ref capture, - or1_token: _, - ref inputs, - or2_token: _, - ref output, - ref body, .. - } = closure; - if !attrs.is_empty() { + input.parse::()?; + if !input.peek(Token![self]) { return Err(Error::new( - closure.span(), - "Attributes are forbidden on visit closure" + input.span(), + "Expected first argument to closure to be `self`" )); } - if asyncness.is_some() || movability.is_some() || capture.is_some() { + input.parse::()?; + input.parse::()?; + let visitor_name = input.parse::()?; + if visitor_name != "visitor" { return Err(Error::new( - closure.span(), - "Modifiers are forbidden on visit closure" + visitor_name.span(), + "Expected second argument to closure to be `visitor`" )); } - if let ::syn::ReturnType::Default = output { - return Err(Error::new( - output.span(), - "Explicit return types are forbidden on visit closures" - )) - } - if inputs.len() != 2 { - return Err(Error::new( - inputs.span(), - "Expected exactly two arguments to visit closure" - )) + input.parse::()?; + if !input.peek(syn::token::Brace) { + return Err(input.error("Expected visitor closure to be braced")); } - fn check_input(pat: &::syn::Pat, expected_name: &str,) -> Result<(), Error> { - match pat { - ::syn::Pat::Ident( - ::syn::PatIdent { - ref attrs, - ref by_ref, - ref mutability, - ref ident, - ref subpat - } - ) => { - if ident != expected_name { - return Err(Error::new( - ident.span(), format!( - "Expected argument `{}` for closure", - expected_name - ) - )) - } - if !attrs.is_empty() { - return Err(Error::new( - pat.span(), - format!( - "Attributes are forbidden on closure arg `{}`", - expected_name - ) - )) - } - if let Some(by) = by_ref { - return Err(Error::new( - by.span(), - format!( - "Explicit mutability is forbidden on closure arg `{}`", - expected_name - ) - )) - } - if let Some(mutability) = mutability { - return Err(Error::new( - mutability.span(), - format!( - "Explicit mutability is forbidden on closure arg `{}`", - expected_name - ) - )) - } - if let Some((_, subpat)) = subpat { - return Err(Error::new( - subpat.span(), - format!( - "Subpatterns are forbidden on closure arg `{}`", - expected_name - ) - )) - } - assert_eq!(ident, expected_name); - return Ok(()) - }, - _ => {}, - } - Err(Error::new(pat.span(), format!( - "Expected argument {} to closure", - expected_name - ))) - } - check_input(&inputs[0], "self")?; - check_input(&inputs[1], "visitor")?; - Ok(VisitClosure((**body).clone())) + let body; + let brace = braced!(body in input); + let body = body.parse::()?; + Ok(VisitClosure { body: quote!({ #body }), brace }) } } /// Extra bounds -#[derive(Default)] +#[derive(Default, Debug)] pub struct CustomBounds { /// Additional bounds on the `Trace` implementation trace: Option, @@ -461,22 +547,17 @@ impl CustomBounds { ) } fn gcsafe_clause(&self, generic_params: &[GenericParam]) -> Option { - create_clause_with_default( + let mut res = create_clause_with_default( &self.gcsafe, generic_params, vec![parse_quote!(GcSafe)] - ) - } - fn rebrand_clause(&self, generic_params: &[GenericParam]) -> Option { - create_clause_with_default( - &self.rebrand, generic_params, - vec![parse_quote!(GcRebrand<'new_gc, Id>)] - ) - } - fn erase_clause(&self, generic_params: &[GenericParam]) -> Option { - create_clause_with_default( - &self.erase, generic_params, - vec![parse_quote!(GcErase<'min, Id>)] - ) + ); + 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( @@ -511,7 +592,7 @@ fn create_clause_with_default( }, _ => None }).collect::>(); - parse_quote!(where #(#type_idents: Trace)*) + parse_quote!(where #(#type_idents: Trace),*) } }) } @@ -531,7 +612,7 @@ impl Parse for CustomBounds { trace_immutable: res.trace_immutable, gcsafe: res.gcsafe, rebrand: res.rebrand, - erase: res.erase + erase: res.erase, }) } } @@ -540,15 +621,16 @@ impl Parse for CustomBounds { /// /// 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: Option, + branded_type: Option, /// The associated type implemented as `GcErase::Erased` - erased: Option, + 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 @@ -559,6 +641,7 @@ pub struct StandardOptions { /// /// 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. @@ -586,7 +669,7 @@ pub enum VisitImpl { /// Ok(()) /// ```` Generic { - generic_impl: Expr + generic_impl: TokenStream }, /// Specialized implementations which are different for /// both `Trace` and `TraceImmutable` @@ -611,16 +694,18 @@ impl MagicVarType { "iter" => MagicVarType::Iter, "visit_func" => MagicVarType::VisitFunc, "b" => MagicVarType::B, - _ => return Err(Error::new(ident.span(), "Invalid magic variable name")) + _ => return Err( + Error::new(ident.span(), + "Invalid magic variable name" + )) }) } } impl VisitImpl { fn expand_impl(&self, mutable: bool) -> Result { - use quote::ToTokens; match *self { VisitImpl::Generic { ref generic_impl } => { - ::syn::parse2(replace_magic_tokens(generic_impl.to_token_stream(), &mut |ident| { + let tokens = replace_magic_tokens(generic_impl.clone(), &mut |ident| { let res = match MagicVarType::parse_ident(ident)? { MagicVarType::Mutability => { if mutable { @@ -660,7 +745,21 @@ impl VisitImpl { }; 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 { @@ -721,7 +820,7 @@ fn replace_magic_tokens(input: TokenStream, func: &mut dyn FnMut(&Ident) -> Resu /// /// In addition to a where clause, you can specify `always` for unconditional /// implementation and `never` to forbid generated implementations -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum TraitRequirements { /// The trait should never be implemented Never, diff --git a/src/manually_traced/core.rs b/src/manually_traced/core.rs index 4df1db1..8088b7e 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -11,13 +11,21 @@ use crate::{GcDirectBarrier, CollectorId}; use zerogc_derive::unsafe_gc_impl; +macro_rules! __rec_trace_tuple { + ($($param:ident),*) => { + // Nothing remaining + }; + ($first_param:ident, $($param:ident),+) => { + trace_tuple!($($param),*); + }; +} macro_rules! trace_tuple { - { $($param:ident)* } => { + { $($param:ident),* } => { + __rec_trace_tuple!($($param),* ); unsafe_gc_impl! { - target => Option, - params => [T], - null_trace => { where T: NullTrace }, - simple_branded_bounds => [Sized], + target => ( $($param,)* ), + params => [$($param),*], + 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 `||`. @@ -27,18 +35,40 @@ macro_rules! trace_tuple { */ NEEDS_TRACE => $($param::NEEDS_TRACE || )* false, NEEDS_DROP => $($param::NEEDS_DROP || )* false, - trace_immutable => |$visitor, $target| { - #[allow(non_snake_case)] - let ($(ref mut $param,)*) = *self; - $($visitor.trace($param)?;)* + visit => |self, visitor| { + ##[allow(non_snake_case)] + let ($(ref #mutability $param,)*) = *self; + $(visitor.#visit_func($param)?;)* Ok(()) - }, - trace_immutable => |$visitor, $target| { + } + } + unsafe impl<'gc, OwningRef, $($param),*> $crate::GcDirectBarrier<'gc, OwningRef> for ($($param,)*) + where $($param: $crate::GcDirectBarrier<'gc, OwningRef>),* { + #[inline] + 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.trace_immutable($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); + };)* + } + } + }; + { $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>),* { @@ -83,27 +113,18 @@ 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_gc! { + unsafe_gc_impl! { target => [T; $size], params => [T], null_trace => { where T: NullTrace }, NEEDS_TRACE => T::NEEDS_TRACE, NEEDS_DROP => T::NEEDS_DROP, - visit => |$visit:expr, $target:ident| { - visit(*$target as [T]); + visit => |self, visitor| { + visitor.#visit_func(#b*self as #b [T]); }, } }; @@ -118,11 +139,11 @@ trace_array! { /// /// The underlying data must support `TraceImmutable` since we /// only have an immutable reference. -unsafe_impl_gc! { +unsafe_gc_impl! { target => &'a T, - params => [T], - bounds = { - Trace => { where T: TraceImmmutable }, + params => ['a, T], + bounds => { + Trace => { where T: TraceImmutable }, TraceImmutable => { where T: TraceImmutable }, /* * TODO: Right now we require `NullTrace` @@ -137,16 +158,13 @@ unsafe_impl_gc! { GcRebrand => { where T: NullTrace, 'a: 'new_gc }, GcErase => { where T: NullTrace } }, - Branded => &'a T, - Erased => &'a T, + 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 - trace => |$visitor:ident, $target:ident| { - $visitor.visit_immutable::(*$target) - }, - trace_immutable => |$visitor:ident, $target:ident| { - $visitor.visit_immutable::(*$target) + visit => |self, visitor| { + visitor.#visit_func::(*self) } } @@ -208,16 +226,16 @@ unsafe impl GcSafe for [T] { const NEEDS_DROP: bool = core::mem::needs_drop::(); } -unsafe_impl_gc! { +unsafe_gc_impl! { target => Option, params => [T], null_trace => { where T: NullTrace }, NEEDS_TRACE => T::NEEDS_TRACE, NEEDS_DROP => T::NEEDS_DROP, - visit => |$visit, $mutability, $target:ident| { + visit => |self, visitor| { match *self { None => Ok(()), - Some(ref $mutability value) => $visit($target), + Some(ref #mutability value) => visitor.#visit_func::(value), } }, } @@ -227,7 +245,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 @@ -241,13 +262,15 @@ 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::NEDS_DROP, + visit => |self, visitor| { + // We can trace `Wrapping` by simply tracing its interior + visitor.#visit_func(#b self.0) } } \ No newline at end of file diff --git a/src/manually_traced/mod.rs b/src/manually_traced/mod.rs index c2e4e04..6ecec50 100644 --- a/src/manually_traced/mod.rs +++ b/src/manually_traced/mod.rs @@ -5,7 +5,9 @@ //! This is done for all stdlib types and some feature gated external libraries. #![doc(hidden)] // This is unstable -use zerogc_derive::unsafe_impl_gc; +use crate::prelude::*; + +use zerogc_derive::unsafe_gc_impl; /// Unsafely implement `GarbageCollected` for the specified type, /// by acquiring a 'lock' in order to trace the underlying value. @@ -56,19 +58,19 @@ use zerogc_derive::unsafe_impl_gc; #[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; @@ -77,9 +79,7 @@ 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> {} + ); }; } @@ -168,19 +168,23 @@ macro_rules! unsafe_immutable_trace_iterable { #[macro_export] macro_rules! unsafe_trace_primitive { ($target:ty) => { - unsafe_impl_gc! { + unsafe_gc_impl! { target => $target, params => [], - null_trace => { /* always */ }, + null_trace => always, NEEDS_TRACE => false, NEEDS_DROP => core::mem::needs_drop::<$target>(), - visit => |$visit:expr, $target:ident| { /* nop */ } + visit => |self, visitor| { /* nop */ } } 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 */ } } diff --git a/src/manually_traced/stdalloc.rs b/src/manually_traced/stdalloc.rs index b3e80b6..f5ab6d5 100644 --- a/src/manually_traced/stdalloc.rs +++ b/src/manually_traced/stdalloc.rs @@ -10,33 +10,59 @@ use alloc::string::String; use crate::prelude::*; -// NOTE: Delegate to slice to avoid code duplication -unsafe_trace_deref!(Vec, target = { [T] }; T); -unsafe_impl_gc! { +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 => |$visit:expr, $target:ident| { + visit => |self, visitor| { // Delegate to slice - visit(**$target as [T]); + visitor.#visit_func::<[T]>(#b**self as #b [T]) } } -unsafe_impl_gc! { +unsafe_gc_impl! { target => Box, params => [T], null_trace => { where T: NullTrace }, NEEDS_TRACE => T::NEEDS_TRACE, NEEDS_DROP => true, // Internal memory - visit => |$visit:expr, $target:ident| { - // Delegate to slice - visit(**$target); + visit => |self, visitor| { + visitor.#visit_func::(#b **self); } } -unsafe_trace_deref!(Box, target = T); // 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], + bounds => { + Trace => { where T: TraceImmutable }, + TraceImmutable => { where 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], + bounds => { + Trace => { where T: TraceImmutable }, + TraceImmutable => { where 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/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 From 952bd16131535ba5c2af1a62da382a62f50c4640 Mon Sep 17 00:00:00 2001 From: Techcable Date: Thu, 21 Jan 2021 21:42:38 -0700 Subject: [PATCH 3/7] Switch all `manually_traced` impls to unsafe_gc_impl!` This procedural derive is **much** cleaner (although it brings in all of syn as a dependency) --- libs/derive/src/macros.rs | 164 +++++++++++++++++--------------- src/lib.rs | 38 ++++---- src/manually_traced/core.rs | 117 ++++++++++++----------- src/manually_traced/indexmap.rs | 100 ++++--------------- src/manually_traced/mod.rs | 124 +----------------------- src/manually_traced/stdalloc.rs | 14 +-- src/manually_traced/stdlib.rs | 61 +++++------- 7 files changed, 215 insertions(+), 403 deletions(-) diff --git a/libs/derive/src/macros.rs b/libs/derive/src/macros.rs index 58d9926..e23064a 100644 --- a/libs/derive/src/macros.rs +++ b/libs/derive/src/macros.rs @@ -86,8 +86,8 @@ impl MacroInput { } else { quote!() }; - let rebrand_impl = self.expand_brand_impl(true); - let erase_impl = self.expand_brand_impl(false); + let rebrand_impl = self.expand_brand_impl(true)?; + let erase_impl = self.expand_brand_impl(false)?; Ok(quote! { #trace_impl #trace_immutable_impl @@ -129,7 +129,7 @@ impl MacroInput { 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 V) -> Result<(), V::Err> { + fn #visit_method_name(&#mutability self, visitor: &mut Visitor) -> Result<(), Visitor::Err> { #visit_impl } } @@ -151,16 +151,18 @@ impl MacroInput { } }) } - fn expand_brand_impl(&self, rebrand: bool /* true => rebrand, false => erase */) -> Option { + fn expand_brand_impl(&self, rebrand: bool /* true => rebrand, false => erase */) -> Result, Error> { 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 None; + return Ok(None); } let target_type = &self.target_type; let mut generics = self.basic_generics(); let default_bounds: Vec = match requirements { - Some(TraitRequirements::Where(_)) => { + 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![] } @@ -185,6 +187,8 @@ impl MacroInput { 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, @@ -193,19 +197,19 @@ impl MacroInput { parse_quote!(<#type_name as GcRebrand<'new_gc, Id>>::Branded) }) } else { - self.options.branded_type.clone().unwrap_or_else(|| { + self.options.erased_type.clone().unwrap_or_else(|| { parse_quote!(<#type_name as GcErase<'min, Id>>::Erased) }) }, colon_token: Default::default(), - bounds: default_bounds.iter().cloned().collect() + 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: default_bounds.iter().cloned().collect() + bounds })) } _ => {} @@ -231,56 +235,6 @@ impl MacroInput { } else { quote!(GcErase<'min, Id>) }; - 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) - } - match target { - ::syn::Type::Path(::syn::TypePath { ref qself, ref path }) => { - let new_qself = qself.clone().map(|mut qself| { - qself.ty = Box::new(rewrite_type( - &*qself.ty, target_type_name, - &mut *rewriter - )?); - Ok(qself) - }).transpose()?; - let new_path = ::syn::Path { - leading_colon: path.leading_colon, - segments: path.segments.iter().cloned().map(|segment| { - // old_segment.ident is ignored... - for arg in &mut segment.arguments { - match arg { - ::syn::PathArguments::None => {}, // Nothing to do here - ::syn::PathArguments::AngleBracketed(ref mut generic_args) => { - for arg in &mut generic_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 - ) - )) - } - } - } - } - } - } - }).collect() - }; - Ok(::syn::Type::Path(::syn::TypePath { qself: new_qself, path: new_path })) - } - _ => return Err(Error::new(target.span(), format!( - "Unable to rewrite type as a `{}`: {}", - target_type_name, target - ))) - } - } fn rewrite_brand_trait( target: &Type, trait_name: &str, target_params: &HashSet, target_trait: TokenStream, associated_type: Ident @@ -307,17 +261,17 @@ impl MacroInput { _ => None }).collect::>(); let associated_type = if rebrand { - let branded = Ok(self.options.branded_type.clone()).transpose().unwrap_or_else(|| { + let branded = self.options.branded_type.clone().map_or_else(|| { rewrite_brand_trait( &self.target_type, "GcRebrand", &target_params, parse_quote!(GcRebrand<'new_gc, Id>), parse_quote!(Branded) ) - })?; + }, Ok)?; quote!(type Branded = #branded;) } else { - let erased = Ok(self.options.branded_type.clone()).transpose().unwrap_or_else(|| { + let erased = Ok(self.options.erased_type.clone()).transpose().unwrap_or_else(|| { rewrite_brand_trait( &self.target_type, "GcErase", &target_params, @@ -327,11 +281,11 @@ impl MacroInput { })?; quote!(type Erased = #erased;) }; - Some(quote! { + Ok(Some(quote! { unsafe impl #impl_generics #target_trait for #target_type #where_clause { - const + #associated_type } - }) + })) } } @@ -585,14 +539,7 @@ fn create_clause_with_default( })) } } - let type_idents = generic_params.iter() - .filter_map(|param| match param { - GenericParam::Type(ref t) => { - Some(t.ident.clone()) - }, - _ => None - }).collect::>(); - parse_quote!(where #(#type_idents: Trace),*) + where_clause } }) } @@ -739,7 +686,7 @@ impl VisitImpl { if mutable { quote!(&mut) } else { - quote!() + quote!(&) } } }; @@ -853,4 +800,71 @@ impl Parse for TraitRequirements { return Err(input.error("Invalid `TraitRequirement`")) } } -} \ No newline at end of file +} + + + +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 8088b7e..508c1ba 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -35,6 +35,8 @@ macro_rules! trace_tuple { */ 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; @@ -123,8 +125,10 @@ macro_rules! trace_array { 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]); + visitor.#visit_func(#b*self as #b [T]) }, } }; @@ -135,13 +139,15 @@ 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. +/* + * 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], + params => ['a, T: 'a], bounds => { Trace => { where T: TraceImmutable }, TraceImmutable => { where T: TraceImmutable }, @@ -156,7 +162,7 @@ unsafe_gc_impl! { * which is only safe if `T: NullTrace` */ GcRebrand => { where T: NullTrace, 'a: 'new_gc }, - GcErase => { where T: NullTrace } + GcErase => { where T: NullTrace, 'a: 'min } }, branded_type => &'a T, erased_type => &'a T, @@ -164,67 +170,64 @@ unsafe_gc_impl! { NEEDS_TRACE => T::NEEDS_TRACE, NEEDS_DROP => false, // We never need to be dropped visit => |self, visitor| { - visitor.#visit_func::(*self) + visitor.visit_immutable::(&**self) } } -/// 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> { - visitor.visit_immutable::(&**self) +/* + * 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<'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)?; + +/* + * 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 NullTrace for [T] {} -unsafe impl GcSafe for [T] { - const NEEDS_DROP: bool = core::mem::needs_drop::(); -} unsafe_gc_impl! { target => Option, @@ -268,7 +271,7 @@ unsafe_gc_impl! { params => [T], null_trace => { where T: NullTrace }, NEEDS_TRACE => T::NEEDS_TRACE, - NEEDS_DROP => T::NEDS_DROP, + NEEDS_DROP => T::NEEDS_DROP, visit => |self, visitor| { // We can trace `Wrapping` by simply tracing its interior visitor.#visit_func(#b self.0) 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 6ecec50..cc1e6bc 100644 --- a/src/manually_traced/mod.rs +++ b/src/manually_traced/mod.rs @@ -5,10 +5,6 @@ //! This is done for all stdlib types and some feature gated external libraries. #![doc(hidden)] // This is unstable -use crate::prelude::*; - -use zerogc_derive::unsafe_gc_impl; - /// Unsafely implement `GarbageCollected` for the specified type, /// by acquiring a 'lock' in order to trace the underlying value. /// @@ -85,65 +81,6 @@ macro_rules! unsafe_trace_lock { -/// 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, /// by assuming it's a 'primitive' and never needs to be traced. /// @@ -174,7 +111,7 @@ macro_rules! unsafe_trace_primitive { null_trace => always, NEEDS_TRACE => false, NEEDS_DROP => core::mem::needs_drop::<$target>(), - visit => |self, visitor| { /* nop */ } + visit => |self, visitor| { /* nop */ Ok(()) } } unsafe impl<'gc, OwningRef> $crate::GcDirectBarrier<'gc, OwningRef> for $target { #[inline(always)] @@ -191,65 +128,6 @@ macro_rules! unsafe_trace_primitive { }; } - -/// 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] -#[deprecated(note = "Use unsafe_impl_gc")] -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 f5ab6d5..c654f9d 100644 --- a/src/manually_traced/stdalloc.rs +++ b/src/manually_traced/stdalloc.rs @@ -30,17 +30,13 @@ unsafe_gc_impl! { NEEDS_TRACE => T::NEEDS_TRACE, NEEDS_DROP => true, // Internal memory visit => |self, visitor| { - visitor.#visit_func::(#b **self); + visitor.#visit_func::(#b **self) } } // We can only trace `Rc` and `Arc` if the inner type implements `TraceImmutable` unsafe_gc_impl! { target => Rc, - params => [T], - bounds => { - Trace => { where T: TraceImmutable }, - TraceImmutable => { where T: TraceImmutable } - }, + params => [T: TraceImmutable], null_trace => { where T: NullTrace }, NEEDS_TRACE => T::NEEDS_TRACE, NEEDS_DROP => true, // Internal memory @@ -51,11 +47,7 @@ unsafe_gc_impl! { } unsafe_gc_impl! { target => Arc, - params => [T], - bounds => { - Trace => { where T: TraceImmutable }, - TraceImmutable => { where T: TraceImmutable } - }, + params => [T: TraceImmutable], null_trace => { where T: NullTrace }, NEEDS_TRACE => T::NEEDS_TRACE, NEEDS_DROP => true, // Internal memory diff --git a/src/manually_traced/stdlib.rs b/src/manually_traced/stdlib.rs index 3f9df70..7e88efc 100644 --- a/src/manually_traced/stdlib.rs +++ b/src/manually_traced/stdlib.rs @@ -8,52 +8,35 @@ 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; - 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); From 42ef1e63651863394e0a3dcc547ff4281cd74791 Mon Sep 17 00:00:00 2001 From: Techcable Date: Fri, 22 Jan 2021 16:20:51 -0700 Subject: [PATCH 4/7] [derive] Use `crate::` when deriving inside zerogc This is the proc-macro equivelant of '$crate' --- libs/derive/src/lib.rs | 105 +++++++++++++++++++++------------- libs/derive/src/macros.rs | 52 +++++++++++------ src/manually_traced/core.rs | 2 +- src/manually_traced/stdlib.rs | 1 - 4 files changed, 101 insertions(+), 59 deletions(-) diff --git a/libs/derive/src/lib.rs b/libs/derive/src/lib.rs index 8df28af..c47101c 100644 --- a/libs/derive/src/lib.rs +++ b/libs/derive/src/lib.rs @@ -15,6 +15,24 @@ 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 } @@ -410,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() { @@ -417,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;)*) } @@ -426,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(); @@ -482,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; @@ -535,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() { @@ -568,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) } }; @@ -576,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; @@ -590,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(); @@ -603,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(), @@ -654,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),*>; @@ -677,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() { @@ -710,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) } }; @@ -718,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; @@ -732,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(); @@ -745,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(), @@ -795,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),*>; @@ -816,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), @@ -884,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(()) } @@ -900,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 { @@ -936,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 { /* @@ -959,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; @@ -978,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(); @@ -1007,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 {} }) } diff --git a/libs/derive/src/macros.rs b/libs/derive/src/macros.rs index e23064a..7a20b3e 100644 --- a/libs/derive/src/macros.rs +++ b/libs/derive/src/macros.rs @@ -16,6 +16,7 @@ use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use quote::{quote, quote_spanned}; +use super::zerogc_crate; #[derive(Debug)] struct GenericParamInput(Vec); @@ -64,6 +65,7 @@ impl MacroInput { 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"); @@ -80,7 +82,7 @@ impl MacroInput { 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 NullTrace for #target_type + unsafe impl #impl_generics #zerogc_crate::NullTrace for #target_type #where_clause {} } } else { @@ -98,6 +100,7 @@ impl MacroInput { }) } 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 { @@ -112,11 +115,15 @@ impl MacroInput { .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!(Trace) } else { quote!(TraceImmutable) }; + 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 = #expr;)) + Some(quote!(const NEEDS_TRACE: bool = { + // Import the trait so we can access `T::NEEDS_TRACE` + use #zerogc_crate::Trace; + #expr + };)) } else { None }; @@ -129,13 +136,14 @@ impl MacroInput { 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> { + 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 @@ -146,12 +154,17 @@ impl MacroInput { let needs_drop = &self.options.needs_drop; let (impl_generics, _, where_clause) = generics.split_for_impl(); Some(quote! { - unsafe impl #impl_generics GcSafe for #target_type #where_clause { - const NEEDS_DROP: bool = #needs_drop; + 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 @@ -172,9 +185,9 @@ impl MacroInput { Some(TraitRequirements::Never) => unreachable!(), None => { if rebrand { - vec![parse_quote!(GcRebrand<'new_gc, Id>)] + vec![parse_quote!(#zerogc_crate::GcRebrand<'new_gc, Id>)] } else { - vec![parse_quote!(GcErase<'min, Id>)] + vec![parse_quote!(#zerogc_crate::GcErase<'min, Id>)] } } }; @@ -194,11 +207,11 @@ impl MacroInput { lifetimes: None, bounded_ty: if rebrand { self.options.branded_type.clone().unwrap_or_else(|| { - parse_quote!(<#type_name as GcRebrand<'new_gc, Id>>::Branded) + 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 GcErase<'min, Id>>::Erased) + parse_quote!(<#type_name as #zerogc_crate::GcErase<'min, Id>>::Erased) }) }, colon_token: Default::default(), @@ -223,7 +236,7 @@ impl MacroInput { */ generics.make_where_clause().predicates .extend(self.bounds.trace_where_clause(&self.params).predicates); - generics.params.push(parse_quote!(Id: CollectorId)); + generics.params.push(parse_quote!(Id: #zerogc_crate::CollectorId)); if rebrand { generics.params.push(parse_quote!('new_gc)); } else { @@ -231,9 +244,9 @@ impl MacroInput { } let (impl_generics, _, where_clause) = generics.split_for_impl(); let target_trait = if rebrand { - quote!(GcRebrand<'new_gc, Id>) + quote!(#zerogc_crate::GcRebrand<'new_gc, Id>) } else { - quote!(GcErase<'min, Id>) + quote!(#zerogc_crate::GcErase<'min, Id>) }; fn rewrite_brand_trait( target: &Type, trait_name: &str, target_params: &HashSet, @@ -265,7 +278,7 @@ impl MacroInput { rewrite_brand_trait( &self.target_type, "GcRebrand", &target_params, - parse_quote!(GcRebrand<'new_gc, Id>), + parse_quote!(#zerogc_crate::GcRebrand<'new_gc, Id>), parse_quote!(Branded) ) }, Ok)?; @@ -275,7 +288,7 @@ impl MacroInput { rewrite_brand_trait( &self.target_type, "GcErase", &target_params, - parse_quote!(GcErase<'min, Id>), + parse_quote!(#zerogc_crate::GcErase<'min, Id>), parse_quote!(Erased) ) })?; @@ -489,21 +502,24 @@ pub struct CustomBounds { } 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!(Trace)] + 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!(TraceImmutable)] + 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!(GcSafe)] + vec![parse_quote!(#zerogc_crate::GcSafe)] ); if self.gcsafe.is_none() { // Extend with the trae bounds diff --git a/src/manually_traced/core.rs b/src/manually_traced/core.rs index 508c1ba..4ee7c68 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -7,7 +7,7 @@ use core::num::Wrapping; use crate::prelude::*; -use crate::{GcDirectBarrier, CollectorId}; +use crate::GcDirectBarrier; use zerogc_derive::unsafe_gc_impl; diff --git a/src/manually_traced/stdlib.rs b/src/manually_traced/stdlib.rs index 7e88efc..f3d8d20 100644 --- a/src/manually_traced/stdlib.rs +++ b/src/manually_traced/stdlib.rs @@ -6,7 +6,6 @@ use crate::prelude::*; use std::collections::{HashMap, HashSet}; -use crate::CollectorId; use zerogc_derive::unsafe_gc_impl; From 9b817f70362877f1cd2a37179a7673f6570dcf90 Mon Sep 17 00:00:00 2001 From: Techcable Date: Fri, 22 Jan 2021 16:21:03 -0700 Subject: [PATCH 5/7] Implement Trace/GcBrand for **all** tuple sizes Before the macors weren't recursing properly, so we only implemented it for 9-argument tuples ^_^ Add a test to verify that varius `core` types actually implement `Trace` properly. Unfortunately, the tests don't compile :( --- src/manually_traced/core.rs | 47 +++++++++++++++++++++++++++++------ src/manually_traced/stdlib.rs | 4 +-- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/manually_traced/core.rs b/src/manually_traced/core.rs index 4ee7c68..7be494a 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -11,20 +11,25 @@ use crate::GcDirectBarrier; use zerogc_derive::unsafe_gc_impl; -macro_rules! __rec_trace_tuple { - ($($param:ident),*) => { - // Nothing remaining +macro_rules! trace_tuple { + { $single_param:ident } => { + trace_tuple_impl!($single_param); }; - ($first_param:ident, $($param:ident),+) => { - trace_tuple!($($param),*); + { $first_param:ident, $($param:ident),* } => { + trace_tuple! { $($param),* } + trace_tuple_impl!( $first_param, $($param),*); }; } -macro_rules! trace_tuple { - { $($param:ident),* } => { - __rec_trace_tuple!($($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, @@ -276,4 +281,30 @@ unsafe_gc_impl! { // We can trace `Wrapping` by simply tracing its interior visitor.#visit_func(#b self.0) } +} + +#[cfg(test)] +mod test { + use crate::dummy_impl::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)] + 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/stdlib.rs b/src/manually_traced/stdlib.rs index f3d8d20..f5c08e5 100644 --- a/src/manually_traced/stdlib.rs +++ b/src/manually_traced/stdlib.rs @@ -3,12 +3,12 @@ //! 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 zerogc_derive::unsafe_gc_impl; +use crate::prelude::*; + unsafe_gc_impl! { target => HashMap, From 5cc53f6ec8564493c30a1258cb2115df0c18d66a Mon Sep 17 00:00:00 2001 From: Techcable Date: Fri, 22 Jan 2021 16:46:33 -0700 Subject: [PATCH 6/7] Add #[zerogc(collector_id(DummyCollectorId))] marker to core::test::Rec Fixes test :) --- src/manually_traced/core.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/manually_traced/core.rs b/src/manually_traced/core.rs index 7be494a..e4b73b1 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -285,7 +285,7 @@ unsafe_gc_impl! { #[cfg(test)] mod test { - use crate::dummy_impl::Gc; + use crate::dummy_impl::{DummyCollectorId, Gc}; use zerogc_derive::Trace; use crate::prelude::*; #[test] @@ -294,6 +294,7 @@ mod test { 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>), From 2785d75ba9216b65800b1f68059f145288de898a Mon Sep 17 00:00:00 2001 From: Techcable Date: Fri, 22 Jan 2021 16:53:16 -0700 Subject: [PATCH 7/7] Implement Trace for unit tuple '()' Again, this is a problem with macro recursion ^_^ --- src/manually_traced/core.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/manually_traced/core.rs b/src/manually_traced/core.rs index e4b73b1..8b6222b 100644 --- a/src/manually_traced/core.rs +++ b/src/manually_traced/core.rs @@ -13,6 +13,7 @@ use zerogc_derive::unsafe_gc_impl; macro_rules! trace_tuple { { $single_param:ident } => { + trace_tuple_impl!(); trace_tuple_impl!($single_param); }; { $first_param:ident, $($param:ident),* } => {