Skip to content

Commit

Permalink
match space and call scan_object() (similar to trace_object())
Browse files Browse the repository at this point in the history
  • Loading branch information
qinsoon committed Apr 29, 2022
1 parent b7adb2d commit 74a71de
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 102 deletions.
52 changes: 22 additions & 30 deletions macros/trace_object/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ mod util;
/// * add `#[fallback_trace]` to the parent plan if the plan is composed with other plans (or parent plans).
/// For example, `GenImmix` is composed with `Gen`, `Gen` is composed with `CommonPlan`, `CommonPlan` is composed
/// with `BasePlan`.
/// * (optional) add `#[scan_work]` to _one_ space field in the plan. `create_scan_work` will be generated based
/// on the space.
/// * add `#[policy_scan]` to any space field that has some policy-specific scan_object(). For objects in those spaces,
/// `scan_object()` in the policy will be called. For other objects, directly call `VM::VMScanning::scan_object()`.
#[proc_macro_error]
#[proc_macro_derive(PlanTraceObject, attributes(trace, scan_work, copy, fallback_trace))]
#[proc_macro_derive(PlanTraceObject, attributes(trace, policy_scan, copy, fallback_trace))]
pub fn derive_plan_trace_object(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = input.ident;
Expand All @@ -34,19 +34,19 @@ pub fn derive_plan_trace_object(input: TokenStream) -> TokenStream {
..
}) = input.data {
let spaces = util::get_fields_with_attribute(fields, "trace");
let scan_work = util::get_unique_field_with_attribute(fields, "scan_work");
let scan_spaces = util::get_fields_with_attribute(fields, "policy_scan");
let fallback = util::get_unique_field_with_attribute(fields, "fallback_trace");

let trace_object_function = generate_trace_object(&spaces, &fallback, &ty_generics);
let create_scan_work_function = generate_create_scan_work(&spaces, &scan_work, &ty_generics);
let scan_object_function = generate_scan_object(&scan_spaces, &ty_generics);
let may_move_objects_function = generate_may_move_objects(&spaces, &fallback, &ty_generics);
quote!{
impl #impl_generics crate::plan::transitive_closure::PlanTraceObject #ty_generics for #ident #ty_generics #where_clause {
#[inline(always)]
#trace_object_function

#[inline(always)]
#create_scan_work_function
#scan_object_function

#[inline(always)]
#may_move_objects_function
Expand Down Expand Up @@ -121,40 +121,32 @@ fn generate_trace_object<'a>(
}
}

