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 pub chunk_map: ChunkMap,
44 pub line_mark_state: AtomicU8,
46 line_unavail_state: AtomicU8,
48 pub reusable_blocks: ReusableBlockPool,
50 pub(super) defrag: Defrag,
52 lines_consumed: AtomicUsize,
54 mark_state: u8,
56 scheduler: Arc<GCWorkScheduler<VM>>,
58 space_args: ImmixSpaceArgs,
60}
61
62pub struct ImmixSpaceArgs {
64 pub mixed_age: bool,
70 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 !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 self.is_marked(object) {
97 return true;
98 }
99
100 if !self.is_movable() {
102 return false;
103 }
104
105 object_forwarding::is_forwarded::<VM>(object)
107 }
108 #[cfg(feature = "object_pinning")]
109 fn pin_object(&self, object: ObjectReference) -> bool {
110 VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.pin_object::<VM>(object)
111 }
112 #[cfg(feature = "object_pinning")]
113 fn unpin_object(&self, object: ObjectReference) -> bool {
114 VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.unpin_object::<VM>(object)
115 }
116 #[cfg(feature = "object_pinning")]
117 fn is_object_pinned(&self, object: ObjectReference) -> bool {
118 VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.is_object_pinned::<VM>(object)
119 }
120 fn is_movable(&self) -> bool {
121 !self.space_args.never_move_objects
122 }
123
124 #[cfg(feature = "sanity")]
125 fn is_sane(&self) -> bool {
126 true
127 }
128 fn initialize_object_metadata(&self, _object: ObjectReference, _bytes: usize) {
129 #[cfg(feature = "vo_bit")]
130 crate::util::metadata::vo_bit::set_vo_bit(_object);
131 }
132 #[cfg(feature = "vo_bit")]
133 fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
134 crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
135 }
136 #[cfg(feature = "vo_bit")]
137 fn find_object_from_internal_pointer(
138 &self,
139 ptr: Address,
140 max_search_bytes: usize,
141 ) -> Option<ObjectReference> {
142 let search_bytes = usize::min(super::MAX_IMMIX_OBJECT_SIZE, max_search_bytes);
144 crate::util::metadata::vo_bit::find_object_from_internal_pointer::<VM>(ptr, search_bytes)
145 }
146 fn sft_trace_object(
147 &self,
148 _queue: &mut OptionObjectQueue,
149 _object: ObjectReference,
150 _worker: GCWorkerMutRef,
151 ) -> ObjectReference {
152 panic!("We do not use SFT to trace objects for Immix. sft_trace_object() cannot be used.")
153 }
154
155 fn debug_print_object_info(&self, object: ObjectReference) {
156 println!("marked = {}", self.is_marked(object));
157 println!(
158 "line marked = {}",
159 Line::from_unaligned_address(object.to_raw_address()).is_marked(self.mark_state)
160 );
161 println!(
162 "block state = {:?}",
163 Block::from_unaligned_address(object.to_raw_address()).get_state()
164 );
165 object_forwarding::debug_print_object_forwarding_info::<VM>(object);
166 self.common.debug_print_object_global_info(object);
167 }
168}
169
170impl<VM: VMBinding> Space<VM> for ImmixSpace<VM> {
171 fn as_space(&self) -> &dyn Space<VM> {
172 self
173 }
174 fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
175 self
176 }
177 fn get_page_resource(&self) -> &dyn PageResource<VM> {
178 &self.pr
179 }
180 fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
181 Some(&mut self.pr)
182 }
183 fn common(&self) -> &CommonSpace<VM> {
184 &self.common
185 }
186 fn initialize_sft(&self, sft_map: &mut dyn SFTMap) {
187 self.common().initialize_sft(self.as_sft(), sft_map)
188 }
189 fn release_multiple_pages(&mut self, _start: Address) {
190 panic!("immixspace only releases pages enmasse")
191 }
192 fn set_copy_for_sft_trace(&mut self, _semantics: Option<CopySemantics>) {
193 panic!("We do not use SFT to trace objects for Immix. set_copy_context() cannot be used.")
194 }
195
196 fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
197 object_enum::enumerate_blocks_from_chunk_map::<Block>(enumerator, &self.chunk_map);
198 }
199
200 fn clear_side_log_bits(&self) {
201 warn!("ImmixSpace::clear_side_log_bits is single-treaded. Consider clearing side metadata in per-chunk work packets.");
203
204 let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
205 for chunk in self.chunk_map.all_chunks() {
206 log_bit.bzero_metadata(chunk.start(), Chunk::BYTES);
207 }
208 }
209
210 fn set_side_log_bits(&self) {
211 warn!("ImmixSpace::set_side_log_bits is single-treaded. Consider setting side metadata in per-chunk work packets.");
213
214 let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
215 for chunk in self.chunk_map.all_chunks() {
216 log_bit.bset_metadata(chunk.start(), Chunk::BYTES);
217 }
218 }
219}
220
221impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for ImmixSpace<VM> {
222 fn trace_object<Q: ObjectQueue, const KIND: TraceKind>(
223 &self,
224 queue: &mut Q,
225 object: ObjectReference,
226 copy: Option<CopySemantics>,
227 worker: &mut GCWorker<VM>,
228 ) -> ObjectReference {
229 if KIND == TRACE_KIND_TRANSITIVE_PIN {
230 self.trace_object_without_moving(queue, object)
231 } else if KIND == TRACE_KIND_DEFRAG {
232 if Block::containing(object).is_defrag_source() {
233 debug_assert!(self.in_defrag());
234 debug_assert!(
235 !crate::plan::is_nursery_gc(worker.mmtk.get_plan()),
236 "Calling PolicyTraceObject on Immix in nursery GC"
237 );
238 self.trace_object_with_opportunistic_copy(
239 queue,
240 object,
241 copy.unwrap(),
242 worker,
243 false,
245 )
246 } else {
247 self.trace_object_without_moving(queue, object)
248 }
249 } else if KIND == TRACE_KIND_FAST {
250 self.trace_object_without_moving(queue, object)
251 } else {
252 unreachable!()
253 }
254 }
255
256 fn post_scan_object(&self, object: ObjectReference) {
257 if super::MARK_LINE_AT_SCAN_TIME && !super::BLOCK_ONLY {
258 debug_assert!(self.in_space(object));
259 self.mark_lines(object);
260 }
261 }
262
263 #[allow(clippy::if_same_then_else)] fn may_move_objects<const KIND: TraceKind>() -> bool {
265 if KIND == TRACE_KIND_DEFRAG {
266 true
267 } else if KIND == TRACE_KIND_FAST || KIND == TRACE_KIND_TRANSITIVE_PIN {
268 false
269 } else if KIND == DEFAULT_TRACE {
270 false
276 } else {
277 unreachable!()
278 }
279 }
280}
281
282impl<VM: VMBinding> ImmixSpace<VM> {
283 #[allow(unused)]
284 const UNMARKED_STATE: u8 = 0;
285 const MARKED_STATE: u8 = 1;
286
287 fn side_metadata_specs() -> Vec<SideMetadataSpec> {
289 metadata::extract_side_metadata(&if super::BLOCK_ONLY {
290 vec![
291 MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
292 MetadataSpec::OnSide(Block::MARK_TABLE),
293 *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
294 *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
295 *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
296 #[cfg(feature = "object_pinning")]
297 *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
298 ]
299 } else {
300 vec![
301 MetadataSpec::OnSide(Line::MARK_TABLE),
302 MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
303 MetadataSpec::OnSide(Block::MARK_TABLE),
304 *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
305 *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
306 *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
307 #[cfg(feature = "object_pinning")]
308 *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
309 ]
310 })
311 }
312
313 pub fn new(
314 args: crate::policy::space::PlanCreateSpaceArgs<VM>,
315 mut space_args: ImmixSpaceArgs,
316 ) -> Self {
317 if args.unlog_traced_object {
318 assert!(
319 args.constraints.needs_log_bit,
320 "Invalid args when the plan does not use log bit"
321 );
322 }
323
324 if cfg!(feature = "immix_non_moving") && !space_args.never_move_objects {
326 info!(
327 "Overriding never_moves_objects for Immix Space {}, as the immix_non_moving feature is set. Block size: 2^{}",
328 args.name,
329 Block::LOG_BYTES,
330 );
331 space_args.never_move_objects = true;
332 }
333
334 if super::BLOCK_ONLY {
336 assert!(
337 space_args.never_move_objects,
338 "Block-only immix must not move objects"
339 );
340 }
341 assert!(
342 Block::LINES / 2 <= u8::MAX as usize - 2,
343 "Number of lines in a block should not exceed BlockState::MARK_MARKED"
344 );
345
346 #[cfg(feature = "vo_bit")]
347 vo_bit::helper::validate_config::<VM>();
348 let vm_map = args.vm_map;
349 let scheduler = args.scheduler.clone();
350 let common =
351 CommonSpace::new(args.into_policy_args(true, false, Self::side_metadata_specs()));
352 let space_index = common.descriptor.get_index();
353 ImmixSpace {
354 pr: if common.vmrequest.is_discontiguous() {
355 BlockPageResource::new_discontiguous(
356 Block::LOG_PAGES,
357 vm_map,
358 scheduler.num_workers(),
359 )
360 } else {
361 BlockPageResource::new_contiguous(
362 Block::LOG_PAGES,
363 common.start,
364 common.extent,
365 vm_map,
366 scheduler.num_workers(),
367 )
368 },
369 common,
370 chunk_map: ChunkMap::new(space_index),
371 line_mark_state: AtomicU8::new(Line::RESET_MARK_STATE),
372 line_unavail_state: AtomicU8::new(Line::RESET_MARK_STATE),
373 lines_consumed: AtomicUsize::new(0),
374 reusable_blocks: ReusableBlockPool::new(scheduler.num_workers()),
375 defrag: Defrag::default(),
376 mark_state: Self::MARKED_STATE,
378 scheduler: scheduler.clone(),
379 space_args,
380 }
381 }
382
383 pub fn flush_page_resource(&self) {
385 self.reusable_blocks.flush_all();
386 #[cfg(target_pointer_width = "64")]
387 self.pr.flush_all()
388 }
389
390 pub fn defrag_headroom_pages(&self) -> usize {
392 self.defrag.defrag_headroom_pages(self)
393 }
394
395 pub fn in_defrag(&self) -> bool {
397 self.defrag.in_defrag()
398 }
399
400 pub fn decide_whether_to_defrag(
402 &self,
403 emergency_collection: bool,
404 collect_whole_heap: bool,
405 collection_attempts: usize,
406 user_triggered_collection: bool,
407 full_heap_system_gc: bool,
408 ) -> bool {
409 self.defrag.decide_whether_to_defrag(
410 self.is_defrag_enabled(),
411 emergency_collection,
412 collect_whole_heap,
413 collection_attempts,
414 user_triggered_collection,
415 self.reusable_blocks.len() == 0,
416 full_heap_system_gc,
417 *self.common.options.immix_always_defrag,
418 );
419 self.defrag.in_defrag()
420 }
421
422 fn scheduler(&self) -> &GCWorkScheduler<VM> {
424 &self.scheduler
425 }
426
427 pub(crate) fn prepare(
428 &mut self,
429 major_gc: bool,
430 plan_stats: Option<StatsForDefrag>,
431 unlog_bits_op: UnlogBitsOperation,
432 ) {
433 if major_gc {
434 if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_on_side() {
436 self.mark_state = Self::MARKED_STATE;
437 } else {
438 unimplemented!("cyclic mark bits is not supported at the moment");
440 }
441
442 if self.is_defrag_enabled() {
444 self.defrag.prepare(self, plan_stats.unwrap());
445 }
446
447 let threshold = self.defrag.defrag_spill_threshold.load(Ordering::Acquire);
449 let space = unsafe { &*(self as *const Self) };
451 let work_packets = self.chunk_map.generate_tasks(|chunk| {
452 Box::new(PrepareBlockState {
453 space,
454 chunk,
455 defrag_threshold: if space.in_defrag() {
456 Some(threshold)
457 } else {
458 None
459 },
460 unlog_bits_op,
461 })
462 });
463 self.scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(work_packets);
464
465 if !super::BLOCK_ONLY {
466 self.line_mark_state.fetch_add(1, Ordering::AcqRel);
467 if self.line_mark_state.load(Ordering::Acquire) > Line::MAX_MARK_STATE {
468 self.line_mark_state
469 .store(Line::RESET_MARK_STATE, Ordering::Release);
470 }
471 }
472 }
473
474 #[cfg(feature = "vo_bit")]
475 if vo_bit::helper::need_to_clear_vo_bits_before_tracing::<VM>() {
476 let maybe_scope = if major_gc {
477 Some(VOBitsClearingScope::FullGC)
480 } else if self.space_args.mixed_age {
481 if super::BLOCK_ONLY {
485 Some(VOBitsClearingScope::BlockOnly)
488 } else {
489 let line_mark_state = self.line_mark_state.load(Ordering::SeqCst);
492 Some(VOBitsClearingScope::Line {
493 state: line_mark_state,
494 })
495 }
496 } else {
497 None
500 };
501
502 if let Some(scope) = maybe_scope {
503 let work_packets = self
504 .chunk_map
505 .generate_tasks(|chunk| Box::new(ClearVOBitsAfterPrepare { chunk, scope }));
506 self.scheduler.work_buckets[WorkBucketStage::ClearVOBits].bulk_add(work_packets);
507 }
508 }
509 }
510
511 pub(crate) fn release(&mut self, major_gc: bool, unlog_bits_op: UnlogBitsOperation) {
513 if major_gc {
514 if !super::BLOCK_ONLY {
516 self.line_unavail_state.store(
517 self.line_mark_state.load(Ordering::Acquire),
518 Ordering::Release,
519 );
520 }
521 }
522 if !super::BLOCK_ONLY {
524 self.reusable_blocks.reset();
525 }
526 let work_packets = self.generate_sweep_tasks(unlog_bits_op);
528 self.scheduler().work_buckets[WorkBucketStage::Release].bulk_add(work_packets);
529
530 self.lines_consumed.store(0, Ordering::Relaxed);
531 }
532
533 pub fn end_of_gc(&mut self) -> bool {
536 let did_defrag = self.defrag.in_defrag();
537 if self.is_defrag_enabled() {
538 self.defrag.reset_in_defrag();
539 }
540 did_defrag
541 }
542
543 fn generate_sweep_tasks(&self, unlog_bits_op: UnlogBitsOperation) -> Vec<Box<dyn GCWork<VM>>> {
545 self.defrag.mark_histograms.lock().clear();
546 let space = unsafe { &*(self as *const Self) };
548 let epilogue = Arc::new(FlushPageResource {
549 space,
550 counter: AtomicUsize::new(0),
551 });
552 let tasks = self.chunk_map.generate_tasks(|chunk| {
553 Box::new(SweepChunk {
554 space,
555 chunk,
556 unlog_bits_op,
557 epilogue: epilogue.clone(),
558 })
559 });
560 epilogue.counter.store(tasks.len(), Ordering::SeqCst);
561 tasks
562 }
563
564 pub fn release_block(&self, block: Block) {
566 block.deinit();
567 self.pr.release_block(block);
568 }
569
570 pub fn get_clean_block(
572 &self,
573 tls: VMThread,
574 copy: bool,
575 alloc_options: AllocationOptions,
576 ) -> Option<Block> {
577 let block_address = self.acquire(tls, Block::PAGES, alloc_options);
578 if block_address.is_zero() {
579 return None;
580 }
581 self.defrag.notify_new_clean_block(copy);
582 let block = Block::from_aligned_address(block_address);
583 block.init(copy);
584 self.chunk_map.set_allocated(block.chunk(), true);
585 self.lines_consumed
586 .fetch_add(Block::LINES, Ordering::SeqCst);
587 Some(block)
588 }
589
590 pub fn get_reusable_block(&self, copy: bool) -> Option<Block> {
592 if super::BLOCK_ONLY {
593 return None;
594 }
595 loop {
596 let block = self.reusable_blocks.pop()?;
597
598 if copy && block.is_defrag_source() {
600 continue;
601 }
602
603 let lines_delta = match block.get_state() {
605 BlockState::Reusable { unavailable_lines } => {
606 Block::LINES - unavailable_lines as usize
607 }
608 BlockState::Unmarked => Block::LINES,
609 _ => unreachable!("{:?} {:?}", block, block.get_state()),
610 };
611 self.lines_consumed.fetch_add(lines_delta, Ordering::SeqCst);
612
613 block.init(copy);
614 return Some(block);
615 }
616 }
617
618 pub fn trace_object_without_moving(
620 &self,
621 queue: &mut impl ObjectQueue,
622 object: ObjectReference,
623 ) -> ObjectReference {
624 #[cfg(feature = "vo_bit")]
625 vo_bit::helper::on_trace_object::<VM>(object);
626
627 if self.attempt_mark(object, self.mark_state) {
628 if !super::BLOCK_ONLY {
630 if !super::MARK_LINE_AT_SCAN_TIME {
631 self.mark_lines(object);
632 }
633 } else {
634 Block::containing(object).set_state(BlockState::Marked);
635 }
636
637 #[cfg(feature = "vo_bit")]
638 vo_bit::helper::on_object_marked::<VM>(object);
639
640 queue.enqueue(object);
642 self.unlog_object_if_needed(object);
643 return object;
644 }
645 object
646 }
647
648 #[allow(clippy::assertions_on_constants)]
650 pub fn trace_object_with_opportunistic_copy(
651 &self,
652 queue: &mut impl ObjectQueue,
653 object: ObjectReference,
654 semantics: CopySemantics,
655 worker: &mut GCWorker<VM>,
656 nursery_collection: bool,
657 ) -> ObjectReference {
658 let copy_context = worker.get_copy_context_mut();
659 debug_assert!(!super::BLOCK_ONLY);
660
661 #[cfg(feature = "vo_bit")]
662 vo_bit::helper::on_trace_object::<VM>(object);
663
664 let forwarding_status = object_forwarding::attempt_to_forward::<VM>(object);
665 if object_forwarding::state_is_forwarded_or_being_forwarded(forwarding_status) {
666 #[allow(clippy::let_and_return)]
670 let new_object =
671 object_forwarding::spin_and_get_forwarded_object::<VM>(object, forwarding_status);
672 #[cfg(debug_assertions)]
673 {
674 if new_object == object {
675 debug_assert!(
676 self.is_marked(object) || self.defrag.space_exhausted() || self.is_pinned(object),
677 "Forwarded object is the same as original object {} even though it should have been copied",
678 object,
679 );
680 } else {
681 debug_assert!(
683 !Block::containing(new_object).is_defrag_source(),
684 "Block {:?} containing forwarded object {} should not be a defragmentation source",
685 Block::containing(new_object),
686 new_object,
687 );
688 }
689 }
690 new_object
691 } else if self.is_marked(object) {
692 object_forwarding::clear_forwarding_bits::<VM>(object);
695 object
696 } else {
697 let new_object = if self.is_pinned(object)
700 || (!nursery_collection && self.defrag.space_exhausted())
701 {
702 self.attempt_mark(object, self.mark_state);
703 object_forwarding::clear_forwarding_bits::<VM>(object);
704 Block::containing(object).set_state(BlockState::Marked);
705
706 #[cfg(feature = "vo_bit")]
707 vo_bit::helper::on_object_marked::<VM>(object);
708
709 if !super::MARK_LINE_AT_SCAN_TIME {
710 self.mark_lines(object);
711 }
712
713 self.unlog_object_if_needed(object);
714
715 object
716 } else {
717 object_forwarding::forward_object::<VM>(
721 object,
722 semantics,
723 copy_context,
724 |new_object| {
725 debug_assert!(
728 !self.common.unlog_traced_object
729 || VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
730 .is_unlogged::<VM>(new_object, Ordering::Relaxed)
731 );
732 #[cfg(feature = "vo_bit")]
733 vo_bit::helper::on_object_forwarded::<VM>(new_object);
734 },
735 )
736 };
737 debug_assert_eq!(
738 Block::containing(new_object).get_state(),
739 BlockState::Marked
740 );
741
742 queue.enqueue(new_object);
743 debug_assert!(new_object.is_live());
744 new_object
745 }
746 }
747
748 fn unlog_object_if_needed(&self, object: ObjectReference) {
749 if self.common.unlog_traced_object {
750 const_assert!(
753 Line::BYTES
754 >= (1
755 << (crate::util::constants::LOG_BITS_IN_BYTE
756 + crate::util::constants::LOG_MIN_OBJECT_SIZE))
757 );
758 const_assert_eq!(
759 crate::vm::object_model::specs::VMGlobalLogBitSpec::LOG_NUM_BITS,
760 0
761 ); VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
767 .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
768 }
769 }
770
771 #[allow(clippy::assertions_on_constants)]
773 pub fn mark_lines(&self, object: ObjectReference) {
774 debug_assert!(!super::BLOCK_ONLY);
775 Line::mark_lines_for_object::<VM>(object, self.line_mark_state.load(Ordering::Acquire));
776 }
777
778 fn attempt_mark(&self, object: ObjectReference, mark_state: u8) -> bool {
780 loop {
781 let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
782 object,
783 None,
784 Ordering::SeqCst,
785 );
786 if old_value == mark_state {
787 return false;
788 }
789
790 if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC
791 .compare_exchange_metadata::<VM, u8>(
792 object,
793 old_value,
794 mark_state,
795 None,
796 Ordering::SeqCst,
797 Ordering::SeqCst,
798 )
799 .is_ok()
800 {
801 break;
802 }
803 }
804 true
805 }
806
807 fn is_marked_with(&self, object: ObjectReference, mark_state: u8) -> bool {
809 let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
810 object,
811 None,
812 Ordering::SeqCst,
813 );
814 old_value == mark_state
815 }
816
817 pub(crate) fn is_marked(&self, object: ObjectReference) -> bool {
818 self.is_marked_with(object, self.mark_state)
819 }
820
821 fn is_pinned(&self, _object: ObjectReference) -> bool {
823 #[cfg(feature = "object_pinning")]
824 return self.is_object_pinned(_object);
825
826 #[cfg(not(feature = "object_pinning"))]
827 false
828 }
829
830 #[allow(clippy::assertions_on_constants)]
838 pub fn get_next_available_lines(&self, search_start: Line) -> Option<(Line, Line)> {
839 debug_assert!(!super::BLOCK_ONLY);
840 let unavail_state = self.line_unavail_state.load(Ordering::Acquire);
841 let current_state = self.line_mark_state.load(Ordering::Acquire);
842 let block = search_start.block();
843 let mark_data = block.line_mark_table();
844 let start_cursor = search_start.get_index_within_block();
845 let mut cursor = start_cursor;
846 while cursor < mark_data.len() {
848 let mark = mark_data.get(cursor);
849 if mark != unavail_state && mark != current_state {
850 break;
851 }
852 cursor += 1;
853 }
854 if cursor == mark_data.len() {
855 return None;
856 }
857 let start = search_start.next_nth(cursor - start_cursor);
858 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 let end = search_start.next_nth(cursor - start_cursor);
867 debug_assert!(RegionIterator::<Line>::new(start, end)
868 .all(|line| !line.is_marked(unavail_state) && !line.is_marked(current_state)));
869 Some((start, end))
870 }
871
872 pub fn is_last_gc_exhaustive(&self, did_defrag_for_last_gc: bool) -> bool {
873 if self.is_defrag_enabled() {
874 did_defrag_for_last_gc
875 } else {
876 true
878 }
879 }
880
881 pub(crate) fn get_pages_allocated(&self) -> usize {
882 self.lines_consumed.load(Ordering::SeqCst) >> (LOG_BYTES_IN_PAGE - Line::LOG_BYTES as u8)
883 }
884
885 fn post_copy(&self, object: ObjectReference, _bytes: usize) {
887 VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.store_atomic::<VM, u8>(
889 object,
890 self.mark_state,
891 None,
892 Ordering::SeqCst,
893 );
894 if !super::MARK_LINE_AT_SCAN_TIME {
896 self.mark_lines(object);
897 }
898 if self.common.unlog_traced_object {
899 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
900 .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
901 }
902 }
903
904 pub(crate) fn prefer_copy_on_nursery_gc(&self) -> bool {
905 self.is_nursery_copy_enabled()
906 }
907
908 pub(crate) fn is_nursery_copy_enabled(&self) -> bool {
909 !self.space_args.never_move_objects && !cfg!(feature = "sticky_immix_non_moving_nursery")
910 }
911
912 pub(crate) fn is_defrag_enabled(&self) -> bool {
913 !self.space_args.never_move_objects
914 }
915}
916
917pub struct PrepareBlockState<VM: VMBinding> {
920 #[allow(dead_code)]
921 pub space: &'static ImmixSpace<VM>,
922 pub chunk: Chunk,
923 pub defrag_threshold: Option<usize>,
924 pub unlog_bits_op: UnlogBitsOperation,
925}
926
927impl<VM: VMBinding> PrepareBlockState<VM> {
928 fn reset_object_mark(&self) {
930 if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC {
933 side.bzero_metadata(self.chunk.start(), Chunk::BYTES);
934 }
935 }
936}
937
938impl<VM: VMBinding> GCWork<VM> for PrepareBlockState<VM> {
939 fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
940 self.reset_object_mark();
942 for block in self.chunk.iter_region::<Block>() {
944 let state = block.get_state();
945 if state == BlockState::Unallocated {
947 continue;
948 }
949 let is_defrag_source = if !self.space.is_defrag_enabled() {
951 false
953 } else if *mmtk.options.immix_defrag_every_block {
954 true
956 } else if let Some(defrag_threshold) = self.defrag_threshold {
957 block.get_holes() > defrag_threshold
959 } else {
960 false
962 };
963 block.set_as_defrag_source(is_defrag_source);
964 block.set_state(BlockState::Unmarked);
966 debug_assert!(!block.get_state().is_reusable());
967 debug_assert_ne!(block.get_state(), BlockState::Marked);
968 }
969
970 self.unlog_bits_op
971 .execute::<VM>(self.chunk.start(), Chunk::BYTES);
972 }
973}
974
975struct SweepChunk<VM: VMBinding> {
977 space: &'static ImmixSpace<VM>,
978 chunk: Chunk,
979 unlog_bits_op: UnlogBitsOperation,
980 epilogue: Arc<FlushPageResource<VM>>,
982}
983
984impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
985 fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
986 assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
987
988 let mut histogram = self.space.defrag.new_histogram();
989 let line_mark_state = if super::BLOCK_ONLY {
990 None
991 } else {
992 Some(self.space.line_mark_state.load(Ordering::Acquire))
993 };
994 let is_moving_gc = mmtk.get_plan().current_gc_may_move_object();
996 let is_defrag_gc = self.space.defrag.in_defrag();
997
998 let mut swept_blocks = 0;
1000 let mut reused_blocks = 0;
1002 let mut unreused_blocks = 0;
1004
1005 for block in self
1007 .chunk
1008 .iter_region::<Block>()
1009 .filter(|block| block.get_state() != BlockState::Unallocated)
1010 {
1011 if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC {
1019 if is_moving_gc {
1020 let objects_may_move = if is_defrag_gc {
1021 block.is_defrag_source()
1023 } else {
1024 true
1028 };
1029 if objects_may_move {
1030 side.bzero_metadata(block.start(), Block::BYTES);
1031 }
1032 }
1033 }
1034
1035 match block.sweep(self.space, &mut histogram, line_mark_state) {
1036 BlockSweepResult::Swept => swept_blocks += 1,
1037 BlockSweepResult::Reused => reused_blocks += 1,
1038 BlockSweepResult::NoReuse => unreused_blocks += 1,
1039 }
1040 }
1041
1042 probe!(
1043 mmtk,
1044 sweep_chunk_immix,
1045 swept_blocks,
1046 reused_blocks,
1047 unreused_blocks
1048 );
1049
1050 let allocated_blocks = reused_blocks + unreused_blocks;
1052
1053 if allocated_blocks == 0 {
1055 self.space.chunk_map.set_allocated(self.chunk, false)
1056 }
1057 self.space.defrag.add_completed_mark_histogram(histogram);
1058
1059 self.unlog_bits_op
1060 .execute::<VM>(self.chunk.start(), Chunk::BYTES);
1061
1062 self.epilogue.finish_one_work_packet();
1063 }
1064}
1065
1066struct FlushPageResource<VM: VMBinding> {
1068 space: &'static ImmixSpace<VM>,
1069 counter: AtomicUsize,
1070}
1071
1072impl<VM: VMBinding> FlushPageResource<VM> {
1073 fn finish_one_work_packet(&self) {
1075 if 1 == self.counter.fetch_sub(1, Ordering::SeqCst) {
1076 self.space.flush_page_resource()
1079 }
1080 }
1081}
1082
1083impl<VM: VMBinding> Drop for FlushPageResource<VM> {
1084 fn drop(&mut self) {
1085 epilogue::debug_assert_counter_zero(&self.counter, "FlushPageResource::counter");
1086 }
1087}
1088
1089use crate::policy::copy_context::PolicyCopyContext;
1090use crate::util::alloc::Allocator;
1091use crate::util::alloc::ImmixAllocator;
1092
1093pub struct ImmixCopyContext<VM: VMBinding> {
1096 allocator: ImmixAllocator<VM>,
1097}
1098
1099impl<VM: VMBinding> PolicyCopyContext for ImmixCopyContext<VM> {
1100 type VM = VM;
1101
1102 fn prepare(&mut self) {
1103 self.allocator.reset();
1104 }
1105 fn release(&mut self) {
1106 self.allocator.reset();
1107 }
1108 fn alloc_copy(
1109 &mut self,
1110 _original: ObjectReference,
1111 bytes: usize,
1112 align: usize,
1113 offset: usize,
1114 ) -> Address {
1115 self.allocator.alloc(bytes, align, offset)
1116 }
1117 fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1118 self.get_space().post_copy(obj, bytes)
1119 }
1120}
1121
1122impl<VM: VMBinding> ImmixCopyContext<VM> {
1123 pub(crate) fn new(
1124 tls: VMWorkerThread,
1125 context: Arc<AllocatorContext<VM>>,
1126 space: &'static ImmixSpace<VM>,
1127 ) -> Self {
1128 ImmixCopyContext {
1129 allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1130 }
1131 }
1132
1133 fn get_space(&self) -> &ImmixSpace<VM> {
1134 self.allocator.immix_space()
1135 }
1136}
1137
1138pub struct ImmixHybridCopyContext<VM: VMBinding> {
1142 copy_allocator: ImmixAllocator<VM>,
1143 defrag_allocator: ImmixAllocator<VM>,
1144}
1145
1146impl<VM: VMBinding> PolicyCopyContext for ImmixHybridCopyContext<VM> {
1147 type VM = VM;
1148
1149 fn prepare(&mut self) {
1150 self.copy_allocator.reset();
1151 self.defrag_allocator.reset();
1152 }
1153 fn release(&mut self) {
1154 self.copy_allocator.reset();
1155 self.defrag_allocator.reset();
1156 }
1157 fn alloc_copy(
1158 &mut self,
1159 _original: ObjectReference,
1160 bytes: usize,
1161 align: usize,
1162 offset: usize,
1163 ) -> Address {
1164 if self.get_space().in_defrag() {
1165 self.defrag_allocator.alloc(bytes, align, offset)
1166 } else {
1167 self.copy_allocator.alloc(bytes, align, offset)
1168 }
1169 }
1170 fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1171 self.get_space().post_copy(obj, bytes)
1172 }
1173}
1174
1175impl<VM: VMBinding> ImmixHybridCopyContext<VM> {
1176 pub(crate) fn new(
1177 tls: VMWorkerThread,
1178 context: Arc<AllocatorContext<VM>>,
1179 space: &'static ImmixSpace<VM>,
1180 ) -> Self {
1181 ImmixHybridCopyContext {
1182 copy_allocator: ImmixAllocator::new(tls.0, Some(space), context.clone(), false),
1183 defrag_allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1184 }
1185 }
1186
1187 fn get_space(&self) -> &ImmixSpace<VM> {
1188 debug_assert_eq!(
1190 self.defrag_allocator.immix_space().common().descriptor,
1191 self.copy_allocator.immix_space().common().descriptor
1192 );
1193 self.defrag_allocator.immix_space()
1195 }
1196}
1197
1198#[cfg(feature = "vo_bit")]
1199#[derive(Clone, Copy)]
1200enum VOBitsClearingScope {
1201 FullGC,
1203 BlockOnly,
1205 Line { state: u8 },
1207}
1208
1209#[cfg(feature = "vo_bit")]
1211struct ClearVOBitsAfterPrepare {
1212 chunk: Chunk,
1213 scope: VOBitsClearingScope,
1214}
1215
1216#[cfg(feature = "vo_bit")]
1217impl<VM: VMBinding> GCWork<VM> for ClearVOBitsAfterPrepare {
1218 fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
1219 match self.scope {
1220 VOBitsClearingScope::FullGC => {
1221 vo_bit::bzero_vo_bit(self.chunk.start(), Chunk::BYTES);
1222 }
1223 VOBitsClearingScope::BlockOnly => {
1224 self.clear_blocks(None);
1225 }
1226 VOBitsClearingScope::Line { state } => {
1227 self.clear_blocks(Some(state));
1228 }
1229 }
1230 }
1231}
1232
1233#[cfg(feature = "vo_bit")]
1234impl ClearVOBitsAfterPrepare {
1235 fn clear_blocks(&mut self, line_mark_state: Option<u8>) {
1236 for block in self
1237 .chunk
1238 .iter_region::<Block>()
1239 .filter(|block| block.get_state() != BlockState::Unallocated)
1240 {
1241 block.clear_vo_bits_for_unmarked_regions(line_mark_state);
1242 }
1243 }
1244}