mmtk/policy/immix/
block.rs

1use super::defrag::Histogram;
2use super::line::{Line, RCArray};
3use super::ImmixSpace;
4use crate::util::constants::*;
5use crate::util::heap::blockpageresource::BlockPool;
6use crate::util::heap::chunk_map::Chunk;
7use crate::util::linear_scan::{Region, RegionIterator, UnstraddlableRegion};
8use crate::util::metadata::side_metadata::*;
9#[cfg(feature = "vo_bit")]
10use crate::util::metadata::vo_bit;
11#[cfg(feature = "object_pinning")]
12use crate::util::metadata::MetadataSpec;
13use crate::util::object_enum::BlockMayHaveObjects;
14use crate::util::{Address, ObjectReference};
15use crate::vm::*;
16use bytemuck::NoUninit;
17use std::sync::atomic::Ordering;
18
19/// The block allocation state.
20#[derive(Debug, PartialEq, Clone, Copy)]
21pub enum BlockState {
22    /// the block is not allocated.
23    Unallocated,
24    /// the block is a young block.
25    Nursery,
26    /// the block is allocated but not marked.
27    Unmarked,
28    /// the block is allocated and marked.
29    Marked,
30    /// RC mutator recycled blocks.
31    Reusing,
32    /// the block is marked as reusable.
33    Reusable { unavailable_lines: u8 },
34}
35
36impl BlockState {
37    /// Private constant
38    const MARK_UNALLOCATED: u8 = 0;
39    /// Private constant
40    const MARK_UNMARKED: u8 = u8::MAX;
41    /// Private constant
42    const MARK_MARKED: u8 = u8::MAX - 1;
43    const MARK_NURSERY: u8 = u8::MAX - 2;
44    const MARK_REUSING: u8 = u8::MAX - 3;
45}
46
47impl From<u8> for BlockState {
48    fn from(state: u8) -> Self {
49        match state {
50            Self::MARK_UNALLOCATED => BlockState::Unallocated,
51            Self::MARK_UNMARKED => BlockState::Unmarked,
52            Self::MARK_MARKED => BlockState::Marked,
53            Self::MARK_NURSERY => BlockState::Nursery,
54            Self::MARK_REUSING => BlockState::Reusing,
55            unavailable_lines => BlockState::Reusable { unavailable_lines },
56        }
57    }
58}
59
60impl From<BlockState> for u8 {
61    fn from(state: BlockState) -> Self {
62        match state {
63            BlockState::Unallocated => BlockState::MARK_UNALLOCATED,
64            BlockState::Unmarked => BlockState::MARK_UNMARKED,
65            BlockState::Marked => BlockState::MARK_MARKED,
66            BlockState::Nursery => BlockState::MARK_NURSERY,
67            BlockState::Reusing => BlockState::MARK_REUSING,
68            BlockState::Reusable { unavailable_lines } => {
69                assert_ne!(unavailable_lines, 0);
70                u8::min(unavailable_lines, u8::MAX - 4)
71            }
72        }
73    }
74}
75
76impl BlockState {
77    /// Test if the block is reuasable.
78    pub const fn is_reusable(&self) -> bool {
79        matches!(self, BlockState::Reusable { .. })
80    }
81}
82
83/// Data structure to reference an immix block.
84#[repr(transparent)]
85#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, NoUninit)]
86pub struct Block(Address);
87
88impl Region for Block {
89    #[cfg(not(feature = "immix_smaller_block"))]
90    const LOG_BYTES: usize = 15;
91    // 8K, or one page if the page is larger. A block cannot be smaller than a page.
92    #[cfg(feature = "immix_smaller_block")]
93    const LOG_BYTES: usize = if 13 > LOG_BYTES_IN_PAGE as usize {
94        13
95    } else {
96        LOG_BYTES_IN_PAGE as usize
97    };
98
99    fn from_aligned_address(address: Address) -> Self {
100        debug_assert!(address.is_aligned_to(Self::BYTES));
101        Self(address)
102    }
103
104    fn start(&self) -> Address {
105        self.0
106    }
107}
108
109/// An objects cannot straddle multiple Immix blocks.
110impl UnstraddlableRegion for Block {}
111
112impl BlockMayHaveObjects for Block {
113    fn may_have_objects(&self) -> bool {
114        self.get_state() != BlockState::Unallocated
115    }
116}
117
118impl Block {
119    /// Log pages in block
120    pub const LOG_PAGES: usize = Self::LOG_BYTES - LOG_BYTES_IN_PAGE as usize;
121    /// Pages in block
122    pub const PAGES: usize = 1 << Self::LOG_PAGES;
123    /// Log lines in block
124    pub const LOG_LINES: usize = Self::LOG_BYTES - Line::LOG_BYTES;
125    /// Lines in block
126    pub const LINES: usize = 1 << Self::LOG_LINES;
127
128    /// Block defrag state table (side)
129    pub const DEFRAG_STATE_TABLE: SideMetadataSpec =
130        crate::util::metadata::side_metadata::spec_defs::IX_BLOCK_DEFRAG;
131
132    /// Block mark table (side)
133    pub const MARK_TABLE: SideMetadataSpec =
134        crate::util::metadata::side_metadata::spec_defs::IX_BLOCK_MARK;
135    pub const LOG_TABLE: SideMetadataSpec =
136        crate::util::metadata::side_metadata::spec_defs::IX_BLOCK_LOG;
137    pub const NURSERY_PROMOTION_STATE_TABLE: SideMetadataSpec =
138        crate::util::metadata::side_metadata::spec_defs::NURSERY_PROMOTION_STATE;
139
140    pub fn calc_dead_lines(&self) -> usize {
141        let mut dead_lines = 0;
142        let rc_array = RCArray::of(*self);
143        for i in 0..Self::LINES {
144            if rc_array.is_dead(i) {
145                dead_lines += 1;
146            }
147        }
148        dead_lines
149    }
150
151    pub const ZERO: Self = Self(Address::ZERO);
152
153    #[allow(unused)]
154    pub fn is_zero(&self) -> bool {
155        self.0.is_zero()
156    }
157
158    /// Get the chunk containing the block.
159    pub fn chunk(&self) -> Chunk {
160        Chunk::from_unaligned_address(self.0)
161    }
162
163    /// Get the address range of the block's line mark table.
164    #[allow(clippy::assertions_on_constants)]
165    pub fn line_mark_table(&self) -> MetadataByteArrayRef<{ Block::LINES }> {
166        debug_assert!(!super::BLOCK_ONLY);
167        MetadataByteArrayRef::<{ Block::LINES }>::new(&Line::MARK_TABLE, self.start(), Self::BYTES)
168    }
169
170    /// Get block mark state.
171    pub fn get_state(&self) -> BlockState {
172        let byte = Self::MARK_TABLE.load_atomic::<u8>(self.start(), Ordering::SeqCst);
173        byte.into()
174    }
175
176    /// Set block mark state.
177    pub fn set_state(&self, state: BlockState) {
178        let state = u8::from(state);
179        Self::MARK_TABLE.store_atomic::<u8>(self.start(), state, Ordering::SeqCst);
180    }
181
182    /// Set block mark state.
183    pub fn fetch_update_state(
184        &self,
185        mut f: impl FnMut(BlockState) -> Option<BlockState>,
186    ) -> Result<BlockState, BlockState> {
187        Self::MARK_TABLE
188            .fetch_update_atomic::<u8, _>(self.start(), Ordering::SeqCst, Ordering::SeqCst, |s| {
189                f(s.into()).map(u8::from)
190            })
191            .map(|x| x.into())
192            .map_err(|x| x.into())
193    }
194
195    pub fn attempt_dealloc(&self, ignore_reusing_blocks: bool) -> bool {
196        self.fetch_update_state(|s| {
197            if (ignore_reusing_blocks && s == BlockState::Reusing) || s == BlockState::Unallocated {
198                None
199            } else {
200                Some(BlockState::Unallocated)
201            }
202        })
203        .is_ok()
204    }
205
206    // Defrag byte
207
208    const DEFRAG_SOURCE_STATE: u8 = u8::MAX;
209
210    /// Test if the block is marked for defragmentation.
211    pub fn is_defrag_source(&self) -> bool {
212        let byte = Self::DEFRAG_STATE_TABLE.load_atomic::<u8>(self.start(), Ordering::SeqCst);
213        // The byte should be 0 (not defrag source) or 255 (defrag source) if this is a major defrag GC, as we set the values in PrepareBlockState.
214        // But it could be any value in a nursery GC.
215        byte == Self::DEFRAG_SOURCE_STATE
216    }
217
218    pub fn in_defrag_block(o: ObjectReference) -> bool {
219        Block::containing(o).is_defrag_source()
220    }
221
222    pub fn address_in_defrag_block(a: Address) -> bool {
223        Block::from_unaligned_address(a).is_defrag_source()
224    }
225
226    /// Mark the block for defragmentation.
227    pub fn set_as_defrag_source(&self, defrag: bool) {
228        let byte = if defrag { Self::DEFRAG_SOURCE_STATE } else { 0 };
229        Self::DEFRAG_STATE_TABLE.store_atomic::<u8>(self.start(), byte, Ordering::SeqCst);
230    }
231
232    /// Record the number of holes in the block.
233    pub fn set_holes(&self, holes: usize) {
234        Self::DEFRAG_STATE_TABLE.store_atomic::<u8>(self.start(), holes as u8, Ordering::SeqCst);
235    }
236
237    /// Get the number of holes.
238    pub fn get_holes(&self) -> usize {
239        let byte = Self::DEFRAG_STATE_TABLE.load_atomic::<u8>(self.start(), Ordering::SeqCst);
240        debug_assert_ne!(byte, Self::DEFRAG_SOURCE_STATE);
241        byte as usize
242    }
243
244    /// Initialize a clean block after acquired from page-resource.
245    pub fn init<VM: VMBinding>(&self, copy: bool, reuse: bool, space: &ImmixSpace<VM>) {
246        if space.rc_enabled {
247            if !reuse {
248                debug_assert_eq!(self.get_state(), BlockState::Unallocated);
249            }
250            self.clear_in_place_promoted();
251            if !copy && reuse {
252                self.set_state(BlockState::Reusing);
253                debug_assert!(!self.is_defrag_source());
254            } else if copy {
255                if reuse {
256                    debug_assert!(!self.is_defrag_source());
257                }
258                self.set_state(BlockState::Unmarked);
259                self.set_as_defrag_source(false);
260            } else {
261                self.set_state(BlockState::Nursery);
262                self.set_as_defrag_source(false);
263            }
264        } else {
265            self.set_state(if copy {
266                BlockState::Marked
267            } else {
268                BlockState::Unmarked
269            });
270            if !reuse {
271                Self::DEFRAG_STATE_TABLE.store_atomic::<u8>(self.start(), 0, Ordering::SeqCst);
272            }
273        }
274    }
275
276    /// Deinitalize a block before releasing.
277    pub fn deinit<VM: VMBinding>(&self, space: &ImmixSpace<VM>) {
278        self.set_state(BlockState::Unallocated);
279        if space.rc_enabled {
280            self.set_as_defrag_source(false);
281        }
282    }
283
284    pub fn start_line(&self) -> Line {
285        Line::from_aligned_address(self.start())
286    }
287
288    pub fn end_line(&self) -> Line {
289        Line::from_aligned_address(self.end())
290    }
291
292    /// Get the range of lines within the block.
293    #[allow(clippy::assertions_on_constants)]
294    pub fn lines(&self) -> RegionIterator<Line> {
295        debug_assert!(!super::BLOCK_ONLY);
296        RegionIterator::<Line>::new(self.start_line(), self.end_line())
297    }
298
299    pub fn clear_rc_table(&self) {
300        crate::util::rc::RC_TABLE.bzero_metadata(self.start(), Block::BYTES);
301    }
302
303    pub fn clear_striddle_table(&self) {
304        crate::util::rc::RC_STRADDLE_LINES.bzero_metadata(self.start(), Block::BYTES);
305    }
306
307    #[allow(unused)]
308    pub(crate) fn clear_mark_table<VM: VMBinding>(&self) {
309        VM::VMObjectModel::LOCAL_MARK_BIT_SPEC
310            .extract_side_spec()
311            .bzero_metadata(self.start(), Self::BYTES);
312    }
313
314    pub(crate) fn initialize_mark_table_as_marked<VM: VMBinding>(&self) {
315        let meta = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.extract_side_spec();
316        let start: *mut u8 = address_to_meta_address(meta, self.start()).to_mut_ptr();
317        let limit: *mut u8 = address_to_meta_address(meta, self.end()).to_mut_ptr();
318        unsafe {
319            let bytes = limit.offset_from(start) as usize;
320            std::ptr::write_bytes(start, 0xffu8, bytes);
321        }
322    }
323
324    pub fn log(&self) -> bool {
325        loop {
326            let old_value: u8 = Self::LOG_TABLE.load_atomic(self.start(), Ordering::Relaxed);
327            if old_value == 1 {
328                return false;
329            }
330            if Self::LOG_TABLE
331                .compare_exchange_atomic(self.start(), 0u8, 1u8, Ordering::SeqCst, Ordering::SeqCst)
332                .is_ok()
333            {
334                return true;
335            }
336        }
337    }
338
339    pub fn set_as_in_place_promoted(&self) {
340        if self.is_in_place_promoted() {
341            return;
342        }
343        unsafe { Self::NURSERY_PROMOTION_STATE_TABLE.store(self.start(), 1u8) };
344    }
345
346    pub fn is_in_place_promoted(&self) -> bool {
347        Self::NURSERY_PROMOTION_STATE_TABLE.load_atomic::<u8>(self.start(), Ordering::Relaxed) != 0
348    }
349
350    pub fn clear_in_place_promoted(&self) {
351        unsafe { Self::NURSERY_PROMOTION_STATE_TABLE.store(self.start(), 0u8) };
352    }
353
354    pub fn unlog(&self) {
355        Self::LOG_TABLE.store_atomic(self.start(), 0u8, Ordering::Relaxed);
356    }
357
358    pub fn clear_field_unlog_table<VM: VMBinding>(&self) {
359        VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
360            .as_spec()
361            .extract_side_spec()
362            .bzero_metadata(self.start(), Block::BYTES);
363    }
364
365    pub fn initialize_field_unlog_table_as_unlogged<VM: VMBinding>(&self) {
366        let meta = *VM::VMObjectModel::GLOBAL_FIELD_UNLOG_BIT_SPEC
367            .as_spec()
368            .extract_side_spec();
369        let start: *mut u8 = address_to_meta_address(&meta, self.start()).to_mut_ptr();
370        let limit: *mut u8 = address_to_meta_address(&meta, self.end()).to_mut_ptr();
371        unsafe {
372            let bytes = limit.offset_from(start) as usize;
373            std::ptr::write_bytes(start, 0xffu8, bytes);
374        }
375    }
376
377    #[allow(clippy::assertions_on_constants)]
378    pub fn rc_dead(&self) -> bool {
379        type UInt = u128;
380        const LOG_BITS_IN_UINT: usize =
381            (std::mem::size_of::<UInt>() << 3).trailing_zeros() as usize;
382        debug_assert!(
383            Self::LOG_BYTES - crate::util::rc::LOG_MIN_OBJECT_SIZE
384                + crate::util::rc::LOG_REF_COUNT_BITS
385                >= LOG_BITS_IN_UINT
386        );
387        let start =
388            address_to_meta_address(&crate::util::rc::RC_TABLE, self.start()).to_ptr::<UInt>();
389        let limit =
390            address_to_meta_address(&crate::util::rc::RC_TABLE, self.end()).to_ptr::<UInt>();
391        let rc_table = unsafe { std::slice::from_raw_parts(start, limit.offset_from(start) as _) };
392        for x in rc_table {
393            if *x != 0 {
394                return false;
395            }
396        }
397        true
398    }
399
400    /// Sweep this block.
401    pub fn sweep<VM: VMBinding>(
402        &self,
403        space: &ImmixSpace<VM>,
404        mark_histogram: &mut Histogram,
405        line_mark_state: Option<u8>,
406    ) -> BlockSweepResult {
407        // This method is not called when using RC.
408        assert!(!space.rc_enabled);
409
410        self.set_as_defrag_source(false);
411        if super::BLOCK_ONLY {
412            match self.get_state() {
413                BlockState::Unallocated => unreachable!("Must not sweep unallocated block."),
414                BlockState::Unmarked => {
415                    #[cfg(feature = "vo_bit")]
416                    vo_bit::helper::on_region_swept::<VM, _>(self, false);
417
418                    // If the pin bit is not on the side, we cannot bulk zero.
419                    // We shouldn't need to clear it here in that case, since the pin bit
420                    // should be overwritten at each object allocation. The same applies below
421                    // when we are sweeping on a line granularity.
422                    #[cfg(feature = "object_pinning")]
423                    if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC {
424                        side.bzero_metadata(self.start(), Block::BYTES);
425                    }
426
427                    // Release the block if it is allocated but not marked by the current GC.
428                    space.release_block(*self, false);
429                    BlockSweepResult::Swept
430                }
431                BlockState::Marked => {
432                    #[cfg(feature = "vo_bit")]
433                    vo_bit::helper::on_region_swept::<VM, _>(self, true);
434
435                    // The block is live.
436                    BlockSweepResult::NoReuse
437                }
438                _ => unreachable!(),
439            }
440        } else {
441            // Calculate number of marked lines and holes.
442            let mut marked_lines = 0;
443            let mut holes = 0;
444            let mut prev_line_is_marked = true;
445            let line_mark_state = line_mark_state.unwrap();
446
447            for line in self.lines() {
448                if line.is_marked(line_mark_state) {
449                    marked_lines += 1;
450                    prev_line_is_marked = true;
451                } else {
452                    if prev_line_is_marked {
453                        holes += 1;
454                    }
455                    // We need to clear the line mark state at least twice in every 128 GC
456                    // otherwise, the line mark state of the last GC will stick around
457                    if line_mark_state > Line::MAX_MARK_STATE - 2 {
458                        line.mark(0);
459                    }
460                    #[cfg(feature = "immix_zero_on_release")]
461                    crate::util::memory::zero(line.start(), Line::BYTES);
462
463                    // We need to clear the pin bit if it is on the side, as this line can be reused
464                    #[cfg(feature = "object_pinning")]
465                    if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC {
466                        side.bzero_metadata(line.start(), Line::BYTES);
467                    }
468
469                    prev_line_is_marked = false;
470                }
471            }
472
473            if marked_lines == 0 {
474                #[cfg(feature = "vo_bit")]
475                vo_bit::helper::on_region_swept::<VM, _>(self, false);
476
477                // Release the block if non of its lines are marked.
478                space.release_block(*self, false);
479                BlockSweepResult::Swept
480            } else {
481                // There are some marked lines. Keep the block live.
482                let is_reusable = marked_lines != Block::LINES;
483                if is_reusable {
484                    // There are holes. Mark the block as reusable.
485                    self.set_state(BlockState::Reusable {
486                        unavailable_lines: usize::min(marked_lines, u8::MAX as usize) as _,
487                    });
488                    space.reusable_blocks.push(*self)
489                } else {
490                    // Clear mark state.
491                    self.set_state(BlockState::Unmarked);
492                }
493                // Update mark_histogram
494                mark_histogram[holes] += marked_lines;
495                // Record number of holes in block side metadata.
496                self.set_holes(holes);
497
498                #[cfg(feature = "vo_bit")]
499                vo_bit::helper::on_region_swept::<VM, _>(self, true);
500
501                if is_reusable {
502                    BlockSweepResult::Reused
503                } else {
504                    BlockSweepResult::NoReuse
505                }
506            }
507        }
508    }
509
510    pub fn rc_sweep_nursery<VM: VMBinding>(&self, space: &ImmixSpace<VM>) -> bool {
511        let is_in_place_promoted = self.is_in_place_promoted();
512        self.clear_in_place_promoted();
513        if is_in_place_promoted {
514            self.set_state(BlockState::Reusable {
515                unavailable_lines: 1 as _,
516            });
517
518            // Bulk clear the VO bits of reusable (unmarked) lines.
519            // Lines that are not marked may contain nursery objects that have never received any inc,
520            // and their VO bits need to be cleared before the lines can be reused.
521            #[cfg(feature = "vo_bit")]
522            {
523                let rc_array = RCArray::of(*self);
524
525                for (i, line) in self.lines().enumerate() {
526                    if rc_array.is_dead(i) {
527                        crate::util::metadata::vo_bit::bzero_vo_bit(line.start(), Line::BYTES);
528                    }
529                }
530            }
531
532            space.reusable_blocks.push(*self);
533            false
534        } else {
535            debug_assert!(self.rc_dead(), "{:?} has non-zero rc value", self);
536            debug_assert_ne!(self.get_state(), super::block::BlockState::Unallocated);
537
538            // Bulk clear the VO bits of the entire block.
539            // This block may contain nursery objects that have never received any inc,
540            // and their VO bits need to be cleared before the block can be reused.
541            #[cfg(feature = "vo_bit")]
542            crate::util::metadata::vo_bit::bzero_vo_bit(self.start(), Self::BYTES);
543
544            space.release_block(*self, false);
545            true
546        }
547    }
548
549    pub fn attempt_mutator_reuse(&self) -> bool {
550        self.fetch_update_state(|s| {
551            if s.is_reusable() {
552                Some(BlockState::Reusing)
553            } else {
554                None
555            }
556        })
557        .is_ok()
558    }
559
560    pub fn rc_sweep_mature<VM: VMBinding>(&self, space: &ImmixSpace<VM>, defrag: bool) -> bool {
561        if self.get_state() == BlockState::Unallocated || self.get_state() == BlockState::Nursery {
562            return false;
563        }
564        if defrag || self.rc_dead() {
565            if self.attempt_dealloc(true) {
566                // Bulk clear the VO bits of the entire block.
567                // Dec operations may reduce some object's RC to 0,
568                // at which time their VO bits are cleared, too.
569                // But some lines may also contain objects that have never received any inc,
570                // and their VO bits need to be cleared before the block can be reused.
571                #[cfg(feature = "vo_bit")]
572                crate::util::metadata::vo_bit::bzero_vo_bit(self.start(), Self::BYTES);
573
574                space.release_block(*self, true);
575                return true;
576            }
577        } else if !super::BLOCK_ONLY {
578            // See the caller of this function.
579            // At least one object is dead in the block.
580            let add_as_reusable = {
581                let has_holes = self.has_holes();
582                self.fetch_update_state(|s| {
583                    if s == BlockState::Reusing
584                        || s == BlockState::Unallocated
585                        || s.is_reusable()
586                        || !has_holes
587                    {
588                        None
589                    } else {
590                        Some(BlockState::Reusable {
591                            unavailable_lines: 1 as _,
592                        })
593                    }
594                })
595                .is_ok()
596            };
597            if add_as_reusable {
598                // Bulk clear the VO bits of reusable (unmarked) lines.
599                // Dec operations may reduce some object's RC to 0,
600                // at which time their VO bits are cleared, too.
601                // But some lines may also contain objects that have never received any inc,
602                // and their VO bits need to be cleared before the block can be reused.
603                #[cfg(feature = "vo_bit")]
604                {
605                    let rc_array = RCArray::of(*self);
606
607                    for (i, line) in self.lines().enumerate() {
608                        if rc_array.is_dead(i) {
609                            crate::util::metadata::vo_bit::bzero_vo_bit(line.start(), Line::BYTES);
610                        }
611                    }
612                }
613                space.reusable_blocks.push(*self);
614            }
615        }
616        false
617    }
618
619    pub fn rc_table_start(&self) -> Address {
620        address_to_meta_address(&crate::util::rc::RC_TABLE, self.start())
621    }
622
623    pub fn has_holes(&self) -> bool {
624        let rc_array = RCArray::of(*self);
625        let mut found_free_line = false;
626        let mut free_lines = 0;
627        for i in 0..Self::LINES {
628            if rc_array.is_dead(i) {
629                if i == 0 || found_free_line {
630                    free_lines += 1
631                } else if !found_free_line {
632                    found_free_line = true;
633                }
634                if free_lines > 0 {
635                    return true;
636                }
637            } else {
638                free_lines = 0;
639                found_free_line = false;
640            }
641        }
642        false
643    }
644
645    /// Clear VO bits metadata for unmarked regions.
646    /// This is useful for clearing VO bits during nursery GC for StickyImmix
647    /// at which time young objects (allocated in unmarked regions) may die
648    /// but we always consider old objects (in marked regions) as live.
649    #[cfg(feature = "vo_bit")]
650    pub fn clear_vo_bits_for_unmarked_regions(&self, line_mark_state: Option<u8>) {
651        match line_mark_state {
652            None => {
653                match self.get_state() {
654                    BlockState::Unmarked => {
655                        // It may contain young objects.  Clear it.
656                        vo_bit::bzero_vo_bit(self.start(), Self::BYTES);
657                    }
658                    BlockState::Marked => {
659                        // It contains old objects.  Skip it.
660                    }
661                    _ => unreachable!(),
662                }
663            }
664            Some(state) => {
665                // With lines.
666                for line in self.lines() {
667                    if !line.is_marked(state) {
668                        // It may contain young objects.  Clear it.
669                        vo_bit::bzero_vo_bit(line.start(), Line::BYTES);
670                    }
671                }
672            }
673        }
674    }
675}
676
677/// A non-block single-linked list to store blocks.
678pub struct ReusableBlockPool {
679    queue: BlockPool<Block>,
680    num_workers: usize,
681}
682
683impl ReusableBlockPool {
684    /// Create empty block list
685    pub fn new(num_workers: usize) -> Self {
686        Self {
687            queue: BlockPool::new(num_workers),
688            num_workers,
689        }
690    }
691
692    /// Get number of blocks in this list.
693    pub fn len(&self) -> usize {
694        self.queue.len()
695    }
696
697    /// Add a block to the list.
698    pub fn push(&self, block: Block) {
699        self.queue.push(block)
700    }
701
702    /// Pop a block out of the list.
703    pub fn pop(&self) -> Option<Block> {
704        self.queue.pop()
705    }
706
707    /// Clear the list.
708    pub fn reset(&mut self) {
709        self.queue = BlockPool::new(self.num_workers);
710    }
711
712    /// Iterate all the blocks in the queue. Call the visitor for each reported block.
713    pub fn iterate_blocks(&self, mut f: impl FnMut(Block)) {
714        self.queue.iterate_blocks(&mut f);
715    }
716
717    /// Flush the block queue
718    pub fn flush_all(&self) {
719        self.queue.flush_all();
720    }
721}
722
723/// The result of sweeping a block.  Mainly used for statistics.
724pub enum BlockSweepResult {
725    /// The block is completely free.
726    Swept,
727    /// The block is partially free, and is reused.
728    Reused,
729    /// The block cannot be reused.  When [`super::BLOCK_ONLY`] is true, it is returned whenever a
730    /// block is not completely free.  Otherwise it is returned when a block is full.
731    NoReuse,
732}