Skip to content

Commit 9047e23

Browse files
authored
Allow querying if the current GC may move objects. (#1128)
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.
1 parent 698a737 commit 9047e23

File tree

14 files changed

+96
-18
lines changed

14 files changed

+96
-18
lines changed

docs/userguide/src/tutorial/code/mygc_semispace/global.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ impl<VM: VMBinding> Plan for MyGC<VM> {
7171
}
7272
// ANCHOR_END: create_copy_config
7373

74+
// Modify
75+
// ANCHOR: current_gc_may_move_object
76+
fn current_gc_may_move_object(&self) -> bool {
77+
true
78+
}
79+
// ANCHOR_END: current_gc_may_move_object
80+
7481
// Modify
7582
// ANCHOR: schedule_collection
7683
fn schedule_collection(&'static self, scheduler: &GCWorkScheduler<VM>) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ space here.
2929
{{#include ../../code/mygc_semispace/global.rs:create_copy_config}}
3030
```
3131

32+
Because the semispace GC copies objects in every single GC, we modify the method
33+
`current_gc_may_move_object()` in `MyGC` so that it always returns `true`.
34+
35+
```rust
36+
{{#include ../../code/mygc_semispace/global.rs:current_gc_may_move_object}}
37+
```
38+
3239
## Introduce collection to MyGC plan
3340

3441
Add a new method to `Plan for MyGC`, `schedule_collection()`. This function

src/plan/generational/copying/global.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
127127
self.gen.get_used_pages() + self.tospace().reserved_pages()
128128
}
129129

130+
fn current_gc_may_move_object(&self) -> bool {
131+
true
132+
}
133+
130134
/// Return the number of pages available for allocation. Assuming all future allocations goes to nursery.
131135
fn get_available_pages(&self) -> usize {
132136
// super.get_available_pages() / 2 to reserve pages for copying

src/plan/generational/immix/global.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,7 @@ impl<VM: VMBinding> Plan for GenImmix<VM> {
137137
let full_heap = !self.gen.is_current_gc_nursery();
138138
self.gen.release(tls);
139139
if full_heap {
140-
let did_defrag = self.immix_space.release(full_heap);
141-
self.last_gc_was_defrag.store(did_defrag, Ordering::Relaxed);
142-
} else {
143-
self.last_gc_was_defrag.store(false, Ordering::Relaxed);
140+
self.immix_space.release(full_heap);
144141
}
145142
self.last_gc_was_full_heap
146143
.store(full_heap, Ordering::Relaxed);
@@ -149,6 +146,17 @@ impl<VM: VMBinding> Plan for GenImmix<VM> {
149146
fn end_of_gc(&mut self, _tls: VMWorkerThread) {
150147
self.gen
151148
.set_next_gc_full_heap(CommonGenPlan::should_next_gc_be_full_heap(self));
149+
150+
let did_defrag = self.immix_space.end_of_gc();
151+
self.last_gc_was_defrag.store(did_defrag, Ordering::Relaxed);
152+
}
153+
154+
fn current_gc_may_move_object(&self) -> bool {
155+
if self.is_current_gc_nursery() {
156+
true
157+
} else {
158+
self.immix_space.in_defrag()
159+
}
152160
}
153161

154162
fn get_collection_reserved_pages(&self) -> usize {

src/plan/global.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,16 @@ pub trait Plan: 'static + HasSpaces + Sync + Downcast {
299299
true
300300
}
301301

302+
/// Return whether the current GC may move any object. The VM binding can make use of this
303+
/// information and choose to or not to update some data structures that record the addresses
304+
/// of objects.
305+
///
306+
/// This function is callable during a GC. From the VM binding's point of view, the information
307+
/// of whether the current GC moves object or not is available since `Collection::stop_mutators`
308+
/// is called, and remains available until (but not including) `resume_mutators` at which time
309+
/// the current GC has just finished.
310+
fn current_gc_may_move_object(&self) -> bool;
311+
302312
/// An object is firstly reached by a sanity GC. So the object is reachable
303313
/// in the current GC, and all the GC work has been done for the object (such as
304314
/// tracing and releasing). A plan can implement this to

src/plan/immix/global.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,16 @@ impl<VM: VMBinding> Plan for Immix<VM> {
9393
fn release(&mut self, tls: VMWorkerThread) {
9494
self.common.release(tls, true);
9595
// release the collected region
96+
self.immix_space.release(true);
97+
}
98+
99+
fn end_of_gc(&mut self, _tls: VMWorkerThread) {
96100
self.last_gc_was_defrag
97-
.store(self.immix_space.release(true), Ordering::Relaxed);
101+
.store(self.immix_space.end_of_gc(), Ordering::Relaxed);
102+
}
103+
104+
fn current_gc_may_move_object(&self) -> bool {
105+
self.immix_space.in_defrag()
98106
}
99107

100108
fn get_collection_reserved_pages(&self) -> usize {

src/plan/markcompact/global.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ impl<VM: VMBinding> Plan for MarkCompact<VM> {
171171
fn get_collection_reserved_pages(&self) -> usize {
172172
0
173173
}
174+
175+
fn current_gc_may_move_object(&self) -> bool {
176+
true
177+
}
174178
}
175179

176180
impl<VM: VMBinding> MarkCompact<VM> {

src/plan/marksweep/global.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ impl<VM: VMBinding> Plan for MarkSweep<VM> {
7373
self.base().collection_required(self, space_full)
7474
}
7575

76+
fn current_gc_may_move_object(&self) -> bool {
77+
false
78+
}
79+
7680
fn get_used_pages(&self) -> usize {
7781
self.common.get_used_pages() + self.ms.reserved_pages()
7882
}

src/plan/nogc/global.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ impl<VM: VMBinding> Plan for NoGC<VM> {
7474
unreachable!("GC triggered in nogc")
7575
}
7676

77+
fn current_gc_may_move_object(&self) -> bool {
78+
false
79+
}
80+
7781
fn get_used_pages(&self) -> usize {
7882
self.nogc_space.reserved_pages()
7983
+ self.immortal.reserved_pages()

src/plan/pageprotect/global.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ impl<VM: VMBinding> Plan for PageProtect<VM> {
6262
self.base().collection_required(self, space_full)
6363
}
6464

65+
fn current_gc_may_move_object(&self) -> bool {
66+
false
67+
}
68+
6569
fn get_used_pages(&self) -> usize {
6670
self.space.reserved_pages() + self.common.get_used_pages()
6771
}

0 commit comments

Comments
 (0)