mmtk/policy/immix/
immixspace.rs

1use super::defrag::StatsForDefrag;
2use super::line::*;
3use super::{block::*, defrag::Defrag};
4use crate::plan::tracing::OptionObjectQueue;
5use crate::plan::Pause;
6use crate::policy::gc_work::{TraceKind, DEFAULT_TRACE, TRACE_KIND_TRANSITIVE_PIN};
7use crate::policy::sft::GCWorkerMutRef;
8use crate::policy::sft::SFT;
9use crate::policy::sft_map::SFTMap;
10use crate::policy::space::{CommonSpace, Space};
11use crate::scheduler::gc_work::PrepareCollector;
12use crate::util::alloc::allocator::AllocationOptions;
13use crate::util::alloc::allocator::AllocatorContext;
14use crate::util::constants::LOG_BYTES_IN_PAGE;
15use crate::util::heap::chunk_map::*;
16use crate::util::heap::BlockPageResource;
17use crate::util::heap::PageResource;
18use crate::util::linear_scan::{Region, RegionIterator, UnstraddlableRegion};
19use crate::util::metadata::log_bit::UnlogBitsOperation;
20use crate::util::metadata::side_metadata::spec_defs::IX_LINE_REUSE_COUNT;
21use crate::util::metadata::side_metadata::*;
22#[cfg(feature = "vo_bit")]
23use crate::util::metadata::vo_bit;
24use crate::util::metadata::{self, MetadataSpec};
25use crate::util::object_enum::ObjectEnumerator;
26use crate::util::object_forwarding;
27use crate::util::rc::RefCountHelper;
28use crate::util::{copy::*, epilogue, object_enum};
29use crate::util::{Address, ObjectReference};
30use crate::vm::*;
31use crate::{
32    plan::ObjectQueue,
33    scheduler::{GCWork, GCWorkScheduler, GCWorker, WorkBucketStage},
34    util::opaque_pointer::{VMThread, VMWorkerThread},
35    MMTK,
36};
37use atomic::Ordering;
38use std::sync::atomic::AtomicUsize;
39use std::sync::OnceLock;
40use std::sync::{atomic::AtomicU8, Arc};
41
42pub(crate) const TRACE_KIND_FAST: TraceKind = 0;
43pub(crate) const TRACE_KIND_DEFRAG: TraceKind = 1;
44
45/// Whether RC-mode mature-space evacuation is compiled in.
46const LXR_MATURE_EVACUATION: bool = !cfg!(feature = "lxr_no_mature_evac");
47
48/// Plan-level hooks invoked by ImmixSpace during mutator allocation.
49/// Default impls are no-ops; LXR provides the concrete implementation.
50pub trait ImmixHooks<VM: VMBinding>: Send + Sync {
51    /// Called after a fresh clean block is acquired. `copy` distinguishes
52    /// mutator vs. GC-copy allocation. The hook owns any plan-specific
53    /// per-block bookkeeping (e.g. nursery list, mark-table init).
54    fn on_clean_block_acquired(&self, _block: Block, _copy: bool) {}
55    /// Called after a reusable block is handed out to a mutator.
56    fn on_reusable_block_acquired(&self, _block: Block, _copy: bool) {}
57    /// Whether tracing is in progress; consulted on the mutator
58    /// reused-line fast path so newly handed-out lines can be marked.
59    fn cm_in_progress_or_final_mark(&self) -> bool {
60        false
61    }
62}
63
64pub struct ImmixSpace<VM: VMBinding> {
65    common: CommonSpace<VM>,
66    pr: BlockPageResource<VM, Block>,
67    /// Allocation status for all chunks in immix space
68    pub chunk_map: ChunkMap,
69    /// Current line mark state
70    pub line_mark_state: AtomicU8,
71    /// Line mark state in previous GC
72    line_unavail_state: AtomicU8,
73    /// A list of all reusable blocks
74    pub reusable_blocks: ReusableBlockPool,
75    /// Defrag utilities
76    pub(super) defrag: Defrag,
77    /// How many lines have been consumed since last GC?
78    lines_consumed: AtomicUsize,
79    reused_lines_consumed: AtomicUsize,
80    /// Object mark state
81    mark_state: u8,
82    /// Work packet scheduler
83    scheduler: Arc<GCWorkScheduler<VM>>,
84    /// Some settings for this space
85    space_args: ImmixSpaceArgs,
86    hooks: OnceLock<&'static dyn ImmixHooks<VM>>,
87    pub rc_enabled: bool,
88    pub is_end_of_satb_or_full_gc: bool,
89    pub rc: RefCountHelper<VM>,
90}
91
92/// Some arguments for Immix Space.
93pub struct ImmixSpaceArgs {
94    /// Whether this ImmixSpace instance contains both young and old objects.
95    /// This affects the updating of valid-object bits.  If some lines or blocks of this ImmixSpace
96    /// instance contain young objects, their VO bits need to be updated during this GC.  Currently
97    /// only StickyImmix is affected.  GenImmix allocates young objects in a separete CopySpace
98    /// nursery and its VO bits can be cleared in bulk.
99    pub mixed_age: bool,
100    /// Disable copying for this Immix space.
101    pub never_move_objects: bool,
102}
103
104unsafe impl<VM: VMBinding> Sync for ImmixSpace<VM> {}
105
106impl<VM: VMBinding> SFT for ImmixSpace<VM> {
107    fn name(&self) -> &'static str {
108        self.get_name()
109    }
110
111    fn get_forwarded_object(&self, object: ObjectReference) -> Option<ObjectReference> {
112        // If we never move objects, look no further.
113        if !self.is_movable() {
114            return None;
115        }
116
117        if object_forwarding::is_forwarded::<VM>(object) {
118            Some(object_forwarding::read_forwarding_pointer::<VM>(object))
119        } else {
120            None
121        }
122    }
123
124    fn is_live(&self, object: ObjectReference) -> bool {
125        if self.rc_enabled {
126            if self.is_end_of_satb_or_full_gc {
127                if self.is_marked(object) {
128                    let block = Block::containing(object);
129                    if block.is_defrag_source() {
130                        if object_forwarding::is_forwarded::<VM>(object) {
131                            let forwarded =
132                                object_forwarding::read_forwarding_pointer::<VM>(object);
133                            return self.is_marked(forwarded) && self.rc.count(forwarded) > 0;
134                        } else {
135                            return false;
136                        }
137                    }
138                    return self.rc.count(object) > 0;
139                } else if object_forwarding::is_forwarded::<VM>(object) {
140                    let forwarded = object_forwarding::read_forwarding_pointer::<VM>(object);
141                    debug_assert!(
142                        forwarded.to_raw_address().is_mapped(),
143                        "Invalid forwarded object: {:?} -> {:?}",
144                        object,
145                        forwarded
146                    );
147                    return self.is_marked(forwarded) && self.rc.count(forwarded) > 0;
148                } else {
149                    return false;
150                }
151            }
152            return self.rc.count(object) > 0 || object_forwarding::is_forwarded::<VM>(object);
153        }
154        // If the mark bit is set, it is live.
155        if self.is_marked(object) {
156            return true;
157        }
158
159        // If we never move objects, look no further.
160        if !self.is_movable() {
161            return false;
162        }
163
164        // If the object is forwarded, it is live, too.
165        object_forwarding::is_forwarded::<VM>(object)
166    }
167
168    fn is_reachable(&self, object: ObjectReference) -> bool {
169        if self.rc_enabled {
170            if object_forwarding::is_forwarded::<VM>(object) {
171                let forwarded = object_forwarding::read_forwarding_pointer::<VM>(object);
172                return self.is_marked(forwarded) && self.rc.count(forwarded) > 0;
173            }
174            self.is_marked(object) && self.rc.count(object) > 0
175        } else {
176            self.is_live(object)
177        }
178    }
179    #[cfg(feature = "object_pinning")]
180    fn pin_object(&self, object: ObjectReference) -> bool {
181        if self.space_args.never_move_objects {
182            false
183        } else {
184            VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.pin_object::<VM>(object)
185        }
186    }
187    #[cfg(feature = "object_pinning")]
188    fn unpin_object(&self, object: ObjectReference) -> bool {
189        if self.space_args.never_move_objects {
190            false
191        } else {
192            VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.unpin_object::<VM>(object)
193        }
194    }
195    #[cfg(feature = "object_pinning")]
196    fn is_object_pinned(&self, object: ObjectReference) -> bool {
197        if self.space_args.never_move_objects {
198            true
199        } else {
200            VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.is_object_pinned::<VM>(object)
201        }
202    }
203    fn is_movable(&self) -> bool {
204        !self.space_args.never_move_objects
205    }
206
207    #[cfg(feature = "sanity")]
208    fn is_sane(&self) -> bool {
209        true
210    }
211    fn initialize_object_metadata(&self, _object: ObjectReference, _bytes: usize) {
212        #[cfg(feature = "vo_bit")]
213        crate::util::metadata::vo_bit::set_vo_bit(_object);
214    }
215    #[cfg(feature = "vo_bit")]
216    fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
217        crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
218    }
219    #[cfg(feature = "vo_bit")]
220    fn find_object_from_internal_pointer(
221        &self,
222        ptr: Address,
223        max_search_bytes: usize,
224    ) -> Option<ObjectReference> {
225        // We don't need to search more than the max object size in the immix space.
226        let search_bytes = usize::min(super::MAX_IMMIX_OBJECT_SIZE, max_search_bytes);
227        crate::util::metadata::vo_bit::find_object_from_internal_pointer::<VM>(ptr, search_bytes)
228    }
229    fn sft_trace_object(
230        &self,
231        _queue: &mut OptionObjectQueue,
232        _object: ObjectReference,
233        _worker: GCWorkerMutRef,
234    ) -> ObjectReference {
235        panic!("We do not use SFT to trace objects for Immix. sft_trace_object() cannot be used.")
236    }
237
238    fn debug_print_object_info(&self, object: ObjectReference) {
239        println!("marked  = {}", self.is_marked(object));
240        // The line mark table isn't mapped when RC is enabled (LXR tracks liveness via
241        // block state and reference counts instead), so skip it in that case.
242        if !self.rc_enabled {
243            println!(
244                "line marked = {}",
245                Line::from_unaligned_address(object.to_raw_address()).is_marked(self.mark_state)
246            );
247        }
248        println!(
249            "block state = {:?}",
250            Block::from_unaligned_address(object.to_raw_address()).get_state()
251        );
252        object_forwarding::debug_print_object_forwarding_info::<VM>(object);
253        self.common.debug_print_object_global_info(object);
254    }
255}
256
257impl<VM: VMBinding> Space<VM> for ImmixSpace<VM> {
258    fn as_space(&self) -> &dyn Space<VM> {
259        self
260    }
261    fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
262        self
263    }
264    fn get_page_resource(&self) -> &dyn PageResource<VM> {
265        &self.pr
266    }
267    fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
268        Some(&mut self.pr)
269    }
270    fn common(&self) -> &CommonSpace<VM> {
271        &self.common
272    }
273    fn initialize_sft(&self, sft_map: &mut dyn SFTMap) {
274        self.common().initialize_sft(self.as_sft(), sft_map)
275    }
276    fn release_multiple_pages(&mut self, _start: Address) {
277        panic!("immixspace only releases pages enmasse")
278    }
279    fn set_copy_for_sft_trace(&mut self, _semantics: Option<CopySemantics>) {
280        panic!("We do not use SFT to trace objects for Immix. set_copy_context() cannot be used.")
281    }
282
283    fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
284        object_enum::enumerate_blocks_from_chunk_map::<Block>(enumerator, &self.chunk_map);
285    }
286
287    fn clear_side_log_bits(&self) {
288        // Remove the following warning if we have a legitimate use case.
289        warn!("ImmixSpace::clear_side_log_bits is single-treaded.  Consider clearing side metadata in per-chunk work packets.");
290
291        let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
292        for chunk in self.chunk_map.all_chunks() {
293            log_bit.bzero_metadata(chunk.start(), Chunk::BYTES);
294        }
295    }
296
297    fn set_side_log_bits(&self) {
298        // Remove the following warning if we have a legitimate use case.
299        warn!("ImmixSpace::set_side_log_bits is single-treaded.  Consider setting side metadata in per-chunk work packets.");
300
301        let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
302        for chunk in self.chunk_map.all_chunks() {
303            log_bit.bset_metadata(chunk.start(), Chunk::BYTES);
304        }
305    }
306}
307
308impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for ImmixSpace<VM> {
309    fn trace_object<Q: ObjectQueue, const KIND: TraceKind>(
310        &self,
311        queue: &mut Q,
312        object: ObjectReference,
313        copy: Option<CopySemantics>,
314        worker: &mut GCWorker<VM>,
315    ) -> ObjectReference {
316        if KIND == TRACE_KIND_TRANSITIVE_PIN {
317            self.trace_object_without_moving(queue, object)
318        } else if KIND == TRACE_KIND_DEFRAG {
319            if Block::containing(object).is_defrag_source() {
320                debug_assert!(self.in_defrag());
321                debug_assert!(
322                    !crate::plan::is_nursery_gc(worker.mmtk.get_plan()),
323                    "Calling PolicyTraceObject on Immix in nursery GC"
324                );
325                self.trace_object_with_opportunistic_copy(
326                    queue,
327                    object,
328                    copy.unwrap(),
329                    worker,
330                    // This should not be nursery collection. Nursery collection does not use PolicyTraceObject.
331                    false,
332                )
333            } else {
334                self.trace_object_without_moving(queue, object)
335            }
336        } else if KIND == TRACE_KIND_FAST {
337            self.trace_object_without_moving(queue, object)
338        } else {
339            unreachable!()
340        }
341    }
342
343    fn post_scan_object(&self, object: ObjectReference) {
344        if super::MARK_LINE_AT_SCAN_TIME && !super::BLOCK_ONLY {
345            debug_assert!(self.in_space(object));
346            self.mark_lines(object);
347        }
348    }
349
350    #[allow(clippy::if_same_then_else)] // DEFAULT_TRACE needs a workaround which is documented below.
351    fn may_move_objects<const KIND: TraceKind>() -> bool {
352        if KIND == TRACE_KIND_DEFRAG {
353            true
354        } else if KIND == TRACE_KIND_FAST || KIND == TRACE_KIND_TRANSITIVE_PIN {
355            false
356        } else if KIND == DEFAULT_TRACE {
357            // FIXME: This is hacky. When we do a default trace, this should be a nonmoving space.
358            // The only exception is the nursery GC for sticky immix, for which, we use default trace.
359            // This function is only used for PlanProcessEdges, and for sticky immix nursery GC, we use
360            // GenNurseryProcessEdges. So it still works. But this is quite hacky anyway.
361            // See https://github.com/mmtk/mmtk-core/issues/1314 for details.
362            false
363        } else {
364            unreachable!()
365        }
366    }
367}
368
369impl<VM: VMBinding> ImmixSpace<VM> {
370    #[allow(unused)]
371    const UNMARKED_STATE: u8 = 0;
372    const MARKED_STATE: u8 = 1;
373
374    /// Get side metadata specs
375    fn side_metadata_specs(rc_enabled: bool) -> Vec<SideMetadataSpec> {
376        if rc_enabled {
377            let meta = vec![
378                MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
379                MetadataSpec::OnSide(Block::MARK_TABLE),
380                *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
381                *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
382                *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
383                MetadataSpec::OnSide(crate::util::rc::RC_STRADDLE_LINES),
384                MetadataSpec::OnSide(Block::LOG_TABLE),
385                MetadataSpec::OnSide(Block::NURSERY_PROMOTION_STATE_TABLE),
386                MetadataSpec::OnSide(IX_LINE_REUSE_COUNT),
387            ];
388            return metadata::extract_side_metadata(&meta);
389        }
390        metadata::extract_side_metadata(&if super::BLOCK_ONLY {
391            vec![
392                MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
393                MetadataSpec::OnSide(Block::MARK_TABLE),
394                *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
395                *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
396                *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
397                #[cfg(feature = "object_pinning")]
398                *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
399            ]
400        } else {
401            vec![
402                MetadataSpec::OnSide(Line::MARK_TABLE),
403                MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
404                MetadataSpec::OnSide(Block::MARK_TABLE),
405                *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
406                *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
407                *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
408                #[cfg(feature = "object_pinning")]
409                *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
410            ]
411        })
412    }
413
414    pub fn new(
415        args: crate::policy::space::PlanCreateSpaceArgs<VM>,
416        mut space_args: ImmixSpaceArgs,
417    ) -> Self {
418        if args.unlog_traced_object {
419            assert!(
420                args.constraints.needs_log_bit,
421                "Invalid args when the plan does not use log bit"
422            );
423        }
424
425        // Make sure we override the space args if we force non moving Immix
426        if cfg!(feature = "immix_non_moving") && !space_args.never_move_objects {
427            info!(
428                "Overriding never_moves_objects for Immix Space {}, as the immix_non_moving feature is set. Block size: 2^{}",
429                args.name,
430                Block::LOG_BYTES,
431            );
432            space_args.never_move_objects = true;
433        }
434
435        // validate features
436        if super::BLOCK_ONLY {
437            assert!(
438                space_args.never_move_objects,
439                "Block-only immix must not move objects"
440            );
441        }
442        assert!(
443            Block::LINES / 2 <= u8::MAX as usize - 2,
444            "Number of lines in a block should not exceed BlockState::MARK_MARKED"
445        );
446
447        // TODO: The VO bit strategy is only relevant to tracing GC.
448        // LXR currently ignores the strategy.
449        #[cfg(feature = "vo_bit")]
450        if !args.constraints.rc_enabled {
451            vo_bit::helper::validate_config::<VM>();
452        }
453
454        let vm_map = args.vm_map;
455        let scheduler = args.scheduler.clone();
456        let rc_enabled = args.constraints.rc_enabled;
457        let common = CommonSpace::new(args.into_policy_args(
458            true,
459            false,
460            Self::side_metadata_specs(rc_enabled),
461        ));
462        let space_index = common.descriptor.get_index();
463        ImmixSpace {
464            pr: if common.vmrequest.is_discontiguous() {
465                BlockPageResource::new_discontiguous(
466                    Block::LOG_PAGES,
467                    vm_map,
468                    scheduler.num_workers(),
469                )
470            } else {
471                BlockPageResource::new_contiguous(
472                    Block::LOG_PAGES,
473                    common.start,
474                    common.extent,
475                    vm_map,
476                    scheduler.num_workers(),
477                )
478            },
479            common,
480            chunk_map: ChunkMap::new(space_index),
481            line_mark_state: AtomicU8::new(Line::RESET_MARK_STATE),
482            line_unavail_state: AtomicU8::new(Line::RESET_MARK_STATE),
483            lines_consumed: AtomicUsize::new(0),
484            reused_lines_consumed: AtomicUsize::new(0),
485            reusable_blocks: ReusableBlockPool::new(scheduler.num_workers()),
486            defrag: Defrag::default(),
487            // Set to the correct mark state when inititialized. We cannot rely on prepare to set it (prepare may get skipped in nursery GCs).
488            mark_state: Self::MARKED_STATE,
489            scheduler,
490            space_args,
491            hooks: OnceLock::new(),
492            rc_enabled,
493            is_end_of_satb_or_full_gc: false,
494            rc: RefCountHelper::NEW,
495        }
496    }
497
498    /// Flush the thread-local queues in BlockPageResource
499    pub fn flush_page_resource(&self) {
500        // FIXME: Do we need this for LXR? We observed this to cause fails on conix.
501        if !self.rc_enabled {
502            self.reusable_blocks.flush_all();
503        }
504        #[cfg(target_pointer_width = "64")]
505        self.pr.flush_all()
506    }
507
508    /// Get the number of defrag headroom pages.
509    pub fn defrag_headroom_pages(&self) -> usize {
510        self.defrag.defrag_headroom_pages(self)
511    }
512
513    /// Check if current GC is a defrag GC.
514    pub fn in_defrag(&self) -> bool {
515        self.defrag.in_defrag()
516    }
517
518    /// check if the current GC should do defragmentation.
519    pub fn decide_whether_to_defrag(
520        &self,
521        emergency_collection: bool,
522        collect_whole_heap: bool,
523        collection_attempts: usize,
524        user_triggered_collection: bool,
525        full_heap_system_gc: bool,
526    ) -> bool {
527        self.defrag.decide_whether_to_defrag(
528            self.is_defrag_enabled(),
529            emergency_collection,
530            collect_whole_heap,
531            collection_attempts,
532            user_triggered_collection,
533            self.reusable_blocks.len() == 0,
534            full_heap_system_gc,
535            self.rc_enabled,
536            *self.common.options.immix_always_defrag,
537        );
538        self.defrag.in_defrag()
539    }
540
541    /// Get work packet scheduler
542    pub fn scheduler(&self) -> &GCWorkScheduler<VM> {
543        &self.scheduler
544    }
545
546    /// Install the plan-level hooks. Called once by the owning plan during `gc_init`.
547    pub fn install_hooks(&self, hooks: &'static dyn ImmixHooks<VM>) {
548        self.hooks
549            .set(hooks)
550            .unwrap_or_else(|_| panic!("ImmixSpace::install_hooks called more than once"));
551    }
552
553    fn hooks(&self) -> Option<&'static dyn ImmixHooks<VM>> {
554        self.hooks.get().copied()
555    }
556
557    pub fn prepare_rc(&mut self, pause: Pause) {
558        // Initialize mark state for tracing
559        if pause == Pause::Full || pause == Pause::InitialMark {
560            // Update mark_state
561            if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_on_side() {
562                self.mark_state = Self::MARKED_STATE;
563            } else {
564                // For header metadata, we use cyclic mark bits.
565                unimplemented!("cyclic mark bits is not supported at the moment");
566            }
567        }
568        // Release nursery blocks
569        if pause != Pause::RefCount {
570            if pause == Pause::Full {
571                // Reset worker TLABs.
572                // The block of the current worker TLAB may be selected as part of the mature evacuation set.
573                for w in &self.scheduler().worker_group.workers_shared {
574                    let result = w.designated_work.push(Box::new(PrepareCollector));
575                    debug_assert!(result.is_ok());
576                }
577            }
578            self.flush_page_resource();
579        }
580        if pause == Pause::FinalMark || pause == Pause::Full {
581            self.is_end_of_satb_or_full_gc = true;
582        }
583    }
584
585    pub fn release_rc(&mut self) {
586        self.flush_page_resource();
587        self.rc.reset_inc_buffer_size();
588        self.is_end_of_satb_or_full_gc = false;
589        self.reused_lines_consumed.store(0, Ordering::Relaxed);
590    }
591
592    pub(crate) fn prepare(
593        &mut self,
594        major_gc: bool,
595        plan_stats: Option<StatsForDefrag>,
596        unlog_bits_op: UnlogBitsOperation,
597    ) {
598        // This function should not be called during RC.
599        // Otherwise the VO bit handling will be incorrect.
600        debug_assert!(!self.rc_enabled);
601
602        if major_gc {
603            // Update mark_state
604            if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_on_side() {
605                self.mark_state = Self::MARKED_STATE;
606            } else {
607                // For header metadata, we use cyclic mark bits.
608                unimplemented!("cyclic mark bits is not supported at the moment");
609            }
610
611            // Prepare defrag info
612            if self.is_defrag_enabled() {
613                self.defrag.prepare(self, plan_stats.unwrap());
614            }
615
616            // Prepare each block for GC
617            let threshold = self.defrag.defrag_spill_threshold.load(Ordering::Acquire);
618            // # Safety: ImmixSpace reference is always valid within this collection cycle.
619            let space = unsafe { &*(self as *const Self) };
620            let work_packets = self.chunk_map.generate_tasks(|chunk| {
621                Box::new(PrepareBlockState {
622                    space,
623                    chunk,
624                    defrag_threshold: if space.in_defrag() {
625                        Some(threshold)
626                    } else {
627                        None
628                    },
629                    unlog_bits_op,
630                })
631            });
632            self.scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(work_packets);
633
634            if !super::BLOCK_ONLY {
635                self.line_mark_state.fetch_add(1, Ordering::AcqRel);
636                if self.line_mark_state.load(Ordering::Acquire) > Line::MAX_MARK_STATE {
637                    self.line_mark_state
638                        .store(Line::RESET_MARK_STATE, Ordering::Release);
639                }
640            }
641        }
642
643        #[cfg(feature = "vo_bit")]
644        if vo_bit::helper::need_to_clear_vo_bits_before_tracing::<VM>() {
645            let maybe_scope = if major_gc {
646                // If it is major GC, we always clear all VO bits because we are doing full-heap
647                // tracing.
648                Some(VOBitsClearingScope::FullGC)
649            } else if self.space_args.mixed_age {
650                // StickyImmix nursery GC.
651                // Some lines (or blocks) contain only young objects,
652                // while other lines (or blocks) contain only old objects.
653                if super::BLOCK_ONLY {
654                    // Block only.  Young objects are only allocated into fully empty blocks.
655                    // Only clear unmarked blocks.
656                    Some(VOBitsClearingScope::BlockOnly)
657                } else {
658                    // Young objects are allocated into empty lines.
659                    // Only clear unmarked lines.
660                    let line_mark_state = self.line_mark_state.load(Ordering::SeqCst);
661                    Some(VOBitsClearingScope::Line {
662                        state: line_mark_state,
663                    })
664                }
665            } else {
666                // GenImmix nursery GC.  We do nothing to the ImmixSpace because the nursery is a
667                // separate CopySpace.  It'll clear its own VO bits.
668                None
669            };
670
671            if let Some(scope) = maybe_scope {
672                let work_packets = self
673                    .chunk_map
674                    .generate_tasks(|chunk| Box::new(ClearVOBitsAfterPrepare { chunk, scope }));
675                self.scheduler.work_buckets[WorkBucketStage::ClearVOBits].bulk_add(work_packets);
676            }
677        }
678    }
679
680    /// Release for the immix space.
681    pub(crate) fn release(&mut self, major_gc: bool, unlog_bits_op: UnlogBitsOperation) {
682        debug_assert!(!self.rc_enabled);
683        if major_gc {
684            // Update line_unavail_state for hole searching after this GC.
685            if !super::BLOCK_ONLY {
686                self.line_unavail_state.store(
687                    self.line_mark_state.load(Ordering::Acquire),
688                    Ordering::Release,
689                );
690            }
691        }
692        // Clear reusable blocks list
693        if !super::BLOCK_ONLY {
694            self.reusable_blocks.reset();
695        }
696        // Sweep chunks and blocks
697        let work_packets = self.generate_sweep_tasks(unlog_bits_op);
698        self.scheduler().work_buckets[WorkBucketStage::Release].bulk_add(work_packets);
699
700        self.lines_consumed.store(0, Ordering::Relaxed);
701    }
702
703    /// This is called when a GC finished.
704    /// Return whether this GC was a defrag GC, as a plan may want to know this.
705    pub fn end_of_gc(&mut self) -> bool {
706        let did_defrag = self.defrag.in_defrag();
707        if self.is_defrag_enabled() {
708            self.defrag.reset_in_defrag();
709        }
710        did_defrag
711    }
712
713    /// Generate chunk sweep tasks
714    fn generate_sweep_tasks(&self, unlog_bits_op: UnlogBitsOperation) -> Vec<Box<dyn GCWork<VM>>> {
715        self.defrag.mark_histograms.lock().clear();
716        // # Safety: ImmixSpace reference is always valid within this collection cycle.
717        let space = unsafe { &*(self as *const Self) };
718        let epilogue = Arc::new(FlushPageResource {
719            space,
720            counter: AtomicUsize::new(0),
721        });
722        let tasks = self.chunk_map.generate_tasks(|chunk| {
723            Box::new(SweepChunk {
724                space,
725                chunk,
726                unlog_bits_op,
727                epilogue: epilogue.clone(),
728            })
729        });
730        epilogue.counter.store(tasks.len(), Ordering::SeqCst);
731        tasks
732    }
733
734    /// Release a block.
735    pub fn release_block(&self, block: Block, zero_unlog_table: bool) {
736        if zero_unlog_table {
737            block.clear_field_unlog_table::<VM>();
738        }
739        block.deinit(self);
740        self.pr.release_block(block);
741    }
742
743    /// Allocate a clean block.
744    pub fn get_clean_block(
745        &self,
746        tls: VMThread,
747        copy: bool,
748        alloc_options: AllocationOptions,
749    ) -> Option<Block> {
750        let block_address = self.acquire(tls, Block::PAGES, alloc_options);
751        if block_address.is_zero() {
752            return None;
753        }
754        let block = Block::from_aligned_address(block_address);
755        if !self.rc_enabled || self.defrag.in_defrag() {
756            self.defrag.notify_new_clean_block(copy);
757        }
758        if let Some(hooks) = self.hooks() {
759            hooks.on_clean_block_acquired(block, copy);
760        }
761        block.init(copy, false, self);
762        self.chunk_map.set_allocated(block.chunk(), true);
763        if !self.rc_enabled {
764            self.lines_consumed
765                .fetch_add(Block::LINES, Ordering::SeqCst);
766        }
767        Some(block)
768    }
769
770    /// Pop a reusable block from the reusable block list.
771    pub fn get_reusable_block(&self, copy: bool) -> Option<Block> {
772        if super::BLOCK_ONLY {
773            return None;
774        }
775        loop {
776            let block = self.reusable_blocks.pop()?;
777            // Skip blocks that should be evacuated.
778            if copy && block.is_defrag_source() {
779                continue;
780            }
781            if self.rc_enabled {
782                if LXR_MATURE_EVACUATION && block.is_defrag_source() {
783                    continue;
784                }
785                // Blocks in the `reusable_blocks` queue can be released after some RC collections.
786                // These blocks can either have `Unallocated` state, or be reallocated again.
787                // Skip these cases and only return the truly reusable blocks.
788                if !block.get_state().is_reusable() {
789                    continue;
790                }
791                if !block.attempt_mutator_reuse() {
792                    continue;
793                }
794                if let Some(hooks) = self.hooks() {
795                    hooks.on_reusable_block_acquired(block, copy);
796                }
797            } else {
798                // Get available lines. Do this before block.init which will reset block state.
799                let lines_delta = match block.get_state() {
800                    BlockState::Reusable { unavailable_lines } => {
801                        Block::LINES - unavailable_lines as usize
802                    }
803                    BlockState::Unmarked => Block::LINES,
804                    _ => unreachable!("{:?} {:?}", block, block.get_state()),
805                };
806                self.lines_consumed.fetch_add(lines_delta, Ordering::SeqCst);
807            }
808
809            block.init(copy, true, self);
810            return Some(block);
811        }
812    }
813
814    pub fn trace_object_without_moving_rc(
815        &self,
816        queue: &mut impl ObjectQueue,
817        object: ObjectReference,
818    ) -> ObjectReference {
819        if self.attempt_mark(object) {
820            let addr = object.to_raw_address().as_usize();
821            let straddle = if (addr & 0b11110000) == 0 {
822                self.rc.object_is_in_straddle_line_no_rc_check(object)
823            } else {
824                false
825            };
826            if !straddle {
827                queue.enqueue(object);
828            }
829        }
830        object
831    }
832
833    /// Trace and mark objects without evacuation.
834    pub fn trace_object_without_moving(
835        &self,
836        queue: &mut impl ObjectQueue,
837        object: ObjectReference,
838    ) -> ObjectReference {
839        // This function should not be called during RC if mature evacuation is not enabled.
840        if LXR_MATURE_EVACUATION {
841            debug_assert!(!self.rc_enabled);
842        }
843
844        #[cfg(feature = "vo_bit")]
845        if !self.rc_enabled {
846            // The VO bit strategy is currently not applicable to RC.
847            // RC clears VO bits during sweeping.
848            vo_bit::helper::on_trace_object::<VM>(object);
849        }
850
851        if self.attempt_mark(object) {
852            if self.rc_enabled {
853                let straddle = self.rc.object_is_in_straddle_line_no_rc_check(object);
854                if straddle {
855                    return object;
856                }
857            } else {
858                // Mark block and lines
859                if !super::BLOCK_ONLY {
860                    if !super::MARK_LINE_AT_SCAN_TIME {
861                        self.mark_lines(object);
862                    }
863                } else {
864                    let block = Block::containing(object);
865                    let state = block.get_state();
866                    if state != BlockState::Nursery && state != BlockState::Marked {
867                        block.set_state(BlockState::Marked);
868                    }
869                }
870            }
871
872            #[cfg(feature = "vo_bit")]
873            if !self.rc_enabled {
874                // The VO bit strategy is currently not applicable to RC.
875                // RC clears VO bits during sweeping.
876                vo_bit::helper::on_object_marked::<VM>(object);
877            }
878
879            // Visit node
880            queue.enqueue(object);
881            if !self.rc_enabled {
882                self.unlog_object_if_needed(object);
883            }
884            return object;
885        }
886        object
887    }
888
889    /// Trace object and do evacuation if required.
890    #[allow(clippy::assertions_on_constants)]
891    pub fn trace_object_with_opportunistic_copy(
892        &self,
893        queue: &mut impl ObjectQueue,
894        object: ObjectReference,
895        semantics: CopySemantics,
896        worker: &mut GCWorker<VM>,
897        nursery_collection: bool,
898    ) -> ObjectReference {
899        // This function should not be called when RC is enabled.
900        // Otherwise the VO bit handling will be incorrect.
901        debug_assert!(!self.rc_enabled);
902
903        let copy_context = worker.get_copy_context_mut();
904        debug_assert!(!super::BLOCK_ONLY);
905
906        #[cfg(feature = "vo_bit")]
907        vo_bit::helper::on_trace_object::<VM>(object);
908
909        let forwarding_status = object_forwarding::attempt_to_forward::<VM>(object);
910        if object_forwarding::state_is_forwarded_or_being_forwarded(forwarding_status) {
911            // We lost the forwarding race as some other thread has set the forwarding word; wait
912            // until the object has been forwarded by the winner. Note that the object may not
913            // necessarily get forwarded since Immix opportunistically moves objects.
914            #[allow(clippy::let_and_return)]
915            let new_object =
916                object_forwarding::spin_and_get_forwarded_object::<VM>(object, forwarding_status);
917            #[cfg(debug_assertions)]
918            {
919                if new_object == object {
920                    debug_assert!(
921                        self.is_marked(object) || self.defrag.space_exhausted() || self.is_pinned(object),
922                        "Forwarded object is the same as original object {} even though it should have been copied",
923                        object,
924                    );
925                } else {
926                    // new_object != object
927                    debug_assert!(
928                        !Block::containing(new_object).is_defrag_source(),
929                        "Block {:?} containing forwarded object {} should not be a defragmentation source",
930                        Block::containing(new_object),
931                        new_object,
932                    );
933                }
934            }
935            new_object
936        } else if self.is_marked(object) {
937            // We won the forwarding race but the object is already marked so we clear the
938            // forwarding status and return the unmoved object
939            object_forwarding::clear_forwarding_bits::<VM>(object);
940            object
941        } else {
942            // We won the forwarding race; actually forward and copy the object if it is not pinned
943            // and we have sufficient space in our copy allocator
944            debug_assert!(!nursery_collection || !self.rc_enabled);
945            let new_object = if self.is_pinned(object)
946                || (!nursery_collection && self.defrag.space_exhausted())
947            {
948                self.attempt_mark(object);
949                object_forwarding::clear_forwarding_bits::<VM>(object);
950                Block::containing(object).set_state(BlockState::Marked);
951
952                #[cfg(feature = "vo_bit")]
953                vo_bit::helper::on_object_marked::<VM>(object);
954
955                if !super::MARK_LINE_AT_SCAN_TIME {
956                    self.mark_lines(object);
957                }
958
959                self.unlog_object_if_needed(object);
960
961                object
962            } else {
963                // We are forwarding objects. When the copy allocator allocates the block, it should
964                // mark the block. So we do not need to explicitly mark it here.
965                // Clippy complains if the "vo_bit" feature is not enabled.
966                #[allow(clippy::let_and_return)]
967                let new_object = object_forwarding::try_forward_object::<VM>(
968                    object,
969                    semantics,
970                    copy_context,
971                    |new_object| {
972                        // post_copy should have set the unlog bit
973                        // if `unlog_traced_object` is true.
974                        debug_assert!(
975                            !self.common.unlog_traced_object
976                                || VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
977                                    .is_unlogged::<VM>(new_object, Ordering::Relaxed)
978                        );
979                        #[cfg(feature = "vo_bit")]
980                        vo_bit::helper::on_object_forwarded::<VM>(new_object);
981                    },
982                )
983                .expect("to-space overflow");
984
985                new_object
986            };
987            debug_assert!({
988                let state = Block::containing(new_object).get_state();
989                state == BlockState::Marked || state == BlockState::Nursery
990            });
991
992            queue.enqueue(new_object);
993            debug_assert!(new_object.is_live());
994            new_object
995        }
996    }
997
998    pub fn rc_trace_object<Q: ObjectQueue>(
999        &self,
1000        queue: &mut Q,
1001        object: ObjectReference,
1002        semantics: CopySemantics,
1003        pause: Pause,
1004        mark: bool,
1005        worker: &mut GCWorker<VM>,
1006    ) -> ObjectReference {
1007        debug_assert!(self.rc_enabled);
1008        if LXR_MATURE_EVACUATION && Block::containing(object).is_defrag_source() {
1009            self.trace_forward_rc_mature_object(queue, object, semantics, pause, worker)
1010        } else if LXR_MATURE_EVACUATION {
1011            self.trace_mark_rc_mature_object(queue, object, pause, mark)
1012        } else {
1013            self.trace_object_without_moving(queue, object)
1014        }
1015    }
1016
1017    pub fn trace_mark_rc_mature_object(
1018        &self,
1019        queue: &mut impl ObjectQueue,
1020        object: ObjectReference,
1021        _pause: Pause,
1022        mark: bool,
1023    ) -> ObjectReference {
1024        debug_assert!(
1025            !object_forwarding::is_forwarded::<VM>(object),
1026            "object {:?} is forwarded",
1027            object
1028        );
1029        if mark && self.attempt_mark(object) {
1030            queue.enqueue(object);
1031        }
1032        object
1033    }
1034
1035    #[allow(clippy::assertions_on_constants)]
1036    pub fn trace_forward_rc_mature_object<Q: ObjectQueue>(
1037        &self,
1038        queue: &mut Q,
1039        object: ObjectReference,
1040        _semantics: CopySemantics,
1041        _pause: Pause,
1042        worker: &mut GCWorker<VM>,
1043    ) -> ObjectReference {
1044        let copy_context = worker.get_copy_context_mut();
1045        let forwarding_status = object_forwarding::attempt_to_forward::<VM>(object);
1046        if object_forwarding::state_is_forwarded_or_being_forwarded(forwarding_status) {
1047            object_forwarding::spin_and_get_forwarded_object::<VM>(object, forwarding_status)
1048        } else {
1049            // Evacuate the mature object
1050            let new = object_forwarding::try_forward_object::<VM>(
1051                object,
1052                CopySemantics::DefaultCopy,
1053                copy_context,
1054                |_new_object| {
1055                    // When using RC, we set the VO bit of the forwarded object.
1056                    #[cfg(feature = "vo_bit")]
1057                    vo_bit::set_vo_bit(_new_object);
1058                },
1059            )
1060            .expect("to-space overflow");
1061            // Transfer RC count
1062            if new.get_size::<VM>() > Line::BYTES {
1063                self.rc.mark_straddle_object(new);
1064            }
1065            self.rc.set(new, self.rc.count(object));
1066            self.attempt_mark(new);
1067            self.unmark(object);
1068            queue.enqueue(new);
1069            debug_assert_ne!(
1070                self.rc.count(new),
1071                0,
1072                "ERROR Invalid {:?} rc={}",
1073                new,
1074                self.rc.count(new)
1075            );
1076            new
1077        }
1078    }
1079
1080    fn unlog_object_if_needed(&self, object: ObjectReference) {
1081        debug_assert!(!self.rc_enabled);
1082        if self.common.unlog_traced_object {
1083            // Make sure the side metadata for the line can fit into one byte. For smaller line size, we should
1084            // use `mark_as_unlogged` instead to mark the bit.
1085            const_assert!(
1086                Line::BYTES
1087                    >= (1
1088                        << (crate::util::constants::LOG_BITS_IN_BYTE
1089                            + crate::util::constants::LOG_MIN_OBJECT_SIZE))
1090            );
1091            const_assert_eq!(
1092                crate::vm::object_model::specs::VMGlobalLogBitSpec::LOG_NUM_BITS,
1093                0
1094            ); // We should put this to the addition, but type casting is not allowed in constant assertions.
1095
1096            // Every immix line is 256 bytes, which is mapped to 4 bytes in the side metadata.
1097            // If we have one object in the line that is mature, we can assume all the objects in the line are mature objects.
1098            // So we can just mark the byte.
1099            VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
1100                .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
1101        }
1102    }
1103
1104    /// Mark all the lines that the given object spans.
1105    #[allow(clippy::assertions_on_constants)]
1106    pub fn mark_lines(&self, object: ObjectReference) {
1107        debug_assert!(!super::BLOCK_ONLY);
1108        if self.rc_enabled {
1109            return;
1110        }
1111        Line::mark_lines_for_object::<VM>(object, self.line_mark_state.load(Ordering::Acquire));
1112    }
1113
1114    /// Atomically mark an object.
1115    pub fn attempt_mark(&self, object: ObjectReference) -> bool {
1116        loop {
1117            let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
1118                object,
1119                None,
1120                Ordering::SeqCst,
1121            );
1122            if old_value == self.mark_state {
1123                return false;
1124            }
1125
1126            if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC
1127                .compare_exchange_metadata::<VM, u8>(
1128                    object,
1129                    old_value,
1130                    self.mark_state,
1131                    None,
1132                    Ordering::SeqCst,
1133                    Ordering::SeqCst,
1134                )
1135                .is_ok()
1136            {
1137                break;
1138            }
1139        }
1140        true
1141    }
1142
1143    /// Atomically unmark an object.  Return true if it changed the mark bit from 1 to 0.
1144    pub fn unmark(&self, object: ObjectReference) -> bool {
1145        let result = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.fetch_update_metadata::<VM, u8, _>(
1146            object,
1147            Ordering::Relaxed,
1148            Ordering::Relaxed,
1149            |v| {
1150                if v != 1 {
1151                    return None;
1152                }
1153                Some(0)
1154            },
1155        );
1156        result.is_ok()
1157    }
1158
1159    fn is_marked_with(&self, object: ObjectReference, mark_state: u8) -> bool {
1160        let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
1161            object,
1162            None,
1163            Ordering::SeqCst,
1164        );
1165        old_value == mark_state
1166    }
1167
1168    pub(crate) fn is_marked(&self, object: ObjectReference) -> bool {
1169        self.is_marked_with(object, self.mark_state)
1170    }
1171
1172    /// Check if an object is pinned.
1173    fn is_pinned(&self, _object: ObjectReference) -> bool {
1174        #[cfg(feature = "object_pinning")]
1175        return self.is_object_pinned(_object);
1176
1177        #[cfg(not(feature = "object_pinning"))]
1178        false
1179    }
1180
1181    /// Hole searching.
1182    ///
1183    /// Linearly scan lines in a block to search for the next
1184    /// hole, starting from the given line. If we find available lines,
1185    /// return a tuple of the start line and the end line (non-inclusive).
1186    ///
1187    /// Returns None if the search could not find any more holes.
1188    #[allow(clippy::assertions_on_constants)]
1189    pub fn get_next_available_lines(&self, copy: bool, search_start: Line) -> Option<(Line, Line)> {
1190        debug_assert!(!super::BLOCK_ONLY);
1191        if self.rc_enabled {
1192            self.rc_get_next_available_lines(copy, search_start)
1193        } else {
1194            self.normal_get_next_available_lines(search_start)
1195        }
1196    }
1197
1198    /// Search holes by ref-counts instead of line marks
1199    #[allow(clippy::assertions_on_constants)]
1200    pub fn rc_get_next_available_lines(
1201        &self,
1202        copy: bool,
1203        search_start: Line,
1204    ) -> Option<(Line, Line)> {
1205        debug_assert!(!super::BLOCK_ONLY);
1206        debug_assert!(self.rc_enabled);
1207        let block = search_start.block();
1208        let rc_array = RCArray::of(block);
1209        let limit = Block::LINES;
1210        // Find start
1211        let first_free_cursor = {
1212            let start_cursor = search_start.get_index_within_block();
1213            let mut first_free_cursor = None;
1214            let mut find_free_line = false;
1215            for i in start_cursor..limit {
1216                if rc_array.is_dead(i) {
1217                    if i == 0 {
1218                        first_free_cursor = Some(i);
1219                        break;
1220                    } else if !find_free_line {
1221                        // This skips the first line of a hole
1222                        // because `mark_straddle_object_with_size` may or may not set the RC
1223                        // of the last line an object straddles.
1224                        find_free_line = true;
1225                    } else {
1226                        first_free_cursor = Some(i);
1227                        break;
1228                    }
1229                } else {
1230                    find_free_line = false;
1231                }
1232            }
1233            first_free_cursor
1234        };
1235        let start = match first_free_cursor {
1236            Some(c) => c,
1237            _ => return None,
1238        };
1239        // Find limit
1240        let end = {
1241            let mut cursor = start + 1;
1242            while cursor < limit {
1243                if !rc_array.is_dead(cursor) {
1244                    break;
1245                }
1246                cursor += 1;
1247            }
1248            cursor
1249        };
1250        // For bindings without `UNIFIED_OBJECT_REFERENCE_ADDRESS`, the object start
1251        // may land in the line immediately before its object reference address. Such a line
1252        // can be considered as empty. We conservatively consider the last line in a hole
1253        // may hold an object start. This is the only place in LXR we need to handle the object start vs object ref issue.
1254        // TODO: This solution is conservative and not ideal, but is the simplest fix.
1255        // An alternative is to shift RC_TABLE (and possibly the RC_STRADDLE_LINES) for the upper bound of (obj ref - obj start),
1256        // when we search for holes. See https://github.com/mmtk/mmtk-core/pull/1576 as a half-done prototype.
1257        let end = if !VM::VMObjectModel::UNIFIED_OBJECT_REFERENCE_ADDRESS && end < limit {
1258            end - 1
1259        } else {
1260            end
1261        };
1262        if end <= start {
1263            // The reservation consumed the entire hole (it was exactly one line). Retry from
1264            // just past it instead of handing out an empty range.
1265            let next_search_start = Line::from_aligned_address(block.start()).next_nth(start + 1);
1266            return self.rc_get_next_available_lines(copy, next_search_start);
1267        }
1268        let start = Line::from_aligned_address(block.start()).next_nth(start);
1269        let end = Line::from_aligned_address(block.start()).next_nth(end);
1270        if self.common.needs_log_bit {
1271            if !copy {
1272                Line::clear_field_unlog_table::<VM>(start..end);
1273            } else {
1274                Line::initialize_field_unlog_table_as_unlogged::<VM>(start..end);
1275            }
1276        }
1277        let num_lines = Line::steps_between(&start, &end).unwrap();
1278        if !copy {
1279            self.reused_lines_consumed
1280                .fetch_add(num_lines, Ordering::Relaxed);
1281        }
1282        if self
1283            .hooks()
1284            .is_some_and(|h| h.cm_in_progress_or_final_mark())
1285        {
1286            Line::initialize_mark_table_as_marked::<VM>(start..end);
1287            Line::inc_reuse_counts(start..end);
1288        }
1289        Some((start, end))
1290    }
1291
1292    #[allow(clippy::assertions_on_constants)]
1293    pub fn normal_get_next_available_lines(&self, search_start: Line) -> Option<(Line, Line)> {
1294        debug_assert!(!self.rc_enabled);
1295        let unavail_state = self.line_unavail_state.load(Ordering::Acquire);
1296        let current_state = self.line_mark_state.load(Ordering::Acquire);
1297        let block = search_start.block();
1298        let mark_data = block.line_mark_table();
1299        let start_cursor = search_start.get_index_within_block();
1300        let mut cursor = start_cursor;
1301        // Find start
1302        while cursor < mark_data.len() {
1303            let mark = mark_data.get(cursor);
1304            if mark != unavail_state && mark != current_state {
1305                break;
1306            }
1307            cursor += 1;
1308        }
1309        if cursor == mark_data.len() {
1310            return None;
1311        }
1312        let start = search_start.next_nth(cursor - start_cursor);
1313        // Find limit
1314        while cursor < mark_data.len() {
1315            let mark = mark_data.get(cursor);
1316            if mark == unavail_state || mark == current_state {
1317                break;
1318            }
1319            cursor += 1;
1320        }
1321        let end = search_start.next_nth(cursor - start_cursor);
1322        debug_assert!(RegionIterator::<Line>::new(start, end)
1323            .all(|line| !line.is_marked(unavail_state) && !line.is_marked(current_state)));
1324        Some((start, end))
1325    }
1326
1327    pub fn is_last_gc_exhaustive(&self, did_defrag_for_last_gc: bool) -> bool {
1328        if self.is_defrag_enabled() {
1329            did_defrag_for_last_gc
1330        } else {
1331            // If defrag is disabled, every GC is exhaustive.
1332            true
1333        }
1334    }
1335
1336    pub(crate) fn get_mutator_recycled_lines_in_pages(&self) -> usize {
1337        debug_assert!(self.rc_enabled);
1338        self.reused_lines_consumed.load(Ordering::Relaxed)
1339            >> (LOG_BYTES_IN_PAGE - Line::LOG_BYTES as u8)
1340    }
1341
1342    pub(crate) fn get_pages_allocated(&self) -> usize {
1343        debug_assert!(!self.rc_enabled);
1344        self.lines_consumed.load(Ordering::Relaxed) >> (LOG_BYTES_IN_PAGE - Line::LOG_BYTES as u8)
1345    }
1346
1347    /// Post copy routine for Immix copy contexts
1348    fn post_copy(&self, object: ObjectReference, _bytes: usize) {
1349        if self.rc_enabled {
1350            return;
1351        }
1352        // Mark the object
1353        VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.store_atomic::<VM, u8>(
1354            object,
1355            self.mark_state,
1356            None,
1357            Ordering::SeqCst,
1358        );
1359        // Mark the line
1360        if !super::MARK_LINE_AT_SCAN_TIME {
1361            self.mark_lines(object);
1362        }
1363        if self.common.unlog_traced_object {
1364            VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
1365                .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
1366        }
1367    }
1368
1369    pub(crate) fn prefer_copy_on_nursery_gc(&self) -> bool {
1370        self.is_nursery_copy_enabled()
1371    }
1372
1373    pub(crate) fn is_nursery_copy_enabled(&self) -> bool {
1374        !self.space_args.never_move_objects && !cfg!(feature = "sticky_immix_non_moving_nursery")
1375    }
1376
1377    pub(crate) fn is_defrag_enabled(&self) -> bool {
1378        !self.space_args.never_move_objects
1379    }
1380}
1381
1382/// A work packet to prepare each block for a major GC.
1383/// Performs the action on a range of chunks.
1384pub struct PrepareBlockState<VM: VMBinding> {
1385    #[allow(dead_code)]
1386    pub space: &'static ImmixSpace<VM>,
1387    pub chunk: Chunk,
1388    pub defrag_threshold: Option<usize>,
1389    pub unlog_bits_op: UnlogBitsOperation,
1390}
1391
1392impl<VM: VMBinding> PrepareBlockState<VM> {
1393    /// Clear object mark table
1394    fn reset_object_mark(&self) {
1395        // NOTE: We reset the mark bits because cyclic mark bit is currently not supported, yet.
1396        // See `ImmixSpace::prepare`.
1397        if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC {
1398            side.bzero_metadata(self.chunk.start(), Chunk::BYTES);
1399        }
1400    }
1401}
1402
1403impl<VM: VMBinding> GCWork<VM> for PrepareBlockState<VM> {
1404    fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
1405        // Clear object mark table for this chunk
1406        self.reset_object_mark();
1407        // Iterate over all blocks in this chunk
1408        for block in self.chunk.iter_region::<Block>() {
1409            let state = block.get_state();
1410            // Skip unallocated blocks.
1411            if state == BlockState::Unallocated {
1412                continue;
1413            }
1414            // Check if this block needs to be defragmented.
1415            let is_defrag_source = if !self.space.is_defrag_enabled() {
1416                // Do not set any block as defrag source if defrag is disabled.
1417                false
1418            } else if *mmtk.options.immix_defrag_every_block {
1419                // Set every block as defrag source if so desired.
1420                true
1421            } else if let Some(defrag_threshold) = self.defrag_threshold {
1422                // This GC is a defrag GC.
1423                block.get_holes() > defrag_threshold
1424            } else {
1425                // Not a defrag GC.
1426                false
1427            };
1428            block.set_as_defrag_source(is_defrag_source);
1429            // Clear block mark data.
1430            block.set_state(BlockState::Unmarked);
1431            debug_assert!(!block.get_state().is_reusable());
1432            debug_assert_ne!(block.get_state(), BlockState::Marked);
1433        }
1434
1435        self.unlog_bits_op
1436            .execute::<VM>(self.chunk.start(), Chunk::BYTES);
1437    }
1438}
1439
1440/// Chunk sweeping work packet.
1441struct SweepChunk<VM: VMBinding> {
1442    space: &'static ImmixSpace<VM>,
1443    chunk: Chunk,
1444    unlog_bits_op: UnlogBitsOperation,
1445    /// A destructor invoked when all `SweepChunk` packets are finished.
1446    epilogue: Arc<FlushPageResource<VM>>,
1447}
1448
1449impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
1450    fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
1451        assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
1452
1453        let mut histogram = self.space.defrag.new_histogram();
1454        let line_mark_state = if super::BLOCK_ONLY {
1455            None
1456        } else {
1457            Some(self.space.line_mark_state.load(Ordering::Acquire))
1458        };
1459        // Hints for clearing side forwarding bits.
1460        let is_moving_gc = mmtk.get_plan().current_gc_may_move_object();
1461        let is_defrag_gc = self.space.defrag.in_defrag();
1462
1463        // number of swept (completely free) blocks.
1464        let mut swept_blocks = 0;
1465        // number of reused blocks.
1466        let mut reused_blocks = 0;
1467        // number of non-free blocks that cannot be reused (e.g. full, or non-empty when block-only).
1468        let mut unreused_blocks = 0;
1469
1470        // Iterate over all allocated blocks in this chunk.
1471        for block in self
1472            .chunk
1473            .iter_region::<Block>()
1474            .filter(|block| block.get_state() != BlockState::Unallocated)
1475        {
1476            // Clear side forwarding bits.
1477            // In the beginning of the next GC, no side forwarding bits shall be set.
1478            // In this way, we can omit clearing forwarding bits when copying object.
1479            // See `GCWorkerCopyContext::post_copy`.
1480            // Note, `block.sweep()` overwrites `DEFRAG_STATE_TABLE` with the number of holes,
1481            // but we need it to know if a block is a defrag source.
1482            // We clear forwarding bits before `block.sweep()`.
1483            if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC {
1484                if is_moving_gc {
1485                    let objects_may_move = if is_defrag_gc {
1486                        // If it is a defrag GC, we only clear forwarding bits for defrag sources.
1487                        block.is_defrag_source()
1488                    } else {
1489                        // Otherwise, it must be a nursery GC of StickyImmix with copying nursery.
1490                        // We don't have information about which block contains moved objects,
1491                        // so we have to clear forwarding bits for all blocks.
1492                        true
1493                    };
1494                    if objects_may_move {
1495                        side.bzero_metadata(block.start(), Block::BYTES);
1496                    }
1497                }
1498            }
1499
1500            match block.sweep(self.space, &mut histogram, line_mark_state) {
1501                BlockSweepResult::Swept => swept_blocks += 1,
1502                BlockSweepResult::Reused => reused_blocks += 1,
1503                BlockSweepResult::NoReuse => unreused_blocks += 1,
1504            }
1505        }
1506
1507        probe!(
1508            mmtk,
1509            sweep_chunk_immix,
1510            swept_blocks,
1511            reused_blocks,
1512            unreused_blocks
1513        );
1514
1515        // number of allocated blocks.
1516        let allocated_blocks = reused_blocks + unreused_blocks;
1517
1518        // Set this chunk as free if there is not live blocks.
1519        if allocated_blocks == 0 {
1520            self.space.chunk_map.set_allocated(self.chunk, false)
1521        }
1522        self.space.defrag.add_completed_mark_histogram(histogram);
1523
1524        self.unlog_bits_op
1525            .execute::<VM>(self.chunk.start(), Chunk::BYTES);
1526
1527        self.epilogue.finish_one_work_packet();
1528    }
1529}
1530
1531/// Count number of remaining work pacets, and flush page resource if all packets are finished.
1532struct FlushPageResource<VM: VMBinding> {
1533    space: &'static ImmixSpace<VM>,
1534    counter: AtomicUsize,
1535}
1536
1537impl<VM: VMBinding> FlushPageResource<VM> {
1538    /// Called after a related work packet is finished.
1539    fn finish_one_work_packet(&self) {
1540        if 1 == self.counter.fetch_sub(1, Ordering::SeqCst) {
1541            // We've finished releasing all the dead blocks to the BlockPageResource's thread-local queues.
1542            // Now flush the BlockPageResource.
1543            self.space.flush_page_resource()
1544        }
1545    }
1546}
1547
1548impl<VM: VMBinding> Drop for FlushPageResource<VM> {
1549    fn drop(&mut self) {
1550        epilogue::debug_assert_counter_zero(&self.counter, "FlushPageResource::counter");
1551    }
1552}
1553
1554use crate::policy::copy_context::PolicyCopyContext;
1555use crate::util::alloc::Allocator;
1556use crate::util::alloc::ImmixAllocator;
1557
1558/// Normal immix copy context. It has one copying Immix allocator.
1559/// Most immix plans use this copy context.
1560pub struct ImmixCopyContext<VM: VMBinding> {
1561    allocator: ImmixAllocator<VM>,
1562}
1563
1564impl<VM: VMBinding> PolicyCopyContext for ImmixCopyContext<VM> {
1565    type VM = VM;
1566
1567    fn prepare(&mut self) {
1568        self.allocator.reset();
1569    }
1570    fn release(&mut self) {
1571        self.allocator.reset();
1572    }
1573    fn alloc_copy(
1574        &mut self,
1575        _original: ObjectReference,
1576        bytes: usize,
1577        align: usize,
1578        offset: usize,
1579    ) -> Address {
1580        self.allocator.alloc(bytes, align, offset)
1581    }
1582    fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1583        self.get_space().post_copy(obj, bytes)
1584    }
1585}
1586
1587impl<VM: VMBinding> ImmixCopyContext<VM> {
1588    pub(crate) fn new(
1589        tls: VMWorkerThread,
1590        context: Arc<AllocatorContext<VM>>,
1591        space: &'static ImmixSpace<VM>,
1592    ) -> Self {
1593        ImmixCopyContext {
1594            allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1595        }
1596    }
1597
1598    fn get_space(&self) -> &ImmixSpace<VM> {
1599        self.allocator.immix_space()
1600    }
1601}
1602
1603/// Hybrid Immix copy context. It includes two different immix allocators. One with `copy = true`
1604/// is used for defrag GCs, and the other is used for other purposes (such as promoting objects from
1605/// nursery to Immix mature space). This is used by generational immix.
1606pub struct ImmixHybridCopyContext<VM: VMBinding> {
1607    copy_allocator: ImmixAllocator<VM>,
1608    defrag_allocator: ImmixAllocator<VM>,
1609}
1610
1611impl<VM: VMBinding> PolicyCopyContext for ImmixHybridCopyContext<VM> {
1612    type VM = VM;
1613
1614    fn prepare(&mut self) {
1615        self.copy_allocator.reset();
1616        self.defrag_allocator.reset();
1617    }
1618    fn release(&mut self) {
1619        self.copy_allocator.reset();
1620        self.defrag_allocator.reset();
1621    }
1622    fn alloc_copy(
1623        &mut self,
1624        _original: ObjectReference,
1625        bytes: usize,
1626        align: usize,
1627        offset: usize,
1628    ) -> Address {
1629        if self.get_space().in_defrag() {
1630            self.defrag_allocator.alloc(bytes, align, offset)
1631        } else {
1632            self.copy_allocator.alloc(bytes, align, offset)
1633        }
1634    }
1635    fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1636        self.get_space().post_copy(obj, bytes)
1637    }
1638}
1639
1640impl<VM: VMBinding> ImmixHybridCopyContext<VM> {
1641    pub(crate) fn new(
1642        tls: VMWorkerThread,
1643        context: Arc<AllocatorContext<VM>>,
1644        space: &'static ImmixSpace<VM>,
1645    ) -> Self {
1646        ImmixHybridCopyContext {
1647            copy_allocator: ImmixAllocator::new(tls.0, Some(space), context.clone(), true),
1648            defrag_allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1649        }
1650    }
1651
1652    fn get_space(&self) -> &ImmixSpace<VM> {
1653        // Both copy allocators should point to the same space.
1654        debug_assert_eq!(
1655            self.defrag_allocator.immix_space().common().descriptor,
1656            self.copy_allocator.immix_space().common().descriptor
1657        );
1658        // Just get the space from either allocator
1659        self.defrag_allocator.immix_space()
1660    }
1661}
1662
1663#[cfg(feature = "vo_bit")]
1664#[derive(Clone, Copy)]
1665enum VOBitsClearingScope {
1666    /// Clear all VO bits in all blocks.
1667    FullGC,
1668    /// Clear unmarked blocks, only.
1669    BlockOnly,
1670    /// Clear unmarked lines, only.  (i.e. lines with line mark state **not** equal to `state`).
1671    Line { state: u8 },
1672}
1673
1674/// A work packet to clear VO bit metadata after Prepare.
1675#[cfg(feature = "vo_bit")]
1676struct ClearVOBitsAfterPrepare {
1677    chunk: Chunk,
1678    scope: VOBitsClearingScope,
1679}
1680
1681#[cfg(feature = "vo_bit")]
1682impl<VM: VMBinding> GCWork<VM> for ClearVOBitsAfterPrepare {
1683    fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
1684        match self.scope {
1685            VOBitsClearingScope::FullGC => {
1686                vo_bit::bzero_vo_bit(self.chunk.start(), Chunk::BYTES);
1687            }
1688            VOBitsClearingScope::BlockOnly => {
1689                self.clear_blocks(None);
1690            }
1691            VOBitsClearingScope::Line { state } => {
1692                self.clear_blocks(Some(state));
1693            }
1694        }
1695    }
1696}
1697
1698#[cfg(feature = "vo_bit")]
1699impl ClearVOBitsAfterPrepare {
1700    fn clear_blocks(&mut self, line_mark_state: Option<u8>) {
1701        for block in self
1702            .chunk
1703            .iter_region::<Block>()
1704            .filter(|block| block.get_state() != BlockState::Unallocated)
1705        {
1706            block.clear_vo_bits_for_unmarked_regions(line_mark_state);
1707        }
1708    }
1709}