pub trait Scanning<VM: VMBinding> {
const UNIQUE_OBJECT_ENQUEUING: bool = false;
// Required methods
fn scan_object<SV: SlotVisitor<VM::VMSlot>>(
tls: VMWorkerThread,
object: ObjectReference,
slot_visitor: &mut SV,
);
fn notify_initial_thread_scan_complete(
partial_scan: bool,
tls: VMWorkerThread,
);
fn scan_roots_in_mutator_thread(
tls: VMWorkerThread,
mutator: &'static mut Mutator<VM>,
factory: impl RootsWorkFactory<VM::VMSlot>,
);
fn scan_vm_specific_roots(
tls: VMWorkerThread,
factory: impl RootsWorkFactory<VM::VMSlot>,
);
fn supports_return_barrier() -> bool;
fn prepare_for_roots_re_scanning();
// Provided methods
fn support_slot_enqueuing(
_tls: VMWorkerThread,
_object: ObjectReference,
) -> bool { ... }
fn scan_object_and_trace_edges<OT: ObjectTracer>(
_tls: VMWorkerThread,
_object: ObjectReference,
_object_tracer: &mut OT,
) { ... }
fn process_weak_refs(
_worker: &mut GCWorker<VM>,
_tracer_context: impl ObjectTracerContext<VM>,
) -> bool { ... }
fn forward_weak_refs(
_worker: &mut GCWorker<VM>,
_tracer_context: impl ObjectTracerContext<VM>,
) { ... }
}
Expand description
VM-specific methods for scanning roots/objects.
Provided Associated Constants§
sourceconst UNIQUE_OBJECT_ENQUEUING: bool = false
const UNIQUE_OBJECT_ENQUEUING: bool = false
When set to true
, all plans will guarantee that during each GC, each live object is
enqueued at most once, and therefore scanned (by either Scanning::scan_object
or
Scanning::scan_object_and_trace_edges
) at most once.
When set to false
, MMTk may enqueue an object multiple times due to optimizations, such as
using non-atomic operatios to mark objects. Consequently, an object may be scanned multiple
times during a GC.
The default value is false
because duplicated object-enqueuing is benign for most VMs, and
related optimizations, such as non-atomic marking, can improve GC speed. VM bindings can
override this if they need. For example, some VMs piggyback on object-scanning to visit
objects during a GC, but may have data race if multiple GC workers visit the same object at
the same time. Such VMs can set this constant to true
to workaround this problem.
Required Methods§
sourcefn scan_object<SV: SlotVisitor<VM::VMSlot>>(
tls: VMWorkerThread,
object: ObjectReference,
slot_visitor: &mut SV,
)
fn scan_object<SV: SlotVisitor<VM::VMSlot>>( tls: VMWorkerThread, object: ObjectReference, slot_visitor: &mut SV, )
Delegated scanning of a object, visiting each reference field encountered.
The VM shall call slot_visitor.visit_slot
on each reference field. This effectively
visits all outgoing edges from the current object in the form of slots.
The VM may skip a reference field if it is not holding an object reference (e.g. if the
field is holding a null reference, or a tagged non-reference value such as small integer).
Even if not skipped, Slot::load
will still return None
if the slot is not holding an
object reference.
The memory_manager::is_mmtk_object
function can be used in this function if
- the “is_mmtk_object” feature is enabled, and
VM::VMObjectModel::NEED_VO_BITS_DURING_TRACING
is true.
Arguments:
tls
: The VM-specific thread-local storage for the current worker.object
: The object to be scanned.slot_visitor
: Called back for each field.
sourcefn notify_initial_thread_scan_complete(partial_scan: bool, tls: VMWorkerThread)
fn notify_initial_thread_scan_complete(partial_scan: bool, tls: VMWorkerThread)
MMTk calls this method at the first time during a collection that thread’s stacks have been scanned. This can be used (for example) to clean up obsolete compiled methods that are no longer being executed.
Arguments:
partial_scan
: Whether the scan was partial or full-heap.tls
: The GC thread that is performing the thread scan.
sourcefn scan_roots_in_mutator_thread(
tls: VMWorkerThread,
mutator: &'static mut Mutator<VM>,
factory: impl RootsWorkFactory<VM::VMSlot>,
)
fn scan_roots_in_mutator_thread( tls: VMWorkerThread, mutator: &'static mut Mutator<VM>, factory: impl RootsWorkFactory<VM::VMSlot>, )
Scan one mutator for stack roots.
Some VM bindings may not be able to implement this method.
For example, the VM binding may only be able to enumerate all threads and
scan them while enumerating, but cannot scan stacks individually when given
the references of threads.
In that case, it can leave this method empty, and deal with stack
roots in Scanning::scan_vm_specific_roots
. However, in that case, MMTk
does not know those roots are stack roots, and cannot perform any possible
optimization for the stack roots.
The memory_manager::is_mmtk_object
function can be used in this function if
- the “is_mmtk_object” feature is enabled.
Arguments:
tls
: The GC thread that is performing this scanning.mutator
: The reference to the mutator whose roots will be scanned.factory
: The VM uses it to create work packets for scanning roots.
sourcefn scan_vm_specific_roots(
tls: VMWorkerThread,
factory: impl RootsWorkFactory<VM::VMSlot>,
)
fn scan_vm_specific_roots( tls: VMWorkerThread, factory: impl RootsWorkFactory<VM::VMSlot>, )
Scan VM-specific roots. The creation of all root scan tasks (except thread scanning) goes here.
The memory_manager::is_mmtk_object
function can be used in this function if
- the “is_mmtk_object” feature is enabled.
Arguments:
tls
: The GC thread that is performing this scanning.factory
: The VM uses it to create work packets for scanning roots.
sourcefn supports_return_barrier() -> bool
fn supports_return_barrier() -> bool
Return whether the VM supports return barriers. This is unused at the moment.
sourcefn prepare_for_roots_re_scanning()
fn prepare_for_roots_re_scanning()
Prepare for another round of root scanning in the same GC. Some GC algorithms
need multiple transitive closures, and each transitive closure starts from
root scanning. We expect the binding to provide the same root set for every
round of root scanning in the same GC. Bindings can use this call to get
ready for another round of root scanning to make sure that the same root
set will be returned in the upcoming calls of root scanning methods,
such as crate::vm::Scanning::scan_roots_in_mutator_thread
and
crate::vm::Scanning::scan_vm_specific_roots
.
Provided Methods§
sourcefn support_slot_enqueuing(
_tls: VMWorkerThread,
_object: ObjectReference,
) -> bool
fn support_slot_enqueuing( _tls: VMWorkerThread, _object: ObjectReference, ) -> bool
Return true if the given object supports slot enqueuing.
- If this returns true, MMTk core will call
scan_object
on the object. - Otherwise, MMTk core will call
scan_object_and_trace_edges
on the object.
For maximum performance, the VM should support slot-enqueuing for as many objects as practical. Also note that this method is called for every object to be scanned, so it must be fast. The VM binding should avoid expensive checks and keep it as efficient as possible.
Arguments:
tls
: The VM-specific thread-local storage for the current worker.object
: The object to be scanned.
sourcefn scan_object_and_trace_edges<OT: ObjectTracer>(
_tls: VMWorkerThread,
_object: ObjectReference,
_object_tracer: &mut OT,
)
fn scan_object_and_trace_edges<OT: ObjectTracer>( _tls: VMWorkerThread, _object: ObjectReference, _object_tracer: &mut OT, )
Delegated scanning of a object, visiting each reference field encountered, and tracing the objects pointed by each field.
The VM shall call object_tracer.trace_object
with the argument being the object reference
held in each reference field. If the GC moves the object, the VM shall update the field so
that it refers to the object using the object reference returned from trace_object
. This
effectively traces through all outgoing edges from the current object directly.
The VM must skip reference fields that are not holding object references (e.g. if the field is holding a null reference, or a tagged non-reference value such as small integer).
The memory_manager::is_mmtk_object
function can be used in this function if
- the “is_mmtk_object” feature is enabled, and
VM::VMObjectModel::NEED_VO_BITS_DURING_TRACING
is true.
Arguments:
tls
: The VM-specific thread-local storage for the current worker.object
: The object to be scanned.object_tracer
: Called back for the object reference held in each field.
sourcefn process_weak_refs(
_worker: &mut GCWorker<VM>,
_tracer_context: impl ObjectTracerContext<VM>,
) -> bool
fn process_weak_refs( _worker: &mut GCWorker<VM>, _tracer_context: impl ObjectTracerContext<VM>, ) -> bool
Process weak references.
This function is called in a GC after the transitive closure from roots is computed, that is, all reachable objects from roots are reached. This function gives the VM binding an opportunitiy to process finalizers and weak references.
MMTk core enables the VM binding to do the following in this function:
- Query if an object is already reached.
- by calling
ObjectReference::is_reachable()
- by calling
- Get the new address of an object if it is already reached.
- by calling
ObjectReference::get_forwarded_object()
- by calling
- Keep an object and its descendents alive if not yet reached.
- using
tracer_context
- using
- Request this function to be called again after transitive closure is finished again.
- by returning
true
- by returning
The tracer_context
parameter provides the VM binding the mechanism for retaining
unreachable objects (i.e. keeping them alive in this GC). The following snippet shows a
typical use case of handling finalizable objects for a Java-like language.
let finalizable_objects: Vec<ObjectReference> = my_vm::get_finalizable_object();
let mut new_finalizable_objects = vec![];
tracer_context.with_tracer(worker, |tracer| {
for object in finalizable_objects {
if object.is_reachable() {
// `object` is still reachable.
// It may have been moved if it is a copying GC.
let new_object = object.get_forwarded_object().unwrap_or(object);
new_finalizable_objects.push(new_object);
} else {
// `object` is unreachable.
// Retain it, and enqueue it for postponed finalization.
let new_object = tracer.trace_object(object);
my_vm::enqueue_finalizable_object_to_be_executed_later(new_object);
}
}
});
Within the closure |tracer| { ... }
, the VM binding can call tracer.trace_object(object)
to retain object
and get its new address if moved. After with_tracer
returns, it will
create work packets in the VMRefClosure
work bucket to compute the transitive closure from
the objects retained in the closure.
The memory_manager::is_mmtk_object
function can be used in this function if
- the “is_mmtk_object” feature is enabled, and
VM::VMObjectModel::NEED_VO_BITS_DURING_TRACING
is true.
Arguments:
worker
: The current GC worker.tracer_context
: Use this to get access anObjectTracer
and use it to retain and update weak references.
If process_weak_refs
returns true
, then process_weak_refs
will be called again after
all work packets in the VMRefClosure
work bucket has been executed, by which time all
objects reachable from the objects retained in this function will have been reached.
§Performance notes
Retain as many objects as needed in one invocation of tracer_context.with_tracer
, and
avoid calling with_tracer
again and again for each object. The tracer
provided by
ObjectTracerFactory::with_tracer
enqueues retained objects in an internal list specific to
this invocation of with_tracer
, and will create reasonably sized work packets to compute
the transitive closure. This means the invocation of with_tracer
has a non-trivial
overhead, but each invocation of tracer.trace_object
is cheap.
Don’t do this:
for object in objects {
tracer_context.with_tracer(worker, |tracer| { // This is expensive! DON'T DO THIS!
tracer.trace_object(object);
});
}
Use ObjectReference::get_forwarded_object()
to get the forwarded address of reachable
objects. Only use tracer.trace_object
for retaining unreachable objects. If
trace_object
is called on an already reached object, it will also return its new address
if moved. However, tracer_context.with_tracer
has a cost, and the VM binding may
accidentally “resurrect” dead objects if failed to check object.is_reachable()
first. If
the VM binding does not intend to retain any objects, it should completely avoid touching
tracer_context
.
Clone the tracer_context
for parallelism. The ObjectTracerContext
has Clone
as
its supertrait. The VM binding can clone it and distribute each clone into a work packet.
By doing so, the VM binding can parallelize the processing of finalizers and weak references
by creating multiple work packets.
sourcefn forward_weak_refs(
_worker: &mut GCWorker<VM>,
_tracer_context: impl ObjectTracerContext<VM>,
)
fn forward_weak_refs( _worker: &mut GCWorker<VM>, _tracer_context: impl ObjectTracerContext<VM>, )
Forward weak references.
This function will only be called in the forwarding stage when using the mark-compact GC algorithm. Mark-compact computes transive closure twice during each GC. It marks objects in the first transitive closure, and forward references in the second transitive closure.
Arguments:
worker
: The current GC worker.tracer_context
: Use this to get access anObjectTracer
and use it to update weak references.