mmtk/policy/immix/
immixspace.rs

1use super::defrag::StatsForDefrag;
2use super::line::*;
3use super::{block::*, defrag::Defrag};
4use crate::plan::tracing::OptionObjectQueue;
5use crate::policy::gc_work::{TraceKind, DEFAULT_TRACE, TRACE_KIND_TRANSITIVE_PIN};
6use crate::policy::sft::GCWorkerMutRef;
7use crate::policy::sft::SFT;
8use crate::policy::sft_map::SFTMap;
9use crate::policy::space::{CommonSpace, Space};
10use crate::util::alloc::allocator::AllocationOptions;
11use crate::util::alloc::allocator::AllocatorContext;
12use crate::util::constants::LOG_BYTES_IN_PAGE;
13use crate::util::heap::chunk_map::*;
14use crate::util::heap::BlockPageResource;
15use crate::util::heap::PageResource;
16use crate::util::linear_scan::{Region, RegionIterator};
17use crate::util::metadata::log_bit::UnlogBitsOperation;
18use crate::util::metadata::side_metadata::SideMetadataSpec;
19#[cfg(feature = "vo_bit")]
20use crate::util::metadata::vo_bit;
21use crate::util::metadata::{self, MetadataSpec};
22use crate::util::object_enum::ObjectEnumerator;
23use crate::util::object_forwarding;
24use crate::util::{copy::*, epilogue, object_enum};
25use crate::util::{Address, ObjectReference};
26use crate::vm::*;
27use crate::{
28    plan::ObjectQueue,
29    scheduler::{GCWork, GCWorkScheduler, GCWorker, WorkBucketStage},
30    util::opaque_pointer::{VMThread, VMWorkerThread},
31    MMTK,
32};
33use atomic::Ordering;
34use std::sync::{atomic::AtomicU8, atomic::AtomicUsize, Arc};
35
36pub(crate) const TRACE_KIND_FAST: TraceKind = 0;
37pub(crate) const TRACE_KIND_DEFRAG: TraceKind = 1;
38
39pub struct ImmixSpace<VM: VMBinding> {
40    common: CommonSpace<VM>,
41    pr: BlockPageResource<VM, Block>,
42    /// Allocation status for all chunks in immix space
43    pub chunk_map: ChunkMap,
44    /// Current line mark state
45    pub line_mark_state: AtomicU8,
46    /// Line mark state in previous GC
47    line_unavail_state: AtomicU8,
48    /// A list of all reusable blocks
49    pub reusable_blocks: ReusableBlockPool,
50    /// Defrag utilities
51    pub(super) defrag: Defrag,
52    /// How many lines have been consumed since last GC?
53    lines_consumed: AtomicUsize,
54    /// Object mark state
55    mark_state: u8,
56    /// Work packet scheduler
57    scheduler: Arc<GCWorkScheduler<VM>>,
58    /// Some settings for this space
59    space_args: ImmixSpaceArgs,
60}
61
62/// Some arguments for Immix Space.
63pub struct ImmixSpaceArgs {
64    /// Whether this ImmixSpace instance contains both young and old objects.
65    /// This affects the updating of valid-object bits.  If some lines or blocks of this ImmixSpace
66    /// instance contain young objects, their VO bits need to be updated during this GC.  Currently
67    /// only StickyImmix is affected.  GenImmix allocates young objects in a separete CopySpace
68    /// nursery and its VO bits can be cleared in bulk.
69    pub mixed_age: bool,
70    /// Disable copying for this Immix space.
71    pub never_move_objects: bool,
72}
73
74unsafe impl<VM: VMBinding> Sync for ImmixSpace<VM> {}
75
76impl<VM: VMBinding> SFT for ImmixSpace<VM> {
77    fn name(&self) -> &'static str {
78        self.get_name()
79    }
80
81    fn get_forwarded_object(&self, object: ObjectReference) -> Option<ObjectReference> {
82        // If we never move objects, look no further.
83        if !self.is_movable() {
84            return None;
85        }
86
87        if object_forwarding::is_forwarded::<VM>(object) {
88            Some(object_forwarding::read_forwarding_pointer::<VM>(object))
89        } else {
90            None
91        }
92    }
93
94    fn is_live(&self, object: ObjectReference) -> bool {
95        // If the mark bit is set, it is live.
96        if self.is_marked(object) {
97            return true;
98        }
99
100        // If we never move objects, look no further.
101        if !self.is_movable() {
102            return false;
103        }
104
105        // If the object is forwarded, it is live, too.
106        object_forwarding::is_forwarded::<VM>(object)
107    }
108    #[cfg(feature = "object_pinning")]
109    fn pin_object(&self, object: ObjectReference) -> bool {
110        if self.space_args.never_move_objects {
111            false
112        } else {
113            VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.pin_object::<VM>(object)
114        }
115    }
116    #[cfg(feature = "object_pinning")]
117    fn unpin_object(&self, object: ObjectReference) -> bool {
118        if self.space_args.never_move_objects {
119            false
120        } else {
121            VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.unpin_object::<VM>(object)
122        }
123    }
124    #[cfg(feature = "object_pinning")]
125    fn is_object_pinned(&self, object: ObjectReference) -> bool {
126        if self.space_args.never_move_objects {
127            true
128        } else {
129            VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.is_object_pinned::<VM>(object)
130        }
131    }
132    fn is_movable(&self) -> bool {
133        !self.space_args.never_move_objects
134    }
135
136    #[cfg(feature = "sanity")]
137    fn is_sane(&self) -> bool {
138        true
139    }
140    fn initialize_object_metadata(&self, _object: ObjectReference, _bytes: usize) {
141        #[cfg(feature = "vo_bit")]
142        crate::util::metadata::vo_bit::set_vo_bit(_object);
143    }
144    #[cfg(feature = "vo_bit")]
145    fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
146        crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
147    }
148    #[cfg(feature = "vo_bit")]
149    fn find_object_from_internal_pointer(
150        &self,
151        ptr: Address,
152        max_search_bytes: usize,
153    ) -> Option<ObjectReference> {
154        // We don't need to search more than the max object size in the immix space.
155        let search_bytes = usize::min(super::MAX_IMMIX_OBJECT_SIZE, max_search_bytes);
156        crate::util::metadata::vo_bit::find_object_from_internal_pointer::<VM>(ptr, search_bytes)
157    }
158    fn sft_trace_object(
159        &self,
160        _queue: &mut OptionObjectQueue,
161        _object: ObjectReference,
162        _worker: GCWorkerMutRef,
163    ) -> ObjectReference {
164        panic!("We do not use SFT to trace objects for Immix. sft_trace_object() cannot be used.")
165    }
166
167    fn debug_print_object_info(&self, object: ObjectReference) {
168        println!("marked  = {}", self.is_marked(object));
169        println!(
170            "line marked = {}",
171            Line::from_unaligned_address(object.to_raw_address()).is_marked(self.mark_state)
172        );
173        println!(
174            "block state = {:?}",
175            Block::from_unaligned_address(object.to_raw_address()).get_state()
176        );
177        object_forwarding::debug_print_object_forwarding_info::<VM>(object);
178        self.common.debug_print_object_global_info(object);
179    }
180}
181
182impl<VM: VMBinding> Space<VM> for ImmixSpace<VM> {
183    fn as_space(&self) -> &dyn Space<VM> {
184        self
185    }
186    fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
187        self
188    }
189    fn get_page_resource(&self) -> &dyn PageResource<VM> {
190        &self.pr
191    }
192    fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
193        Some(&mut self.pr)
194    }
195    fn common(&self) -> &CommonSpace<VM> {
196        &self.common
197    }
198    fn initialize_sft(&self, sft_map: &mut dyn SFTMap) {
199        self.common().initialize_sft(self.as_sft(), sft_map)
200    }
201    fn release_multiple_pages(&mut self, _start: Address) {
202        panic!("immixspace only releases pages enmasse")
203    }
204    fn set_copy_for_sft_trace(&mut self, _semantics: Option<CopySemantics>) {
205        panic!("We do not use SFT to trace objects for Immix. set_copy_context() cannot be used.")
206    }
207
208    fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
209        object_enum::enumerate_blocks_from_chunk_map::<Block>(enumerator, &self.chunk_map);
210    }
211
212    fn clear_side_log_bits(&self) {
213        // Remove the following warning if we have a legitimate use case.
214        warn!("ImmixSpace::clear_side_log_bits is single-treaded.  Consider clearing side metadata in per-chunk work packets.");
215
216        let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
217        for chunk in self.chunk_map.all_chunks() {
218            log_bit.bzero_metadata(chunk.start(), Chunk::BYTES);
219        }
220    }
221
222    fn set_side_log_bits(&self) {
223        // Remove the following warning if we have a legitimate use case.
224        warn!("ImmixSpace::set_side_log_bits is single-treaded.  Consider setting side metadata in per-chunk work packets.");
225
226        let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
227        for chunk in self.chunk_map.all_chunks() {
228            log_bit.bset_metadata(chunk.start(), Chunk::BYTES);
229        }
230    }
231}
232
233impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for ImmixSpace<VM> {
234    fn trace_object<Q: ObjectQueue, const KIND: TraceKind>(
235        &self,
236        queue: &mut Q,
237        object: ObjectReference,
238        copy: Option<CopySemantics>,
239        worker: &mut GCWorker<VM>,
240    ) -> ObjectReference {
241        if KIND == TRACE_KIND_TRANSITIVE_PIN {
242            self.trace_object_without_moving(queue, object)
243        } else if KIND == TRACE_KIND_DEFRAG {
244            if Block::containing(object).is_defrag_source() {
245                debug_assert!(self.in_defrag());
246                debug_assert!(
247                    !crate::plan::is_nursery_gc(worker.mmtk.get_plan()),
248                    "Calling PolicyTraceObject on Immix in nursery GC"
249                );
250                self.trace_object_with_opportunistic_copy(
251                    queue,
252                    object,
253                    copy.unwrap(),
254                    worker,
255                    // This should not be nursery collection. Nursery collection does not use PolicyTraceObject.
256                    false,
257                )
258            } else {
259                self.trace_object_without_moving(queue, object)
260            }
261        } else if KIND == TRACE_KIND_FAST {
262            self.trace_object_without_moving(queue, object)
263        } else {
264            unreachable!()
265        }
266    }
267
268    fn post_scan_object(&self, object: ObjectReference) {
269        if super::MARK_LINE_AT_SCAN_TIME && !super::BLOCK_ONLY {
270            debug_assert!(self.in_space(object));
271            self.mark_lines(object);
272        }
273    }
274
275    #[allow(clippy::if_same_then_else)] // DEFAULT_TRACE needs a workaround which is documented below.
276    fn may_move_objects<const KIND: TraceKind>() -> bool {
277        if KIND == TRACE_KIND_DEFRAG {
278            true
279        } else if KIND == TRACE_KIND_FAST || KIND == TRACE_KIND_TRANSITIVE_PIN {
280            false
281        } else if KIND == DEFAULT_TRACE {
282            // FIXME: This is hacky. When we do a default trace, this should be a nonmoving space.
283            // The only exception is the nursery GC for sticky immix, for which, we use default trace.
284            // This function is only used for PlanTrace, and for sticky immix nursery GC, we use
285            // GenNurseryTrace. So it still works. But this is quite hacky anyway.
286            // See https://github.com/mmtk/mmtk-core/issues/1314 for details.
287            false
288        } else {
289            unreachable!()
290        }
291    }
292}
293
294impl<VM: VMBinding> ImmixSpace<VM> {
295    #[allow(unused)]
296    const UNMARKED_STATE: u8 = 0;
297    const MARKED_STATE: u8 = 1;
298
299    /// Get side metadata specs
300    fn side_metadata_specs() -> Vec<SideMetadataSpec> {
301        metadata::extract_side_metadata(&if super::BLOCK_ONLY {
302            vec![
303                MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
304                MetadataSpec::OnSide(Block::MARK_TABLE),
305                *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
306                *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
307                *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
308                #[cfg(feature = "object_pinning")]
309                *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
310            ]
311        } else {
312            vec![
313                MetadataSpec::OnSide(Line::MARK_TABLE),
314                MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
315                MetadataSpec::OnSide(Block::MARK_TABLE),
316                *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
317                *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
318                *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
319                #[cfg(feature = "object_pinning")]
320                *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
321            ]
322        })
323    }
324
325    pub fn new(
326        args: crate::policy::space::PlanCreateSpaceArgs<VM>,
327        mut space_args: ImmixSpaceArgs,
328    ) -> Self {
329        if args.unlog_traced_object {
330            assert!(
331                args.constraints.needs_log_bit,
332                "Invalid args when the plan does not use log bit"
333            );
334        }
335
336        // Make sure we override the space args if we force non moving Immix
337        if cfg!(feature = "immix_non_moving") && !space_args.never_move_objects {
338            info!(
339                "Overriding never_moves_objects for Immix Space {}, as the immix_non_moving feature is set. Block size: 2^{}",
340                args.name,
341                Block::LOG_BYTES,
342            );
343            space_args.never_move_objects = true;
344        }
345
346        // validate features
347        if super::BLOCK_ONLY {
348            assert!(
349                space_args.never_move_objects,
350                "Block-only immix must not move objects"
351            );
352        }
353        assert!(
354            Block::LINES / 2 <= u8::MAX as usize - 2,
355            "Number of lines in a block should not exceed BlockState::MARK_MARKED"
356        );
357
358        #[cfg(feature = "vo_bit")]
359        vo_bit::helper::validate_config::<VM>();
360        let vm_map = args.vm_map;
361        let scheduler = args.scheduler.clone();
362        let common =
363            CommonSpace::new(args.into_policy_args(true, false, Self::side_metadata_specs()));
364        let space_index = common.descriptor.get_index();
365        ImmixSpace {
366            pr: if common.vmrequest.is_discontiguous() {
367                BlockPageResource::new_discontiguous(
368                    Block::LOG_PAGES,
369                    vm_map,
370                    scheduler.num_workers(),
371                )
372            } else {
373                BlockPageResource::new_contiguous(
374                    Block::LOG_PAGES,
375                    common.start,
376                    common.extent,
377                    vm_map,
378                    scheduler.num_workers(),
379                )
380            },
381            common,
382            chunk_map: ChunkMap::new(space_index),
383            line_mark_state: AtomicU8::new(Line::RESET_MARK_STATE),
384            line_unavail_state: AtomicU8::new(Line::RESET_MARK_STATE),
385            lines_consumed: AtomicUsize::new(0),
386            reusable_blocks: ReusableBlockPool::new(scheduler.num_workers()),
387            defrag: Defrag::default(),
388            // Set to the correct mark state when inititialized. We cannot rely on prepare to set it (prepare may get skipped in nursery GCs).
389            mark_state: Self::MARKED_STATE,
390            scheduler: scheduler.clone(),
391            space_args,
392        }
393    }
394
395    /// Flush the thread-local queues in BlockPageResource
396    pub fn flush_page_resource(&self) {
397        self.reusable_blocks.flush_all();
398        #[cfg(target_pointer_width = "64")]
399        self.pr.flush_all()
400    }
401
402    /// Get the number of defrag headroom pages.
403    pub fn defrag_headroom_pages(&self) -> usize {
404        self.defrag.defrag_headroom_pages(self)
405    }
406
407    /// Check if current GC is a defrag GC.
408    pub fn in_defrag(&self) -> bool {
409        self.defrag.in_defrag()
410    }
411
412    /// check if the current GC should do defragmentation.
413    pub fn decide_whether_to_defrag(
414        &self,
415        emergency_collection: bool,
416        collect_whole_heap: bool,
417        collection_attempts: usize,
418        user_triggered_collection: bool,
419        full_heap_system_gc: bool,
420    ) -> bool {
421        self.defrag.decide_whether_to_defrag(
422            self.is_defrag_enabled(),
423            emergency_collection,
424            collect_whole_heap,
425            collection_attempts,
426            user_triggered_collection,
427            self.reusable_blocks.len() == 0,
428            full_heap_system_gc,
429            *self.common.options.immix_always_defrag,
430        );
431        self.defrag.in_defrag()
432    }
433
434    /// Get work packet scheduler
435    fn scheduler(&self) -> &GCWorkScheduler<VM> {
436        &self.scheduler
437    }
438
439    pub(crate) fn prepare(
440        &mut self,
441        major_gc: bool,
442        plan_stats: Option<StatsForDefrag>,
443        unlog_bits_op: UnlogBitsOperation,
444    ) {
445        if major_gc {
446            // Update mark_state
447            if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_on_side() {
448                self.mark_state = Self::MARKED_STATE;
449            } else {
450                // For header metadata, we use cyclic mark bits.
451                unimplemented!("cyclic mark bits is not supported at the moment");
452            }
453
454            // Prepare defrag info
455            if self.is_defrag_enabled() {
456                self.defrag.prepare(self, plan_stats.unwrap());
457            }
458
459            // Prepare each block for GC
460            let threshold = self.defrag.defrag_spill_threshold.load(Ordering::Acquire);
461            // # Safety: ImmixSpace reference is always valid within this collection cycle.
462            let space = unsafe { &*(self as *const Self) };
463            let work_packets = self.chunk_map.generate_tasks(|chunk| {
464                Box::new(PrepareBlockState {
465                    space,
466                    chunk,
467                    defrag_threshold: if space.in_defrag() {
468                        Some(threshold)
469                    } else {
470                        None
471                    },
472                    unlog_bits_op,
473                })
474            });
475            self.scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(work_packets);
476
477            if !super::BLOCK_ONLY {
478                self.line_mark_state.fetch_add(1, Ordering::AcqRel);
479                if self.line_mark_state.load(Ordering::Acquire) > Line::MAX_MARK_STATE {
480                    self.line_mark_state
481                        .store(Line::RESET_MARK_STATE, Ordering::Release);
482                }
483            }
484        }
485
486        #[cfg(feature = "vo_bit")]
487        if vo_bit::helper::need_to_clear_vo_bits_before_tracing::<VM>() {
488            let maybe_scope = if major_gc {
489                // If it is major GC, we always clear all VO bits because we are doing full-heap
490                // tracing.
491                Some(VOBitsClearingScope::FullGC)
492            } else if self.space_args.mixed_age {
493                // StickyImmix nursery GC.
494                // Some lines (or blocks) contain only young objects,
495                // while other lines (or blocks) contain only old objects.
496                if super::BLOCK_ONLY {
497                    // Block only.  Young objects are only allocated into fully empty blocks.
498                    // Only clear unmarked blocks.
499                    Some(VOBitsClearingScope::BlockOnly)
500                } else {
501                    // Young objects are allocated into empty lines.
502                    // Only clear unmarked lines.
503                    let line_mark_state = self.line_mark_state.load(Ordering::SeqCst);
504                    Some(VOBitsClearingScope::Line {
505                        state: line_mark_state,
506                    })
507                }
508            } else {
509                // GenImmix nursery GC.  We do nothing to the ImmixSpace because the nursery is a
510                // separate CopySpace.  It'll clear its own VO bits.
511                None
512            };
513
514            if let Some(scope) = maybe_scope {
515                let work_packets = self
516                    .chunk_map
517                    .generate_tasks(|chunk| Box::new(ClearVOBitsAfterPrepare { chunk, scope }));
518                self.scheduler.work_buckets[WorkBucketStage::ClearVOBits].bulk_add(work_packets);
519            }
520        }
521    }
522
523    /// Release for the immix space.
524    pub(crate) fn release(&mut self, major_gc: bool, unlog_bits_op: UnlogBitsOperation) {
525        if major_gc {
526            // Update line_unavail_state for hole searching after this GC.
527            if !super::BLOCK_ONLY {
528                self.line_unavail_state.store(
529                    self.line_mark_state.load(Ordering::Acquire),
530                    Ordering::Release,
531                );
532            }
533        }
534        // Clear reusable blocks list
535        if !super::BLOCK_ONLY {
536            self.reusable_blocks.reset();
537        }
538        // Sweep chunks and blocks
539        let work_packets = self.generate_sweep_tasks(unlog_bits_op);
540        self.scheduler().work_buckets[WorkBucketStage::Release].bulk_add(work_packets);
541
542        self.lines_consumed.store(0, Ordering::Relaxed);
543    }
544
545    /// This is called when a GC finished.
546    /// Return whether this GC was a defrag GC, as a plan may want to know this.
547    pub fn end_of_gc(&mut self) -> bool {
548        let did_defrag = self.defrag.in_defrag();
549        if self.is_defrag_enabled() {
550            self.defrag.reset_in_defrag();
551        }
552        did_defrag
553    }
554
555    /// Generate chunk sweep tasks
556    fn generate_sweep_tasks(&self, unlog_bits_op: UnlogBitsOperation) -> Vec<Box<dyn GCWork<VM>>> {
557        self.defrag.mark_histograms.lock().clear();
558        // # Safety: ImmixSpace reference is always valid within this collection cycle.
559        let space = unsafe { &*(self as *const Self) };
560        let epilogue = Arc::new(FlushPageResource {
561            space,
562            counter: AtomicUsize::new(0),
563        });
564        let tasks = self.chunk_map.generate_tasks(|chunk| {
565            Box::new(SweepChunk {
566                space,
567                chunk,
568                unlog_bits_op,
569                epilogue: epilogue.clone(),
570            })
571        });
572        epilogue.counter.store(tasks.len(), Ordering::SeqCst);
573        tasks
574    }
575
576    /// Release a block.
577    pub fn release_block(&self, block: Block) {
578        block.deinit();
579        self.pr.release_block(block);
580    }
581
582    /// Allocate a clean block.
583    pub fn get_clean_block(
584        &self,
585        tls: VMThread,
586        copy: bool,
587        alloc_options: AllocationOptions,
588    ) -> Option<Block> {
589        let block_address = self.acquire(tls, Block::PAGES, alloc_options);
590        if block_address.is_zero() {
591            return None;
592        }
593        self.defrag.notify_new_clean_block(copy);
594        let block = Block::from_aligned_address(block_address);
595        block.init(copy);
596        self.chunk_map.set_allocated(block.chunk(), true);
597        self.lines_consumed
598            .fetch_add(Block::LINES, Ordering::SeqCst);
599        Some(block)
600    }
601
602    /// Pop a reusable block from the reusable block list.
603    pub fn get_reusable_block(&self, copy: bool) -> Option<Block> {
604        if super::BLOCK_ONLY {
605            return None;
606        }
607        loop {
608            let block = self.reusable_blocks.pop()?;
609
610            // Skip blocks that should be evacuated.
611            if copy && block.is_defrag_source() {
612                continue;
613            }
614
615            // Get available lines. Do this before block.init which will reset block state.
616            let lines_delta = match block.get_state() {
617                BlockState::Reusable { unavailable_lines } => {
618                    Block::LINES - unavailable_lines as usize
619                }
620                BlockState::Unmarked => Block::LINES,
621                _ => unreachable!("{:?} {:?}", block, block.get_state()),
622            };
623            self.lines_consumed.fetch_add(lines_delta, Ordering::SeqCst);
624
625            block.init(copy);
626            return Some(block);
627        }
628    }
629
630    /// Trace and mark objects without evacuation.
631    pub fn trace_object_without_moving(
632        &self,
633        queue: &mut impl ObjectQueue,
634        object: ObjectReference,
635    ) -> ObjectReference {
636        #[cfg(feature = "vo_bit")]
637        vo_bit::helper::on_trace_object::<VM>(object);
638
639        if self.attempt_mark(object, self.mark_state) {
640            // Mark block and lines
641            if !super::BLOCK_ONLY {
642                if !super::MARK_LINE_AT_SCAN_TIME {
643                    self.mark_lines(object);
644                }
645            } else {
646                Block::containing(object).set_state(BlockState::Marked);
647            }
648
649            #[cfg(feature = "vo_bit")]
650            vo_bit::helper::on_object_marked::<VM>(object);
651
652            // Visit node
653            queue.enqueue(object);
654            self.unlog_object_if_needed(object);
655            return object;
656        }
657        object
658    }
659
660    /// Trace object and do evacuation if required.
661    #[allow(clippy::assertions_on_constants)]
662    pub fn trace_object_with_opportunistic_copy(
663        &self,
664        queue: &mut impl ObjectQueue,
665        object: ObjectReference,
666        semantics: CopySemantics,
667        worker: &mut GCWorker<VM>,
668        nursery_collection: bool,
669    ) -> ObjectReference {
670        let copy_context = worker.get_copy_context_mut();
671        debug_assert!(!super::BLOCK_ONLY);
672
673        #[cfg(feature = "vo_bit")]
674        vo_bit::helper::on_trace_object::<VM>(object);
675
676        let forwarding_status = object_forwarding::attempt_to_forward::<VM>(object);
677        if object_forwarding::state_is_forwarded_or_being_forwarded(forwarding_status) {
678            // We lost the forwarding race as some other thread has set the forwarding word; wait
679            // until the object has been forwarded by the winner. Note that the object may not
680            // necessarily get forwarded since Immix opportunistically moves objects.
681            #[allow(clippy::let_and_return)]
682            let new_object =
683                object_forwarding::spin_and_get_forwarded_object::<VM>(object, forwarding_status);
684            #[cfg(debug_assertions)]
685            {
686                if new_object == object {
687                    debug_assert!(
688                        self.is_marked(object) || self.defrag.space_exhausted() || self.is_pinned(object),
689                        "Forwarded object is the same as original object {} even though it should have been copied",
690                        object,
691                    );
692                } else {
693                    // new_object != object
694                    debug_assert!(
695                        !Block::containing(new_object).is_defrag_source(),
696                        "Block {:?} containing forwarded object {} should not be a defragmentation source",
697                        Block::containing(new_object),
698                        new_object,
699                    );
700                }
701            }
702            new_object
703        } else if self.is_marked(object) {
704            // We won the forwarding race but the object is already marked so we clear the
705            // forwarding status and return the unmoved object
706            object_forwarding::clear_forwarding_bits::<VM>(object);
707            object
708        } else {
709            // We won the forwarding race; actually forward and copy the object if it is not pinned
710            // and we have sufficient space in our copy allocator
711            let new_object = if self.is_pinned(object)
712                || (!nursery_collection && self.defrag.space_exhausted())
713            {
714                self.attempt_mark(object, self.mark_state);
715                object_forwarding::clear_forwarding_bits::<VM>(object);
716                Block::containing(object).set_state(BlockState::Marked);
717
718                #[cfg(feature = "vo_bit")]
719                vo_bit::helper::on_object_marked::<VM>(object);
720
721                if !super::MARK_LINE_AT_SCAN_TIME {
722                    self.mark_lines(object);
723                }
724
725                self.unlog_object_if_needed(object);
726
727                object
728            } else {
729                // We are forwarding objects. When the copy allocator allocates the block, it should
730                // mark the block. So we do not need to explicitly mark it here.
731
732                object_forwarding::forward_object::<VM>(
733                    object,
734                    semantics,
735                    copy_context,
736                    |new_object| {
737                        // post_copy should have set the unlog bit
738                        // if `unlog_traced_object` is true.
739                        debug_assert!(
740                            !self.common.unlog_traced_object
741                                || VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
742                                    .is_unlogged::<VM>(new_object, Ordering::Relaxed)
743                        );
744                        #[cfg(feature = "vo_bit")]
745                        vo_bit::helper::on_object_forwarded::<VM>(new_object);
746                    },
747                )
748            };
749            debug_assert_eq!(
750                Block::containing(new_object).get_state(),
751                BlockState::Marked
752            );
753
754            queue.enqueue(new_object);
755            debug_assert!(new_object.is_live());
756            new_object
757        }
758    }
759
760    fn unlog_object_if_needed(&self, object: ObjectReference) {
761        if self.common.unlog_traced_object {
762            // Make sure the side metadata for the line can fit into one byte. For smaller line size, we should
763            // use `mark_as_unlogged` instead to mark the bit.
764            const_assert!(
765                Line::BYTES
766                    >= (1
767                        << (crate::util::constants::LOG_BITS_IN_BYTE
768                            + crate::util::constants::LOG_MIN_OBJECT_SIZE))
769            );
770            const_assert_eq!(
771                crate::vm::object_model::specs::VMGlobalLogBitSpec::LOG_NUM_BITS,
772                0
773            ); // We should put this to the addition, but type casting is not allowed in constant assertions.
774
775            // Every immix line is 256 bytes, which is mapped to 4 bytes in the side metadata.
776            // If we have one object in the line that is mature, we can assume all the objects in the line are mature objects.
777            // So we can just mark the byte.
778            VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
779                .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
780        }
781    }
782
783    /// Mark all the lines that the given object spans.
784    #[allow(clippy::assertions_on_constants)]
785    pub fn mark_lines(&self, object: ObjectReference) {
786        debug_assert!(!super::BLOCK_ONLY);
787        Line::mark_lines_for_object::<VM>(object, self.line_mark_state.load(Ordering::Acquire));
788    }
789
790    /// Atomically mark an object.
791    fn attempt_mark(&self, object: ObjectReference, mark_state: u8) -> bool {
792        loop {
793            let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
794                object,
795                None,
796                Ordering::SeqCst,
797            );
798            if old_value == mark_state {
799                return false;
800            }
801
802            if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC
803                .compare_exchange_metadata::<VM, u8>(
804                    object,
805                    old_value,
806                    mark_state,
807                    None,
808                    Ordering::SeqCst,
809                    Ordering::SeqCst,
810                )
811                .is_ok()
812            {
813                break;
814            }
815        }
816        true
817    }
818
819    /// Check if an object is marked.
820    fn is_marked_with(&self, object: ObjectReference, mark_state: u8) -> bool {
821        let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
822            object,
823            None,
824            Ordering::SeqCst,
825        );
826        old_value == mark_state
827    }
828
829    pub(crate) fn is_marked(&self, object: ObjectReference) -> bool {
830        self.is_marked_with(object, self.mark_state)
831    }
832
833    /// Check if an object is pinned.
834    fn is_pinned(&self, _object: ObjectReference) -> bool {
835        #[cfg(feature = "object_pinning")]
836        return self.is_object_pinned(_object);
837
838        #[cfg(not(feature = "object_pinning"))]
839        false
840    }
841
842    /// Hole searching.
843    ///
844    /// Linearly scan lines in a block to search for the next
845    /// hole, starting from the given line. If we find available lines,
846    /// return a tuple of the start line and the end line (non-inclusive).
847    ///
848    /// Returns None if the search could not find any more holes.
849    #[allow(clippy::assertions_on_constants)]
850    pub fn get_next_available_lines(&self, search_start: Line) -> Option<(Line, Line)> {
851        debug_assert!(!super::BLOCK_ONLY);
852        let unavail_state = self.line_unavail_state.load(Ordering::Acquire);
853        let current_state = self.line_mark_state.load(Ordering::Acquire);
854        let block = search_start.block();
855        let mark_data = block.line_mark_table();
856        let start_cursor = search_start.get_index_within_block();
857        let mut cursor = start_cursor;
858        // Find start
859        while cursor < mark_data.len() {
860            let mark = mark_data.get(cursor);
861            if mark != unavail_state && mark != current_state {
862                break;
863            }
864            cursor += 1;
865        }
866        if cursor == mark_data.len() {
867            return None;
868        }
869        let start = search_start.next_nth(cursor - start_cursor);
870        // Find limit
871        while cursor < mark_data.len() {
872            let mark = mark_data.get(cursor);
873            if mark == unavail_state || mark == current_state {
874                break;
875            }
876            cursor += 1;
877        }
878        let end = search_start.next_nth(cursor - start_cursor);
879        debug_assert!(RegionIterator::<Line>::new(start, end)
880            .all(|line| !line.is_marked(unavail_state) && !line.is_marked(current_state)));
881        Some((start, end))
882    }
883
884    pub fn is_last_gc_exhaustive(&self, did_defrag_for_last_gc: bool) -> bool {
885        if self.is_defrag_enabled() {
886            did_defrag_for_last_gc
887        } else {
888            // If defrag is disabled, every GC is exhaustive.
889            true
890        }
891    }
892
893    pub(crate) fn get_pages_allocated(&self) -> usize {
894        self.lines_consumed.load(Ordering::SeqCst) >> (LOG_BYTES_IN_PAGE - Line::LOG_BYTES as u8)
895    }
896
897    /// Post copy routine for Immix copy contexts
898    fn post_copy(&self, object: ObjectReference, _bytes: usize) {
899        // Mark the object
900        VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.store_atomic::<VM, u8>(
901            object,
902            self.mark_state,
903            None,
904            Ordering::SeqCst,
905        );
906        // Mark the line
907        if !super::MARK_LINE_AT_SCAN_TIME {
908            self.mark_lines(object);
909        }
910        if self.common.unlog_traced_object {
911            VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
912                .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
913        }
914    }
915
916    pub(crate) fn prefer_copy_on_nursery_gc(&self) -> bool {
917        self.is_nursery_copy_enabled()
918    }
919
920    pub(crate) fn is_nursery_copy_enabled(&self) -> bool {
921        !self.space_args.never_move_objects && !cfg!(feature = "sticky_immix_non_moving_nursery")
922    }
923
924    pub(crate) fn is_defrag_enabled(&self) -> bool {
925        !self.space_args.never_move_objects
926    }
927}
928
929/// A work packet to prepare each block for a major GC.
930/// Performs the action on a range of chunks.
931pub struct PrepareBlockState<VM: VMBinding> {
932    #[allow(dead_code)]
933    pub space: &'static ImmixSpace<VM>,
934    pub chunk: Chunk,
935    pub defrag_threshold: Option<usize>,
936    pub unlog_bits_op: UnlogBitsOperation,
937}
938
939impl<VM: VMBinding> PrepareBlockState<VM> {
940    /// Clear object mark table
941    fn reset_object_mark(&self) {
942        // NOTE: We reset the mark bits because cyclic mark bit is currently not supported, yet.
943        // See `ImmixSpace::prepare`.
944        if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC {
945            side.bzero_metadata(self.chunk.start(), Chunk::BYTES);
946        }
947    }
948}
949
950impl<VM: VMBinding> GCWork<VM> for PrepareBlockState<VM> {
951    fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
952        // Clear object mark table for this chunk
953        self.reset_object_mark();
954        // Iterate over all blocks in this chunk
955        for block in self.chunk.iter_region::<Block>() {
956            let state = block.get_state();
957            // Skip unallocated blocks.
958            if state == BlockState::Unallocated {
959                continue;
960            }
961            // Check if this block needs to be defragmented.
962            let is_defrag_source = if !self.space.is_defrag_enabled() {
963                // Do not set any block as defrag source if defrag is disabled.
964                false
965            } else if *mmtk.options.immix_defrag_every_block {
966                // Set every block as defrag source if so desired.
967                true
968            } else if let Some(defrag_threshold) = self.defrag_threshold {
969                // This GC is a defrag GC.
970                block.get_holes() > defrag_threshold
971            } else {
972                // Not a defrag GC.
973                false
974            };
975            block.set_as_defrag_source(is_defrag_source);
976            // Clear block mark data.
977            block.set_state(BlockState::Unmarked);
978            debug_assert!(!block.get_state().is_reusable());
979            debug_assert_ne!(block.get_state(), BlockState::Marked);
980        }
981
982        self.unlog_bits_op
983            .execute::<VM>(self.chunk.start(), Chunk::BYTES);
984    }
985}
986
987/// Chunk sweeping work packet.
988struct SweepChunk<VM: VMBinding> {
989    space: &'static ImmixSpace<VM>,
990    chunk: Chunk,
991    unlog_bits_op: UnlogBitsOperation,
992    /// A destructor invoked when all `SweepChunk` packets are finished.
993    epilogue: Arc<FlushPageResource<VM>>,
994}
995
996impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
997    fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
998        assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
999
1000        let mut histogram = self.space.defrag.new_histogram();
1001        let line_mark_state = if super::BLOCK_ONLY {
1002            None
1003        } else {
1004            Some(self.space.line_mark_state.load(Ordering::Acquire))
1005        };
1006        // Hints for clearing side forwarding bits.
1007        let is_moving_gc = mmtk.get_plan().current_gc_may_move_object();
1008        let is_defrag_gc = self.space.defrag.in_defrag();
1009
1010        // number of swept (completely free) blocks.
1011        let mut swept_blocks = 0;
1012        // number of reused blocks.
1013        let mut reused_blocks = 0;
1014        // number of non-free blocks that cannot be reused (e.g. full, or non-empty when block-only).
1015        let mut unreused_blocks = 0;
1016
1017        // Iterate over all allocated blocks in this chunk.
1018        for block in self
1019            .chunk
1020            .iter_region::<Block>()
1021            .filter(|block| block.get_state() != BlockState::Unallocated)
1022        {
1023            // Clear side forwarding bits.
1024            // In the beginning of the next GC, no side forwarding bits shall be set.
1025            // In this way, we can omit clearing forwarding bits when copying object.
1026            // See `GCWorkerCopyContext::post_copy`.
1027            // Note, `block.sweep()` overwrites `DEFRAG_STATE_TABLE` with the number of holes,
1028            // but we need it to know if a block is a defrag source.
1029            // We clear forwarding bits before `block.sweep()`.
1030            if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC {
1031                if is_moving_gc {
1032                    let objects_may_move = if is_defrag_gc {
1033                        // If it is a defrag GC, we only clear forwarding bits for defrag sources.
1034                        block.is_defrag_source()
1035                    } else {
1036                        // Otherwise, it must be a nursery GC of StickyImmix with copying nursery.
1037                        // We don't have information about which block contains moved objects,
1038                        // so we have to clear forwarding bits for all blocks.
1039                        true
1040                    };
1041                    if objects_may_move {
1042                        side.bzero_metadata(block.start(), Block::BYTES);
1043                    }
1044                }
1045            }
1046
1047            match block.sweep(self.space, &mut histogram, line_mark_state) {
1048                BlockSweepResult::Swept => swept_blocks += 1,
1049                BlockSweepResult::Reused => reused_blocks += 1,
1050                BlockSweepResult::NoReuse => unreused_blocks += 1,
1051            }
1052        }
1053
1054        probe!(
1055            mmtk,
1056            sweep_chunk_immix,
1057            swept_blocks,
1058            reused_blocks,
1059            unreused_blocks
1060        );
1061
1062        // number of allocated blocks.
1063        let allocated_blocks = reused_blocks + unreused_blocks;
1064
1065        // Set this chunk as free if there is not live blocks.
1066        if allocated_blocks == 0 {
1067            self.space.chunk_map.set_allocated(self.chunk, false)
1068        }
1069        self.space.defrag.add_completed_mark_histogram(histogram);
1070
1071        self.unlog_bits_op
1072            .execute::<VM>(self.chunk.start(), Chunk::BYTES);
1073
1074        self.epilogue.finish_one_work_packet();
1075    }
1076}
1077
1078/// Count number of remaining work pacets, and flush page resource if all packets are finished.
1079struct FlushPageResource<VM: VMBinding> {
1080    space: &'static ImmixSpace<VM>,
1081    counter: AtomicUsize,
1082}
1083
1084impl<VM: VMBinding> FlushPageResource<VM> {
1085    /// Called after a related work packet is finished.
1086    fn finish_one_work_packet(&self) {
1087        if 1 == self.counter.fetch_sub(1, Ordering::SeqCst) {
1088            // We've finished releasing all the dead blocks to the BlockPageResource's thread-local queues.
1089            // Now flush the BlockPageResource.
1090            self.space.flush_page_resource()
1091        }
1092    }
1093}
1094
1095impl<VM: VMBinding> Drop for FlushPageResource<VM> {
1096    fn drop(&mut self) {
1097        epilogue::debug_assert_counter_zero(&self.counter, "FlushPageResource::counter");
1098    }
1099}
1100
1101use crate::policy::copy_context::PolicyCopyContext;
1102use crate::util::alloc::Allocator;
1103use crate::util::alloc::ImmixAllocator;
1104
1105/// Normal immix copy context. It has one copying Immix allocator.
1106/// Most immix plans use this copy context.
1107pub struct ImmixCopyContext<VM: VMBinding> {
1108    allocator: ImmixAllocator<VM>,
1109}
1110
1111impl<VM: VMBinding> PolicyCopyContext for ImmixCopyContext<VM> {
1112    type VM = VM;
1113
1114    fn prepare(&mut self) {
1115        self.allocator.reset();
1116    }
1117    fn release(&mut self) {
1118        self.allocator.reset();
1119    }
1120    fn alloc_copy(
1121        &mut self,
1122        _original: ObjectReference,
1123        bytes: usize,
1124        align: usize,
1125        offset: usize,
1126    ) -> Address {
1127        self.allocator.alloc(bytes, align, offset)
1128    }
1129    fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1130        self.get_space().post_copy(obj, bytes)
1131    }
1132}
1133
1134impl<VM: VMBinding> ImmixCopyContext<VM> {
1135    pub(crate) fn new(
1136        tls: VMWorkerThread,
1137        context: Arc<AllocatorContext<VM>>,
1138        space: &'static ImmixSpace<VM>,
1139    ) -> Self {
1140        ImmixCopyContext {
1141            allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1142        }
1143    }
1144
1145    fn get_space(&self) -> &ImmixSpace<VM> {
1146        self.allocator.immix_space()
1147    }
1148}
1149
1150/// Hybrid Immix copy context. It includes two different immix allocators. One with `copy = true`
1151/// is used for defrag GCs, and the other is used for other purposes (such as promoting objects from
1152/// nursery to Immix mature space). This is used by generational immix.
1153pub struct ImmixHybridCopyContext<VM: VMBinding> {
1154    copy_allocator: ImmixAllocator<VM>,
1155    defrag_allocator: ImmixAllocator<VM>,
1156}
1157
1158impl<VM: VMBinding> PolicyCopyContext for ImmixHybridCopyContext<VM> {
1159    type VM = VM;
1160
1161    fn prepare(&mut self) {
1162        self.copy_allocator.reset();
1163        self.defrag_allocator.reset();
1164    }
1165    fn release(&mut self) {
1166        self.copy_allocator.reset();
1167        self.defrag_allocator.reset();
1168    }
1169    fn alloc_copy(
1170        &mut self,
1171        _original: ObjectReference,
1172        bytes: usize,
1173        align: usize,
1174        offset: usize,
1175    ) -> Address {
1176        if self.get_space().in_defrag() {
1177            self.defrag_allocator.alloc(bytes, align, offset)
1178        } else {
1179            self.copy_allocator.alloc(bytes, align, offset)
1180        }
1181    }
1182    fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1183        self.get_space().post_copy(obj, bytes)
1184    }
1185}
1186
1187impl<VM: VMBinding> ImmixHybridCopyContext<VM> {
1188    pub(crate) fn new(
1189        tls: VMWorkerThread,
1190        context: Arc<AllocatorContext<VM>>,
1191        space: &'static ImmixSpace<VM>,
1192    ) -> Self {
1193        ImmixHybridCopyContext {
1194            copy_allocator: ImmixAllocator::new(tls.0, Some(space), context.clone(), false),
1195            defrag_allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1196        }
1197    }
1198
1199    fn get_space(&self) -> &ImmixSpace<VM> {
1200        // Both copy allocators should point to the same space.
1201        debug_assert_eq!(
1202            self.defrag_allocator.immix_space().common().descriptor,
1203            self.copy_allocator.immix_space().common().descriptor
1204        );
1205        // Just get the space from either allocator
1206        self.defrag_allocator.immix_space()
1207    }
1208}
1209
1210#[cfg(feature = "vo_bit")]
1211#[derive(Clone, Copy)]
1212enum VOBitsClearingScope {
1213    /// Clear all VO bits in all blocks.
1214    FullGC,
1215    /// Clear unmarked blocks, only.
1216    BlockOnly,
1217    /// Clear unmarked lines, only.  (i.e. lines with line mark state **not** equal to `state`).
1218    Line { state: u8 },
1219}
1220
1221/// A work packet to clear VO bit metadata after Prepare.
1222#[cfg(feature = "vo_bit")]
1223struct ClearVOBitsAfterPrepare {
1224    chunk: Chunk,
1225    scope: VOBitsClearingScope,
1226}
1227
1228#[cfg(feature = "vo_bit")]
1229impl<VM: VMBinding> GCWork<VM> for ClearVOBitsAfterPrepare {
1230    fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
1231        match self.scope {
1232            VOBitsClearingScope::FullGC => {
1233                vo_bit::bzero_vo_bit(self.chunk.start(), Chunk::BYTES);
1234            }
1235            VOBitsClearingScope::BlockOnly => {
1236                self.clear_blocks(None);
1237            }
1238            VOBitsClearingScope::Line { state } => {
1239                self.clear_blocks(Some(state));
1240            }
1241        }
1242    }
1243}
1244
1245#[cfg(feature = "vo_bit")]
1246impl ClearVOBitsAfterPrepare {
1247    fn clear_blocks(&mut self, line_mark_state: Option<u8>) {
1248        for block in self
1249            .chunk
1250            .iter_region::<Block>()
1251            .filter(|block| block.get_state() != BlockState::Unallocated)
1252        {
1253            block.clear_vo_bits_for_unmarked_regions(line_mark_state);
1254        }
1255    }
1256}