Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow querying if the current GC may move objects. #1128

Merged
merged 7 commits into from
May 16, 2024
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
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);
Copy link
Member

Choose a reason for hiding this comment

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

We still need to call immix_space.release() here, right?

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. New objects can be allocated into the immix_space and the LOS, and we still need to release them. I'll fix it.

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
Loading