mmtk::vm::scanning

Trait Scanning

source
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§

source

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§

source

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.
source

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.
source

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.
source

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.
source

fn supports_return_barrier() -> bool

Return whether the VM supports return barriers. This is unused at the moment.

source

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§

source

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.
source

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.
source

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:

  1. Query if an object is already reached.
    • by calling ObjectReference::is_reachable()
  2. Get the new address of an object if it is already reached.
    • by calling ObjectReference::get_forwarded_object()
  3. Keep an object and its descendents alive if not yet reached.
    • using tracer_context
  4. Request this function to be called again after transitive closure is finished again.
    • by returning true

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 an ObjectTracer 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.

source

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 an ObjectTracer and use it to update weak references.

Object Safety§

This trait is not object safe.

Implementors§