Skip to content

Commit 2e29b97

Browse files
authored
Refactor derive macros and add HasSpaces trait (#934)
The main part of this PR is adding a `HasSpaces` trait that enumerates spaces in a plan. This trait can be automatically generated using derive macro and attributes, saving the effort to enumerate spaces by hand. - This PR adds a `HasSpaces` trait that enumerates spaces of a plan. Plans (including NoGC) are now required to implement this trait. - This PR implements derived macro for `HasSpaces` - The `#[trace]` and `#[fallback_trace]` attributes are replaced by the `#[space]` and `#[parent]` attributes for generality. A dedicated `#[copy_semantics(xxxxx)]` attribute is added to specify copy semantics for `trace_object`. - The manually written `Plan::get_spaces()` method is removed in favor for the automatically generated `HasSpace::for_each_space` method. - Added a `Plan::verify_side_metadata_sanity` that calls `Space::verify_side_metadata_sanity` for all spaces in the plan. This replaces the manual invocations of `Space::verify_side_metadata_sanity` in the constructors of plans. This PR also slightly refactors the mmtk-macros crate. - The `syn` dependency is one major version behind the latest. This PR bumps the version and refactors the code to adapt to this change. - `mmtk-core/macros/src/lib.rs` is cleaned up so that it only contains functions with minimum function bodies. Those functions for derive macros are required to be in the root module of the crate. Their function bodies are off-loaded to other modules. This PR is a stepping stone for #925. This PR makes it possible to enumerate spaces of a plan without resorting to unsafe global maps.
1 parent b1355a7 commit 2e29b97

File tree

24 files changed

+339
-380
lines changed

24 files changed

+339
-380
lines changed

docs/userguide/src/tutorial/code/mygc_semispace/gc_work.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl<VM: VMBinding> crate::scheduler::GCWorkContext for MyGCWorkContext2<VM> {
2323
type PlanType = MyGC<VM>;
2424
type ProcessEdgesWorkType = PlanProcessEdges<Self::VM, MyGC<VM>, DEFAULT_TRACE>;
2525
}
26-
// ANCHOR: workcontext_plan
26+
// ANCHOR_END: workcontext_plan
2727

2828
use crate::util::ObjectReference;
2929
use crate::util::copy::CopySemantics;

docs/userguide/src/tutorial/code/mygc_semispace/global.rs

+9-23
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::scheduler::*; // Modify
1414
use crate::util::alloc::allocators::AllocatorSelector;
1515
use crate::util::copy::*;
1616
use crate::util::heap::VMRequest;
17-
use crate::util::metadata::side_metadata::{SideMetadataSanity, SideMetadataContext};
17+
use crate::util::metadata::side_metadata::SideMetadataContext;
1818
use crate::util::opaque_pointer::*;
1919
use crate::vm::VMBinding;
2020
use enum_map::EnumMap;
@@ -24,18 +24,20 @@ use std::sync::atomic::{AtomicBool, Ordering}; // Add
2424
// Remove #[allow(unused_imports)].
2525
// Remove handle_user_collection_request().
2626

27-
use mmtk_macros::PlanTraceObject;
27+
use mmtk_macros::{HasSpaces, PlanTraceObject};
2828

2929
// Modify
3030
// ANCHOR: plan_def
31-
#[derive(PlanTraceObject)]
31+
#[derive(HasSpaces, PlanTraceObject)]
3232
pub struct MyGC<VM: VMBinding> {
3333
pub hi: AtomicBool,
34-
#[trace(CopySemantics::DefaultCopy)]
34+
#[space]
35+
#[copy_semantics(CopySemantics::DefaultCopy)]
3536
pub copyspace0: CopySpace<VM>,
36-
#[trace(CopySemantics::DefaultCopy)]
37+
#[space]
38+
#[copy_semantics(CopySemantics::DefaultCopy)]
3739
pub copyspace1: CopySpace<VM>,
38-
#[fallback_trace]
40+
#[parent]
3941
pub common: CommonPlan<VM>,
4042
}
4143
// ANCHOR_END: plan_def
@@ -51,8 +53,6 @@ pub const MYGC_CONSTRAINTS: PlanConstraints = PlanConstraints {
5153
// ANCHOR_END: constraints
5254

5355
impl<VM: VMBinding> Plan for MyGC<VM> {
54-
type VM = VM;
55-
5656
fn constraints(&self) -> &'static PlanConstraints {
5757
&MYGC_CONSTRAINTS
5858
}
@@ -74,15 +74,6 @@ impl<VM: VMBinding> Plan for MyGC<VM> {
7474
}
7575
// ANCHOR_END: create_copy_config
7676

77-
// ANCHOR: get_spaces
78-
fn get_spaces(&self) -> Vec<&dyn Space<Self::VM>> {
79-
let mut ret = self.common.get_spaces();
80-
ret.push(&self.copyspace0);
81-
ret.push(&self.copyspace1);
82-
ret
83-
}
84-
// ANCHOR_EN: get_spaces
85-
8677
// Modify
8778
// ANCHOR: schedule_collection
8879
fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
@@ -188,12 +179,7 @@ impl<VM: VMBinding> MyGC<VM> {
188179
common: CommonPlan::new(plan_args),
189180
};
190181

191-
// Use SideMetadataSanity to check if each spec is valid. This is also needed for check
192-
// side metadata in extreme_assertions.
193-
let mut side_metadata_sanity_checker = SideMetadataSanity::new();
194-
res.common.verify_side_metadata_sanity(&mut side_metadata_sanity_checker);
195-
res.copyspace0.verify_side_metadata_sanity(&mut side_metadata_sanity_checker);
196-
res.copyspace1.verify_side_metadata_sanity(&mut side_metadata_sanity_checker);
182+
res.verify_side_metadata_sanity();
197183

198184
res
199185
}

docs/userguide/src/tutorial/mygc/ss/alloc.md

+9-6
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,16 @@ Finished code (step 2):
6666
{{#include ../../code/mygc_semispace/global.rs:plan_def}}
6767
```
6868

69-
Note that we have attributes on some fields. These attributes tell MMTk's macros on
70-
how to generate code to trace objects in this plan. Although there are other approaches that
71-
you can implement object tracing, in this tutorial we use the macros, as it is the simplest.
72-
Make sure you import the macros. We will discuss on what those attributes mean in later sections.
69+
Note that `MyGC` now also derives `PlanTraceObject` besides `HasSpaces`, and we
70+
have attributes on some fields. These attributes tell MMTk's macros how to
71+
generate code to visit each space of this plan as well as trace objects in this
72+
plan. Although there are other approaches that you can implement object
73+
tracing, in this tutorial we use the macros, as it is the simplest. Make sure
74+
you import the macros. We will discuss on what those attributes mean in later
75+
sections.
7376

7477
```rust
75-
use mmtk_macros::PlanTraceObject;
78+
use mmtk_macros::{HasSpaces, PlanTraceObject};
7679
```
7780

7881
### Implement the Plan trait for MyGC
@@ -230,4 +233,4 @@ by the common plan:
230233

231234
With this, you should have the allocation working, but not garbage collection.
232235
Try building again. If you run HelloWorld or Fannkunchredux, they should
233-
work. DaCapo's lusearch should fail, as it requires garbage to be collected.
236+
work. DaCapo's lusearch should fail, as it requires garbage to be collected.

docs/userguide/src/tutorial/mygc/ss/collection.md

+14-6
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,19 @@ it can use `PlanProcessEdges`.
181181

182182
You can manually provide an implementation of `PlanTraceObject` for `MyGC`. But you can also use the derive macro MMTK provides,
183183
and the macro will generate an implementation of `PlanTraceObject`:
184-
* add `#[derive(PlanTraceObject)]` for `MyGC` (import the macro properly: `use mmtk_macros::PlanTraceObject`)
185-
* add `#[trace(CopySemantics::Default)]` to both copy space fields, `copyspace0` and `copyspace1`. This tells the macro to generate
186-
trace code for both spaces, and for any copying in the spaces, use `CopySemantics::DefaultCopy` that we have configured early.
187-
* add `#[fallback_trace]` to `common`. This tells the macro that if an object is not found in any space with `#[trace]` in ths plan,
188-
try find the space for the object in the 'parent' plan. In our case, we fall back to the `CommonPlan`, as the object may be
184+
185+
* Make sure `MyGC` already has the `#[derive(HasSpaces)]` attribute because all plans need to
186+
implement the `HasSpaces` trait anyway. (import the macro properly: `use mmtk_macros::HasSpaces`)
187+
* Add `#[derive(PlanTraceObject)]` for `MyGC` (import the macro properly: `use mmtk_macros::PlanTraceObject`)
188+
* Add both `#[space]` and `#[copy_semantics(CopySemantics::Default)]` to both copy space fields,
189+
`copyspace0` and `copyspace1`. `#[space]` tells the macro that both `copyspace0` and `copyspace1`
190+
are spaces in the `MyGC` plan, and the generated trace code will check both spaces.
191+
`#[copy_semantics(CopySemantics::DefaultCopy)]` specifies the copy semantics to use when tracing
192+
objects in the corresponding space.
193+
* Add `#[parent]` to `common`. This tells the macro that there are more spaces defined in `common`
194+
and its nested structs. If an object is not found in any space with `#[space]` in this plan,
195+
the trace code will try to find the space for the object in the 'parent' plan. In our case, the
196+
trace code will proceed by checking spaces in the `CommonPlan`, as the object may be
189197
in large object space or immortal space in the common plan. `CommonPlan` also implements `PlanTraceObject`, so it knows how to
190198
find a space for the object and trace it in the same way.
191199

@@ -238,7 +246,7 @@ In the end, use `MyGCProcessEdges` as `ProcessEdgesWorkType` in the `GCWorkConte
238246
## Summary
239247

240248
You should now have MyGC working and able to collect garbage. All three
241-
benchmarks should be able to pass now.
249+
benchmarks should be able to pass now.
242250

243251
If the benchmarks pass - good job! You have built a functional copying
244252
collector!

macros/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ proc-macro = true
1313

1414
[dependencies]
1515
proc-macro2 = "1.0.37"
16-
syn = { version = "1.0.91", features = ["extra-traits"] }
16+
syn = { version = "2.0.29", features = ["extra-traits"] }
1717
quote = "1.0.18"
1818
proc-macro-error = "1.0.4"

macros/src/has_spaces_impl.rs

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use proc_macro2::TokenStream as TokenStream2;
2+
use proc_macro_error::abort_call_site;
3+
use quote::quote;
4+
use syn::{DeriveInput, Field};
5+
6+
use crate::util;
7+
8+
pub(crate) fn derive(input: DeriveInput) -> TokenStream2 {
9+
let ident = input.ident;
10+
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
11+
12+
let syn::Data::Struct(syn::DataStruct {
13+
fields: syn::Fields::Named(ref fields),
14+
..
15+
}) = input.data else {
16+
abort_call_site!("`#[derive(HasSpaces)]` only supports structs with named fields.");
17+
};
18+
19+
let spaces = util::get_fields_with_attribute(fields, "space");
20+
let parent = util::get_unique_field_with_attribute(fields, "parent");
21+
22+
let items = generate_impl_items(&spaces, &parent);
23+
24+
quote! {
25+
impl #impl_generics crate::plan::HasSpaces for #ident #ty_generics #where_clause {
26+
type VM = VM;
27+
28+
#items
29+
}
30+
}
31+
}
32+
33+
pub(crate) fn generate_impl_items<'a>(
34+
space_fields: &[&'a Field],
35+
parent_field: &Option<&'a Field>,
36+
) -> TokenStream2 {
37+
// Currently we implement callback-style visitor methods.
38+
// Iterators should be more powerful, but is more difficult to implement.
39+
40+
let mut space_visitors = vec![];
41+
let mut space_visitors_mut = vec![];
42+
43+
for f in space_fields {
44+
let f_ident = f.ident.as_ref().unwrap();
45+
46+
let visitor = quote! {
47+
__func(&self.#f_ident);
48+
};
49+
50+
let visitor_mut = quote! {
51+
__func(&mut self.#f_ident);
52+
};
53+
54+
space_visitors.push(visitor);
55+
space_visitors_mut.push(visitor_mut);
56+
}
57+
58+
let (parent_visitor, parent_visitor_mut) = if let Some(f) = parent_field {
59+
let f_ident = f.ident.as_ref().unwrap();
60+
let visitor = quote! {
61+
self.#f_ident.for_each_space(__func)
62+
};
63+
let visitor_mut = quote! {
64+
self.#f_ident.for_each_space_mut(__func)
65+
};
66+
(visitor, visitor_mut)
67+
} else {
68+
(quote! {}, quote! {})
69+
};
70+
71+
quote! {
72+
fn for_each_space(&self, __func: &mut dyn FnMut(&dyn Space<VM>)) {
73+
#(#space_visitors)*
74+
#parent_visitor
75+
}
76+
77+
fn for_each_space_mut(&mut self, __func: &mut dyn FnMut(&mut dyn Space<VM>)) {
78+
#(#space_visitors_mut)*
79+
#parent_visitor_mut
80+
}
81+
}
82+
}

macros/src/lib.rs

+41-47
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,60 @@ extern crate quote;
44
extern crate syn;
55

66
use proc_macro::TokenStream;
7-
use proc_macro_error::abort_call_site;
87
use proc_macro_error::proc_macro_error;
9-
use quote::quote;
108
use syn::parse_macro_input;
119
use syn::DeriveInput;
1210

11+
mod has_spaces_impl;
1312
mod plan_trace_object_impl;
1413
mod util;
1514

1615
const DEBUG_MACRO_OUTPUT: bool = false;
1716

18-
/// Generally a plan needs to add these attributes in order for the macro to work. The macro will
19-
/// generate an implementation of `PlanTraceObject` for the plan. With `PlanTraceObject`, the plan use
20-
/// `PlanProcessEdges` for GC tracing. The attributes only affects code generation in the macro, thus
21-
/// only affects the generated `PlanTraceObject` implementation.
22-
/// * add `#[derive(PlanTraceObject)]` to the plan struct.
23-
/// * add `#[trace]` to each space field the plan struct has. If the policy is a copying policy,
24-
/// it needs to further specify the copy semantic (`#[trace(CopySemantics::X)]`)
25-
/// * add `#[fallback_trace]` to the parent plan if the plan is composed with other plans (or parent plans).
26-
/// For example, `GenImmix` is composed with `Gen`, `Gen` is composed with `CommonPlan`, `CommonPlan` is composed
27-
/// with `BasePlan`.
28-
/// * add `#[post_scan]` to any space field that has some policy-specific post_scan_object(). For objects in those spaces,
29-
/// `post_scan_object()` in the policy will be called after `VM::VMScanning::scan_object()`.
17+
/// This macro will generate an implementation of `HasSpaces` for a plan or any structs that
18+
/// contain spaces, including `Gen`, `CommonPlan` and `BasePlan`.
19+
///
20+
/// The `HasSpaces` trait is responsible for enumerating spaces in a struct. When using this
21+
/// derive macro, the user should do the following.
22+
///
23+
/// * Make sure the struct has a generic type parameter named `VM` which requires `VMBinding`.
24+
/// For example, `struct MyPlan<VM: VMBinding>` will work.
25+
/// * Add `#[space]` for each space field in the struct.
26+
/// * Add `#[parent]` to the field that contain more space fields. This attribute is usually
27+
/// added to `Gen`, `CommonPlan` or `BasePlan` fields. There can be at most one parent in
28+
/// a struct.
3029
#[proc_macro_error]
31-
#[proc_macro_derive(PlanTraceObject, attributes(trace, post_scan, fallback_trace))]
32-
pub fn derive_plan_trace_object(input: TokenStream) -> TokenStream {
30+
#[proc_macro_derive(HasSpaces, attributes(space, parent))]
31+
pub fn derive_has_spaces(input: TokenStream) -> TokenStream {
3332
let input = parse_macro_input!(input as DeriveInput);
34-
let ident = input.ident;
35-
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
36-
37-
let output = if let syn::Data::Struct(syn::DataStruct {
38-
fields: syn::Fields::Named(ref fields),
39-
..
40-
}) = input.data
41-
{
42-
let spaces = util::get_fields_with_attribute(fields, "trace");
43-
let post_scan_spaces = util::get_fields_with_attribute(fields, "post_scan");
44-
let fallback = util::get_unique_field_with_attribute(fields, "fallback_trace");
33+
let output = has_spaces_impl::derive(input);
4534

46-
let trace_object_function =
47-
plan_trace_object_impl::generate_trace_object(&spaces, &fallback, &ty_generics);
48-
let post_scan_object_function = plan_trace_object_impl::generate_post_scan_object(
49-
&post_scan_spaces,
50-
&fallback,
51-
&ty_generics,
52-
);
53-
let may_move_objects_function =
54-
plan_trace_object_impl::generate_may_move_objects(&spaces, &fallback, &ty_generics);
55-
quote! {
56-
impl #impl_generics crate::plan::PlanTraceObject #ty_generics for #ident #ty_generics #where_clause {
57-
#trace_object_function
58-
59-
#post_scan_object_function
35+
output.into()
36+
}
6037

61-
#may_move_objects_function
62-
}
63-
}
64-
} else {
65-
abort_call_site!("`#[derive(PlanTraceObject)]` only supports structs with named fields.")
66-
};
38+
/// The macro will generate an implementation of `PlanTraceObject` for the plan. With
39+
/// `PlanTraceObject`, the plan will be able to use `PlanProcessEdges` for GC tracing.
40+
///
41+
/// The user should add `#[space]` and `#[parent]` attributes to fields as specified by the
42+
/// `HasSpaces` trait. When using this derive macro, all spaces must implement the
43+
/// `PolicyTraceObject` trait. The generated `trace_object` method will check for spaces in the
44+
/// current plan and, if the object is not in any of them, check for plans in the parent struct.
45+
/// The parent struct must also implement the `PlanTraceObject` trait.
46+
///
47+
/// In addition, the user can add the following attributes to fields in order to control the
48+
/// behavior of the generated `trace_object` method.
49+
///
50+
/// * Add `#[copy_semantics(CopySemantics::X)]` to a space field to specify that when tracing
51+
/// objects in that space, `Some(CopySemantics::X)` will be passed to the `Space::trace_object`
52+
/// method as the `copy` argument.
53+
/// * Add `#[post_scan]` to any space field that has some policy-specific `post_scan_object()`. For
54+
/// objects in those spaces, `post_scan_object()` in the policy will be called after
55+
/// `VM::VMScanning::scan_object()`.
56+
#[proc_macro_error]
57+
#[proc_macro_derive(PlanTraceObject, attributes(space, parent, copy_semantics, post_scan))]
58+
pub fn derive_plan_trace_object(input: TokenStream) -> TokenStream {
59+
let input = parse_macro_input!(input as DeriveInput);
60+
let output = plan_trace_object_impl::derive(input);
6761

6862
// Debug the output - use the following code to debug the generated code (when cargo exapand is not working)
6963
if DEBUG_MACRO_OUTPUT {

0 commit comments

Comments
 (0)