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        // Skip objects that are not reference counted.
451        if !self.lxr.is_rc_object(o) {
452            return None;
453        }
454        // println!(" - inc {:?}: {:?} rc={}", s, o, self.rc.count(o));
455        let new = self.process_inc_and_evacuate(worker, o, depth);
456        // Put this into remset if this is a mature slot, or a weak root
457        if K != EDGE_KIND_ROOT || add_root_to_remset {
458            self.record_mature_evac_remset(s, new);
459        }
460        if new != o {
461            s.store(new)
462        }
463        Some(new)
464    }
465
466    fn process_incs<const K: EdgeKind>(
467        &mut self,
468        worker: &mut GCWorker<VM>,
469        mut incs: AddressBuffer<'_, VM::VMSlot>,
470        depth: u32,
471        add_root_to_remset: bool,
472    ) -> Option<Vec<ObjectReference>> {
473        if K == EDGE_KIND_ROOT {
474            // An optimization with Rust zero allocation. However, it only works if
475            // VMSlot has exactly the same size and alignment as ObjectReference, which
476            // is not guaranteed.
477            // TODO: Check performance of this optimization. If it is not significant, we can remove it to simplify the code.
478            if std::mem::size_of::<VM::VMSlot>() == std::mem::size_of::<ObjectReference>()
479                && std::mem::align_of::<VM::VMSlot>() == std::mem::align_of::<ObjectReference>()
480            {
481                let roots = incs.as_mut_ptr() as *mut ObjectReference;
482                let mut num_roots = 0usize;
483                for s in incs.iter() {
484                    if let Some(new) = self.process_slot::<K>(worker, *s, depth, add_root_to_remset)
485                    {
486                        unsafe {
487                            roots.add(num_roots).write(new);
488                        }
489                        num_roots += 1;
490                    }
491                }
492                return if num_roots != 0 {
493                    let cap = incs.capacity();
494                    std::mem::forget(incs); // roots references incs now. we dont need incs.
495                    let roots =
496                        unsafe { Vec::<ObjectReference>::from_raw_parts(roots, num_roots, cap) };
497                    Some(roots)
498                } else {
499                    None
500                };
501            }
502
503            // General case: we need to allocate a new vector and push into it.
504            let mut roots = Vec::with_capacity(incs.len());
505            for s in incs.iter() {
506                if let Some(new) = self.process_slot::<K>(worker, *s, depth, add_root_to_remset) {
507                    roots.push(new);
508                }
509            }
510            if roots.is_empty() {
511                None
512            } else {
513                Some(roots)
514            }
515        } else {
516            for s in incs.iter() {
517                self.process_slot::<K>(worker, *s, depth, false);
518            }
519            None
520        }
521    }
522}
523
524pub type EdgeKind = u8;
525pub const EDGE_KIND_ROOT: u8 = 0;
526pub const EDGE_KIND_NURSERY: u8 = 1;
527pub const EDGE_KIND_MATURE: u8 = 2;
528
529enum AddressBuffer<'a, S: Slot> {
530    Owned(Vec<S>),
531    Ref(&'a mut Vec<S>),
532}
533
534impl<S: Slot> Deref for AddressBuffer<'_, S> {
535    type Target = Vec<S>;
536    fn deref(&self) -> &Self::Target {
537        match self {
538            Self::Owned(x) => x,
539            Self::Ref(x) => x,
540        }
541    }
542}
543
544impl<S: Slot> DerefMut for AddressBuffer<'_, S> {
545    fn deref_mut(&mut self) -> &mut Self::Target {
546        match self {
547            Self::Owned(x) => x,
548            Self::Ref(x) => x,
549        }
550    }
551}
552
553impl<VM: VMBinding, const KIND: EdgeKind> GCWork<VM> for ProcessIncs<VM, KIND> {
554    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
555        self.lxr = mmtk.get_plan().downcast_ref::<LXR<VM>>().unwrap();
556        self.pause = self.lxr.current_pause().unwrap();
557        self.in_cm = self.lxr.concurrent_work_in_progress();
558        if NO_EVAC.load(Ordering::Relaxed) {
559            self.no_evac = true;
560        } else {
561            let over_space = mmtk.get_plan().get_used_pages()
562                - mmtk.get_plan().get_collection_reserved_pages()
563                > mmtk.get_plan().get_total_pages();
564            if over_space {
565                self.no_evac = true;
566                NO_EVAC.store(true, Ordering::Relaxed);
567            }
568        }
569        // Process main buffer
570        let root_slots = if KIND == EDGE_KIND_ROOT
571            && (self.pause == Pause::FinalMark || self.pause == Pause::Full)
572        {
573            self.incs.clone()
574        } else {
575            vec![]
576        };
577        let roots = {
578            let incs = std::mem::take(&mut self.incs);
579            self.process_incs::<KIND>(worker, AddressBuffer::Owned(incs), self.depth, false)
580        };
581        if let Some(roots) = &roots {
582            if self.lxr.cm_enabled()
583                && self.pause == Pause::InitialMark
584                && !self.root_kind.unwrap().should_skip_mark_and_decs()
585            {
586                if cfg!(any(feature = "sanity", debug_assertions)) {
587                    for r in roots {
588                        assert!(
589                            r.to_raw_address().is_mapped(),
590                            "Invalid object {:?}: address is not mapped",
591                            r
592                        );
593                    }
594                }
595                worker.scheduler().work_buckets[WorkBucketStage::ConcurrentResumable]
596                    .add(LXRConcurrentTraceObjects::new(roots.clone(), mmtk));
597            }
598        }
599        // Trace from the root slots even if `roots` is `None`: non-RC roots are left out of
600        // `roots`, but objects reachable from them must still be marked.
601        if self.pause == Pause::FinalMark || self.pause == Pause::Full {
602            if !root_slots.is_empty() && self.root_kind != Some(RootKind::Weak) {
603                if self.pause == Pause::FinalMark {
604                    let mut w = LXRStopTheWorldProcessEdges::<_, false>::new(
605                        root_slots,
606                        true,
607                        mmtk,
608                        WorkBucketStage::Closure,
609                    );
610                    w.root_kind = self.root_kind;
611                    worker.add_work(WorkBucketStage::Closure, w)
612                } else {
613                    let mut w = LXRStopTheWorldProcessEdges::<_, true>::new(
614                        root_slots,
615                        true,
616                        mmtk,
617                        WorkBucketStage::Closure,
618                    );
619                    w.root_kind = self.root_kind;
620                    worker.add_work(WorkBucketStage::Closure, w)
621                };
622            }
623        } else if let Some(roots) = roots {
624            if !self.root_kind.unwrap().should_skip_decs() {
625                self.lxr.curr_roots.read().unwrap().push(roots);
626            }
627        }
628        // Process recursively generated buffer
629        let mut depth = self.depth;
630        let mut incs = vec![];
631        // Incs are processed by the worker that discovered them, unless we split.
632        // Split size defaults to `usize::MAX` (never split).
633        let split_size = *self.lxr.base().options.lxr_min_packet_split_size;
634        // Split depth defaults to 16 (split after 16 recursions).
635        let split_depth = *self.lxr.base().options.lxr_min_packet_split_depth;
636        while !self.new_incs.is_empty() {
637            self.new_incs_count = 0;
638            depth += 1;
639            incs.clear();
640            self.new_incs.swap(&mut incs);
641            if depth as usize >= split_depth && incs.len() > split_size {
642                let (a, b) = incs.split_at(incs.len() / 2);
643                let mut w = ProcessIncs::<VM, EDGE_KIND_NURSERY>::new(b.to_vec(), self.lxr);
644                w.depth = depth;
645                worker.add_work(WorkBucketStage::Unconstrained, w);
646                incs = a.to_vec();
647            }
648            if !incs.is_empty() {
649                self.process_incs::<EDGE_KIND_NURSERY>(
650                    worker,
651                    AddressBuffer::Ref(&mut incs),
652                    depth,
653                    false,
654                );
655            }
656        }
657        self.survival_ratio_predictor_local.sync();
658    }
659}
660
661pub struct ProcessDecs<VM: VMBinding> {
662    /// Decrements to process
663    decs: Option<Vec<ObjectReference>>,
664    decs_arc: Option<Arc<Vec<ObjectReference>>>,
665    /// Recursively generated new decrements
666    new_decs: VectorQueue<ObjectReference>,
667    counter: LazySweepingJobsCounter,
668    mark_objects: VectorQueue<ObjectReference>,
669    mark_dead_objects: bool,
670    mature_sweeping_in_progress: bool,
671    rc: RefCountHelper<VM>,
672}
673
674impl<VM: VMBinding> ProcessDecs<VM> {
675    pub fn new(decs: Vec<ObjectReference>, counter: LazySweepingJobsCounter) -> Self {
676        Self {
677            decs: Some(decs),
678            decs_arc: None,
679            new_decs: VectorQueue::default(),
680            counter,
681            mark_objects: VectorQueue::default(),
682            mark_dead_objects: false,
683            mature_sweeping_in_progress: false,
684            rc: RefCountHelper::NEW,
685        }
686    }
687
688    pub fn new_arc(decs: Arc<Vec<ObjectReference>>, counter: LazySweepingJobsCounter) -> Self {
689        Self {
690            decs: None,
691            decs_arc: Some(decs),
692            new_decs: VectorQueue::default(),
693            counter,
694            mark_objects: VectorQueue::default(),
695            mark_dead_objects: false,
696            mature_sweeping_in_progress: false,
697            rc: RefCountHelper::NEW,
698        }
699    }
700
701    fn recursive_dec(&mut self, worker: &mut GCWorker<VM>, o: ObjectReference) {
702        self.new_decs.push(o);
703        if self.new_decs.is_full() {
704            self.flush(worker)
705        }
706    }
707
708    fn new_work(&self, worker: &mut GCWorker<VM>, w: ProcessDecs<VM>) {
709        worker.add_work(WorkBucketStage::Unconstrained, w);
710    }
711
712    fn flush(&mut self, worker: &mut GCWorker<VM>) {
713        let mmtk = worker.mmtk;
714        if !self.new_decs.is_empty() {
715            let new_decs = self.new_decs.take();
716            self.new_work(
717                worker,
718                ProcessDecs::new(new_decs, self.counter.clone_with_decs()),
719            );
720        }
721        if !self.mark_objects.is_empty() {
722            let objects = self.mark_objects.take();
723            let w = LXRConcurrentTraceObjects::new(objects, mmtk);
724            if LAZY_DECREMENTS {
725                worker.add_work(WorkBucketStage::Unconstrained, w);
726            } else {
727                worker.scheduler().work_buckets[WorkBucketStage::ConcurrentResumable].add(w);
728            }
729        }
730    }
731
732    #[cold]
733    fn process_dead_object(
734        &mut self,
735        worker: &mut GCWorker<VM>,
736        o: ObjectReference,
737        lxr: &LXR<VM>,
738    ) -> bool {
739        if self.mark_dead_objects {
740            lxr.mark(o);
741        }
742        // Recursively decrease field ref counts
743        let tls = worker.tls.0;
744        o.iterate_fields::<VM, _>(tls, |slot| {
745            if let Some(x) = slot.load() {
746                // println!(" -- rec dec {:?}.{:?} -> {:?}", o, slot, x);
747                let rc = self.rc.count(x);
748                if rc != MAX_REF_COUNT && rc != 0 {
749                    self.recursive_dec(worker, x);
750                }
751                if self.mark_dead_objects && !lxr.is_marked(x) {
752                    if cfg!(any(feature = "sanity", debug_assertions)) {
753                        assert!(
754                            x.to_raw_address().is_mapped(),
755                            "Invalid object {:?}.{:?} -> {:?}: address is not mapped",
756                            o,
757                            slot,
758                            x
759                        );
760                    }
761                    self.mark_objects.push(x);
762                    if self.mark_objects.is_full() {
763                        self.flush(worker);
764                    }
765                }
766            }
767        });
768        let in_ix_space = lxr.immix_space.in_space(o);
769        let in_los = !in_ix_space && lxr.los().in_space(o);
770        debug_assert!(
771            in_ix_space || in_los,
772            "{:?} is not reference counted, so its count can never reach zero",
773            o
774        );
775        if in_ix_space {
776            // Clear the VO bit if `o` is in the immix space.
777            // Note that if the object is in the LOS,
778            // the VO bit will be cleared in `LargeObjectSpace::release_object`.
779            #[cfg(feature = "vo_bit")]
780            crate::util::metadata::vo_bit::unset_vo_bit(o);
781
782            self.rc.unmark_straddle_object(o);
783        }
784        if RefCountHelper::<VM>::SANITY {
785            unsafe { o.to_raw_address().store(0xdeadusize) };
786        }
787        if in_ix_space {
788            let block = Block::containing(o);
789            lxr.add_to_possibly_dead_mature_blocks(block, false);
790            false
791        } else {
792            in_los
793        }
794    }
795
796    fn process_decs(&mut self, worker: &mut GCWorker<VM>, decs: &[ObjectReference], lxr: &LXR<VM>) {
797        for o in decs.iter() {
798            if self.rc.is_dead_or_stuck(*o)
799                || (self.mature_sweeping_in_progress && !lxr.is_marked(*o))
800            {
801                continue;
802            }
803            let o = if MATURE_EVACUATION && object_forwarding::is_forwarded::<VM>(*o) {
804                object_forwarding::read_forwarding_pointer::<VM>(*o)
805            } else {
806                *o
807            };
808            let mut dead = false;
809            let mut is_los = false;
810            let mut already_run = false;
811            let result = self.rc.clone().fetch_update(o, |c| {
812                if already_run {
813                    log::warn!("fetch_update is re-run! o: {o}");
814                } else {
815                    already_run = true;
816                }
817                if c == 1 && !dead {
818                    dead = true;
819                    is_los = self.process_dead_object(worker, o, lxr);
820                }
821                debug_assert!(c <= MAX_REF_COUNT);
822                if c == 0 || c == MAX_REF_COUNT {
823                    None /* sticky */
824                } else {
825                    Some(c - 1)
826                }
827            });
828            if result == Ok(1) && is_los {
829                lxr.los().rc_free(o);
830            }
831        }
832    }
833}
834
835impl<VM: VMBinding> GCWork<VM> for ProcessDecs<VM> {
836    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
837        let lxr = mmtk.get_plan().downcast_ref::<LXR<VM>>().unwrap();
838        self.mark_dead_objects = if LAZY_DECREMENTS {
839            lxr.concurrent_work_in_progress() && lxr.previous_pause() != Some(Pause::InitialMark)
840        } else {
841            lxr.concurrent_work_in_progress() && lxr.current_pause() != Some(Pause::InitialMark)
842        };
843        self.mature_sweeping_in_progress = if LAZY_DECREMENTS {
844            lxr.previous_pause() == Some(Pause::FinalMark)
845                || lxr.current_pause() == Some(Pause::Full)
846        } else {
847            lxr.current_pause() == Some(Pause::FinalMark)
848                || lxr.current_pause() == Some(Pause::Full)
849        };
850        if let Some(decs) = std::mem::take(&mut self.decs) {
851            self.process_decs(worker, &decs, lxr);
852        } else if let Some(decs) = std::mem::take(&mut self.decs_arc) {
853            self.process_decs(worker, &decs, lxr);
854        }
855        let mut decs = vec![];
856        while !self.new_decs.is_empty() {
857            decs.clear();
858            self.new_decs.swap(&mut decs);
859            self.process_decs(worker, &decs, lxr);
860        }
861        self.flush(worker);
862    }
863}
864
865pub struct CollectSlotRoots<VM: VMBinding> {
866    base: ProcessEdgesBase<VM>,
867}
868
869impl<VM: VMBinding> CollectSlotRoots<VM> {
870    pub fn new(
871        slots: Vec<VM::VMSlot>,
872        roots: bool,
873        mmtk: &'static MMTK<VM>,
874        bucket: WorkBucketStage,
875    ) -> Self {
876        debug_assert!(roots);
877        let base = ProcessEdgesBase::new(slots, roots, mmtk, bucket);
878        Self { base }
879    }
880}
881
882impl<VM: VMBinding> GCWork<VM> for CollectSlotRoots<VM> {
883    fn do_work(&mut self, worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
884        self.set_worker(worker);
885        if !self.slots.is_empty() {
886            let lxr = self.mmtk().get_plan().downcast_ref::<LXR<VM>>().unwrap();
887            let roots = std::mem::take(&mut self.slots);
888            let mut w = ProcessIncs::<_, EDGE_KIND_ROOT>::new(roots, lxr);
889            w.root_kind = self.root_kind;
890            GCWork::do_work(&mut w, self.worker(), self.mmtk());
891        }
892    }
893}
894
895impl<VM: VMBinding> Deref for CollectSlotRoots<VM> {
896    type Target = ProcessEdgesBase<VM>;
897    fn deref(&self) -> &Self::Target {
898        &self.base
899    }
900}
901
902impl<VM: VMBinding> DerefMut for CollectSlotRoots<VM> {
903    fn deref_mut(&mut self) -> &mut Self::Target {
904        &mut self.base
905    }
906}
907
908#[cfg(feature = "non_moving_root")]
909/// Collects roots reported as objects rather than as slots. Node counterpart of
910/// [`CollectSlotRoots`].
911pub struct CollectNodeRoots<VM: VMBinding> {
912    nodes: Vec<ObjectReference>,
913    _p: PhantomData<VM>,
914}
915
916#[cfg(feature = "non_moving_root")]
917impl<VM: VMBinding> CollectNodeRoots<VM> {
918    pub fn new(nodes: Vec<ObjectReference>) -> Self {
919        Self {
920            nodes,
921            _p: PhantomData,
922        }
923    }
924}
925
926#[cfg(feature = "non_moving_root")]
927impl<VM: VMBinding> GCWork<VM> for CollectNodeRoots<VM> {
928    fn do_work(&mut self, worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
929        if !self.nodes.is_empty() {
930            let lxr = mmtk.get_plan().downcast_ref::<LXR<VM>>().unwrap();
931            let roots = std::mem::take(&mut self.nodes);
932            let mut w = ProcessIncs::<_, EDGE_KIND_ROOT>::new(vec![], lxr);
933            // `RootsWorkFactory` passes no `RootKind` for node roots; they are strong roots.
934            w.root_kind = Some(RootKind::Strong);
935            w.do_work_root_nodes(roots, worker, mmtk);
936        }
937    }
938}