Skip to content

Commit 93281e9

Browse files
authored
Use macro to generate process edges for plans (#575)
This PR adds `PlanProcessEdges`, and a few macros/traits that are used in `PlanProcessEdges`. This PR removes policy-specific code from plan, and use macros to generate trace object work for each plan. This PR closes #258, and closes #576. * add a trait `PolicyTraceObject`. Each policy provides an implementation of this. With this trait, a plan no longer include any policy-specific code in trace object. * add a trait `PlanTraceObject` and related macros. With the macro, plan implementer can declaratively specify trace object behavior with attributes, and the implementation of `PlanTraceObject` will be generated by the macro. * add a type `PlanProcessEdges`. This uses `PlanTraceObject` to provide a general implementation of `ProcessEdgesWork`. We no longer need any plan-specific process edges work.
1 parent 3dbdd7a commit 93281e9

File tree

38 files changed

+953
-389
lines changed

38 files changed

+953
-389
lines changed

.github/workflows/cargo-publish.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,9 @@ jobs:
1919
override: true
2020
- name: Cargo login
2121
run: cargo login ${{ secrets.CI_CARGO_LOGIN }}
22-
- name: Cargo publish
23-
run: cargo publish
22+
- name: Publish sub crates
23+
run: |
24+
cargo publish --manifest-path=macros/Cargo.toml
25+
- name: Public mmtk-core
26+
run: |
27+
cargo publish

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ crate-type = ["rlib"]
1818
doctest = false
1919

2020
[dependencies]
21+
# MMTk macros
22+
mmtk-macros = { version = "0.11.0", path = "macros/" }
23+
2124
custom_derive = "0.1"
2225
enum_derive = "0.1"
2326
libc = "0.2"

docs/tutorial/code/mygc_semispace/gc_work.rs

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,95 @@ use crate::vm::VMBinding;
55
use std::ops::{Deref, DerefMut};
66
// ANCHOR_END: imports
77

8-
// ANCHOR: workcontext
8+
// ANCHOR: workcontext_sft
99
pub struct MyGCWorkContext<VM: VMBinding>(std::marker::PhantomData<VM>);
1010
impl<VM: VMBinding> crate::scheduler::GCWorkContext for MyGCWorkContext<VM> {
1111
type VM = VM;
1212
type PlanType = MyGC<VM>;
1313
type ProcessEdgesWorkType = SFTProcessEdges<Self::VM>;
1414
}
15-
// ANCHOR_END: workcontext
15+
// ANCHOR_END: workcontext_sft
16+
17+
// ANCHOR: workcontext_plan
18+
use crate::scheduler::gc_work::PlanProcessEdges;
19+
use crate::policy::gc_work::DEFAULT_TRACE;
20+
pub struct MyGCWorkContext2<VM: VMBinding>(std::marker::PhantomData<VM>);
21+
impl<VM: VMBinding> crate::scheduler::GCWorkContext for MyGCWorkContext2<VM> {
22+
type VM = VM;
23+
type PlanType = MyGC<VM>;
24+
type ProcessEdgesWorkType = PlanProcessEdges<Self::VM, MyGC<VM>, DEFAULT_TRACE>;
25+
}
26+
// ANCHOR: workcontext_plan
27+
28+
use crate::util::{Address, ObjectReference};
29+
use crate::util::copy::CopySemantics;
30+
use crate::MMTK;
31+
use crate::policy::space::Space;
32+
33+
// ANCHOR: mygc_process_edges
34+
pub struct MyGCProcessEdges<VM: VMBinding> {
35+
plan: &'static MyGC<VM>,
36+
base: ProcessEdgesBase<VM>,
37+
}
38+
// ANCHOR_END: mygc_process_edges
39+
40+
// ANCHOR: mygc_process_edges_impl
41+
impl<VM:VMBinding> ProcessEdgesWork for MyGCProcessEdges<VM> {
42+
type VM = VM;
43+
fn new(edges: Vec<Address>, roots: bool, mmtk: &'static MMTK<VM>) -> Self {
44+
let base = ProcessEdgesBase::new(edges, roots, mmtk);
45+
let plan = base.plan().downcast_ref::<MyGC<VM>>().unwrap();
46+
Self { base, plan }
47+
}
48+
49+
#[inline]
50+
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
51+
if object.is_null() {
52+
return object;
53+
}
54+
if self.plan.tospace().in_space(object) {
55+
self.plan.tospace().trace_object::<Self>(
56+
self,
57+
object,
58+
Some(CopySemantics::DefaultCopy),
59+
self.worker(),
60+
)
61+
} else if self.plan.fromspace().in_space(object) {
62+
self.plan.fromspace().trace_object::<Self>(
63+
self,
64+
object,
65+
Some(CopySemantics::DefaultCopy),
66+
self.worker(),
67+
)
68+
} else {
69+
self.plan.common.trace_object::<Self>(self, object)
70+
}
71+
}
72+
}
73+
// ANCHOR_END: mygc_process_edges_impl
74+
75+
// ANCHOR: mygc_process_edges_deref
76+
impl<VM: VMBinding> Deref for MyGCProcessEdges<VM> {
77+
type Target = ProcessEdgesBase<VM>;
78+
#[inline]
79+
fn deref(&self) -> &Self::Target {
80+
&self.base
81+
}
82+
}
83+
84+
impl<VM: VMBinding> DerefMut for MyGCProcessEdges<VM> {
85+
#[inline]
86+
fn deref_mut(&mut self) -> &mut Self::Target {
87+
&mut self.base
88+
}
89+
}
90+
// ANCHOR_END: mygc_process_edges_deref
91+
92+
// ANCHOR: workcontext_mygc
93+
pub struct MyGCWorkContext3<VM: VMBinding>(std::marker::PhantomData<VM>);
94+
impl<VM: VMBinding> crate::scheduler::GCWorkContext for MyGCWorkContext3<VM> {
95+
type VM = VM;
96+
type PlanType = MyGC<VM>;
97+
type ProcessEdgesWorkType = MyGCProcessEdges<Self::VM>;
98+
}
99+
// ANCHOR: workcontext_mygc

