Description
TL;DR: The type parameter of XxxxxSpace::trace_object<T: TransitiveClosure>
and the trace: &mut T
parameter can be removed, and the information should be carried by return value.
TL;DR2: The trace: &mut T
parameter of Scanning::scan_object
should also be removed and replaced by a lambda. (See comment below)
TODO list:
- Remove the
<T: TransitiveClosure>
parameter fromXxxxxSpace::trace_object
- Remove the
<T: TransitiveClosure>
parameter fromScanning::scan_object
- Remove the
TransitiveClosure
trait completely
What?
Each space (like this one) has a trace_object<T: TransitiveClosure>
method that takes a trace: &mut T
parameter.
We mentioned the removal of the <T: TransitiveClosure>
type parameter in #110. In that issue, @qinsoon mentioned that once we remove plan-specific ProcessEdgesWork, we can remove it.
But I think we can remove it earlier than that, and plan-specific edge-processing is not even the core of the problem. The trace
parameter is more like an "out parameter" in some languages.
What does trace: &mut T
do?
It only serves one purpose for any Space: to enqueue the newly marked object.
Space::trace_object
does two things:
- Mark an object if it is not already marked. When an object is marked (in GC terms, when a "grey" object becomes "black"), its edges should be enqueued (i.e. adjacent object shall turn "grey").
- Forward an object if it is not already forwarded. If the current object reference points to a tombstone, find the address of the forwarded object.
So the result of the function should also consist of two parts:
- If the object is newly marked, and
- if the object is forwarded and, if it is, what is the new address.
The current trace_object
signature is
pub fn trace_object<T: TransitiveClosure>(
&self,
trace: &mut T,
object: ObjectReference,
semantics: Option<CopySemantics>,
worker: &mut GCWorker<VM>,
) -> ObjectReference {
With this signature, the return value ObjectReference
only carries the information of part (2). Part (1) is done by calling trace.process_node(object)
just before returning. See:
MallocSpace
: here, just before returning.CopySpace
: here, just before returning.ImmixSpace
: without copying and with copying, just before returning.MarkCompactSpace
: marking and forwarding, almost just before returning.LargeObjectSpace
: here, just before returning.
Currently, the T: TransitiveClosure
type parameter can only be instances of ProcessEdgesWork
, and the ProcessEdgesWork::process_node(object)
has only one implementation. It simply adds object
into ProcessEdgesBase::nodes
. This makes the polymorphism against T: TransitiveClosure
pointless, for it is not polymorphic at all.
What should it return?
trace_object
should have returned this:
pub struct TraceObjectResult {
pub is_newly_marked: bool, // true if the object is newly marked
pub forwarded: Option<ObjectReference>, // If an object is forwarded, return Some(newobj), otherwise None.
MallocSpace::trace_object
should return TraceObjectResult(true, None)
if called on a "grey" object, and TraceObjectResult(false, None)
if the object is "black".
If an object is in a from-space, CopySpace::trace_object
should return TraceObjectResult(true, Some(newobj))
the first time it visits an object, but TraceObjectResult(false, Some(newobj))
for subsequent visits. For objects in to-spaces, the second part should be None
.
The Immix space, depending on whether it is defragging, may either return TraceObjectResult(xxx, None)
or TraceObjectResult(xxx, Some(newobj))
.
How should it be used?
trace_object
is called in ProcessEdgesWork::process_edge
. The default implementation is:
#[inline]
fn process_edge(&mut self, slot: Address) {
let object = unsafe { slot.load::<ObjectReference>() };
let new_object = self.trace_object(object);
if Self::OVERWRITE_REFERENCE {
unsafe { slot.store(new_object) };
}
}
ImmixProcessEdges
and GenNurseryProcessEdges
override it, but the only difference is the way to decide whether to store back to the slot. With this change, it only needs to be implemented in one way:
#[inline]
fn process_edge(&mut self, slot: Address) {
let object = unsafe { slot.load::<ObjectReference>() };
let TraceObjectResult(newly_marked, forwarded) = self.trace_object(object);
if newly_marked {
self.process_node(forwarded.unwrap_or(object)); // enqueue IFF newly marked
}
if let Some(new_object) = forwarded {
unsafe { slot.store(new_object) }; // store IFF forwarded.
}
}
Discussion
Is it compatible with mark-compact? @tianleq
Is there any performance issue?