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#[derive(Debug, PartialEq, Clone, Copy)]
21pub enum BlockState {
22 Unallocated,
24 Nursery,
26 Unmarked,
28 Marked,
30 Reusing,
32 Reusable { unavailable_lines: u8 },
34}
35
36impl BlockState {
37 const MARK_UNALLOCATED: u8 = 0;
39 const MARK_UNMARKED: u8 = u8::MAX;
41 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 pub const fn is_reusable(&self) -> bool {
79 matches!(self, BlockState::Reusable { .. })
80 }
81}
82
83#[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")]
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
109impl 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 pub const LOG_PAGES: usize = Self::LOG_BYTES - LOG_BYTES_IN_PAGE as usize;
121 pub const PAGES: usize = 1 << Self::LOG_PAGES;
123 pub const LOG_LINES: usize = Self::LOG_BYTES - Line::LOG_BYTES;
125 pub const LINES: usize = 1 << Self::LOG_LINES;
127
128 pub const DEFRAG_STATE_TABLE: SideMetadataSpec =
130 crate::util::metadata::side_metadata::spec_defs::IX_BLOCK_DEFRAG;
131
132 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 pub fn chunk(&self) -> Chunk {
160 Chunk::from_unaligned_address(self.0)
161 }
162
163 #[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 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 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 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 const DEFRAG_SOURCE_STATE: u8 = u8::MAX;
209
210 pub fn is_defrag_source(&self) -> bool {
212 let byte = Self::DEFRAG_STATE_TABLE.load_atomic::<u8>(self.start(), Ordering::SeqCst);
213 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 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 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 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 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 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 #[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 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 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 #[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 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 BlockSweepResult::NoReuse
437 }
438 _ => unreachable!(),
439 }
440 } else {
441 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 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 #[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 space.release_block(*self, false);
479 BlockSweepResult::Swept
480 } else {
481 let is_reusable = marked_lines != Block::LINES;
483 if is_reusable {
484 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 self.set_state(BlockState::Unmarked);
492 }
493 mark_histogram[holes] += marked_lines;
495 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 #[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 #[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 #[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 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 #[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 #[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 vo_bit::bzero_vo_bit(self.start(), Self::BYTES);
657 }
658 BlockState::Marked => {
659 }
661 _ => unreachable!(),
662 }
663 }
664 Some(state) => {
665 for line in self.lines() {
667 if !line.is_marked(state) {
668 vo_bit::bzero_vo_bit(line.start(), Line::BYTES);
670 }
671 }
672 }
673 }
674 }
675}
676
677pub struct ReusableBlockPool {
679 queue: BlockPool<Block>,
680 num_workers: usize,
681}
682
683impl ReusableBlockPool {
684 pub fn new(num_workers: usize) -> Self {
686 Self {
687 queue: BlockPool::new(num_workers),
688 num_workers,
689 }
690 }
691
692 pub fn len(&self) -> usize {
694 self.queue.len()
695 }
696
697 pub fn push(&self, block: Block) {
699 self.queue.push(block)
700 }
701
702 pub fn pop(&self) -> Option<Block> {
704 self.queue.pop()
705 }
706
707 pub fn reset(&mut self) {
709 self.queue = BlockPool::new(self.num_workers);
710 }
711
712 pub fn iterate_blocks(&self, mut f: impl FnMut(Block)) {
714 self.queue.iterate_blocks(&mut f);
715 }
716
717 pub fn flush_all(&self) {
719 self.queue.flush_all();
720 }
721}
722
723pub enum BlockSweepResult {
725 Swept,
727 Reused,
729 NoReuse,
732}