mmtk/plan/lxr/gc_work/
rc.rs

1use super::super::LazySweepingJobsCounter;
2use super::super::SurvivalRatioPredictorLocal;
3use super::super::LXR;
4use super::super::{LAZY_DECREMENTS, MATURE_EVACUATION, NO_EVAC, NURSERY_EVACUATION};
5use super::tracing::LXRConcurrentTraceObjects;
6use super::tracing::LXRStopTheWorldProcessEdges;
7#[cfg(feature = "non_moving_root")]
8use super::tracing::LXRStopTheWorldProcessNodes;
9#[cfg(feature = "non_moving_root")]
10use super::tracing::ProcessModBufSATB;
11use super::ProcessEdgesBase;
12use crate::plan::VectorQueue;
13use crate::policy::immix::block::BlockState;
14use crate::scheduler::gc_work::RootKind;
15use crate::util::copy::CopySemantics;
16use crate::util::linear_scan::UnstraddlableRegion;
17use crate::util::metadata::side_metadata::SideMetadataSpec;
18use crate::util::rc::*;
19use crate::vm::slot::Slot;
20use crate::{
21    plan::concurrent::global::ConcurrentPlan,
22    plan::concurrent::Pause,
23    plan::global::Plan,
24    policy::{immix::block::Block, space::Space},
25    scheduler::{GCWork, GCWorker, WorkBucketStage},
26    util::{metadata::side_metadata, object_forwarding, ObjectReference},
27    vm::*,
28    MMTK,
29};
30use atomic::Ordering;
31#[cfg(feature = "non_moving_root")]
32use std::marker::PhantomData;
33use std::ops::{Deref, DerefMut};
34use std::sync::Arc;
35
36pub struct ProcessIncs<VM: VMBinding, const KIND: EdgeKind> {
37    /// Increments to process
38    incs: Vec<VM::VMSlot>,
39    /// Recursively generated new increments
40    new_incs: VectorQueue<VM::VMSlot>,
41    new_incs_count: u32,
42    pause: Pause,
43    in_cm: bool,
44    no_evac: bool,
45    pub root_kind: Option<RootKind>,
46    depth: u32,
47    lxr: &'static LXR<VM>,
48    rc: RefCountHelper<VM>,
49    survival_ratio_predictor_local: SurvivalRatioPredictorLocal,
50}
51
52unsafe impl<VM: VMBinding, const KIND: EdgeKind> Send for ProcessIncs<VM, KIND> {}
53
54impl<VM: VMBinding, const KIND: EdgeKind> ProcessIncs<VM, KIND> {
55    const CAPACITY: usize = 1024;
56    const UNLOG_BITS: SideMetadataSpec = *VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
57        .as_spec()
58        .extract_side_spec();
59
60    fn __default(lxr: &'static LXR<VM>) -> Self {
61        Self {
62            incs: vec![],
63            new_incs: VectorQueue::default(),
64            new_incs_count: 0,
65            lxr,
66            pause: Pause::RefCount,
67            in_cm: false,
68            no_evac: false,
69            depth: 1,
70            rc: RefCountHelper::NEW,
71            root_kind: None,
72            survival_ratio_predictor_local: SurvivalRatioPredictorLocal::default(),
73        }
74    }
75
76    fn add_new_slot(&mut self, worker: &mut GCWorker<VM>, s: VM::VMSlot) {
77        self.new_incs.push(s);
78        self.new_incs_count += 1;
79        if self.new_incs_count as usize >= Self::CAPACITY {
80            self.flush(worker);
81        }
82    }
83
84    pub fn new(incs: Vec<VM::VMSlot>, lxr: &'static LXR<VM>) -> Self {
85        Self {
86            incs,
87            ..Self::__default(lxr)
88        }
89    }
90
91    #[cfg(feature = "non_moving_root")]
92    /// Reference-count root objects reported as objects rather than as slots.
93    /// Node counterpart of `process_incs::<EDGE_KIND_ROOT>`. Nothing here may move an object.
94    fn process_root_nodes(
95        &mut self,
96        worker: &mut GCWorker<VM>,
97        nodes: Vec<ObjectReference>,
98    ) -> (Vec<ObjectReference>, Vec<ObjectReference>) {
99        let mut roots = Vec::with_capacity(nodes.len());
100        let mut uncounted = vec![];
101        for o in nodes {
102            if !self.lxr.is_rc_object(o) {
103                uncounted.push(o);
104                continue;
105            }
106            let los = self.lxr.los().in_space(o);
107            if self.inc(o) {
108                // Promote without moving.
109                self.promote(worker, o, false, los, 0);
110            }
111            roots.push(o);
112        }
113        // Promotion above can queue further increments; hand them on rather than
114        // dropping them.
115        self.flush(worker);
116        (roots, uncounted)
117    }
118
119    #[cfg(feature = "non_moving_root")]
120    /// Node-shaped counterpart of [`GCWork::do_work`] below for `KIND == EDGE_KIND_ROOT`.
121    /// However, the major difference is that we cannot move any of these objects here, as
122    /// we don't know their slots.
123    pub fn do_work_root_nodes(
124        &mut self,
125        nodes: Vec<ObjectReference>,
126        worker: &mut GCWorker<VM>,
127        mmtk: &'static MMTK<VM>,
128    ) {
129        debug_assert_eq!(KIND, EDGE_KIND_ROOT);
130        let lxr = self.lxr;
131        self.pause = lxr.current_pause().unwrap();
132        self.in_cm = lxr.concurrent_work_in_progress();
133        let (roots, uncounted) = self.process_root_nodes(worker, nodes);
134        let mut to_trace = roots.clone();
135        to_trace.extend_from_slice(&uncounted);
136        if to_trace.is_empty() {
137            return;
138        }
139        // Roots arriving mid-cycle must also enter the SATB snapshot, or the cycle
140        // collector can conclude their subgraphs are unreachable.
141        if lxr.cm_enabled() && self.in_cm {
142            worker.add_work(
143                WorkBucketStage::FinishConcurrentWork,
144                ProcessModBufSATB::new(to_trace.clone()),
145            );
146        }
147        // Nodes, unlike slots, are never re-scanned, so a stop-the-world tracing pause has to
148        // mark from them here or they are never marked at all.
149        match self.pause {
150            Pause::Full => worker.add_work(
151                WorkBucketStage::PinningRootsTrace,
152                LXRStopTheWorldProcessNodes::<VM, true>::new(
153                    to_trace,
154                    mmtk,
155                    WorkBucketStage::Closure,
156                ),
157            ),
158            Pause::FinalMark => worker.add_work(
159                WorkBucketStage::PinningRootsTrace,
160                LXRStopTheWorldProcessNodes::<VM, false>::new(
161                    to_trace,
162                    mmtk,
163                    WorkBucketStage::Closure,
164                ),
165            ),
166            // Seed concurrent marking with these roots, exactly as the slot path does --
167            // otherwise node roots seed nothing until `FinalMark`.
168            Pause::InitialMark if lxr.cm_enabled() => {
169                worker.scheduler().work_buckets[WorkBucketStage::ConcurrentResumable]
170                    .add(LXRConcurrentTraceObjects::new(to_trace, mmtk));
171            }
172            // `RefCount` has no marking phase.
173            _ => {}
174        }
175        // Recorded so the matching decrements are applied in the next pause.
176        if !roots.is_empty() {
177            lxr.curr_roots.read().unwrap().push(roots);
178        }
179    }
180
181    fn promote(
182        &mut self,
183        worker: &mut GCWorker<VM>,
184        o: ObjectReference,
185        copied: bool,
186        los: bool,
187        depth: u32,
188    ) {
189        let size = o.get_size::<VM>();
190
191        if !los {
192            let block = Block::containing(o);
193            let in_nursery_block = block.get_state() == BlockState::Nursery;
194            if !copied && in_nursery_block {
195                block.set_as_in_place_promoted();
196            }
197            self.rc.promote_with_size(o, size);
198            self.survival_ratio_predictor_local
199                .record_promotion(size, copied);
200        } else {
201            // println!("promote los {:?} {}", o, self.immix().is_marked(o));
202        }
203        // Don't mark copied objects in initial mark pause. The concurrent marker will do it (and can also resursively mark the old objects).
204        if self.in_cm || self.pause == Pause::FinalMark {
205            debug_assert!(self.lxr.is_marked(o), "{:?} is not marked", o);
206        }
207        self.scan_nursery_object(worker, o, los, !copied, depth, size);
208    }
209
210    fn record_mature_evac_remset2(
211        &mut self,
212        slot_in_defrag: bool,
213        s: VM::VMSlot,
214        o: ObjectReference,
215    ) {
216        if !(MATURE_EVACUATION && (self.in_cm || self.pause == Pause::FinalMark)) {
217            return;
218        }
219        if !slot_in_defrag && self.lxr.in_defrag(o) {
220            self.lxr.mature_evac_remset.record(s, o, self.lxr);
221        }
222    }
223
224    fn record_mature_evac_remset(&mut self, s: VM::VMSlot, o: ObjectReference) {
225        if !(MATURE_EVACUATION && (self.in_cm || self.pause == Pause::FinalMark)) {
226            return;
227        }
228        self.record_mature_evac_remset2(self.lxr.address_in_defrag(s.to_address()), s, o);
229    }
230
231    fn scan_nursery_object(
232        &mut self,
233        worker: &mut GCWorker<VM>,
234        o: ObjectReference,
235        los: bool,
236        in_place_promotion: bool,
237        _depth: u32,
238        size: usize,
239    ) {
240        let heap_bytes_per_unlog_byte =
241            VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC.heap_bytes_per_unlog_byte();
242        if los {
243            let start =
244                side_metadata::address_to_meta_address(&Self::UNLOG_BITS, o.to_raw_address())
245                    .to_mut_ptr::<u8>();
246            let limit = side_metadata::address_to_meta_address(
247                &Self::UNLOG_BITS,
248                (o.to_raw_address() + size).align_up(heap_bytes_per_unlog_byte),
249            )
250            .to_mut_ptr::<u8>();
251            unsafe {
252                let bytes = limit.offset_from(start) as usize;
253                std::ptr::write_bytes(start, 0xffu8, bytes);
254            }
255            o.to_raw_address().unlog_field_relaxed::<VM>();
256        } else if in_place_promotion {
257            // Set every unlog bit covering the object. We align to the nearest u32 boundary,
258            // so we may overset unlog bits for adjacent objects.
259            let step = heap_bytes_per_unlog_byte << 2;
260            let start = VM::VMObjectModel::ref_to_object_start(o);
261            let aligned_end = (start + size).align_up(step);
262            let mut cursor = start.align_down(step);
263            let mut meta = side_metadata::address_to_meta_address(&Self::UNLOG_BITS, cursor);
264            while cursor < aligned_end {
265                unsafe { meta.store(0xffffffffu32) }
266                meta += 4usize;
267                cursor += step;
268            }
269        };
270        // Promotion above arms the per-field unlog bits. Do the same for object log bits.
271        #[cfg(feature = "lxr_object_log")]
272        VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.mark_as_unlogged::<VM>(o, Ordering::SeqCst);
273        let obj_in_defrag = !los && Block::in_defrag_block(o);
274        let tls = worker.tls.0;
275        o.iterate_fields::<VM, _>(tls, |slot| {
276            let Some(target) = slot.load() else {
277                return;
278            };
279            debug_assert!(
280                target.to_raw_address().is_mapped(),
281                "Unmapped obj {:?}.{:?} -> {:?}",
282                o,
283                slot,
284                target
285            );
286            debug_assert!(
287                target.is_in_any_space(),
288                "Unmapped obj {:?}.{:?} -> {:?}",
289                o,
290                slot,
291                target
292            );
293            let rc = self.rc.count(target);
294            if rc == 0 {
295                self.add_new_slot(worker, slot);
296            } else {
297                if rc != crate::util::rc::MAX_REF_COUNT {
298                    let _ = self.rc.inc(target);
299                }
300                self.record_mature_evac_remset2(obj_in_defrag, slot, target);
301            }
302        });
303    }
304
305    #[cold]
306    fn flush(&mut self, worker: &mut GCWorker<VM>) {
307        if !self.new_incs.is_empty() {
308            let new_incs = self.new_incs.take();
309            let mut w = ProcessIncs::<VM, EDGE_KIND_NURSERY>::new(new_incs, self.lxr);
310            w.depth += 1;
311            worker.add_work(WorkBucketStage::Unconstrained, w);
312        }
313        self.new_incs_count = 0;
314    }
315
316    /// Return true if the object's ref count is incremented and the count was zero before the increment
317    fn inc(&self, o: ObjectReference) -> bool {
318        self.rc.inc(o) == Ok(0)
319    }
320
321    fn dont_evacuate(&self, o: ObjectReference, los: bool) -> bool {
322        if los {
323            return true;
324        }
325        // Skip mature object
326        if self.rc.count(o) != 0 {
327            return true;
328        }
329        // Skip recycled lines
330        if Block::containing(o).get_state() != BlockState::Nursery {
331            return true;
332        }
333        if cfg!(debug_assertions) {
334            let cls = unsafe { (o.to_raw_address() + 8usize).load::<u32>() };
335            assert!(cls != 0, "ERROR {:?} rc={}", o, self.rc.count(o));
336        }
337        false
338    }
339
340    fn process_inc_and_evacuate(
341        &mut self,
342        worker: &mut GCWorker<VM>,
343        o: ObjectReference,
344        depth: u32,
345    ) -> ObjectReference {
346        let los = self.lxr.los().in_space(o);
347        if NURSERY_EVACUATION && !los && object_forwarding::is_forwarded_or_being_forwarded::<VM>(o)
348        {
349            while object_forwarding::is_being_forwarded::<VM>(o) {
350                std::hint::spin_loop();
351            }
352            let new = if object_forwarding::is_forwarded::<VM>(o) {
353                object_forwarding::read_forwarding_pointer::<VM>(o)
354            } else {
355                o
356            };
357            let promoted = self.inc(new);
358            if promoted && new == o {
359                self.promote(worker, o, false, los, depth);
360            }
361            return new;
362        }
363        if !NURSERY_EVACUATION || self.dont_evacuate(o, los) {
364            if self.inc(o) {
365                self.promote(worker, o, false, los, depth);
366            }
367            return o;
368        }
369        let forwarding_status = object_forwarding::attempt_to_forward::<VM>(o);
370        if object_forwarding::state_is_forwarded_or_being_forwarded(forwarding_status) {
371            // Object is moved to a new location.
372            let new = object_forwarding::spin_and_get_forwarded_object::<VM>(o, forwarding_status);
373            self.inc(new);
374            new
375        } else {
376            let is_nursery = self.rc.count(o) == 0;
377            if is_nursery && !self.no_evac {
378                // Evacuate the object
379                let new = object_forwarding::try_forward_object::<VM>(
380                    o,
381                    CopySemantics::DefaultCopy,
382                    worker.get_copy_context_mut(),
383                    |_new| {
384                        #[cfg(feature = "vo_bit")]
385                        {
386                            // Set the VO bit of the new object.
387                            crate::util::metadata::vo_bit::set_vo_bit(_new);
388                            // Clear the VO bit of the old object.
389                            // Note that sweeping can also clear the VO bit when the line is freed,
390                            // but no RC inc/dec should be performed on the old object from now on.
391                            // We clear it eagerly to detect inc/dec errors.
392                            crate::util::metadata::vo_bit::unset_vo_bit(o);
393                        }
394                    },
395                );
396                if let Some(new) = new {
397                    self.inc(new);
398                    self.promote(worker, new, true, false, depth);
399                    new
400                } else {
401                    warn!("to-space overflow");
402                    // Object is not moved.
403                    let promoted = self.inc(o);
404                    object_forwarding::clear_forwarding_bits::<VM>(o);
405                    if promoted {
406                        self.promote(worker, o, false, los, depth);
407                    }
408                    NO_EVAC.store(true, Ordering::Relaxed);
409                    self.no_evac = true;
410                    o
411                }
412            } else {
413                // Object is not moved.
414                let promoted = self.inc(o);
415                object_forwarding::clear_forwarding_bits::<VM>(o);
416                if promoted {
417                    self.promote(worker, o, false, los, depth);
418                }
419                o
420            }
421        }
422    }
423
424    /// Return `None` if the increment of the slot should be delayed
425    fn unlog_and_load_rc_object<const K: EdgeKind>(
426        &mut self,
427        s: VM::VMSlot,
428    ) -> Option<ObjectReference> {
429        let o = s.load();
430        // unlog slot
431        if K == EDGE_KIND_MATURE {
432            s.to_address().unlog_field_relaxed::<VM>();
433        }
434        o
435    }
436
437    fn process_slot<const K: EdgeKind>(
438        &mut self,
439        worker: &mut GCWorker<VM>,
440        s: VM::VMSlot,
441        depth: u32,
442        add_root_to_remset: bool,
443    ) -> Option<ObjectReference> {
444        let o = match self.unlog_and_load_rc_object::<K>(s) {
445            Some(o) => o,
446            _ => {
447                return None;
448            }
449        };
450        // println!(" - inc {:?}: {:?} rc={}", s, o, self.rc.count(o));
451        let new = self.process_inc_and_evacuate(worker, o, depth);
452        // Put this into remset if this is a mature slot, or a weak root
453        if K != EDGE_KIND_ROOT || add_root_to_remset {
454            self.record_mature_evac_remset(s, new);
455        }
456        if new != o {
457            s.store(new)
458        }
459        Some(new)
460    }
461
462    fn process_incs<const K: EdgeKind>(
463        &mut self,
464        worker: &mut GCWorker<VM>,
465        mut incs: AddressBuffer<'_, VM::VMSlot>,
466        depth: u32,
467        add_root_to_remset: bool,
468    ) -> Option<Vec<ObjectReference>> {
469        if K == EDGE_KIND_ROOT {
470            // An optimization with Rust zero allocation. However, it only works if
471            // VMSlot has exactly the same size and alignment as ObjectReference, which
472            // is not guaranteed.
473            // TODO: Check performance of this optimization. If it is not significant, we can remove it to simplify the code.
474            if std::mem::size_of::<VM::VMSlot>() == std::mem::size_of::<ObjectReference>()
475                && std::mem::align_of::<VM::VMSlot>() == std::mem::align_of::<ObjectReference>()
476            {
477                let roots = incs.as_mut_ptr() as *mut ObjectReference;
478                let mut num_roots = 0usize;
479                for s in incs.iter() {
480                    if let Some(new) = self.process_slot::<K>(worker, *s, depth, add_root_to_remset)
481                    {
482                        unsafe {
483                            roots.add(num_roots).write(new);
484                        }
485                        num_roots += 1;
486                    }
487                }
488                return if num_roots != 0 {
489                    let cap = incs.capacity();
490                    std::mem::forget(incs); // roots references incs now. we dont need incs.
491                    let roots =
492                        unsafe { Vec::<ObjectReference>::from_raw_parts(roots, num_roots, cap) };
493                    Some(roots)
494                } else {
495                    None
496                };
497            }
498
499            // General case: we need to allocate a new vector and push into it.
500            let mut roots = Vec::with_capacity(incs.len());
501            for s in incs.iter() {
502                if let Some(new) = self.process_slot::<K>(worker, *s, depth, add_root_to_remset) {
503                    roots.push(new);
504                }
505            }
506            if roots.is_empty() {
507                None
508            } else {
509                Some(roots)
510            }
511        } else {
512            for s in incs.iter() {
513                self.process_slot::<K>(worker, *s, depth, false);
514            }
515            None
516        }
517    }
518}
519
520pub type EdgeKind = u8;
521pub const EDGE_KIND_ROOT: u8 = 0;
522pub const EDGE_KIND_NURSERY: u8 = 1;
523pub const EDGE_KIND_MATURE: u8 = 2;
524
525enum AddressBuffer<'a, S: Slot> {
526    Owned(Vec<S>),
527    Ref(&'a mut Vec<S>),
528}
529
530impl<S: Slot> Deref for AddressBuffer<'_, S> {
531    type Target = Vec<S>;
532    fn deref(&self) -> &Self::Target {
533        match self {
534            Self::Owned(x) => x,
535            Self::Ref(x) => x,
536        }
537    }
538}
539
540impl<S: Slot> DerefMut for AddressBuffer<'_, S> {
541    fn deref_mut(&mut self) -> &mut Self::Target {
542        match self {
543            Self::Owned(x) => x,
544            Self::Ref(x) => x,
545        }
546    }
547}
548
549impl<VM: VMBinding, const KIND: EdgeKind> GCWork<VM> for ProcessIncs<VM, KIND> {
550    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
551        self.lxr = mmtk.get_plan().downcast_ref::<LXR<VM>>().unwrap();
552        self.pause = self.lxr.current_pause().unwrap();
553        self.in_cm = self.lxr.concurrent_work_in_progress();
554        if NO_EVAC.load(Ordering::Relaxed) {
555            self.no_evac = true;
556        } else {
557            let over_space = mmtk.get_plan().get_used_pages()
558                - mmtk.get_plan().get_collection_reserved_pages()
559                > mmtk.get_plan().get_total_pages();
560            if over_space {
561                self.no_evac = true;
562                NO_EVAC.store(true, Ordering::Relaxed);
563            }
564        }
565        // Process main buffer
566        let root_slots = if KIND == EDGE_KIND_ROOT
567            && (self.pause == Pause::FinalMark || self.pause == Pause::Full)
568        {
569            self.incs.clone()
570        } else {
571            vec![]
572        };
573        let roots = {
574            let incs = std::mem::take(&mut self.incs);
575            self.process_incs::<KIND>(worker, AddressBuffer::Owned(incs), self.depth, false)
576        };
577        if let Some(roots) = roots {
578            if self.lxr.cm_enabled()
579                && self.pause == Pause::InitialMark
580                && !self.root_kind.unwrap().should_skip_mark_and_decs()
581            {
582                if cfg!(any(feature = "sanity", debug_assertions)) {
583                    for r in &roots {
584                        assert!(
585                            r.to_raw_address().is_mapped(),
586                            "Invalid object {:?}: address is not mapped",
587                            r
588                        );
589                    }
590                }
591                worker.scheduler().work_buckets[WorkBucketStage::ConcurrentResumable]
592                    .add(LXRConcurrentTraceObjects::new(roots.clone(), mmtk));
593            }
594            if self.pause == Pause::FinalMark || self.pause == Pause::Full {
595                if !root_slots.is_empty() && self.root_kind != Some(RootKind::Weak) {
596                    if self.pause == Pause::FinalMark {
597                        let mut w = LXRStopTheWorldProcessEdges::<_, false>::new(
598                            root_slots,
599                            true,
600                            mmtk,
601                            WorkBucketStage::Closure,
602                        );
603                        w.root_kind = self.root_kind;
604                        worker.add_work(WorkBucketStage::Closure, w)
605                    } else {
606                        let mut w = LXRStopTheWorldProcessEdges::<_, true>::new(
607                            root_slots,
608                            true,
609                            mmtk,
610                            WorkBucketStage::Closure,
611                        );
612                        w.root_kind = self.root_kind;
613                        worker.add_work(WorkBucketStage::Closure, w)
614                    };
615                }
616            } else if !self.root_kind.unwrap().should_skip_decs() {
617                self.lxr.curr_roots.read().unwrap().push(roots);
618            }
619        }
620        // Process recursively generated buffer
621        let mut depth = self.depth;
622        let mut incs = vec![];
623        // Incs are processed by the worker that discovered them, unless we split.
624        // Split size defaults to `usize::MAX` (never split).
625        let split_size = *self.lxr.base().options.lxr_min_packet_split_size;
626        // Split depth defaults to 16 (split after 16 recursions).
627        let split_depth = *self.lxr.base().options.lxr_min_packet_split_depth;
628        while !self.new_incs.is_empty() {
629            self.new_incs_count = 0;
630            depth += 1;
631            incs.clear();
632            self.new_incs.swap(&mut incs);
633            if depth as usize >= split_depth && incs.len() > split_size {
634                let (a, b) = incs.split_at(incs.len() / 2);
635                let mut w = ProcessIncs::<VM, EDGE_KIND_NURSERY>::new(b.to_vec(), self.lxr);
636                w.depth = depth;
637                worker.add_work(WorkBucketStage::Unconstrained, w);
638                incs = a.to_vec();
639            }
640            if !incs.is_empty() {
641                self.process_incs::<EDGE_KIND_NURSERY>(
642                    worker,
643                    AddressBuffer::Ref(&mut incs),
644                    depth,
645                    false,
646                );
647            }
648        }
649        self.survival_ratio_predictor_local.sync();
650    }
651}
652
653pub struct ProcessDecs<VM: VMBinding> {
654    /// Decrements to process
655    decs: Option<Vec<ObjectReference>>,
656    decs_arc: Option<Arc<Vec<ObjectReference>>>,
657    /// Recursively generated new decrements
658    new_decs: VectorQueue<ObjectReference>,
659    counter: LazySweepingJobsCounter,
660    mark_objects: VectorQueue<ObjectReference>,
661    mark_dead_objects: bool,
662    mature_sweeping_in_progress: bool,
663    rc: RefCountHelper<VM>,
664}
665
666impl<VM: VMBinding> ProcessDecs<VM> {
667    pub fn new(decs: Vec<ObjectReference>, counter: LazySweepingJobsCounter) -> Self {
668        Self {
669            decs: Some(decs),
670            decs_arc: None,
671            new_decs: VectorQueue::default(),
672            counter,
673            mark_objects: VectorQueue::default(),
674            mark_dead_objects: false,
675            mature_sweeping_in_progress: false,
676            rc: RefCountHelper::NEW,
677        }
678    }
679
680    pub fn new_arc(decs: Arc<Vec<ObjectReference>>, counter: LazySweepingJobsCounter) -> Self {
681        Self {
682            decs: None,
683            decs_arc: Some(decs),
684            new_decs: VectorQueue::default(),
685            counter,
686            mark_objects: VectorQueue::default(),
687            mark_dead_objects: false,
688            mature_sweeping_in_progress: false,
689            rc: RefCountHelper::NEW,
690        }
691    }
692
693    fn recursive_dec(&mut self, worker: &mut GCWorker<VM>, o: ObjectReference) {
694        self.new_decs.push(o);
695        if self.new_decs.is_full() {
696            self.flush(worker)
697        }
698    }
699
700    fn new_work(&self, worker: &mut GCWorker<VM>, w: ProcessDecs<VM>) {
701        worker.add_work(WorkBucketStage::Unconstrained, w);
702    }
703
704    fn flush(&mut self, worker: &mut GCWorker<VM>) {
705        let mmtk = worker.mmtk;
706        if !self.new_decs.is_empty() {
707            let new_decs = self.new_decs.take();
708            self.new_work(
709                worker,
710                ProcessDecs::new(new_decs, self.counter.clone_with_decs()),
711            );
712        }
713        if !self.mark_objects.is_empty() {
714            let objects = self.mark_objects.take();
715            let w = LXRConcurrentTraceObjects::new(objects, mmtk);
716            if LAZY_DECREMENTS {
717                worker.add_work(WorkBucketStage::Unconstrained, w);
718            } else {
719                worker.scheduler().work_buckets[WorkBucketStage::ConcurrentResumable].add(w);
720            }
721        }
722    }
723
724    #[cold]
725    fn process_dead_object(
726        &mut self,
727        worker: &mut GCWorker<VM>,
728        o: ObjectReference,
729        lxr: &LXR<VM>,
730    ) -> bool {
731        if self.mark_dead_objects {
732            lxr.mark(o);
733        }
734        // Recursively decrease field ref counts
735        let tls = worker.tls.0;
736        o.iterate_fields::<VM, _>(tls, |slot| {
737            if let Some(x) = slot.load() {
738                // println!(" -- rec dec {:?}.{:?} -> {:?}", o, slot, x);
739                let rc = self.rc.count(x);
740                if rc != MAX_REF_COUNT && rc != 0 {
741                    self.recursive_dec(worker, x);
742                }
743                if self.mark_dead_objects && !lxr.is_marked(x) {
744                    if cfg!(any(feature = "sanity", debug_assertions)) {
745                        assert!(
746                            x.to_raw_address().is_mapped(),
747                            "Invalid object {:?}.{:?} -> {:?}: address is not mapped",
748                            o,
749                            slot,
750                            x
751                        );
752                    }
753                    self.mark_objects.push(x);
754                    if self.mark_objects.is_full() {
755                        self.flush(worker);
756                    }
757                }
758            }
759        });
760        let in_ix_space = lxr.immix_space.in_space(o);
761        if in_ix_space {
762            // Clear the VO bit if `o` is in the immix space.
763            // Note that if the object is in the LOS,
764            // the VO bit will be cleared in `LargeObjectSpace::release_object`.
765            #[cfg(feature = "vo_bit")]
766            crate::util::metadata::vo_bit::unset_vo_bit(o);
767
768            self.rc.unmark_straddle_object(o);
769        }
770        if RefCountHelper::<VM>::SANITY {
771            unsafe { o.to_raw_address().store(0xdeadusize) };
772        }
773        if in_ix_space {
774            let block = Block::containing(o);
775            lxr.add_to_possibly_dead_mature_blocks(block, false);
776            false
777        } else {
778            true
779        }
780    }
781
782    fn process_decs(&mut self, worker: &mut GCWorker<VM>, decs: &[ObjectReference], lxr: &LXR<VM>) {
783        for o in decs.iter() {
784            if self.rc.is_dead_or_stuck(*o)
785                || (self.mature_sweeping_in_progress && !lxr.is_marked(*o))
786            {
787                continue;
788            }
789            let o = if MATURE_EVACUATION && object_forwarding::is_forwarded::<VM>(*o) {
790                object_forwarding::read_forwarding_pointer::<VM>(*o)
791            } else {
792                *o
793            };
794            let mut dead = false;
795            let mut is_los = false;
796            let mut already_run = false;
797            let result = self.rc.clone().fetch_update(o, |c| {
798                if already_run {
799                    log::warn!("fetch_update is re-run! o: {o}");
800                } else {
801                    already_run = true;
802                }
803                if c == 1 && !dead {
804                    dead = true;
805                    is_los = self.process_dead_object(worker, o, lxr);
806                }
807                debug_assert!(c <= MAX_REF_COUNT);
808                if c == 0 || c == MAX_REF_COUNT {
809                    None /* sticky */
810                } else {
811                    Some(c - 1)
812                }
813            });
814            if result == Ok(1) && is_los {
815                lxr.los().rc_free(o);
816            }
817        }
818    }
819}
820
821impl<VM: VMBinding> GCWork<VM> for ProcessDecs<VM> {
822    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
823        let lxr = mmtk.get_plan().downcast_ref::<LXR<VM>>().unwrap();
824        self.mark_dead_objects = if LAZY_DECREMENTS {
825            lxr.concurrent_work_in_progress() && lxr.previous_pause() != Some(Pause::InitialMark)
826        } else {
827            lxr.concurrent_work_in_progress() && lxr.current_pause() != Some(Pause::InitialMark)
828        };
829        self.mature_sweeping_in_progress = if LAZY_DECREMENTS {
830            lxr.previous_pause() == Some(Pause::FinalMark)
831                || lxr.current_pause() == Some(Pause::Full)
832        } else {
833            lxr.current_pause() == Some(Pause::FinalMark)
834                || lxr.current_pause() == Some(Pause::Full)
835        };
836        if let Some(decs) = std::mem::take(&mut self.decs) {
837            self.process_decs(worker, &decs, lxr);
838        } else if let Some(decs) = std::mem::take(&mut self.decs_arc) {
839            self.process_decs(worker, &decs, lxr);
840        }
841        let mut decs = vec![];
842        while !self.new_decs.is_empty() {
843            decs.clear();
844            self.new_decs.swap(&mut decs);
845            self.process_decs(worker, &decs, lxr);
846        }
847        self.flush(worker);
848    }
849}
850
851pub struct CollectSlotRoots<VM: VMBinding> {
852    base: ProcessEdgesBase<VM>,
853}
854
855impl<VM: VMBinding> CollectSlotRoots<VM> {
856    pub fn new(
857        slots: Vec<VM::VMSlot>,
858        roots: bool,
859        mmtk: &'static MMTK<VM>,
860        bucket: WorkBucketStage,
861    ) -> Self {
862        debug_assert!(roots);
863        let base = ProcessEdgesBase::new(slots, roots, mmtk, bucket);
864        Self { base }
865    }
866}
867
868impl<VM: VMBinding> GCWork<VM> for CollectSlotRoots<VM> {
869    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
870        self.set_worker(worker);
871        if !self.slots.is_empty() {
872            let lxr = self.mmtk().get_plan().downcast_ref::<LXR<VM>>().unwrap();
873            let roots = std::mem::take(&mut self.slots);
874            let mut w = ProcessIncs::<_, EDGE_KIND_ROOT>::new(roots, lxr);
875            w.root_kind = self.root_kind;
876            GCWork::do_work(&mut w, self.worker(), self.mmtk());
877        }
878    }
879}
880
881impl<VM: VMBinding> Deref for CollectSlotRoots<VM> {
882    type Target = ProcessEdgesBase<VM>;
883    fn deref(&self) -> &Self::Target {
884        &self.base
885    }
886}
887
888impl<VM: VMBinding> DerefMut for CollectSlotRoots<VM> {
889    fn deref_mut(&mut self) -> &mut Self::Target {
890        &mut self.base
891    }
892}
893
894#[cfg(feature = "non_moving_root")]
895/// Collects roots reported as objects rather than as slots. Node counterpart of
896/// [`CollectSlotRoots`].
897pub struct CollectNodeRoots<VM: VMBinding> {
898    nodes: Vec<ObjectReference>,
899    _p: PhantomData<VM>,
900}
901
902#[cfg(feature = "non_moving_root")]
903impl<VM: VMBinding> CollectNodeRoots<VM> {
904    pub fn new(nodes: Vec<ObjectReference>) -> Self {
905        Self {
906            nodes,
907            _p: PhantomData,
908        }
909    }
910}
911
912#[cfg(feature = "non_moving_root")]
913impl<VM: VMBinding> GCWork<VM> for CollectNodeRoots<VM> {
914    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
915        if !self.nodes.is_empty() {
916            let lxr = mmtk.get_plan().downcast_ref::<LXR<VM>>().unwrap();
917            let roots = std::mem::take(&mut self.nodes);
918            let mut w = ProcessIncs::<_, EDGE_KIND_ROOT>::new(vec![], lxr);
919            // `RootsWorkFactory` passes no `RootKind` for node roots; they are strong roots.
920            w.root_kind = Some(RootKind::Strong);
921            w.do_work_root_nodes(roots, worker, mmtk);
922        }
923    }
924}