Skip to content

Commit e2951ad

Browse files
authored
NULL and movement check in process_edge (#1032)
This PR does two things: 1. `ProcessEdgesWork::process_edge` will skip slots that are not holding references (i.e. if `Edge::load()` returns `ObjectReference::NULL`). 2. `ProcessEdgesWork::process_edge` will skip the `Edge::store()` if the object is not moved. Doing (1) removes unnecessary invocations of `trace_object()` as well as the subsequent `Edge::store()`. It also allows slots to hold non-reference tagged values. In that case, the VM binding can return `ObjectReference::NULL` in `Edge::load()` so that mmtk-core will simply skip the slot, fixing #1031 Doing (2) removes unnecessary `Edge::store()` operations in the case where the objects are not moved during `trace_object`. It reduces the STW time in most cases, fixing #574 Fixes: #1031 Fixes: #574
1 parent 658bce8 commit e2951ad

File tree

14 files changed

+82
-40
lines changed

14 files changed

+82
-40
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ impl<VM: VMBinding> ProcessEdgesWork for MyGCProcessEdges<VM> {
5656
}
5757

5858
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
59-
if object.is_null() {
60-
return object;
61-
}
59+
debug_assert!(!object.is_null());
6260
let worker = self.worker();
6361
let queue = &mut self.base.nodes;
6462
if self.plan.tospace().in_space(object) {

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ In `gc_work.rs`:
149149
the tospace and fromspace:
150150
```rust
151151
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
152-
if object.is_null() {
153-
return object;
154-
}
152+
debug_assert!(!object.is_null());
155153
156154
// Add this!
157155
else if self.plan().youngspace().in_space(object) {

src/plan/generational/gc_work.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,25 @@ impl<VM: VMBinding, P: GenerationalPlanExt<VM> + PlanTraceObject<VM>> ProcessEdg
3636
Self { plan, base }
3737
}
3838
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
39-
if object.is_null() {
40-
return object;
41-
}
39+
debug_assert!(!object.is_null());
40+
4241
// We cannot borrow `self` twice in a call, so we extract `worker` as a local variable.
4342
let worker = self.worker();
4443
self.plan
4544
.trace_object_nursery(&mut self.base.nodes, object, worker)
4645
}
4746
fn process_edge(&mut self, slot: EdgeOf<Self>) {
4847
let object = slot.load();
48+
if object.is_null() {
49+
return;
50+
}
4951
let new_object = self.trace_object(object);
5052
debug_assert!(!self.plan.is_object_in_nursery(new_object));
51-
slot.store(new_object);
53+
// Note: If `object` is a mature object, `trace_object` will not call `space.trace_object`,
54+
// but will still return `object`. In that case, we don't need to write it back.
55+
if new_object != object {
56+
slot.store(new_object);
57+
}
5258
}
5359

5460
fn create_scan_work(

src/policy/copyspace.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ impl<VM: VMBinding> CopySpace<VM> {
217217
worker: &mut GCWorker<VM>,
218218
) -> ObjectReference {
219219
trace!("copyspace.trace_object(, {:?}, {:?})", object, semantics,);
220+
debug_assert!(!object.is_null());
220221

221222
// If this is not from space, we do not need to trace it (the object has been copied to the tosapce)
222223
if !self.is_from_space() {

src/policy/immix/immixspace.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for ImmixSpace
184184
copy: Option<CopySemantics>,
185185
worker: &mut GCWorker<VM>,
186186
) -> ObjectReference {
187+
debug_assert!(!object.is_null());
187188
if KIND == TRACE_KIND_TRANSITIVE_PIN {
188189
self.trace_object_without_moving(queue, object)
189190
} else if KIND == TRACE_KIND_DEFRAG {

src/policy/immortalspace.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ impl<VM: VMBinding> ImmortalSpace<VM> {
187187
queue: &mut Q,
188188
object: ObjectReference,
189189
) -> ObjectReference {
190+
debug_assert!(!object.is_null());
190191
#[cfg(feature = "vo_bit")]
191192
debug_assert!(
192193
crate::util::metadata::vo_bit::is_vo_bit_set::<VM>(object),

src/policy/largeobjectspace.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ impl<VM: VMBinding> LargeObjectSpace<VM> {
189189
queue: &mut Q,
190190
object: ObjectReference,
191191
) -> ObjectReference {
192+
debug_assert!(!object.is_null());
192193
#[cfg(feature = "vo_bit")]
193194
debug_assert!(
194195
crate::util::metadata::vo_bit::is_vo_bit_set::<VM>(object),

src/policy/markcompactspace.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for MarkCompac
130130
_copy: Option<CopySemantics>,
131131
_worker: &mut GCWorker<VM>,
132132
) -> ObjectReference {
133+
debug_assert!(!object.is_null());
133134
debug_assert!(
134135
KIND != TRACE_KIND_TRANSITIVE_PIN,
135136
"MarkCompact does not support transitive pin trace."

src/policy/marksweepspace/malloc_ms/global.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,9 +400,7 @@ impl<VM: VMBinding> MallocSpace<VM> {
400400
queue: &mut Q,
401401
object: ObjectReference,
402402
) -> ObjectReference {
403-
if object.is_null() {
404-
return object;
405-
}
403+
debug_assert!(!object.is_null());
406404

407405
assert!(
408406
self.in_space(object),

src/policy/marksweepspace/native_ms/global.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,7 @@ impl<VM: VMBinding> MarkSweepSpace<VM> {
241241
queue: &mut Q,
242242
object: ObjectReference,
243243
) -> ObjectReference {
244-
if object.is_null() {
245-
return object;
246-
}
244+
debug_assert!(!object.is_null());
247245
debug_assert!(
248246
self.in_space(object),
249247
"Cannot mark an object {} that was not alloced by free list allocator.",

src/scheduler/gc_work.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,8 @@ pub(crate) struct ProcessEdgesWorkTracer<E: ProcessEdgesWork> {
273273
impl<E: ProcessEdgesWork> ObjectTracer for ProcessEdgesWorkTracer<E> {
274274
/// Forward the `trace_object` call to the underlying `ProcessEdgesWork`,
275275
/// and flush as soon as the underlying buffer of `process_edges_work` is full.
276-
///
277-
/// This function is inlined because `trace_object` is probably the hottest function in MMTk.
278-
/// If this function is called in small closures, please profile the program and make sure the
279-
/// closure is inlined, too.
280276
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
277+
debug_assert!(!object.is_null());
281278
let result = self.process_edges_work.trace_object(object);
282279
self.flush_if_full();
283280
result
@@ -663,8 +660,12 @@ pub trait ProcessEdgesWork:
663660
/// trace the object and store back the new object reference if necessary.
664661
fn process_edge(&mut self, slot: EdgeOf<Self>) {
665662
let object = slot.load();
663+
if object.is_null() {
664+
return;
665+
}
666666
let new_object = self.trace_object(object);
667-
if Self::OVERWRITE_REFERENCE {
667+
debug_assert!(!new_object.is_null());
668+
if Self::OVERWRITE_REFERENCE && new_object != object {
668669
slot.store(new_object);
669670
}
670671
}
@@ -721,9 +722,7 @@ impl<VM: VMBinding> ProcessEdgesWork for SFTProcessEdges<VM> {
721722
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
722723
use crate::policy::sft::GCWorkerMutRef;
723724

724-
if object.is_null() {
725-
return object;
726-
}
725+
debug_assert!(!object.is_null());
727726

728727
// Erase <VM> type parameter
729728
let worker = GCWorkerMutRef::new(self.worker());
@@ -997,9 +996,7 @@ impl<VM: VMBinding, P: PlanTraceObject<VM> + Plan<VM = VM>, const KIND: TraceKin
997996
}
998997

999998
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference {
1000-
if object.is_null() {
1001-
return object;
1002-
}
999+
debug_assert!(!object.is_null());
10031000
// We cannot borrow `self` twice in a call, so we extract `worker` as a local variable.
10041001
let worker = self.worker();
10051002
self.plan
@@ -1008,8 +1005,12 @@ impl<VM: VMBinding, P: PlanTraceObject<VM> + Plan<VM = VM>, const KIND: TraceKin
10081005

10091006
fn process_edge(&mut self, slot: EdgeOf<Self>) {
10101007
let object = slot.load();
1008+
if object.is_null() {
1009+
return;
1010+
}
10111011
let new_object = self.trace_object(object);
1012-
if P::may_move_objects::<KIND>() {
1012+
debug_assert!(!new_object.is_null());
1013+
if P::may_move_objects::<KIND>() && new_object != object {
10131014
slot.store(new_object);
10141015
}
10151016
}

src/util/reference_processor.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,20 +216,23 @@ impl ReferenceProcessor {
216216
e: &mut E,
217217
referent: ObjectReference,
218218
) -> ObjectReference {
219+
debug_assert!(!referent.is_null());
219220
e.trace_object(referent)
220221
}
221222

222223
fn get_forwarded_reference<E: ProcessEdgesWork>(
223224
e: &mut E,
224225
object: ObjectReference,
225226
) -> ObjectReference {
227+
debug_assert!(!object.is_null());
226228
e.trace_object(object)
227229
}
228230

229231
fn keep_referent_alive<E: ProcessEdgesWork>(
230232
e: &mut E,
231233
referent: ObjectReference,
232234
) -> ObjectReference {
235+
debug_assert!(!referent.is_null());
233236
e.trace_object(referent)
234237
}
235238

@@ -285,23 +288,29 @@ impl ReferenceProcessor {
285288
reference: ObjectReference,
286289
) -> ObjectReference {
287290
let old_referent = <E::VM as VMBinding>::VMReferenceGlue::get_referent(reference);
288-
let new_referent = ReferenceProcessor::get_forwarded_referent(trace, old_referent);
289-
<E::VM as VMBinding>::VMReferenceGlue::set_referent(reference, new_referent);
290-
let new_reference = ReferenceProcessor::get_forwarded_reference(trace, reference);
291291
{
292292
use crate::vm::ObjectModel;
293293
trace!(
294294
"Forwarding reference: {} (size: {})",
295295
reference,
296296
<E::VM as VMBinding>::VMObjectModel::get_current_size(reference)
297297
);
298+
}
299+
300+
if !<E::VM as VMBinding>::VMReferenceGlue::is_referent_cleared(old_referent) {
301+
let new_referent = ReferenceProcessor::get_forwarded_referent(trace, old_referent);
302+
<E::VM as VMBinding>::VMReferenceGlue::set_referent(reference, new_referent);
303+
298304
trace!(
299305
" referent: {} (forwarded to {})",
300306
old_referent,
301307
new_referent
302308
);
303-
trace!(" reference: forwarded to {}", new_reference);
304309
}
310+
311+
let new_reference = ReferenceProcessor::get_forwarded_reference(trace, reference);
312+
trace!(" reference: forwarded to {}", new_reference);
313+
305314
debug_assert!(
306315
!new_reference.is_null(),
307316
"reference {:?}'s forwarding pointer is NULL",

src/vm/edge_shape.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ use atomic::Atomic;
77
use crate::util::constants::{BYTES_IN_ADDRESS, LOG_BYTES_IN_ADDRESS};
88
use crate::util::{Address, ObjectReference};
99

10-
/// An abstract edge. An edge holds an object reference. When we load from it, we get an
11-
/// ObjectReference; we can also store an ObjectReference into it.
10+
/// An `Edge` represents a slot in an object (a.k.a. a field), on the stack (i.e. a local variable)
11+
/// or any other places (such as global variables). A slot may hold an object reference. We can
12+
/// load the object reference from it, and we can store an ObjectReference into it. For some VMs,
13+
/// a slot may sometimes not hold an object reference. For example, it can hold a special `NULL`
14+
/// pointer which does not point to any object, or it can hold a tagged non-reference value, such
15+
/// as small integers and special values such as `true`, `false`, `null` (a.k.a. "none", "nil",
16+
/// etc. for other VMs), `undefined`, etc.
1217
///
1318
/// This intends to abstract out the differences of reference field representation among different
1419
/// VMs. If the VM represent a reference field as a word that holds the pointer to the object, it
@@ -40,10 +45,31 @@ use crate::util::{Address, ObjectReference};
4045
/// semantics, such as whether it holds strong or weak references. If a VM holds a weak reference
4146
/// in a word as a pointer, it can also use `SimpleEdge` for weak reference fields.
4247
pub trait Edge: Copy + Send + Debug + PartialEq + Eq + Hash {
43-
/// Load object reference from the edge.
48+
/// Load object reference from the slot.
49+
///
50+
/// If the slot is not holding an object reference (For example, if it is holding NULL or a
51+
/// tagged non-reference value. See trait-level doc comment.), this method should return
52+
/// `ObjectReference::NULL`.
53+
///
54+
/// If the slot holds an object reference with tag bits, the returned value shall be the object
55+
/// reference with the tag bits removed.
4456
fn load(&self) -> ObjectReference;
4557

46-
/// Store the object reference `object` into the edge.
58+
/// Store the object reference `object` into the slot.
59+
///
60+
/// If the slot holds an object reference with tag bits, this method must preserve the tag
61+
/// bits while updating the object reference so that it points to the forwarded object given by
62+
/// the parameter `object`.
63+
///
64+
/// FIXME: This design is inefficient for handling object references with tag bits. Consider
65+
/// introducing a new updating function to do the load, trace and store in one function.
66+
/// See: <https://github.com/mmtk/mmtk-core/issues/1033>
67+
///
68+
/// FIXME: This method is currently used by both moving GC algorithms and the subsuming write
69+
/// barrier ([`crate::memory_manager::object_reference_write`]). The two reference writing
70+
/// operations have different semantics, and need to be implemented differently if the VM
71+
/// supports offsetted or tagged references.
72+
/// See: <https://github.com/mmtk/mmtk-core/issues/1038>
4773
fn store(&self, object: ObjectReference);
4874

4975
/// Prefetch the edge so that a subsequent `load` will be faster.

src/vm/scanning.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ impl<ES: Edge, F: FnMut(ES)> EdgeVisitor<ES> for F {
2626

2727
/// Callback trait of scanning functions that directly trace through edges.
2828
pub trait ObjectTracer {
29-
/// Call this function for the content of each edge,
30-
/// and assign the returned value back to the edge.
29+
/// Call this function to trace through an object graph edge which points to `object`.
30+
/// `object` must point to a valid object, and cannot be `ObjectReference::NULL`.
3131
///
32-
/// Note: This function is performance-critical.
33-
/// Implementations should consider inlining if necessary.
32+
/// The return value is the new object reference for `object` if it is moved, or `object` if
33+
/// not moved. If moved, the caller should update the slot that holds the reference to
34+
/// `object` so that it points to the new location.
35+
///
36+
/// Note: This function is performance-critical, therefore must be implemented efficiently.
3437
fn trace_object(&mut self, object: ObjectReference) -> ObjectReference;
3538
}
3639

0 commit comments

Comments
 (0)