docs/tutorial/code/mygc_semispace/global.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,18 @@ use std::sync::Arc;
2929
// Remove #[allow(unused_imports)].
3030
// Remove handle_user_collection_request().
3131

32+
use mmtk_macros::PlanTraceObject;
33+
3234
// Modify
3335
// ANCHOR: plan_def
36+
#[derive(PlanTraceObject)]
3437
pub struct MyGC<VM: VMBinding> {
3538
pub hi: AtomicBool,
39+
#[trace(CopySemantics::DefaultCopy)]
3640
pub copyspace0: CopySpace<VM>,
41+
#[trace(CopySemantics::DefaultCopy)]
3742
pub copyspace1: CopySpace<VM>,
43+
#[fallback_trace]
3844
pub common: CommonPlan<VM>,
3945
}
4046
// ANCHOR_END: plan_def

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

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ method `schedule_common_work()` that will add common work packets for you.
4343

4444
To use `schedule_common_work()`, first we need to create a type `MyGCWorkContext`
4545
and implement the trait `GCWorkContext` for it. We create `gc_work.rs` and add the
46-
following implementation. Note that we do not set a specific `ProcessEdgesWorkType`
47-
and we will use the default [`SFTProcessEdges`](https://www.mmtk.io/mmtk-core/mmtk/scheduler/gc_work/struct.SFTProcessEdges.html),
46+
following implementation. Note that we will use the default
47+
[`SFTProcessEdges`](https://www.mmtk.io/mmtk-core/mmtk/scheduler/gc_work/struct.SFTProcessEdges.html),
4848
which is a general work packet that a plan can use to trace objects. For plans
4949
like semispace, `SFTProcessEdges` is sufficient. For more complex GC plans,
5050
one can create and write their own work packet that implements the `ProcessEdgesWork` trait.
51+
We will discuss about this later, and discuss the alternatives.
5152

5253
```rust
53-
{{#include ../../../code/mygc_semispace/gc_work.rs:workcontext}}
54+
{{#include ../../../code/mygc_semispace/gc_work.rs:workcontext_sft}}
5455
```
5556

5657
Then we implement `schedule_collection()` using `MyGCWorkContext` and `schedule_common_work()`.
@@ -111,35 +112,6 @@ there aren't any preparation steps for the mutator in this GC.
111112
In `create_mygc_mutator()`, find the field `prep_func` and change it from
112113
`mygc_mutator_noop()` to `mygc_mutator_prepare()`.
113114

114-
115-
## Scan objects
116-
117-
Next, we'll add the code to allow the plan to collect garbage - filling out
118-
functions for work packets.
119-
120-
In `gc_work.rs`, add a new method to `ProcessEdgesWork for MyGCProcessEdges`,
121-
`trace_object(&mut self, object: ObjectReference)`.
122-
This method should return an ObjectReference, and use the
123-
inline attribute.
124-
Check if the object passed into the function is null
125-
(`object.is_null()`). If it is, return the object.
126-
Otherwise, check which space the object is in, and forward the call to the
127-
policy-specific object tracing code. If it is in neither space, forward the
128-
call to the common space and let the common space to handle object tracing in
129-
its spaces (e.g. immortal or large object space):
130-
131-
```rust
132-
{{#include ../../../code/mygc_semispace/gc_work.rs:trace_object}}
133-
```
134-
135-
Add two new implementation blocks, `Deref` and `DerefMut` for
136-
`MyGCProcessEdges`. These allow `MyGCProcessEdges` to be dereferenced to
137-
`ProcessEdgesBase`, and allows easy access to fields in `ProcessEdgesBase`.
138-
139-
```rust
140-
{{#include ../../../code/mygc_semispace/gc_work.rs:deref}}
141-
```
142-
143115
## Release
144116

145117
Finally, we need to fill out the functions that are, roughly speaking,
@@ -178,6 +150,91 @@ will then go to the new tospace.
178150
Delete `mygc_mutator_noop()`. It was a placeholder for the prepare and
179151
release functions that you have now added, so it is now dead code.
180152

153+
## ProcessEdgesWork for MyGC
154+
155+
[`ProcessEdgesWork`](https://www.mmtk.io/mmtk-core/mmtk/scheduler/gc_work/trait.ProcessEdgesWork.html)
156+
is the key work packet for tracing objects in a GC. A `ProcessEdgesWork` implementation
157+
defines how to trace objects, and how to generate more work packets based on the current tracing
158+
to finish the object closure.
159+
160+
`GCWorkContext` specifies a type
161+
that implements `ProcessEdgesWork`, and we used `SFTProcessEdges` earlier. In
162+
this section, we discuss what `SFTProcessEdges` does, and what the alternatives
163+
are.
164+
165+
### Approach 1: Use `SFTProcessEdges`
166+
167+
[`SFTProcessEdges`](https://www.mmtk.io/mmtk-core/mmtk/scheduler/gc_work/struct.SFTProcessEdges.html) dispatches
168+
the tracing of objects to their respective spaces through [Space Function Table (SFT)](https://www.mmtk.io/mmtk-core/mmtk/policy/space/trait.SFT.html).
169+
As long as all the policies in a plan provide an implementation of `sft_trace_object()` in their SFT implementations,
170+
the plan can use `SFTProcessEdges`. Currently most policies provide an implementation for `sft_trace_object()`, except
171+
mark compact and immix. Those two policies use multiple GC traces, and due to the limitation of SFT, SFT does not allow
172+
multiple `sft_trace_object()` for a policy.
173+
174+
`SFTProcessEdges` is the simplest approach when all the policies support it. Fortunately, we can use it for our GC, semispace.
175+
176+
### Approach 2: Derive `PlanTraceObject` and use `PlanProcessEdges`
177+
178+
`PlanProcessEdges` is another general `ProcessEdgesWork` implementation that can be used by most plans. When a plan
179+
implements the [`PlanTraceObject`](https://www.mmtk.io/mmtk-core/mmtk/plan/transitive_closure/trait.PlanTraceObject.html),
180+
it can use `PlanProcessEdges`.
181+
182+
You can manually provide an implementation of `PlanTraceObject` for `MyGC`. But you can also use the derive macro MMTK provides,
183+
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
189+
in large object space or immortal space in the common plan. `CommonPlan` also implements `PlanTraceObject`, so it knows how to
190+
find a space for the object and trace it in the same way.
191+
192+
With the derive macro, your `MyGC` struct should look like this:
193+
```rust
194+
{{#include ../../../code/mygc_semispace/global.rs:plan_def}}
195+
```
196+
197+
Once this is done, you can specify `PlanProcessEdges` as the `ProcessEdgesWorkType` in your GC work context:
198+
```rust
199+
{{#include ../../../code/mygc_semispace/gc_work.rs:workcontext_plan}}
200+
```
201+
202+
### Approach 3: Implement your own `ProcessEdgesWork`
203+
204+
Apart from the two approaches above, you can always implement your own `ProcessEdgesWork`. This is
205+
an overkill for simple plans like semi space, but might be necessary for more complex plans.
206+
We discuss how to implement it for `MyGC`.
207+
208+
Create a struct `MyGCProcessEdges<VM: VMBinding>` in the `gc_work` module. It includes a reference
209+
back to the plan, and a `ProcessEdgesBase` field:
210+
```rust
211+
{{#include ../../../code/mygc_semispace/gc_work.rs:mygc_process_edges}}
212+
```
213+
214+
Implement `ProcessEdgesWork` for `MyGCProcessEdges`. As most methods in the trait have a default
215+
implemetation, we only need to implement `new()` and `trace_object()` for our plan. However, this
216+
may not be true when you implement it for other GC plans. It would be better to check the default
217+
implementation of `ProcessEdgesWork`.
218+
219+
For `trace_object()`, what we do is similar to the approach above (except that we need to write the code
220+
ourselves rather than letting the macro to generate it for us). We try to figure out
221+
which space the object is in, and invoke `trace_object()` for the object on that space. If the
222+
object is not in any of the semi spaces in the plan, we forward the call to `CommonPlan`.
223+
```rust
224+
{{#include ../../../code/mygc_semispace/gc_work.rs:mygc_process_edges_impl}}
225+
```
226+
227+
We would also need to implement `Deref` and `DerefMut` to our `ProcessEdgesWork` impl to be
228+
dereferenced as `ProcessEdgesBase`.
229+
```rust
230+
{{#include ../../../code/mygc_semispace/gc_work.rs:mygc_process_edges_deref}}
231+
```
232+
233+
In the end, use `MyGCProcessEdges` as `ProcessEdgesWorkType` in the `GCWorkContext`:
234+
```rust
235+
{{#include ../../../code/mygc_semispace/gc_work.rs:workcontext_mygc}}
236+
```
237+
181238
## Summary
182239

183240
You should now have MyGC working and able to collect garbage. All three

macros/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "mmtk-macros"
3+
# the macro crate uses the same version as mmtk-core
4+
version = "0.11.0"
5+
edition = "2021"
6+
license = "MIT OR Apache-2.0"
7+
description = "MMTk macros provides procedural macros used by mmtk-core."
8+
homepage = "https://www.mmtk.io"
9+
repository = "https://github.com/mmtk/mmtk-core/tree/master/macros"
10+
11+
[lib]
12+
proc-macro = true
13+
14+
[dependencies]
15+
proc-macro2 = "1.0.37"
16+
syn = { version = "1.0.91", features = ["extra-traits"] }
17+
quote = "1.0.18"
18+
proc-macro-error = "1.0.4"

macros/src/lib.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
extern crate proc_macro;
2+
extern crate syn;
3+
extern crate proc_macro_error;
4+
extern crate quote;
5+
6+
use proc_macro::TokenStream;
7+
use proc_macro_error::proc_macro_error;
8+
use syn::{parse_macro_input};
9+
use proc_macro_error::abort_call_site;
10+
use quote::quote;
11+
use syn::DeriveInput;
12+
13+
mod util;
14+
mod plan_trace_object_impl;
15+
16+
const DEBUG_MACRO_OUTPUT: bool = false;
17+
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()`.
30+
#[proc_macro_error]
31+
#[proc_macro_derive(PlanTraceObject, attributes(trace, post_scan, fallback_trace))]
32+
pub fn derive_plan_trace_object(input: TokenStream) -> TokenStream {
33+
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+
let spaces = util::get_fields_with_attribute(fields, "trace");
42+
let post_scan_spaces = util::get_fields_with_attribute(fields, "post_scan");
43+
let fallback = util::get_unique_field_with_attribute(fields, "fallback_trace");
44+
45+
let trace_object_function = plan_trace_object_impl::generate_trace_object(&spaces, &fallback, &ty_generics);
46+
let post_scan_object_function = plan_trace_object_impl::generate_post_scan_object(&post_scan_spaces, &ty_generics);
47+
let may_move_objects_function = plan_trace_object_impl::generate_may_move_objects(&spaces, &fallback, &ty_generics);
48+
quote!{
49+
impl #impl_generics crate::plan::PlanTraceObject #ty_generics for #ident #ty_generics #where_clause {
50+
#trace_object_function
51+
52+
#post_scan_object_function
53+
54+
#may_move_objects_function
55+
}
56+
}
57+
} else {
58+
abort_call_site!("`#[derive(PlanTraceObject)]` only supports structs with named fields.")
59+
};
60+
61+
// Debug the output - use the following code to debug the generated code (when cargo exapand is not working)
62+
if DEBUG_MACRO_OUTPUT {
63+
use quote::ToTokens;
64+
println!("{}", output.to_token_stream());
65+
}
66+
67+
output.into()
68+
}

0 commit comments

Comments
 (0)