Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions docs/tutorial/code/mygc_semispace/gc_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,29 @@ impl<VM:VMBinding> ProcessEdgesWork for MyGCProcessEdges<VM> {
Self { base, plan }
}

#[inline]
#[inline(always)] // Ensure this function is always inlined because it is very hot.
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
if object.is_null() {
return object;
}
let worker = self.worker();
let queue = &mut self.base.nodes;
if self.plan.tospace().in_space(object) {
self.plan.tospace().trace_object::<Self>(
self,
self.plan.tospace().trace_object(
queue,
object,
Some(CopySemantics::DefaultCopy),
self.worker(),
worker,
)
} else if self.plan.fromspace().in_space(object) {
self.plan.fromspace().trace_object::<Self>(
self,
self.plan.fromspace().trace_object(
queue,
object,
Some(CopySemantics::DefaultCopy),
self.worker(),
worker,
)
} else {
self.plan.common.trace_object::<Self>(self, object)
self.plan.common.trace_object(queue, object)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/tutorial/src/mygc/ss/exercise_solution.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ In `gc_work.rs`:
1. Add the youngspace to trace_object, following the same format as
the tospace and fromspace:
```rust
#[inline(always)] // Ensure this function is always inlined because it is very hot.
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
if object.is_null() {
return object;
Expand Down
6 changes: 3 additions & 3 deletions macros/src/plan_trace_object_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) fn generate_trace_object<'a>(

quote! {
if self.#f_ident.in_space(__mmtk_objref) {
return <#f_ty as PolicyTraceObject #ty_generics>::trace_object::<T, KIND>(&self.#f_ident, __mmtk_trace, __mmtk_objref, #copy, __mmtk_worker);
return <#f_ty as PolicyTraceObject #ty_generics>::trace_object::<Q, KIND>(&self.#f_ident, __mmtk_queue, __mmtk_objref, #copy, __mmtk_worker);
}
}
});
Expand All @@ -44,7 +44,7 @@ pub(crate) fn generate_trace_object<'a>(
let f_ident = f.ident.as_ref().unwrap();
let ref f_ty = f.ty;
quote! {
<#f_ty as PlanTraceObject #ty_generics>::trace_object::<T, KIND>(&self.#f_ident, __mmtk_trace, __mmtk_objref, __mmtk_worker)
<#f_ty as PlanTraceObject #ty_generics>::trace_object::<Q, KIND>(&self.#f_ident, __mmtk_queue, __mmtk_objref, __mmtk_worker)
}
} else {
quote! {
Expand All @@ -54,7 +54,7 @@ pub(crate) fn generate_trace_object<'a>(

quote! {
#[inline(always)]
fn trace_object<T: crate::plan::TransitiveClosure, const KIND: crate::policy::gc_work::TraceKind>(&self, __mmtk_trace: &mut T, __mmtk_objref: crate::util::ObjectReference, __mmtk_worker: &mut crate::scheduler::GCWorker<VM>) -> crate::util::ObjectReference {
fn trace_object<Q: crate::plan::ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(&self, __mmtk_queue: &mut Q, __mmtk_objref: crate::util::ObjectReference, __mmtk_worker: &mut crate::scheduler::GCWorker<VM>) -> crate::util::ObjectReference {
use crate::policy::space::Space;
use crate::policy::gc_work::PolicyTraceObject;
use crate::plan::PlanTraceObject;
Expand Down
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ pub mod util;
pub mod vm;

pub use crate::plan::{
AllocationSemantics, BarrierSelector, Mutator, MutatorContext, Plan, TraceLocal,
TransitiveClosure,
AllocationSemantics, BarrierSelector, Mutator, MutatorContext, ObjectQueue, Plan,
};
pub use crate::policy::copy_context::PolicyCopyContext;
5 changes: 4 additions & 1 deletion src/plan/generational/gc_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ impl<VM: VMBinding> ProcessEdgesWork for GenNurseryProcessEdges<VM> {
if object.is_null() {
return object;
}
self.gen.trace_object_nursery(self, object, self.worker())
// We cannot borrow `self` twice in a call, so we extract `worker` as a local variable.
let worker = self.worker();
self.gen
.trace_object_nursery(&mut self.base.nodes, object, worker)
}
#[inline]
fn process_edge(&mut self, slot: Address) {
Expand Down
22 changes: 11 additions & 11 deletions src/plan/generational/global.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::plan::global::CommonPlan;
use crate::plan::ObjectQueue;
use crate::plan::Plan;
use crate::plan::PlanConstraints;
use crate::plan::TransitiveClosure;
use crate::policy::copyspace::CopySpace;
use crate::policy::space::Space;
use crate::scheduler::*;
Expand Down Expand Up @@ -178,42 +178,42 @@ impl<VM: VMBinding> Gen<VM> {
}

/// Trace objects for spaces in generational and common plans for a full heap GC.
pub fn trace_object_full_heap<T: TransitiveClosure>(
pub fn trace_object_full_heap<Q: ObjectQueue>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we mark this as #[inline(always)]? Also the trace_object_nursery function below as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried. However, if I mark this as #[inline(always)], the Rust compiler will decide not to inline another call inside this function. Then I marked another function as #[inline(always)], and then the Rust compiler will again decide not to inline yet another function. And this went on and on. So I decided to fix the inlining problem later, not in this PR, because the performance of generational GC doesn't seem to be affected by this PR.

&self,
trace: &mut T,
queue: &mut Q,
object: ObjectReference,
worker: &mut GCWorker<VM>,
) -> ObjectReference {
if self.nursery.in_space(object) {
return self.nursery.trace_object::<T>(
trace,
return self.nursery.trace_object::<Q>(
queue,
object,
Some(CopySemantics::PromoteToMature),
worker,
);
}
self.common.trace_object::<T>(trace, object)
self.common.trace_object::<Q>(queue, object)
}

/// Trace objects for spaces in generational and common plans for a nursery GC.
pub fn trace_object_nursery<T: TransitiveClosure>(
pub fn trace_object_nursery<Q: ObjectQueue>(
&self,
trace: &mut T,
queue: &mut Q,
object: ObjectReference,
worker: &mut GCWorker<VM>,
) -> ObjectReference {
// Evacuate nursery objects
if self.nursery.in_space(object) {
return self.nursery.trace_object::<T>(
trace,
return self.nursery.trace_object::<Q>(
queue,
object,
Some(CopySemantics::PromoteToMature),
worker,
);
}
// We may alloc large object into LOS as nursery objects. Trace them here.
if self.common.get_los().in_space(object) {
return self.common.get_los().trace_object::<T>(trace, object);
return self.common.get_los().trace_object::<Q>(queue, object);
}
object
}
Expand Down
28 changes: 14 additions & 14 deletions src/plan/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::gc_requester::GCRequester;
use super::PlanConstraints;
use crate::mmtk::MMTK;
use crate::plan::generational::global::Gen;
use crate::plan::transitive_closure::TransitiveClosure;
use crate::plan::tracing::ObjectQueue;
use crate::plan::Mutator;
use crate::policy::immortalspace::ImmortalSpace;
use crate::policy::largeobjectspace::LargeObjectSpace;
Expand Down Expand Up @@ -601,33 +601,33 @@ impl<VM: VMBinding> BasePlan<VM> {
pages
}

pub fn trace_object<T: TransitiveClosure>(
pub fn trace_object<Q: ObjectQueue>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it is helpful to mark this as #[cold] or #[inline(never)]. This may reduce the inlined code size of the hot trace_object calls.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It may reduce the inline code size. But I am not sure if we should mark it as #[cold] or #[inline(never)].

  1. The VM space (if exists) may not be cold.
  2. If in_space is cheap and simple, inlining it should help skipping objects that are not in those spaces.

&self,
_trace: &mut T,
_queue: &mut Q,
_object: ObjectReference,
) -> ObjectReference {
#[cfg(feature = "code_space")]
if self.code_space.in_space(_object) {
trace!("trace_object: object in code space");
return self.code_space.trace_object::<T>(_trace, _object);
return self.code_space.trace_object::<Q>(_queue, _object);
}

#[cfg(feature = "code_space")]
if self.code_lo_space.in_space(_object) {
trace!("trace_object: object in large code space");
return self.code_lo_space.trace_object::<T>(_trace, _object);
return self.code_lo_space.trace_object::<Q>(_queue, _object);
}

#[cfg(feature = "ro_space")]
if self.ro_space.in_space(_object) {
trace!("trace_object: object in ro_space space");
return self.ro_space.trace_object(_trace, _object);
return self.ro_space.trace_object(_queue, _object);
}

#[cfg(feature = "vm_space")]
if self.vm_space.in_space(_object) {
trace!("trace_object: object in boot space");
return self.vm_space.trace_object(_trace, _object);
return self.vm_space.trace_object(_queue, _object);
}
panic!("No special case for space in trace_object({:?})", _object);
}
Expand Down Expand Up @@ -903,20 +903,20 @@ impl<VM: VMBinding> CommonPlan<VM> {
self.immortal.reserved_pages() + self.los.reserved_pages() + self.base.get_used_pages()
}

pub fn trace_object<T: TransitiveClosure>(
pub fn trace_object<Q: ObjectQueue>(
&self,
trace: &mut T,
queue: &mut Q,
object: ObjectReference,
) -> ObjectReference {
if self.immortal.in_space(object) {
trace!("trace_object: object in immortal space");
return self.immortal.trace_object(trace, object);
return self.immortal.trace_object(queue, object);
}
if self.los.in_space(object) {
trace!("trace_object: object in los");
return self.los.trace_object(trace, object);
return self.los.trace_object(queue, object);
}
self.base.trace_object::<T>(trace, object)
self.base.trace_object::<Q>(queue, object)
}

pub fn prepare(&mut self, tls: VMWorkerThread, full_heap: bool) {
Expand Down Expand Up @@ -974,9 +974,9 @@ pub trait PlanTraceObject<VM: VMBinding> {
/// * `trace`: the current transitive closure
/// * `object`: the object to trace. This is a non-nullable object reference.
/// * `worker`: the GC worker that is tracing this object.
fn trace_object<T: TransitiveClosure, const KIND: TraceKind>(
fn trace_object<Q: ObjectQueue, const KIND: TraceKind>(
&self,
trace: &mut T,
queue: &mut Q,
object: ObjectReference,
worker: &mut GCWorker<VM>,
) -> ObjectReference;
Expand Down
7 changes: 2 additions & 5 deletions src/plan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,8 @@ mod plan_constraints;
pub use plan_constraints::PlanConstraints;
pub use plan_constraints::DEFAULT_PLAN_CONSTRAINTS;

mod tracelocal;
pub use tracelocal::TraceLocal;

mod transitive_closure;
pub use transitive_closure::{ObjectsClosure, TransitiveClosure};
mod tracing;
pub use tracing::{ObjectQueue, ObjectsClosure, VectorObjectQueue};

mod generational;
mod immix;
Expand Down
32 changes: 0 additions & 32 deletions src/plan/tracelocal.rs

This file was deleted.

62 changes: 50 additions & 12 deletions src/plan/transitive_closure.rs → src/plan/tracing.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! The fundamental mechanism for performing a transitive closure over an object graph.
//! This module contains code useful for tracing,
//! i.e. visiting the reachable objects by traversing all or part of an object graph.

use std::mem;

Expand All @@ -7,19 +8,56 @@ use crate::scheduler::{GCWorker, WorkBucketStage};
use crate::util::{Address, ObjectReference};
use crate::vm::EdgeVisitor;

/// This trait is the fundamental mechanism for performing a
/// transitive closure over an object graph.
pub trait TransitiveClosure {
// The signature of this function changes during the port
// because the argument `ObjectReference source` is never used in the original version
// See issue #5
fn process_node(&mut self, object: ObjectReference);
/// This trait represents an object queue to enqueue objects during tracing.
pub trait ObjectQueue {
/// Enqueue an object into the queue.
fn enqueue(&mut self, object: ObjectReference);
}

impl<T: ProcessEdgesWork> TransitiveClosure for T {
#[inline]
fn process_node(&mut self, object: ObjectReference) {
ProcessEdgesWork::process_node(self, object);
/// An implementation of `ObjectQueue` using a `Vec`.
pub struct VectorObjectQueue {
/// Enqueued nodes.
nodes: Vec<ObjectReference>,
}

impl VectorObjectQueue {
/// Reserve a capacity of this on first enqueue to avoid frequent resizing.
const CAPACITY: usize = 4096;

/// Create an empty `VectorObjectQueue`.
pub fn new() -> Self {
Self { nodes: Vec::new() }
}

/// Return `true` if the queue is empty.
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}

/// Return the contents of the underlying vector. It will empty the queue.
pub fn take(&mut self) -> Vec<ObjectReference> {
std::mem::take(&mut self.nodes)
}

/// Consume this `VectorObjectQueue` and return its underlying vector.
pub fn into_vec(self) -> Vec<ObjectReference> {
self.nodes
}
}

impl Default for VectorObjectQueue {
fn default() -> Self {
Self::new()
}
}

impl ObjectQueue for VectorObjectQueue {
#[inline(always)]
fn enqueue(&mut self, object: ObjectReference) {
if self.nodes.is_empty() {
self.nodes.reserve(Self::CAPACITY);
}
self.nodes.push(object);
}
}

Expand Down
Loading