fn generate_create_scan_work<'a>(
space_fields: &[&'a Field],
scan_work_field: &Option<&'a Field>,
fn generate_scan_object<'a>(
scan_object_fields: &[&'a Field],
ty_generics: &TypeGenerics,
) -> TokenStream2 {
if let Some(f) = scan_work_field {
// If the plan names a field for scan work, use it
let scan_field_handler = scan_object_fields.iter().map(|f| {
let f_ident = f.ident.as_ref().unwrap();
let ref f_ty = f.ty;

quote! {
fn create_scan_work<E: crate::scheduler::gc_work::ProcessEdgesWork<VM = VM>>(&'static self, nodes: Vec<crate::util::ObjectReference>) -> Box<dyn crate::scheduler::GCWork<VM>> {
if self.#f_ident.in_space(__mmtk_objref) {
use crate::policy::gc_work::PolicyTraceObject;
<#f_ty as PolicyTraceObject #ty_generics>::create_scan_work::<E>(&self.#f_ident, nodes)
<#f_ty as PolicyTraceObject #ty_generics>::scan_object::<EV>(&self.#f_ident, __mmtk_worker_tls, __mmtk_objref, __mmtk_ev);
return;
}
}
} else if !space_fields.is_empty() {
// If the plan does not name a specific field for scan work, just use the first space for scan work
let f = space_fields[0];
let f_ident = f.ident.as_ref().unwrap();
let ref f_ty = f.ty;
});

quote! {
fn create_scan_work<E: crate::scheduler::gc_work::ProcessEdgesWork<VM = VM>>(&'static self, nodes: Vec<crate::util::ObjectReference>) -> Box<dyn crate::scheduler::GCWork<VM>> {
use crate::policy::gc_work::PolicyTraceObject;
<#f_ty as PolicyTraceObject #ty_generics>::create_scan_work::<E>(&self.#f_ident, nodes)
}
}
} else {
// Otherwise, just panic
quote! {
fn create_scan_work<E: crate::scheduler::gc_work::ProcessEdgesWork<VM = VM>>(&'static self, nodes: Vec<crate::util::ObjectReference>) -> Box<dyn crate::scheduler::GCWork<VM>> {
panic!("Unable to create a scan work packet for the plan (the plan does not name a #[scan_work] field, or a #[trace] field")
}
quote! {
fn scan_object<EV: crate::vm::EdgeVisitor>(&self, __mmtk_worker_tls: crate::util::opaque_pointer::VMWorkerThread, __mmtk_objref: crate::util::ObjectReference, __mmtk_ev: &mut EV) {
use crate::vm::Scanning;

// Plan specific
#(#scan_field_handler)*

// Default
VM::VMScanning::scan_object(__mmtk_worker_tls, __mmtk_objref, __mmtk_ev);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/plan/generational/immix/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub struct GenImmix<VM: VMBinding> {
#[fallback_trace]
pub gen: Gen<VM>,
/// An immix space as the mature space.
#[scan_work]
#[policy_scan]
#[trace(CopySemantics::Mature)]
pub immix: ImmixSpace<VM>,
/// Whether the last GC was a defrag GC for the immix space.
Expand Down
2 changes: 1 addition & 1 deletion src/plan/immix/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use macro_trace_object::PlanTraceObject;

#[derive(PlanTraceObject)]
pub struct Immix<VM: VMBinding> {
#[scan_work]
#[policy_scan]
#[trace(CopySemantics::DefaultCopy)]
pub immix_space: ImmixSpace<VM>,
#[fallback_trace]
Expand Down
63 changes: 55 additions & 8 deletions src/plan/transitive_closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl<'a, E: ProcessEdgesWork> Drop for ObjectsClosure<'a, E> {

use crate::policy::gc_work::TraceKind;
use crate::scheduler::GCWork;
use crate::util::VMWorkerThread;
use crate::vm::VMBinding;

/// A plan that uses `PlanProcessEdges` needs to provide an implementation for this trait.
Expand All @@ -93,13 +94,15 @@ pub trait PlanTraceObject<VM: VMBinding> {
worker: &mut GCWorker<VM>,
) -> ObjectReference;

/// Create a scan objects work packet for the plan. Usually [`ScanObjects`](scheduler/gc_work/ScanObjects)
/// is used. If a plan or any policy in the plan uses a specific scan work packet, the work packet is required
/// to handle objects that is in any space in the plan.
fn create_scan_work<E: ProcessEdgesWork<VM = VM>>(
&'static self,
nodes: Vec<ObjectReference>,
) -> Box<dyn GCWork<VM>>;
/// Scan objects in the plan. It is expected that each object will be scanned by `VM::VMScanning::scan_object()`.
/// If the object is in a policy that has some policy specific behaviors for scanning (e.g. mark lines in Immix),
/// this method should also invoke those policy specific methods.
fn scan_object<EV: EdgeVisitor>(
&self,
tls: VMWorkerThread,
object: ObjectReference,
edge_visitor: &mut EV,
);

/// Whether objects in this plan may move. If any of the spaces used by the plan may move objects, this should
/// return true.
Expand Down Expand Up @@ -138,7 +141,7 @@ impl<

#[inline(always)]
fn create_scan_work(&self, nodes: Vec<ObjectReference>) -> Box<dyn GCWork<Self::VM>> {
self.plan.create_scan_work::<Self>(nodes)
Box::new(PlanScanObjects::<Self, P>::new(self.plan, nodes, false))
}

#[inline(always)]
Expand Down Expand Up @@ -185,3 +188,47 @@ impl<
&mut self.base
}
}

use std::marker::PhantomData;

/// This provides an implementation of scanning objects work. Each object will be scanned by calling `scan_object()`
/// in `PlanTraceObject`.
pub struct PlanScanObjects<
E: ProcessEdgesWork,
P: 'static + Plan<VM = E::VM> + PlanTraceObject<E::VM> + Sync,
> {
plan: &'static P,
buffer: Vec<ObjectReference>,
#[allow(dead_code)]
concurrent: bool,
phantom: PhantomData<E>,
}

impl<E: ProcessEdgesWork, P: 'static + Plan<VM = E::VM> + PlanTraceObject<E::VM> + Sync>
PlanScanObjects<E, P>
{
pub fn new(plan: &'static P, buffer: Vec<ObjectReference>, concurrent: bool) -> Self {
Self {
plan,
buffer,
concurrent,
phantom: PhantomData,
}
}
}

impl<E: ProcessEdgesWork, P: 'static + Plan<VM = E::VM> + PlanTraceObject<E::VM> + Sync>
GCWork<E::VM> for PlanScanObjects<E, P>
{
fn do_work(&mut self, worker: &mut GCWorker<E::VM>, _mmtk: &'static MMTK<E::VM>) {
trace!("PlanScanObjects");
{
let tls = worker.tls;
let mut closure = ObjectsClosure::<E>::new(worker);
for object in &self.buffer {
self.plan.scan_object(tls, *object, &mut closure);
}
}
trace!("PlanScanObjects End");
}
}
25 changes: 12 additions & 13 deletions src/policy/gc_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ pub(crate) type TraceKind = u8;
pub const DEFAULT_TRACE: u8 = u8::MAX;

use crate::plan::TransitiveClosure;
use crate::scheduler::gc_work::ProcessEdgesWork;
use crate::scheduler::GCWork;
use crate::scheduler::GCWorker;
use crate::util::copy::CopySemantics;
use crate::util::opaque_pointer::VMWorkerThread;
use crate::util::ObjectReference;
use crate::vm::EdgeVisitor;
use crate::vm::VMBinding;

/// This trait defines policy-specific behavior for tracing objects.
Expand All @@ -28,18 +28,17 @@ pub trait PolicyTraceObject<VM: VMBinding> {
worker: &mut GCWorker<VM>,
) -> ObjectReference;

/// Create a scan work packet. Note that a plan currently only uses one type
/// of the scan work packet. So a policy either uses the general `ScanObjects`
/// work, or implement their own packet. Their implementation needs to handle
/// cases that objects are not in this current space.
/// Policy-specific scan object. The implementation needs to guarantee that
/// they will call `VM::VMScanning::scan_object()` (or `Self::vm_scan_object()`) besides any space-specific work for the object.
#[inline(always)]
fn create_scan_work<E: ProcessEdgesWork<VM = VM>>(
&'static self,
nodes: Vec<ObjectReference>,
) -> Box<dyn GCWork<VM>> {
Box::new(crate::scheduler::gc_work::ScanObjects::<E>::new(
nodes, false,
))
fn scan_object<EV: EdgeVisitor>(
&self,
tls: VMWorkerThread,
object: ObjectReference,
edge_visitor: &mut EV,
) {
use crate::vm::Scanning;
VM::VMScanning::scan_object(tls, object, edge_visitor)
}

/// Return whether the policy moves objects.
Expand Down
60 changes: 12 additions & 48 deletions src/policy/immix/immixspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use super::{
chunk::{Chunk, ChunkMap, ChunkState},
defrag::Defrag,
};
use crate::plan::ObjectsClosure;
use crate::policy::gc_work::TraceKind;
use crate::policy::space::SpaceOptions;
use crate::policy::space::*;
Expand All @@ -24,7 +23,7 @@ use crate::util::{Address, ObjectReference};
use crate::vm::*;
use crate::{
plan::TransitiveClosure,
scheduler::{gc_work::ProcessEdgesWork, GCWork, GCWorkScheduler, GCWorker, WorkBucketStage},
scheduler::{GCWork, GCWorkScheduler, GCWorker, WorkBucketStage},
util::{
heap::FreeListPageResource,
opaque_pointer::{VMThread, VMWorkerThread},
Expand Down Expand Up @@ -136,13 +135,17 @@ impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for ImmixSpace
}

#[inline(always)]
fn create_scan_work<E: ProcessEdgesWork<VM = VM>>(
&'static self,
nodes: Vec<ObjectReference>,
) -> Box<dyn GCWork<VM>> {
Box::new(crate::policy::immix::ScanObjectsAndMarkLines::<E>::new(
nodes, false, self,
))
fn scan_object<EV: EdgeVisitor>(
&self,
tls: VMWorkerThread,
object: ObjectReference,
edge_visitor: &mut EV,
) {
VM::VMScanning::scan_object(tls, object, edge_visitor);
if super::MARK_LINE_AT_SCAN_TIME && !super::BLOCK_ONLY {
debug_assert!(self.in_space(object));
self.mark_lines(object);
}
}

#[inline(always)]
Expand Down Expand Up @@ -613,45 +616,6 @@ impl<VM: VMBinding> GCWork<VM> for PrepareBlockState<VM> {
}
}

/// A work packet to scan the fields of each objects and mark lines.
pub struct ScanObjectsAndMarkLines<Edges: ProcessEdgesWork> {
buffer: Vec<ObjectReference>,
#[allow(unused)]
concurrent: bool,
immix_space: &'static ImmixSpace<Edges::VM>,
}

impl<Edges: ProcessEdgesWork> ScanObjectsAndMarkLines<Edges> {
pub fn new(
buffer: Vec<ObjectReference>,
concurrent: bool,
immix_space: &'static ImmixSpace<Edges::VM>,
) -> Self {
Self {
buffer,
concurrent,
immix_space,
}
}
}

impl<E: ProcessEdgesWork> GCWork<E::VM> for ScanObjectsAndMarkLines<E> {
fn do_work(&mut self, worker: &mut GCWorker<E::VM>, _mmtk: &'static MMTK<E::VM>) {
trace!("ScanObjectsAndMarkLines");
let tls = worker.tls;
let mut closure = ObjectsClosure::<E>::new(worker);
for object in &self.buffer {
<E::VM as VMBinding>::VMScanning::scan_object(tls, *object, &mut closure);
if super::MARK_LINE_AT_SCAN_TIME
&& !super::BLOCK_ONLY
&& self.immix_space.in_space(*object)
{
self.immix_space.mark_lines(*object);
}
}
}
}

use crate::plan::Plan;
use crate::policy::copy_context::PolicyCopyContext;
use crate::util::alloc::Allocator;
Expand Down
6 changes: 5 additions & 1 deletion src/scheduler/gc_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ impl<E: ProcessEdgesWork> GCWork<E::VM> for E {
/// (such as `Space.set_copy_for_sft_trace()`, `SFT.sft_trace_object()`).
/// Some plans are not using this type, mostly due to more complex tracing. Either it is impossible to use this type, or
/// there is performance overheads for using this general trace type. In such cases, they implement their specific process edges.
// TODO: This is not used any more. Should we remove it?
pub struct SFTProcessEdges<VM: VMBinding> {
pub base: ProcessEdgesBase<VM>,
}
Expand Down Expand Up @@ -540,7 +541,10 @@ impl<VM: VMBinding> DerefMut for SFTProcessEdges<VM> {
}
}

/// Scan & update a list of object slots
/// Scan & update a list of object slots.
/// Note that this work packet does not do any policy-specific scan
/// object work (it won't call `scan_object()` in [`policy::gc_work::PolicytraceObject`]).
/// It should be used only for policies that do not have policy-specific scan_object().
pub struct ScanObjects<Edges: ProcessEdgesWork> {
buffer: Vec<ObjectReference>,
#[allow(unused)]
Expand Down

0 comments on commit 74a71de

Please sign in to comment.