Skip to content

Commit

Permalink
Allow querying if the current GC may move objects. (#1128)
Browse files Browse the repository at this point in the history
The major changes include:

-   Adding a method `Plan::current_gc_may_move_object`.
-   Immix-based plans now reset the in-defrag state at the end of GC.

The main change of this PR is allowing querying if the current GC may
move any object.

Whether a plan moves objects or not is not a simple "yes or no"
question. It may vary in each GC.

-   Some plans, such as MarkSweep, never moves any object.
- Some plans, such as SemiSpace and MarkCompact, moves objects in every
GC.
- Some plans, such as Immix, moves object in defrag GC, while "fast" GCs
are non-moving.
- Some plans, such as Immix and StickyImmix, depends on Cargo features.
"immix_non_moving" prevents all movement, and
"sticky_immix_non_moving_nursery" prevents movement in nursery GCs.

The information of whether the current GC is useful inside mmtk-core.
For example, we can skip clearing on-the-side forwarding bits in
non-moving GCs because no objects will be forwarded. Because this should
happen during the Release stage, we postponed the time for Immix-based
plans to reset the in-defrag state to the end of GC so that
`Plan::current_gc_may_move_object` remains callable during the Release
stage. *(Note that this PR does not fix
#1118, but this mechanism
introduced in this PR can be used to implement the clearing of side
forwarding bits efficiently.)*

The information is also useful for VM bindings. Therefore,
`Plan::current_gc_may_move_object` is part of the public API.

- For VMs that have "potential pinning parents" (PPP, i.e. non-root
objects that have fields that cannot be updated), the VM binding must
pin the children of those fields before starting tracing. If the VM
binding knows the current GC never moves any object, it can skip pinning
the children of PPPs.
- For VMs that maintain weak tables (tables that contain weak references
to heap objects) in the runtime, the VM binding needs to scan the weak
tables, updating entries for objects that are moved, and deleting
entries for unreachable objects. (Note the cost of updating entries may
be high if the table is hashed by address.) If the VM binding knows the
current GC never moves any object, it will not need to update the
entries for moved live objects. And if the current GC is also a nursery
GC, the VM binding will only need to scan entries added before the last
GC.

This new method complements existing mechanisms for checking if objects
can be moved. They are useful for different purposes and cannot replace
each other.

- The scope of the new `Plan::current_gc_may_move_object` is one GC. It
is useful for the VM to decide whether to pin PPPs objects before a GC
and how to process weak references or weak tables.
- `PlanConstraints::moves_objects` is a per-plan constant. It is useful
for the VM to determine the object layout w.r.t. address-based hashing.
- The scope of `ObjectReference::is_movable` is a single object. It is
useful for the VM to skip pinning certain objects when interacting with
native code.
- The scope of `PlanTraceObject::may_move_object` is a trace. It is an
internal mechanism for MMTk core to specialize work packets for
different kinds of traces.
  • Loading branch information
wks authored May 16, 2024
1 parent 698a737 commit 9047e23
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 18 deletions.
7 changes: 7 additions & 0 deletions docs/userguide/src/tutorial/code/mygc_semispace/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ impl<VM: VMBinding> Plan for MyGC<VM> {
}
// ANCHOR_END: create_copy_config

// Modify
// ANCHOR: current_gc_may_move_object
fn current_gc_may_move_object(&self) -> bool {
true
}
// ANCHOR_END: current_gc_may_move_object

// Modify
// ANCHOR: schedule_collection
fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {
Expand Down
7 changes: 7 additions & 0 deletions docs/userguide/src/tutorial/mygc/ss/collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ space here.
{{#include ../../code/mygc_semispace/global.rs:create_copy_config}}
```

Because the semispace GC copies objects in every single GC, we modify the method
`current_gc_may_move_object()` in `MyGC` so that it always returns `true`.

```rust
{{#include ../../code/mygc_semispace/global.rs:current_gc_may_move_object}}
```

## Introduce collection to MyGC plan

Add a new method to `Plan for MyGC`, `schedule_collection()`. This function
Expand Down
4 changes: 4 additions & 0 deletions src/plan/generational/copying/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
self.gen.get_used_pages() + self.tospace().reserved_pages()
}

fn current_gc_may_move_object(&self) -> bool {
true
}

/// Return the number of pages available for allocation. Assuming all future allocations goes to nursery.
fn get_available_pages(&self) -> usize {
// super.get_available_pages() / 2 to reserve pages for copying
Expand Down
16 changes: 12 additions & 4 deletions src/plan/generational/immix/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,7 @@ impl<VM: VMBinding> Plan for GenImmix<VM> {
let full_heap = !self.gen.is_current_gc_nursery();
self.gen.release(tls);
if full_heap {
let did_defrag = self.immix_space.release(full_heap);
self.last_gc_was_defrag.store(did_defrag, Ordering::Relaxed);
} else {
self.last_gc_was_defrag.store(false, Ordering::Relaxed);
self.immix_space.release(full_heap);
}
self.last_gc_was_full_heap
.store(full_heap, Ordering::Relaxed);
Expand All @@ -149,6 +146,17 @@ impl<VM: VMBinding> Plan for GenImmix<VM> {
fn end_of_gc(&mut self, _tls: VMWorkerThread) {
self.gen
.set_next_gc_full_heap(CommonGenPlan::should_next_gc_be_full_heap(self));

let did_defrag = self.immix_space.end_of_gc();
self.last_gc_was_defrag.store(did_defrag, Ordering::Relaxed);
}

fn current_gc_may_move_object(&self) -> bool {
if self.is_current_gc_nursery() {
true
} else {
self.immix_space.in_defrag()
}
}

fn get_collection_reserved_pages(&self) -> usize {
Expand Down
10 changes: 10 additions & 0 deletions src/plan/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,16 @@ pub trait Plan: 'static + HasSpaces + Sync + Downcast {
true
}

/// Return whether the current GC may move any object. The VM binding can make use of this
/// information and choose to or not to update some data structures that record the addresses
/// of objects.
///
/// This function is callable during a GC. From the VM binding's point of view, the information
/// of whether the current GC moves object or not is available since `Collection::stop_mutators`
/// is called, and remains available until (but not including) `resume_mutators` at which time
/// the current GC has just finished.
fn current_gc_may_move_object(&self) -> bool;

/// An object is firstly reached by a sanity GC. So the object is reachable
/// in the current GC, and all the GC work has been done for the object (such as
/// tracing and releasing). A plan can implement this to
Expand Down
10 changes: 9 additions & 1 deletion src/plan/immix/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,16 @@ impl<VM: VMBinding> Plan for Immix<VM> {
fn release(&mut self, tls: VMWorkerThread) {
self.common.release(tls, true);
// release the collected region
self.immix_space.release(true);
}

fn end_of_gc(&mut self, _tls: VMWorkerThread) {
self.last_gc_was_defrag
.store(self.immix_space.release(true), Ordering::Relaxed);
.store(self.immix_space.end_of_gc(), Ordering::Relaxed);
}

fn current_gc_may_move_object(&self) -> bool {
self.immix_space.in_defrag()
}

fn get_collection_reserved_pages(&self) -> usize {
Expand Down
4 changes: 4 additions & 0 deletions src/plan/markcompact/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ impl<VM: VMBinding> Plan for MarkCompact<VM> {
fn get_collection_reserved_pages(&self) -> usize {
0
}

fn current_gc_may_move_object(&self) -> bool {
true
}
}

impl<VM: VMBinding> MarkCompact<VM> {
Expand Down
4 changes: 4 additions & 0 deletions src/plan/marksweep/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ impl<VM: VMBinding> Plan for MarkSweep<VM> {
self.base().collection_required(self, space_full)
}

fn current_gc_may_move_object(&self) -> bool {
false
}

fn get_used_pages(&self) -> usize {
self.common.get_used_pages() + self.ms.reserved_pages()
}
Expand Down
4 changes: 4 additions & 0 deletions src/plan/nogc/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ impl<VM: VMBinding> Plan for NoGC<VM> {
unreachable!("GC triggered in nogc")
}

fn current_gc_may_move_object(&self) -> bool {
false
}

fn get_used_pages(&self) -> usize {
self.nogc_space.reserved_pages()
+ self.immortal.reserved_pages()
Expand Down
4 changes: 4 additions & 0 deletions src/plan/pageprotect/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ impl<VM: VMBinding> Plan for PageProtect<VM> {
self.base().collection_required(self, space_full)
}

fn current_gc_may_move_object(&self) -> bool {
false
}

fn get_used_pages(&self) -> usize {
self.space.reserved_pages() + self.common.get_used_pages()
}
Expand Down
4 changes: 4 additions & 0 deletions src/plan/semispace/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ impl<VM: VMBinding> Plan for SemiSpace<VM> {
self.base().collection_required(self, space_full)
}

fn current_gc_may_move_object(&self) -> bool {
true
}

fn get_collection_reserved_pages(&self) -> usize {
self.tospace().reserved_pages()
}
Expand Down
17 changes: 14 additions & 3 deletions src/plan/sticky/immix/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::plan::PlanConstraints;
use crate::policy::gc_work::TraceKind;
use crate::policy::gc_work::TRACE_KIND_TRANSITIVE_PIN;
use crate::policy::immix::ImmixSpace;
use crate::policy::immix::PREFER_COPY_ON_NURSERY_GC;
use crate::policy::immix::TRACE_KIND_FAST;
use crate::policy::sft::SFT;
use crate::policy::space::Space;
Expand Down Expand Up @@ -126,9 +127,7 @@ impl<VM: VMBinding> Plan for StickyImmix<VM> {

fn release(&mut self, tls: crate::util::VMWorkerThread) {
if self.is_current_gc_nursery() {
let was_defrag = self.immix.immix_space.release(false);
self.immix
.set_last_gc_was_defrag(was_defrag, Ordering::Relaxed);
self.immix.immix_space.release(false);
self.immix.common.los.release(false);
} else {
self.immix.release(tls);
Expand All @@ -140,6 +139,10 @@ impl<VM: VMBinding> Plan for StickyImmix<VM> {
crate::plan::generational::global::CommonGenPlan::should_next_gc_be_full_heap(self);
self.next_gc_full_heap
.store(next_gc_full_heap, Ordering::Relaxed);

let was_defrag = self.immix.immix_space.end_of_gc();
self.immix
.set_last_gc_was_defrag(was_defrag, Ordering::Relaxed);
}

fn collection_required(&self, space_full: bool, space: Option<SpaceStats<Self::VM>>) -> bool {
Expand All @@ -158,6 +161,14 @@ impl<VM: VMBinding> Plan for StickyImmix<VM> {
self.gc_full_heap.load(Ordering::Relaxed) && self.immix.last_collection_was_exhaustive()
}

fn current_gc_may_move_object(&self) -> bool {
if self.is_current_gc_nursery() {
PREFER_COPY_ON_NURSERY_GC
} else {
self.get_immix_space().in_defrag()
}
}

fn get_collection_reserved_pages(&self) -> usize {
self.immix.get_collection_reserved_pages()
}
Expand Down
4 changes: 2 additions & 2 deletions src/policy/immix/defrag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ impl Defrag {
.store(threshold, Ordering::Release);
}

/// Release work. Should be called in ImmixSpace::release.
/// Reset the in-defrag state.
#[allow(clippy::assertions_on_constants)]
pub fn release<VM: VMBinding>(&self, _space: &ImmixSpace<VM>) {
pub fn reset_in_defrag(&self) {
debug_assert!(super::DEFRAG);
self.in_defrag_collection.store(false, Ordering::Release);
}
Expand Down
19 changes: 11 additions & 8 deletions src/policy/immix/immixspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,12 +440,10 @@ impl<VM: VMBinding> ImmixSpace<VM> {
}
}

/// Release for the immix space. This is called when a GC finished.
/// Return whether this GC was a defrag GC, as a plan may want to know this.
pub fn release(&mut self, major_gc: bool) -> bool {
let did_defrag = self.defrag.in_defrag();
/// Release for the immix space.
pub fn release(&mut self, major_gc: bool) {
if major_gc {
// Update line_unavail_state for hole searching afte this GC.
// Update line_unavail_state for hole searching after this GC.
if !super::BLOCK_ONLY {
self.line_unavail_state.store(
self.line_mark_state.load(Ordering::Acquire),
Expand All @@ -460,12 +458,17 @@ impl<VM: VMBinding> ImmixSpace<VM> {
// Sweep chunks and blocks
let work_packets = self.generate_sweep_tasks();
self.scheduler().work_buckets[WorkBucketStage::Release].bulk_add(work_packets);
if super::DEFRAG {
self.defrag.release(self);
}

self.lines_consumed.store(0, Ordering::Relaxed);
}

/// This is called when a GC finished.
/// Return whether this GC was a defrag GC, as a plan may want to know this.
pub fn end_of_gc(&mut self) -> bool {
let did_defrag = self.defrag.in_defrag();
if super::DEFRAG {
self.defrag.reset_in_defrag();
}
did_defrag
}

Expand Down

0 comments on commit 9047e23

Please sign in to comment.