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