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")]
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
104impl 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 pub const LOG_PAGES: usize = Self::LOG_BYTES - LOG_BYTES_IN_PAGE as usize;
116 pub const PAGES: usize = 1 << Self::LOG_PAGES;
118 pub const LOG_LINES: usize = Self::LOG_BYTES - Line::LOG_BYTES;
120 pub const LINES: usize = 1 << Self::LOG_LINES;
122
123 pub const DEFRAG_STATE_TABLE: SideMetadataSpec =
125 crate::util::metadata::side_metadata::spec_defs::IX_BLOCK_DEFRAG;
126
127 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 pub fn chunk(&self) -> Chunk {
155 Chunk::from_unaligned_address(self.0)
156 }
157
158 #[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 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 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 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 const DEFRAG_SOURCE_STATE: u8 = u8::MAX;
204
205 pub fn is_defrag_source(&self) -> bool {
207 let byte = Self::DEFRAG_STATE_TABLE.load_atomic::<u8>(self.start(), Ordering::SeqCst);
208 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 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 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 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 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 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 #[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 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 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 #[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 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 BlockSweepResult::NoReuse
432 }
433 _ => unreachable!(),
434 }
435 } else {
436 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 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 #[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 space.release_block(*self, false);
474 BlockSweepResult::Swept
475 } else {
476 let is_reusable = marked_lines != Block::LINES;
478 if is_reusable {
479 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 self.set_state(BlockState::Unmarked);
487 }
488 mark_histogram[holes] += marked_lines;
490 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 #[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 #[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 #[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 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 #[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 #[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 vo_bit::bzero_vo_bit(self.start(), Self::BYTES);
652 }
653 BlockState::Marked => {
654 }
656 _ => unreachable!(),
657 }
658 }
659 Some(state) => {
660 for line in self.lines() {
662 if !line.is_marked(state) {
663 vo_bit::bzero_vo_bit(line.start(), Line::BYTES);
665 }
666 }
667 }
668 }
669 }
670}
671
672pub struct ReusableBlockPool {
674 queue: BlockPool<Block>,
675 num_workers: usize,
676}
677
678impl ReusableBlockPool {
679 pub fn new(num_workers: usize) -> Self {
681 Self {
682 queue: BlockPool::new(num_workers),
683 num_workers,
684 }
685 }
686
687 pub fn len(&self) -> usize {
689 self.queue.len()
690 }
691
692 pub fn push(&self, block: Block) {
694 self.queue.push(block)
695 }
696
697 pub fn pop(&self) -> Option<Block> {
699 self.queue.pop()
700 }
701
702 pub fn reset(&mut self) {
704 self.queue = BlockPool::new(self.num_workers);
705 }
706
707 pub fn iterate_blocks(&self, mut f: impl FnMut(Block)) {
709 self.queue.iterate_blocks(&mut f);
710 }
711
712 pub fn flush_all(&self) {
714 self.queue.flush_all();
715 }
716}
717
718pub enum BlockSweepResult {
720 Swept,
722 Reused,
724 NoReuse,
727}