mmtk/util/
reference_processor.rs

1use std::collections::HashSet;
2use std::sync::atomic::AtomicBool;
3use std::sync::atomic::Ordering;
4use std::sync::Mutex;
5use std::vec::Vec;
6
7use crate::plan::is_nursery_gc;
8use crate::plan::tracing::gc_work::DefaultObjectTracerContext;
9use crate::plan::tracing::Trace;
10use crate::scheduler::WorkBucketStage;
11use crate::util::ObjectReference;
12use crate::util::VMWorkerThread;
13use crate::vm::ObjectTracer;
14use crate::vm::ObjectTracerContext;
15use crate::vm::ReferenceGlue;
16use crate::vm::VMBinding;
17
18/// Holds all reference processors for each weak reference Semantics.
19/// Currently this is based on Java's weak reference semantics (soft/weak/phantom).
20/// We should make changes to make this general rather than Java specific.
21pub struct ReferenceProcessors {
22    soft: ReferenceProcessor,
23    weak: ReferenceProcessor,
24    phantom: ReferenceProcessor,
25}
26
27impl ReferenceProcessors {
28    pub fn new() -> Self {
29        ReferenceProcessors {
30            soft: ReferenceProcessor::new(Semantics::SOFT),
31            weak: ReferenceProcessor::new(Semantics::WEAK),
32            phantom: ReferenceProcessor::new(Semantics::PHANTOM),
33        }
34    }
35
36    pub fn get(&self, semantics: Semantics) -> &ReferenceProcessor {
37        match semantics {
38            Semantics::SOFT => &self.soft,
39            Semantics::WEAK => &self.weak,
40            Semantics::PHANTOM => &self.phantom,
41        }
42    }
43
44    pub fn add_soft_candidate(&self, reff: ObjectReference) {
45        trace!("Add soft candidate: {}", reff);
46        self.soft.add_candidate(reff);
47    }
48
49    pub fn add_weak_candidate(&self, reff: ObjectReference) {
50        trace!("Add weak candidate: {}", reff);
51        self.weak.add_candidate(reff);
52    }
53
54    pub fn add_phantom_candidate(&self, reff: ObjectReference) {
55        trace!("Add phantom candidate: {}", reff);
56        self.phantom.add_candidate(reff);
57    }
58
59    /// This will invoke enqueue for each reference processor, which will
60    /// call back to the VM to enqueue references whose referents are cleared
61    /// in this GC.
62    pub fn enqueue_refs<VM: VMBinding>(&self, tls: VMWorkerThread) {
63        self.soft.enqueue::<VM>(tls);
64        self.weak.enqueue::<VM>(tls);
65        self.phantom.enqueue::<VM>(tls);
66    }
67
68    /// A separate reference forwarding step. Normally when we scan refs, we deal with forwarding.
69    /// However, for some plans like mark compact, at the point we do ref scanning, we do not know
70    /// the forwarding addresses yet, thus we cannot do forwarding during scan refs. And for those
71    /// plans, this separate step is required.
72    pub fn forward_refs<VM: VMBinding, OT: ObjectTracer>(
73        &self,
74        trace: &mut OT,
75        mmtk: &'static MMTK<VM>,
76    ) {
77        debug_assert!(
78            mmtk.get_plan().constraints().needs_forward_after_liveness,
79            "A plan with needs_forward_after_liveness=false does not need a separate forward step"
80        );
81        self.soft
82            .forward::<VM, OT>(trace, is_nursery_gc(mmtk.get_plan()));
83        self.weak
84            .forward::<VM, OT>(trace, is_nursery_gc(mmtk.get_plan()));
85        self.phantom
86            .forward::<VM, OT>(trace, is_nursery_gc(mmtk.get_plan()));
87    }
88
89    // Methods for scanning weak references. It needs to be called in a decreasing order of reference strengths, i.e. soft > weak > phantom
90
91    pub fn retain_soft_refs<VM: VMBinding, OT: ObjectTracer>(
92        &self,
93        trace: &mut OT,
94        mmtk: &'static MMTK<VM>,
95    ) {
96        self.soft
97            .retain::<VM, OT>(trace, is_nursery_gc(mmtk.get_plan()));
98    }
99
100    /// Scan soft references.
101    pub fn scan_soft_refs<VM: VMBinding>(&self, mmtk: &'static MMTK<VM>) {
102        // This will update the references (and the referents).
103        self.soft.scan::<VM>(is_nursery_gc(mmtk.get_plan()));
104    }
105
106    /// Scan weak references.
107    pub fn scan_weak_refs<VM: VMBinding>(&self, mmtk: &'static MMTK<VM>) {
108        self.weak.scan::<VM>(is_nursery_gc(mmtk.get_plan()));
109    }
110
111    /// Scan phantom references.
112    pub fn scan_phantom_refs<VM: VMBinding>(&self, mmtk: &'static MMTK<VM>) {
113        self.phantom.scan::<VM>(is_nursery_gc(mmtk.get_plan()));
114    }
115}
116
117impl Default for ReferenceProcessors {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123// XXX: We differ from the original implementation
124//      by ignoring "stress," i.e. where the array
125//      of references is grown by 1 each time. We
126//      can't do this here b/c std::vec::Vec doesn't
127//      allow us to customize its behaviour like that.
128//      (Similarly, GROWTH_FACTOR is locked at 2.0, but
129//      luckily this is also the value used by Java MMTk.)
130const INITIAL_SIZE: usize = 256;
131
132/// We create a reference processor for each semantics. Generally we expect these
133/// to happen for each processor:
134/// 1. The VM adds reference candidates. They could either do it when a weak reference
135///    is created, or when a weak reference is traced during GC.
136/// 2. We scan references after the GC determins liveness.
137/// 3. We forward references if the GC needs forwarding after liveness.
138/// 4. We inform the binding of references whose referents are cleared during this GC by enqueue'ing.
139pub struct ReferenceProcessor {
140    /// Most of the reference processor is protected by a mutex.
141    sync: Mutex<ReferenceProcessorSync>,
142
143    /// The semantics for the reference processor
144    semantics: Semantics,
145
146    /// Is it allowed to add candidate to this reference processor? The value is true for most of the time,
147    /// but it is set to false once we finish forwarding references, at which point we do not expect to encounter
148    /// any 'new' reference in the same GC. This makes sure that no new entry will be added to our reference table once
149    /// we finish forwarding, as we will not be able to process the entry in that GC.
150    // This avoids an issue in the following scenario in mark compact:
151    // 1. First trace: add a candidate WR
152    // 2. Weak reference scan: scan the reference table, as MC does not forward object in the first trace. This scan does not update any reference.
153    // 3. Second trace: call add_candidate again with WR, but WR gets ignored as we already have WR in our reference table.
154    // 4. Weak reference forward: call trace_object for WR, which pushes WR to the node buffer and update WR -> WR' in our reference table.
155    // 5. When we trace objects in the node buffer, we will attempt to add WR as a candidate. As we have updated WR to WR' in our reference
156    //    table, we would accept WR as a candidate. But we will not trace WR again, and WR will be invalid after this GC.
157    // This flag is set to false after Step 4, so in Step 5, we will ignore adding WR.
158    allow_new_candidate: AtomicBool,
159}
160
161#[derive(Debug, PartialEq, Clone, Copy)]
162pub enum Semantics {
163    SOFT,
164    WEAK,
165    PHANTOM,
166}
167
168struct ReferenceProcessorSync {
169    /// The table of reference objects for the current semantics. We add references to this table by
170    /// add_candidate(). After scanning this table, a reference in the table should either
171    /// stay in the table (if the referent is alive) or go to enqueued_reference (if the referent is dead and cleared).
172    /// Note that this table should not have duplicate entries, otherwise we will scan the duplicates multiple times, and
173    /// that may lead to incorrect results.
174    references: HashSet<ObjectReference>,
175
176    /// References whose referents are cleared during this GC. We add references to this table during
177    /// scanning, and we pop from this table during the enqueue work at the end of GC.
178    enqueued_references: Vec<ObjectReference>,
179
180    /// Index into the references table for the start of nursery objects
181    nursery_index: usize,
182}
183
184impl ReferenceProcessor {
185    pub fn new(semantics: Semantics) -> Self {
186        ReferenceProcessor {
187            sync: Mutex::new(ReferenceProcessorSync {
188                references: HashSet::with_capacity(INITIAL_SIZE),
189                enqueued_references: vec![],
190                nursery_index: 0,
191            }),
192            semantics,
193            allow_new_candidate: AtomicBool::new(true),
194        }
195    }
196
197    /// Add a candidate.
198    pub fn add_candidate(&self, reff: ObjectReference) {
199        if !self.allow_new_candidate.load(Ordering::SeqCst) {
200            return;
201        }
202
203        let mut sync = self.sync.lock().unwrap();
204        sync.references.insert(reff);
205    }
206
207    fn disallow_new_candidate(&self) {
208        self.allow_new_candidate.store(false, Ordering::SeqCst);
209    }
210
211    fn allow_new_candidate(&self) {
212        self.allow_new_candidate.store(true, Ordering::SeqCst);
213    }
214
215    // These functions call `ObjectReference::get_forwarded_object`, not `trace_object()`.
216    // They are used by steps that do not expand the transitive closure.  Processing weak and
217    // phantom references never expand the transitive closure.  Soft references, when not retained,
218    // do not expand the transitive closure, either.
219    // These functions are intended to make the code easier to understand.
220
221    /// Return the new `ObjectReference` of a referent if it is already moved, or its current
222    /// `ObjectReference` otherwise.  The referent must be live when calling this function.
223    fn get_forwarded_referent(referent: ObjectReference) -> ObjectReference {
224        debug_assert!(referent.is_live());
225        referent.get_forwarded_object().unwrap_or(referent)
226    }
227
228    /// Return the new `ObjectReference` of a reference object if it is already moved, or its
229    /// current `ObjectReference` otherwise.  The reference object must be live when calling this
230    /// function.
231    fn get_forwarded_reference(object: ObjectReference) -> ObjectReference {
232        debug_assert!(object.is_live());
233        object.get_forwarded_object().unwrap_or(object)
234    }
235
236    // These funcions call `trace_object()`, which will ensure the object and its descendents will
237    // be traced.  They are only called in steps that expand the transitive closure.  That include
238    // retaining soft references, and (for MarkCompact) tracing objects for forwarding.
239    // Note that finalizers also expand the transitive closure.
240    // These functions are intended to make the code easier to understand.
241
242    /// This function is called when retaining soft reference.  It
243    /// -   keeps the referent alive, and
244    /// -   adds the referent to the tracing queue if not yet reached, so that its children will be
245    ///     kept alive, too, and
246    /// -   gets the new object reference of the referent if it is moved.
247    fn keep_referent_alive<OT: ObjectTracer>(
248        tracer: &mut OT,
249        referent: ObjectReference,
250    ) -> ObjectReference {
251        tracer.trace_object(referent)
252    }
253
254    /// This function is called when forwarding the references and referents (for MarkCompact). It
255    /// -   adds the reference or the referent to the tracing queue if not yet reached, so that
256    ///     the children of the reference or referent will be visited and forwarded, too, and
257    /// -   gets the forwarded object reference of the object.
258    fn trace_forward_object<OT: ObjectTracer>(
259        tracer: &mut OT,
260        referent: ObjectReference,
261    ) -> ObjectReference {
262        tracer.trace_object(referent)
263    }
264
265    /// Inform the binding to enqueue the weak references whose referents were cleared in this GC.
266    pub fn enqueue<VM: VMBinding>(&self, tls: VMWorkerThread) {
267        // We will acquire a lock below. If anyone tries to insert new weak refs which will acquire the same lock, a deadlock will occur.
268        // This does happen for OpenJDK with ConcurrentImmix where a write barrier is triggered during the enqueueing of weak references,
269        // and the write barrier scans the objects and attempts to add new weak references.
270        // Disallow new candidates to prevent the deadlock.
271        self.disallow_new_candidate();
272        let mut sync = self.sync.lock().unwrap();
273
274        // This is the end of a GC. We do some assertions here to make sure our reference tables are correct.
275        #[cfg(debug_assertions)]
276        {
277            // For references in the table, the reference needs to be valid, and if the referent is not cleared, it should be valid as well
278            sync.references.iter().for_each(|reff| {
279                debug_assert!(reff.is_in_any_space());
280                if let Some(referent) = VM::VMReferenceGlue::get_referent(*reff) {
281                    debug_assert!(
282                        referent.is_in_any_space(),
283                        "Referent {:?} (of reference {:?}) is not in any space",
284                        referent,
285                        reff
286                    );
287                }
288            });
289            // For references that will be enqueue'd, the reference needs to be valid, and the referent needs to be cleared.
290            sync.enqueued_references.iter().for_each(|reff| {
291                debug_assert!(reff.is_in_any_space());
292                let maybe_referent = VM::VMReferenceGlue::get_referent(*reff);
293                debug_assert!(maybe_referent.is_none());
294            });
295        }
296
297        if !sync.enqueued_references.is_empty() {
298            trace!("enqueue: {:?}", sync.enqueued_references);
299            VM::VMReferenceGlue::enqueue_references(&sync.enqueued_references, tls);
300            sync.enqueued_references.clear();
301        }
302
303        self.allow_new_candidate();
304    }
305
306    /// Forward the reference tables in the reference processor. This is only needed if a plan does not forward
307    /// objects in their first transitive closure.
308    /// nursery is not used for this.
309    pub fn forward<VM: VMBinding, OT: ObjectTracer>(&self, trace: &mut OT, _nursery: bool) {
310        let mut sync = self.sync.lock().unwrap();
311        debug!("Starting ReferenceProcessor.forward({:?})", self.semantics);
312
313        // Forward a single reference
314        fn forward_reference<VM: VMBinding, OT: ObjectTracer>(
315            trace: &mut OT,
316            reference: ObjectReference,
317        ) -> ObjectReference {
318            {
319                use crate::vm::ObjectModel;
320                trace!(
321                    "Forwarding reference: {} (size: {})",
322                    reference,
323                    <VM as VMBinding>::VMObjectModel::get_current_size(reference)
324                );
325            }
326
327            if let Some(old_referent) = <VM as VMBinding>::VMReferenceGlue::get_referent(reference)
328            {
329                let new_referent = ReferenceProcessor::trace_forward_object(trace, old_referent);
330                <VM as VMBinding>::VMReferenceGlue::set_referent(reference, new_referent);
331
332                trace!(
333                    " referent: {} (forwarded to {})",
334                    old_referent,
335                    new_referent
336                );
337            }
338
339            let new_reference = ReferenceProcessor::trace_forward_object(trace, reference);
340            trace!(" reference: forwarded to {}", new_reference);
341
342            new_reference
343        }
344
345        sync.references = sync
346            .references
347            .iter()
348            .map(|reff| forward_reference::<VM, OT>(trace, *reff))
349            .collect();
350
351        sync.enqueued_references = sync
352            .enqueued_references
353            .iter()
354            .map(|reff| forward_reference::<VM, OT>(trace, *reff))
355            .collect();
356
357        debug!("Ending ReferenceProcessor.forward({:?})", self.semantics);
358
359        // We finish forwarding. No longer accept new candidates.
360        self.disallow_new_candidate();
361    }
362
363    /// Scan the reference table, and update each reference/referent.
364    /// It doesn't keep the reference or the referent alive.
365    // TODO: nursery is currently ignored. We used to use Vec for the reference table, and use an int
366    // to point to the reference that we last scanned. However, when we use HashSet for reference table,
367    // we can no longer do that.
368    fn scan<VM: VMBinding>(&self, _nursery: bool) {
369        let mut sync = self.sync.lock().unwrap();
370
371        debug!("Starting ReferenceProcessor.scan({:?})", self.semantics);
372
373        trace!(
374            "{:?} Reference table is {:?}",
375            self.semantics,
376            sync.references
377        );
378
379        //debug_assert!(sync.enqueued_references.is_empty());
380        // Put enqueued reference in this vec
381        let mut enqueued_references = vec![];
382
383        // Determinine liveness for each reference and only keep the refs if `process_reference()` returns Some.
384        let new_set: HashSet<ObjectReference> = sync
385            .references
386            .iter()
387            .filter_map(|reff| self.process_reference::<VM>(*reff, &mut enqueued_references))
388            .collect();
389
390        let num_old = sync.references.len();
391        let num_new = new_set.len();
392        let num_enqueued = enqueued_references.len();
393
394        debug!(
395            "{:?} reference table from {} to {} ({} enqueued)",
396            self.semantics, num_old, num_new, num_enqueued,
397        );
398
399        let semantics_int = self.semantics as usize;
400
401        probe!(
402            mmtk,
403            reference_scanned,
404            semantics_int,
405            num_old,
406            num_new,
407            num_enqueued
408        );
409
410        sync.references = new_set;
411        sync.enqueued_references.extend(enqueued_references);
412
413        debug!("Ending ReferenceProcessor.scan({:?})", self.semantics);
414    }
415
416    /// Retain referent in the reference table. This method deals only with soft references.
417    /// It retains the referent if the reference is definitely reachable. This method does
418    /// not update reference or referent. So after this method, scan() should be used to update
419    /// the references/referents.
420    fn retain<VM: VMBinding, OT: ObjectTracer>(&self, trace: &mut OT, _nursery: bool) {
421        debug_assert!(self.semantics == Semantics::SOFT);
422
423        let sync = self.sync.lock().unwrap();
424
425        debug!("Starting ReferenceProcessor.retain({:?})", self.semantics);
426        trace!(
427            "{:?} Reference table is {:?}",
428            self.semantics,
429            sync.references
430        );
431
432        let num_refs = sync.references.len();
433        let mut num_live = 0usize;
434        let mut num_retained = 0usize;
435
436        for reference in sync.references.iter() {
437            trace!("Processing reference: {:?}", reference);
438
439            if !reference.is_live() {
440                // Reference is currently unreachable but may get reachable by the
441                // following trace. We postpone the decision.
442                continue;
443            }
444            num_live += 1;
445            // Reference is definitely reachable.  Retain the referent.
446            if let Some(referent) = VM::VMReferenceGlue::get_referent(*reference) {
447                Self::keep_referent_alive(trace, referent);
448                num_retained += 1;
449                trace!(" ~> {:?} (retained)", referent);
450            }
451        }
452
453        probe!(mmtk, reference_retained, num_refs, num_live, num_retained,);
454
455        debug!("Ending ReferenceProcessor.retain({:?})", self.semantics);
456    }
457
458    /// Process a reference.
459    /// * If both the reference and the referent is alive, return the updated reference and update its referent properly.
460    /// * If the reference is alive, and the referent is not cleared but not alive, return None and the reference (with cleared referent) is enqueued.
461    /// * For other cases, return None.
462    ///
463    /// If a None value is returned, the reference can be removed from the reference table. Otherwise, the updated reference should be kept
464    /// in the reference table.
465    fn process_reference<VM: VMBinding>(
466        &self,
467        reference: ObjectReference,
468        enqueued_references: &mut Vec<ObjectReference>,
469    ) -> Option<ObjectReference> {
470        trace!("Process reference: {}", reference);
471
472        // If the reference is dead, we're done with it. Let it (and
473        // possibly its referent) be garbage-collected.
474        if !reference.is_live() {
475            VM::VMReferenceGlue::clear_referent(reference);
476            trace!(" UNREACHABLE reference: {}", reference);
477            return None;
478        }
479
480        // The reference object is live.
481        let new_reference = Self::get_forwarded_reference(reference);
482        trace!(" forwarded to: {}", new_reference);
483
484        // Get the old referent.
485        let maybe_old_referent = VM::VMReferenceGlue::get_referent(reference);
486        trace!(" referent: {:?}", maybe_old_referent);
487
488        // If the application has cleared the referent the Java spec says
489        // this does not cause the Reference object to be enqueued. We
490        // simply allow the Reference object to fall out of our
491        // waiting list.
492        let Some(old_referent) = maybe_old_referent else {
493            trace!("  (cleared referent) ");
494            return None;
495        };
496
497        if old_referent.is_live() {
498            // Referent is still reachable in a way that is as strong as
499            // or stronger than the current reference level.
500            let new_referent = Self::get_forwarded_referent(old_referent);
501            debug_assert!(new_referent.is_live());
502            trace!("  forwarded referent to: {}", new_referent);
503
504            // The reference object stays on the waiting list, and the
505            // referent is untouched. The only thing we must do is
506            // ensure that the former addresses are updated with the
507            // new forwarding addresses in case the collector is a
508            // copying collector.
509
510            // Update the referent
511            VM::VMReferenceGlue::set_referent(new_reference, new_referent);
512            Some(new_reference)
513        } else {
514            // Referent is unreachable. Clear the referent and enqueue the reference object.
515            trace!("  UNREACHABLE referent: {}", old_referent);
516
517            VM::VMReferenceGlue::clear_referent(new_reference);
518            enqueued_references.push(new_reference);
519            None
520        }
521    }
522}
523
524use crate::scheduler::GCWork;
525use crate::scheduler::GCWorker;
526use crate::MMTK;
527use std::marker::PhantomData;
528
529#[derive(Default)]
530pub(crate) struct RescanReferences<VM: VMBinding> {
531    pub soft: bool,
532    pub weak: bool,
533    pub phantom_data: PhantomData<VM>,
534}
535
536impl<VM: VMBinding> GCWork<VM> for RescanReferences<VM> {
537    fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
538        if self.soft {
539            mmtk.reference_processors.scan_soft_refs(mmtk);
540        }
541        if self.weak {
542            mmtk.reference_processors.scan_weak_refs(mmtk);
543        }
544    }
545}
546
547#[derive(Default)]
548pub(crate) struct SoftRefProcessing<T: Trace>(PhantomData<T>);
549impl<T: Trace> GCWork<T::VM> for SoftRefProcessing<T> {
550    fn do_work(&mut self, worker: &mut GCWorker<T::VM>, mmtk: &'static MMTK<T::VM>) {
551        if !mmtk.state.is_emergency_collection() {
552            // Postpone the scanning to the end of the transitive closure from strongly reachable
553            // soft references.
554            let rescan = Box::new(RescanReferences {
555                soft: true,
556                weak: false,
557                phantom_data: PhantomData,
558            });
559            worker.scheduler().work_buckets[WorkBucketStage::SoftRefClosure].set_sentinel(rescan);
560
561            // Retain soft references.  This will expand the transitive closure.
562            let tracer_context =
563                DefaultObjectTracerContext::<T>::new(WorkBucketStage::SoftRefClosure);
564            tracer_context.with_tracer(worker, |tracer| {
565                mmtk.reference_processors.retain_soft_refs(tracer, mmtk);
566            });
567        } else {
568            // Scan soft references immediately without retaining.
569            mmtk.reference_processors.scan_soft_refs(mmtk);
570        }
571    }
572}
573
574impl<T: Trace> SoftRefProcessing<T> {
575    pub fn new() -> Self {
576        Self(PhantomData)
577    }
578}
579
580#[derive(Default)]
581pub(crate) struct WeakRefProcessing<VM: VMBinding>(PhantomData<VM>);
582impl<VM: VMBinding> GCWork<VM> for WeakRefProcessing<VM> {
583    fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
584        mmtk.reference_processors.scan_weak_refs(mmtk);
585    }
586}
587impl<VM: VMBinding> WeakRefProcessing<VM> {
588    pub fn new() -> Self {
589        Self(PhantomData)
590    }
591}
592
593#[derive(Default)]
594pub(crate) struct PhantomRefProcessing<VM: VMBinding>(PhantomData<VM>);
595impl<VM: VMBinding> GCWork<VM> for PhantomRefProcessing<VM> {
596    fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
597        mmtk.reference_processors.scan_phantom_refs(mmtk);
598    }
599}
600impl<VM: VMBinding> PhantomRefProcessing<VM> {
601    pub fn new() -> Self {
602        Self(PhantomData)
603    }
604}
605
606#[derive(Default)]
607pub(crate) struct RefForwarding<T: Trace>(PhantomData<T>);
608impl<T: Trace> GCWork<T::VM> for RefForwarding<T> {
609    fn do_work(&mut self, worker: &mut GCWorker<T::VM>, mmtk: &'static MMTK<T::VM>) {
610        let tracer_context = DefaultObjectTracerContext::<T>::new(WorkBucketStage::RefForwarding);
611        tracer_context.with_tracer(worker, |tracer| {
612            mmtk.reference_processors.forward_refs(tracer, mmtk);
613        });
614    }
615}
616impl<T: Trace> RefForwarding<T> {
617    pub fn new() -> Self {
618        Self(PhantomData)
619    }
620}
621
622#[derive(Default)]
623pub(crate) struct RefEnqueue<VM: VMBinding>(PhantomData<VM>);
624impl<VM: VMBinding> GCWork<VM> for RefEnqueue<VM> {
625    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
626        mmtk.reference_processors.enqueue_refs::<VM>(worker.tls);
627    }
628}
629impl<VM: VMBinding> RefEnqueue<VM> {
630    pub fn new() -> Self {
631        Self(PhantomData)
632    }
633}