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