1use super::defrag::StatsForDefrag;
2use super::line::*;
3use super::{block::*, defrag::Defrag};
4use crate::plan::tracing::OptionObjectQueue;
5use crate::plan::Pause;
6use crate::policy::gc_work::{TraceKind, DEFAULT_TRACE, TRACE_KIND_TRANSITIVE_PIN};
7use crate::policy::sft::GCWorkerMutRef;
8use crate::policy::sft::SFT;
9use crate::policy::sft_map::SFTMap;
10use crate::policy::space::{CommonSpace, Space};
11use crate::scheduler::gc_work::PrepareCollector;
12use crate::util::alloc::allocator::AllocationOptions;
13use crate::util::alloc::allocator::AllocatorContext;
14use crate::util::constants::LOG_BYTES_IN_PAGE;
15use crate::util::heap::chunk_map::*;
16use crate::util::heap::BlockPageResource;
17use crate::util::heap::PageResource;
18use crate::util::linear_scan::{Region, RegionIterator, UnstraddlableRegion};
19use crate::util::metadata::log_bit::UnlogBitsOperation;
20use crate::util::metadata::side_metadata::spec_defs::IX_LINE_REUSE_COUNT;
21use crate::util::metadata::side_metadata::*;
22#[cfg(feature = "vo_bit")]
23use crate::util::metadata::vo_bit;
24use crate::util::metadata::{self, MetadataSpec};
25use crate::util::object_enum::ObjectEnumerator;
26use crate::util::object_forwarding;
27use crate::util::rc::RefCountHelper;
28use crate::util::{copy::*, epilogue, object_enum};
29use crate::util::{Address, ObjectReference};
30use crate::vm::*;
31use crate::{
32 plan::ObjectQueue,
33 scheduler::{GCWork, GCWorkScheduler, GCWorker, WorkBucketStage},
34 util::opaque_pointer::{VMThread, VMWorkerThread},
35 MMTK,
36};
37use atomic::Ordering;
38use std::sync::atomic::AtomicUsize;
39use std::sync::OnceLock;
40use std::sync::{atomic::AtomicU8, Arc};
41
42pub(crate) const TRACE_KIND_FAST: TraceKind = 0;
43pub(crate) const TRACE_KIND_DEFRAG: TraceKind = 1;
44
45const LXR_MATURE_EVACUATION: bool = !cfg!(feature = "lxr_no_mature_evac");
47
48pub trait ImmixHooks<VM: VMBinding>: Send + Sync {
51 fn on_clean_block_acquired(&self, _block: Block, _copy: bool) {}
55 fn on_reusable_block_acquired(&self, _block: Block, _copy: bool) {}
57 fn cm_in_progress_or_final_mark(&self) -> bool {
60 false
61 }
62}
63
64pub struct ImmixSpace<VM: VMBinding> {
65 common: CommonSpace<VM>,
66 pr: BlockPageResource<VM, Block>,
67 pub chunk_map: ChunkMap,
69 pub line_mark_state: AtomicU8,
71 line_unavail_state: AtomicU8,
73 pub reusable_blocks: ReusableBlockPool,
75 pub(super) defrag: Defrag,
77 lines_consumed: AtomicUsize,
79 reused_lines_consumed: AtomicUsize,
80 mark_state: u8,
82 scheduler: Arc<GCWorkScheduler<VM>>,
84 space_args: ImmixSpaceArgs,
86 hooks: OnceLock<&'static dyn ImmixHooks<VM>>,
87 pub rc_enabled: bool,
88 pub is_end_of_satb_or_full_gc: bool,
89 pub rc: RefCountHelper<VM>,
90}
91
92pub struct ImmixSpaceArgs {
94 pub mixed_age: bool,
100 pub never_move_objects: bool,
102}
103
104unsafe impl<VM: VMBinding> Sync for ImmixSpace<VM> {}
105
106impl<VM: VMBinding> SFT for ImmixSpace<VM> {
107 fn name(&self) -> &'static str {
108 self.get_name()
109 }
110
111 fn get_forwarded_object(&self, object: ObjectReference) -> Option<ObjectReference> {
112 if !self.is_movable() {
114 return None;
115 }
116
117 if object_forwarding::is_forwarded::<VM>(object) {
118 Some(object_forwarding::read_forwarding_pointer::<VM>(object))
119 } else {
120 None
121 }
122 }
123
124 fn is_live(&self, object: ObjectReference) -> bool {
125 if self.rc_enabled {
126 if self.is_end_of_satb_or_full_gc {
127 if self.is_marked(object) {
128 let block = Block::containing(object);
129 if block.is_defrag_source() {
130 if object_forwarding::is_forwarded::<VM>(object) {
131 let forwarded =
132 object_forwarding::read_forwarding_pointer::<VM>(object);
133 return self.is_marked(forwarded) && self.rc.count(forwarded) > 0;
134 } else {
135 return false;
136 }
137 }
138 return self.rc.count(object) > 0;
139 } else if object_forwarding::is_forwarded::<VM>(object) {
140 let forwarded = object_forwarding::read_forwarding_pointer::<VM>(object);
141 debug_assert!(
142 forwarded.to_raw_address().is_mapped(),
143 "Invalid forwarded object: {:?} -> {:?}",
144 object,
145 forwarded
146 );
147 return self.is_marked(forwarded) && self.rc.count(forwarded) > 0;
148 } else {
149 return false;
150 }
151 }
152 return self.rc.count(object) > 0 || object_forwarding::is_forwarded::<VM>(object);
153 }
154 if self.is_marked(object) {
156 return true;
157 }
158
159 if !self.is_movable() {
161 return false;
162 }
163
164 object_forwarding::is_forwarded::<VM>(object)
166 }
167
168 fn is_reachable(&self, object: ObjectReference) -> bool {
169 if self.rc_enabled {
170 if object_forwarding::is_forwarded::<VM>(object) {
171 let forwarded = object_forwarding::read_forwarding_pointer::<VM>(object);
172 return self.is_marked(forwarded) && self.rc.count(forwarded) > 0;
173 }
174 self.is_marked(object) && self.rc.count(object) > 0
175 } else {
176 self.is_live(object)
177 }
178 }
179 #[cfg(feature = "object_pinning")]
180 fn pin_object(&self, object: ObjectReference) -> bool {
181 if self.space_args.never_move_objects {
182 false
183 } else {
184 VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.pin_object::<VM>(object)
185 }
186 }
187 #[cfg(feature = "object_pinning")]
188 fn unpin_object(&self, object: ObjectReference) -> bool {
189 if self.space_args.never_move_objects {
190 false
191 } else {
192 VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.unpin_object::<VM>(object)
193 }
194 }
195 #[cfg(feature = "object_pinning")]
196 fn is_object_pinned(&self, object: ObjectReference) -> bool {
197 if self.space_args.never_move_objects {
198 true
199 } else {
200 VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC.is_object_pinned::<VM>(object)
201 }
202 }
203 fn is_movable(&self) -> bool {
204 !self.space_args.never_move_objects
205 }
206
207 #[cfg(feature = "sanity")]
208 fn is_sane(&self) -> bool {
209 true
210 }
211 fn initialize_object_metadata(&self, _object: ObjectReference, _bytes: usize) {
212 #[cfg(feature = "vo_bit")]
213 crate::util::metadata::vo_bit::set_vo_bit(_object);
214 }
215 #[cfg(feature = "vo_bit")]
216 fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
217 crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
218 }
219 #[cfg(feature = "vo_bit")]
220 fn find_object_from_internal_pointer(
221 &self,
222 ptr: Address,
223 max_search_bytes: usize,
224 ) -> Option<ObjectReference> {
225 let search_bytes = usize::min(super::MAX_IMMIX_OBJECT_SIZE, max_search_bytes);
227 crate::util::metadata::vo_bit::find_object_from_internal_pointer::<VM>(ptr, search_bytes)
228 }
229 fn sft_trace_object(
230 &self,
231 _queue: &mut OptionObjectQueue,
232 _object: ObjectReference,
233 _worker: GCWorkerMutRef,
234 ) -> ObjectReference {
235 panic!("We do not use SFT to trace objects for Immix. sft_trace_object() cannot be used.")
236 }
237
238 fn debug_print_object_info(&self, object: ObjectReference) {
239 println!("marked = {}", self.is_marked(object));
240 if !self.rc_enabled {
243 println!(
244 "line marked = {}",
245 Line::from_unaligned_address(object.to_raw_address()).is_marked(self.mark_state)
246 );
247 }
248 println!(
249 "block state = {:?}",
250 Block::from_unaligned_address(object.to_raw_address()).get_state()
251 );
252 object_forwarding::debug_print_object_forwarding_info::<VM>(object);
253 self.common.debug_print_object_global_info(object);
254 }
255}
256
257impl<VM: VMBinding> Space<VM> for ImmixSpace<VM> {
258 fn as_space(&self) -> &dyn Space<VM> {
259 self
260 }
261 fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
262 self
263 }
264 fn get_page_resource(&self) -> &dyn PageResource<VM> {
265 &self.pr
266 }
267 fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
268 Some(&mut self.pr)
269 }
270 fn common(&self) -> &CommonSpace<VM> {
271 &self.common
272 }
273 fn initialize_sft(&self, sft_map: &mut dyn SFTMap) {
274 self.common().initialize_sft(self.as_sft(), sft_map)
275 }
276 fn release_multiple_pages(&mut self, _start: Address) {
277 panic!("immixspace only releases pages enmasse")
278 }
279 fn set_copy_for_sft_trace(&mut self, _semantics: Option<CopySemantics>) {
280 panic!("We do not use SFT to trace objects for Immix. set_copy_context() cannot be used.")
281 }
282
283 fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
284 object_enum::enumerate_blocks_from_chunk_map::<Block>(enumerator, &self.chunk_map);
285 }
286
287 fn clear_side_log_bits(&self) {
288 warn!("ImmixSpace::clear_side_log_bits is single-treaded. Consider clearing side metadata in per-chunk work packets.");
290
291 let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
292 for chunk in self.chunk_map.all_chunks() {
293 log_bit.bzero_metadata(chunk.start(), Chunk::BYTES);
294 }
295 }
296
297 fn set_side_log_bits(&self) {
298 warn!("ImmixSpace::set_side_log_bits is single-treaded. Consider setting side metadata in per-chunk work packets.");
300
301 let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
302 for chunk in self.chunk_map.all_chunks() {
303 log_bit.bset_metadata(chunk.start(), Chunk::BYTES);
304 }
305 }
306}
307
308impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for ImmixSpace<VM> {
309 fn trace_object<Q: ObjectQueue, const KIND: TraceKind>(
310 &self,
311 queue: &mut Q,
312 object: ObjectReference,
313 copy: Option<CopySemantics>,
314 worker: &mut GCWorker<VM>,
315 ) -> ObjectReference {
316 if KIND == TRACE_KIND_TRANSITIVE_PIN {
317 self.trace_object_without_moving(queue, object)
318 } else if KIND == TRACE_KIND_DEFRAG {
319 if Block::containing(object).is_defrag_source() {
320 debug_assert!(self.in_defrag());
321 debug_assert!(
322 !crate::plan::is_nursery_gc(worker.mmtk.get_plan()),
323 "Calling PolicyTraceObject on Immix in nursery GC"
324 );
325 self.trace_object_with_opportunistic_copy(
326 queue,
327 object,
328 copy.unwrap(),
329 worker,
330 false,
332 )
333 } else {
334 self.trace_object_without_moving(queue, object)
335 }
336 } else if KIND == TRACE_KIND_FAST {
337 self.trace_object_without_moving(queue, object)
338 } else {
339 unreachable!()
340 }
341 }
342
343 fn post_scan_object(&self, object: ObjectReference) {
344 if super::MARK_LINE_AT_SCAN_TIME && !super::BLOCK_ONLY {
345 debug_assert!(self.in_space(object));
346 self.mark_lines(object);
347 }
348 }
349
350 #[allow(clippy::if_same_then_else)] fn may_move_objects<const KIND: TraceKind>() -> bool {
352 if KIND == TRACE_KIND_DEFRAG {
353 true
354 } else if KIND == TRACE_KIND_FAST || KIND == TRACE_KIND_TRANSITIVE_PIN {
355 false
356 } else if KIND == DEFAULT_TRACE {
357 false
363 } else {
364 unreachable!()
365 }
366 }
367}
368
369impl<VM: VMBinding> ImmixSpace<VM> {
370 #[allow(unused)]
371 const UNMARKED_STATE: u8 = 0;
372 const MARKED_STATE: u8 = 1;
373
374 fn side_metadata_specs(rc_enabled: bool) -> Vec<SideMetadataSpec> {
376 if rc_enabled {
377 let meta = vec![
378 MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
379 MetadataSpec::OnSide(Block::MARK_TABLE),
380 *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
381 *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
382 *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
383 MetadataSpec::OnSide(crate::util::rc::RC_STRADDLE_LINES),
384 MetadataSpec::OnSide(Block::LOG_TABLE),
385 MetadataSpec::OnSide(Block::NURSERY_PROMOTION_STATE_TABLE),
386 MetadataSpec::OnSide(IX_LINE_REUSE_COUNT),
387 ];
388 return metadata::extract_side_metadata(&meta);
389 }
390 metadata::extract_side_metadata(&if super::BLOCK_ONLY {
391 vec![
392 MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
393 MetadataSpec::OnSide(Block::MARK_TABLE),
394 *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
395 *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
396 *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
397 #[cfg(feature = "object_pinning")]
398 *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
399 ]
400 } else {
401 vec![
402 MetadataSpec::OnSide(Line::MARK_TABLE),
403 MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
404 MetadataSpec::OnSide(Block::MARK_TABLE),
405 *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
406 *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
407 *VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
408 #[cfg(feature = "object_pinning")]
409 *VM::VMObjectModel::LOCAL_PINNING_BIT_SPEC,
410 ]
411 })
412 }
413
414 pub fn new(
415 args: crate::policy::space::PlanCreateSpaceArgs<VM>,
416 mut space_args: ImmixSpaceArgs,
417 ) -> Self {
418 if args.unlog_traced_object {
419 assert!(
420 args.constraints.needs_log_bit,
421 "Invalid args when the plan does not use log bit"
422 );
423 }
424
425 if cfg!(feature = "immix_non_moving") && !space_args.never_move_objects {
427 info!(
428 "Overriding never_moves_objects for Immix Space {}, as the immix_non_moving feature is set. Block size: 2^{}",
429 args.name,
430 Block::LOG_BYTES,
431 );
432 space_args.never_move_objects = true;
433 }
434
435 if super::BLOCK_ONLY {
437 assert!(
438 space_args.never_move_objects,
439 "Block-only immix must not move objects"
440 );
441 }
442 assert!(
443 Block::LINES / 2 <= u8::MAX as usize - 2,
444 "Number of lines in a block should not exceed BlockState::MARK_MARKED"
445 );
446
447 #[cfg(feature = "vo_bit")]
450 if !args.constraints.rc_enabled {
451 vo_bit::helper::validate_config::<VM>();
452 }
453
454 let vm_map = args.vm_map;
455 let scheduler = args.scheduler.clone();
456 let rc_enabled = args.constraints.rc_enabled;
457 let common = CommonSpace::new(args.into_policy_args(
458 true,
459 false,
460 Self::side_metadata_specs(rc_enabled),
461 ));
462 let space_index = common.descriptor.get_index();
463 ImmixSpace {
464 pr: if common.vmrequest.is_discontiguous() {
465 BlockPageResource::new_discontiguous(
466 Block::LOG_PAGES,
467 vm_map,
468 scheduler.num_workers(),
469 )
470 } else {
471 BlockPageResource::new_contiguous(
472 Block::LOG_PAGES,
473 common.start,
474 common.extent,
475 vm_map,
476 scheduler.num_workers(),
477 )
478 },
479 common,
480 chunk_map: ChunkMap::new(space_index),
481 line_mark_state: AtomicU8::new(Line::RESET_MARK_STATE),
482 line_unavail_state: AtomicU8::new(Line::RESET_MARK_STATE),
483 lines_consumed: AtomicUsize::new(0),
484 reused_lines_consumed: AtomicUsize::new(0),
485 reusable_blocks: ReusableBlockPool::new(scheduler.num_workers()),
486 defrag: Defrag::default(),
487 mark_state: Self::MARKED_STATE,
489 scheduler,
490 space_args,
491 hooks: OnceLock::new(),
492 rc_enabled,
493 is_end_of_satb_or_full_gc: false,
494 rc: RefCountHelper::NEW,
495 }
496 }
497
498 pub fn flush_page_resource(&self) {
500 if !self.rc_enabled {
502 self.reusable_blocks.flush_all();
503 }
504 #[cfg(target_pointer_width = "64")]
505 self.pr.flush_all()
506 }
507
508 pub fn defrag_headroom_pages(&self) -> usize {
510 self.defrag.defrag_headroom_pages(self)
511 }
512
513 pub fn in_defrag(&self) -> bool {
515 self.defrag.in_defrag()
516 }
517
518 pub fn decide_whether_to_defrag(
520 &self,
521 emergency_collection: bool,
522 collect_whole_heap: bool,
523 collection_attempts: usize,
524 user_triggered_collection: bool,
525 full_heap_system_gc: bool,
526 ) -> bool {
527 self.defrag.decide_whether_to_defrag(
528 self.is_defrag_enabled(),
529 emergency_collection,
530 collect_whole_heap,
531 collection_attempts,
532 user_triggered_collection,
533 self.reusable_blocks.len() == 0,
534 full_heap_system_gc,
535 self.rc_enabled,
536 *self.common.options.immix_always_defrag,
537 );
538 self.defrag.in_defrag()
539 }
540
541 pub fn scheduler(&self) -> &GCWorkScheduler<VM> {
543 &self.scheduler
544 }
545
546 pub fn install_hooks(&self, hooks: &'static dyn ImmixHooks<VM>) {
548 self.hooks
549 .set(hooks)
550 .unwrap_or_else(|_| panic!("ImmixSpace::install_hooks called more than once"));
551 }
552
553 fn hooks(&self) -> Option<&'static dyn ImmixHooks<VM>> {
554 self.hooks.get().copied()
555 }
556
557 pub fn prepare_rc(&mut self, pause: Pause) {
558 if pause == Pause::Full || pause == Pause::InitialMark {
560 if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_on_side() {
562 self.mark_state = Self::MARKED_STATE;
563 } else {
564 unimplemented!("cyclic mark bits is not supported at the moment");
566 }
567 }
568 if pause != Pause::RefCount {
570 if pause == Pause::Full {
571 for w in &self.scheduler().worker_group.workers_shared {
574 let result = w.designated_work.push(Box::new(PrepareCollector));
575 debug_assert!(result.is_ok());
576 }
577 }
578 self.flush_page_resource();
579 }
580 if pause == Pause::FinalMark || pause == Pause::Full {
581 self.is_end_of_satb_or_full_gc = true;
582 }
583 }
584
585 pub fn release_rc(&mut self) {
586 self.flush_page_resource();
587 self.rc.reset_inc_buffer_size();
588 self.is_end_of_satb_or_full_gc = false;
589 self.reused_lines_consumed.store(0, Ordering::Relaxed);
590 }
591
592 pub(crate) fn prepare(
593 &mut self,
594 major_gc: bool,
595 plan_stats: Option<StatsForDefrag>,
596 unlog_bits_op: UnlogBitsOperation,
597 ) {
598 debug_assert!(!self.rc_enabled);
601
602 if major_gc {
603 if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_on_side() {
605 self.mark_state = Self::MARKED_STATE;
606 } else {
607 unimplemented!("cyclic mark bits is not supported at the moment");
609 }
610
611 if self.is_defrag_enabled() {
613 self.defrag.prepare(self, plan_stats.unwrap());
614 }
615
616 let threshold = self.defrag.defrag_spill_threshold.load(Ordering::Acquire);
618 let space = unsafe { &*(self as *const Self) };
620 let work_packets = self.chunk_map.generate_tasks(|chunk| {
621 Box::new(PrepareBlockState {
622 space,
623 chunk,
624 defrag_threshold: if space.in_defrag() {
625 Some(threshold)
626 } else {
627 None
628 },
629 unlog_bits_op,
630 })
631 });
632 self.scheduler().work_buckets[WorkBucketStage::Prepare].bulk_add(work_packets);
633
634 if !super::BLOCK_ONLY {
635 self.line_mark_state.fetch_add(1, Ordering::AcqRel);
636 if self.line_mark_state.load(Ordering::Acquire) > Line::MAX_MARK_STATE {
637 self.line_mark_state
638 .store(Line::RESET_MARK_STATE, Ordering::Release);
639 }
640 }
641 }
642
643 #[cfg(feature = "vo_bit")]
644 if vo_bit::helper::need_to_clear_vo_bits_before_tracing::<VM>() {
645 let maybe_scope = if major_gc {
646 Some(VOBitsClearingScope::FullGC)
649 } else if self.space_args.mixed_age {
650 if super::BLOCK_ONLY {
654 Some(VOBitsClearingScope::BlockOnly)
657 } else {
658 let line_mark_state = self.line_mark_state.load(Ordering::SeqCst);
661 Some(VOBitsClearingScope::Line {
662 state: line_mark_state,
663 })
664 }
665 } else {
666 None
669 };
670
671 if let Some(scope) = maybe_scope {
672 let work_packets = self
673 .chunk_map
674 .generate_tasks(|chunk| Box::new(ClearVOBitsAfterPrepare { chunk, scope }));
675 self.scheduler.work_buckets[WorkBucketStage::ClearVOBits].bulk_add(work_packets);
676 }
677 }
678 }
679
680 pub(crate) fn release(&mut self, major_gc: bool, unlog_bits_op: UnlogBitsOperation) {
682 debug_assert!(!self.rc_enabled);
683 if major_gc {
684 if !super::BLOCK_ONLY {
686 self.line_unavail_state.store(
687 self.line_mark_state.load(Ordering::Acquire),
688 Ordering::Release,
689 );
690 }
691 }
692 if !super::BLOCK_ONLY {
694 self.reusable_blocks.reset();
695 }
696 let work_packets = self.generate_sweep_tasks(unlog_bits_op);
698 self.scheduler().work_buckets[WorkBucketStage::Release].bulk_add(work_packets);
699
700 self.lines_consumed.store(0, Ordering::Relaxed);
701 }
702
703 pub fn end_of_gc(&mut self) -> bool {
706 let did_defrag = self.defrag.in_defrag();
707 if self.is_defrag_enabled() {
708 self.defrag.reset_in_defrag();
709 }
710 did_defrag
711 }
712
713 fn generate_sweep_tasks(&self, unlog_bits_op: UnlogBitsOperation) -> Vec<Box<dyn GCWork<VM>>> {
715 self.defrag.mark_histograms.lock().clear();
716 let space = unsafe { &*(self as *const Self) };
718 let epilogue = Arc::new(FlushPageResource {
719 space,
720 counter: AtomicUsize::new(0),
721 });
722 let tasks = self.chunk_map.generate_tasks(|chunk| {
723 Box::new(SweepChunk {
724 space,
725 chunk,
726 unlog_bits_op,
727 epilogue: epilogue.clone(),
728 })
729 });
730 epilogue.counter.store(tasks.len(), Ordering::SeqCst);
731 tasks
732 }
733
734 pub fn release_block(&self, block: Block, zero_unlog_table: bool) {
736 if zero_unlog_table {
737 block.clear_field_unlog_table::<VM>();
738 }
739 block.deinit(self);
740 self.pr.release_block(block);
741 }
742
743 pub fn get_clean_block(
745 &self,
746 tls: VMThread,
747 copy: bool,
748 alloc_options: AllocationOptions,
749 ) -> Option<Block> {
750 let block_address = self.acquire(tls, Block::PAGES, alloc_options);
751 if block_address.is_zero() {
752 return None;
753 }
754 let block = Block::from_aligned_address(block_address);
755 if !self.rc_enabled || self.defrag.in_defrag() {
756 self.defrag.notify_new_clean_block(copy);
757 }
758 if let Some(hooks) = self.hooks() {
759 hooks.on_clean_block_acquired(block, copy);
760 }
761 block.init(copy, false, self);
762 self.chunk_map.set_allocated(block.chunk(), true);
763 if !self.rc_enabled {
764 self.lines_consumed
765 .fetch_add(Block::LINES, Ordering::SeqCst);
766 }
767 Some(block)
768 }
769
770 pub fn get_reusable_block(&self, copy: bool) -> Option<Block> {
772 if super::BLOCK_ONLY {
773 return None;
774 }
775 loop {
776 let block = self.reusable_blocks.pop()?;
777 if copy && block.is_defrag_source() {
779 continue;
780 }
781 if self.rc_enabled {
782 if LXR_MATURE_EVACUATION && block.is_defrag_source() {
783 continue;
784 }
785 if !block.get_state().is_reusable() {
789 continue;
790 }
791 if !block.attempt_mutator_reuse() {
792 continue;
793 }
794 if let Some(hooks) = self.hooks() {
795 hooks.on_reusable_block_acquired(block, copy);
796 }
797 } else {
798 let lines_delta = match block.get_state() {
800 BlockState::Reusable { unavailable_lines } => {
801 Block::LINES - unavailable_lines as usize
802 }
803 BlockState::Unmarked => Block::LINES,
804 _ => unreachable!("{:?} {:?}", block, block.get_state()),
805 };
806 self.lines_consumed.fetch_add(lines_delta, Ordering::SeqCst);
807 }
808
809 block.init(copy, true, self);
810 return Some(block);
811 }
812 }
813
814 pub fn trace_object_without_moving_rc(
815 &self,
816 queue: &mut impl ObjectQueue,
817 object: ObjectReference,
818 ) -> ObjectReference {
819 if self.attempt_mark(object) {
820 let addr = object.to_raw_address().as_usize();
821 let straddle = if (addr & 0b11110000) == 0 {
822 self.rc.object_is_in_straddle_line_no_rc_check(object)
823 } else {
824 false
825 };
826 if !straddle {
827 queue.enqueue(object);
828 }
829 }
830 object
831 }
832
833 pub fn trace_object_without_moving(
835 &self,
836 queue: &mut impl ObjectQueue,
837 object: ObjectReference,
838 ) -> ObjectReference {
839 if LXR_MATURE_EVACUATION {
841 debug_assert!(!self.rc_enabled);
842 }
843
844 #[cfg(feature = "vo_bit")]
845 if !self.rc_enabled {
846 vo_bit::helper::on_trace_object::<VM>(object);
849 }
850
851 if self.attempt_mark(object) {
852 if self.rc_enabled {
853 let straddle = self.rc.object_is_in_straddle_line_no_rc_check(object);
854 if straddle {
855 return object;
856 }
857 } else {
858 if !super::BLOCK_ONLY {
860 if !super::MARK_LINE_AT_SCAN_TIME {
861 self.mark_lines(object);
862 }
863 } else {
864 let block = Block::containing(object);
865 let state = block.get_state();
866 if state != BlockState::Nursery && state != BlockState::Marked {
867 block.set_state(BlockState::Marked);
868 }
869 }
870 }
871
872 #[cfg(feature = "vo_bit")]
873 if !self.rc_enabled {
874 vo_bit::helper::on_object_marked::<VM>(object);
877 }
878
879 queue.enqueue(object);
881 if !self.rc_enabled {
882 self.unlog_object_if_needed(object);
883 }
884 return object;
885 }
886 object
887 }
888
889 #[allow(clippy::assertions_on_constants)]
891 pub fn trace_object_with_opportunistic_copy(
892 &self,
893 queue: &mut impl ObjectQueue,
894 object: ObjectReference,
895 semantics: CopySemantics,
896 worker: &mut GCWorker<VM>,
897 nursery_collection: bool,
898 ) -> ObjectReference {
899 debug_assert!(!self.rc_enabled);
902
903 let copy_context = worker.get_copy_context_mut();
904 debug_assert!(!super::BLOCK_ONLY);
905
906 #[cfg(feature = "vo_bit")]
907 vo_bit::helper::on_trace_object::<VM>(object);
908
909 let forwarding_status = object_forwarding::attempt_to_forward::<VM>(object);
910 if object_forwarding::state_is_forwarded_or_being_forwarded(forwarding_status) {
911 #[allow(clippy::let_and_return)]
915 let new_object =
916 object_forwarding::spin_and_get_forwarded_object::<VM>(object, forwarding_status);
917 #[cfg(debug_assertions)]
918 {
919 if new_object == object {
920 debug_assert!(
921 self.is_marked(object) || self.defrag.space_exhausted() || self.is_pinned(object),
922 "Forwarded object is the same as original object {} even though it should have been copied",
923 object,
924 );
925 } else {
926 debug_assert!(
928 !Block::containing(new_object).is_defrag_source(),
929 "Block {:?} containing forwarded object {} should not be a defragmentation source",
930 Block::containing(new_object),
931 new_object,
932 );
933 }
934 }
935 new_object
936 } else if self.is_marked(object) {
937 object_forwarding::clear_forwarding_bits::<VM>(object);
940 object
941 } else {
942 debug_assert!(!nursery_collection || !self.rc_enabled);
945 let new_object = if self.is_pinned(object)
946 || (!nursery_collection && self.defrag.space_exhausted())
947 {
948 self.attempt_mark(object);
949 object_forwarding::clear_forwarding_bits::<VM>(object);
950 Block::containing(object).set_state(BlockState::Marked);
951
952 #[cfg(feature = "vo_bit")]
953 vo_bit::helper::on_object_marked::<VM>(object);
954
955 if !super::MARK_LINE_AT_SCAN_TIME {
956 self.mark_lines(object);
957 }
958
959 self.unlog_object_if_needed(object);
960
961 object
962 } else {
963 #[allow(clippy::let_and_return)]
967 let new_object = object_forwarding::try_forward_object::<VM>(
968 object,
969 semantics,
970 copy_context,
971 |new_object| {
972 debug_assert!(
975 !self.common.unlog_traced_object
976 || VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
977 .is_unlogged::<VM>(new_object, Ordering::Relaxed)
978 );
979 #[cfg(feature = "vo_bit")]
980 vo_bit::helper::on_object_forwarded::<VM>(new_object);
981 },
982 )
983 .expect("to-space overflow");
984
985 new_object
986 };
987 debug_assert!({
988 let state = Block::containing(new_object).get_state();
989 state == BlockState::Marked || state == BlockState::Nursery
990 });
991
992 queue.enqueue(new_object);
993 debug_assert!(new_object.is_live());
994 new_object
995 }
996 }
997
998 pub fn rc_trace_object<Q: ObjectQueue>(
999 &self,
1000 queue: &mut Q,
1001 object: ObjectReference,
1002 semantics: CopySemantics,
1003 pause: Pause,
1004 mark: bool,
1005 worker: &mut GCWorker<VM>,
1006 ) -> ObjectReference {
1007 debug_assert!(self.rc_enabled);
1008 if LXR_MATURE_EVACUATION && Block::containing(object).is_defrag_source() {
1009 self.trace_forward_rc_mature_object(queue, object, semantics, pause, worker)
1010 } else if LXR_MATURE_EVACUATION {
1011 self.trace_mark_rc_mature_object(queue, object, pause, mark)
1012 } else {
1013 self.trace_object_without_moving(queue, object)
1014 }
1015 }
1016
1017 pub fn trace_mark_rc_mature_object(
1018 &self,
1019 queue: &mut impl ObjectQueue,
1020 object: ObjectReference,
1021 _pause: Pause,
1022 mark: bool,
1023 ) -> ObjectReference {
1024 debug_assert!(
1025 !object_forwarding::is_forwarded::<VM>(object),
1026 "object {:?} is forwarded",
1027 object
1028 );
1029 if mark && self.attempt_mark(object) {
1030 queue.enqueue(object);
1031 }
1032 object
1033 }
1034
1035 #[allow(clippy::assertions_on_constants)]
1036 pub fn trace_forward_rc_mature_object<Q: ObjectQueue>(
1037 &self,
1038 queue: &mut Q,
1039 object: ObjectReference,
1040 _semantics: CopySemantics,
1041 _pause: Pause,
1042 worker: &mut GCWorker<VM>,
1043 ) -> ObjectReference {
1044 let copy_context = worker.get_copy_context_mut();
1045 let forwarding_status = object_forwarding::attempt_to_forward::<VM>(object);
1046 if object_forwarding::state_is_forwarded_or_being_forwarded(forwarding_status) {
1047 object_forwarding::spin_and_get_forwarded_object::<VM>(object, forwarding_status)
1048 } else {
1049 let new = object_forwarding::try_forward_object::<VM>(
1051 object,
1052 CopySemantics::DefaultCopy,
1053 copy_context,
1054 |_new_object| {
1055 #[cfg(feature = "vo_bit")]
1057 vo_bit::set_vo_bit(_new_object);
1058 },
1059 )
1060 .expect("to-space overflow");
1061 if new.get_size::<VM>() > Line::BYTES {
1063 self.rc.mark_straddle_object(new);
1064 }
1065 self.rc.set(new, self.rc.count(object));
1066 self.attempt_mark(new);
1067 self.unmark(object);
1068 queue.enqueue(new);
1069 debug_assert_ne!(
1070 self.rc.count(new),
1071 0,
1072 "ERROR Invalid {:?} rc={}",
1073 new,
1074 self.rc.count(new)
1075 );
1076 new
1077 }
1078 }
1079
1080 fn unlog_object_if_needed(&self, object: ObjectReference) {
1081 debug_assert!(!self.rc_enabled);
1082 if self.common.unlog_traced_object {
1083 const_assert!(
1086 Line::BYTES
1087 >= (1
1088 << (crate::util::constants::LOG_BITS_IN_BYTE
1089 + crate::util::constants::LOG_MIN_OBJECT_SIZE))
1090 );
1091 const_assert_eq!(
1092 crate::vm::object_model::specs::VMGlobalLogBitSpec::LOG_NUM_BITS,
1093 0
1094 ); VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
1100 .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
1101 }
1102 }
1103
1104 #[allow(clippy::assertions_on_constants)]
1106 pub fn mark_lines(&self, object: ObjectReference) {
1107 debug_assert!(!super::BLOCK_ONLY);
1108 if self.rc_enabled {
1109 return;
1110 }
1111 Line::mark_lines_for_object::<VM>(object, self.line_mark_state.load(Ordering::Acquire));
1112 }
1113
1114 pub fn attempt_mark(&self, object: ObjectReference) -> bool {
1116 loop {
1117 let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
1118 object,
1119 None,
1120 Ordering::SeqCst,
1121 );
1122 if old_value == self.mark_state {
1123 return false;
1124 }
1125
1126 if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC
1127 .compare_exchange_metadata::<VM, u8>(
1128 object,
1129 old_value,
1130 self.mark_state,
1131 None,
1132 Ordering::SeqCst,
1133 Ordering::SeqCst,
1134 )
1135 .is_ok()
1136 {
1137 break;
1138 }
1139 }
1140 true
1141 }
1142
1143 pub fn unmark(&self, object: ObjectReference) -> bool {
1145 let result = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.fetch_update_metadata::<VM, u8, _>(
1146 object,
1147 Ordering::Relaxed,
1148 Ordering::Relaxed,
1149 |v| {
1150 if v != 1 {
1151 return None;
1152 }
1153 Some(0)
1154 },
1155 );
1156 result.is_ok()
1157 }
1158
1159 fn is_marked_with(&self, object: ObjectReference, mark_state: u8) -> bool {
1160 let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
1161 object,
1162 None,
1163 Ordering::SeqCst,
1164 );
1165 old_value == mark_state
1166 }
1167
1168 pub(crate) fn is_marked(&self, object: ObjectReference) -> bool {
1169 self.is_marked_with(object, self.mark_state)
1170 }
1171
1172 fn is_pinned(&self, _object: ObjectReference) -> bool {
1174 #[cfg(feature = "object_pinning")]
1175 return self.is_object_pinned(_object);
1176
1177 #[cfg(not(feature = "object_pinning"))]
1178 false
1179 }
1180
1181 #[allow(clippy::assertions_on_constants)]
1189 pub fn get_next_available_lines(&self, copy: bool, search_start: Line) -> Option<(Line, Line)> {
1190 debug_assert!(!super::BLOCK_ONLY);
1191 if self.rc_enabled {
1192 self.rc_get_next_available_lines(copy, search_start)
1193 } else {
1194 self.normal_get_next_available_lines(search_start)
1195 }
1196 }
1197
1198 #[allow(clippy::assertions_on_constants)]
1200 pub fn rc_get_next_available_lines(
1201 &self,
1202 copy: bool,
1203 search_start: Line,
1204 ) -> Option<(Line, Line)> {
1205 debug_assert!(!super::BLOCK_ONLY);
1206 debug_assert!(self.rc_enabled);
1207 let block = search_start.block();
1208 let rc_array = RCArray::of(block);
1209 let limit = Block::LINES;
1210 let first_free_cursor = {
1212 let start_cursor = search_start.get_index_within_block();
1213 let mut first_free_cursor = None;
1214 let mut find_free_line = false;
1215 for i in start_cursor..limit {
1216 if rc_array.is_dead(i) {
1217 if i == 0 {
1218 first_free_cursor = Some(i);
1219 break;
1220 } else if !find_free_line {
1221 find_free_line = true;
1225 } else {
1226 first_free_cursor = Some(i);
1227 break;
1228 }
1229 } else {
1230 find_free_line = false;
1231 }
1232 }
1233 first_free_cursor
1234 };
1235 let start = match first_free_cursor {
1236 Some(c) => c,
1237 _ => return None,
1238 };
1239 let end = {
1241 let mut cursor = start + 1;
1242 while cursor < limit {
1243 if !rc_array.is_dead(cursor) {
1244 break;
1245 }
1246 cursor += 1;
1247 }
1248 cursor
1249 };
1250 let end = if !VM::VMObjectModel::UNIFIED_OBJECT_REFERENCE_ADDRESS && end < limit {
1258 end - 1
1259 } else {
1260 end
1261 };
1262 if end <= start {
1263 let next_search_start = Line::from_aligned_address(block.start()).next_nth(start + 1);
1266 return self.rc_get_next_available_lines(copy, next_search_start);
1267 }
1268 let start = Line::from_aligned_address(block.start()).next_nth(start);
1269 let end = Line::from_aligned_address(block.start()).next_nth(end);
1270 if self.common.needs_log_bit {
1271 if !copy {
1272 Line::clear_field_unlog_table::<VM>(start..end);
1273 } else {
1274 Line::initialize_field_unlog_table_as_unlogged::<VM>(start..end);
1275 }
1276 }
1277 let num_lines = Line::steps_between(&start, &end).unwrap();
1278 if !copy {
1279 self.reused_lines_consumed
1280 .fetch_add(num_lines, Ordering::Relaxed);
1281 }
1282 if self
1283 .hooks()
1284 .is_some_and(|h| h.cm_in_progress_or_final_mark())
1285 {
1286 Line::initialize_mark_table_as_marked::<VM>(start..end);
1287 Line::inc_reuse_counts(start..end);
1288 }
1289 Some((start, end))
1290 }
1291
1292 #[allow(clippy::assertions_on_constants)]
1293 pub fn normal_get_next_available_lines(&self, search_start: Line) -> Option<(Line, Line)> {
1294 debug_assert!(!self.rc_enabled);
1295 let unavail_state = self.line_unavail_state.load(Ordering::Acquire);
1296 let current_state = self.line_mark_state.load(Ordering::Acquire);
1297 let block = search_start.block();
1298 let mark_data = block.line_mark_table();
1299 let start_cursor = search_start.get_index_within_block();
1300 let mut cursor = start_cursor;
1301 while cursor < mark_data.len() {
1303 let mark = mark_data.get(cursor);
1304 if mark != unavail_state && mark != current_state {
1305 break;
1306 }
1307 cursor += 1;
1308 }
1309 if cursor == mark_data.len() {
1310 return None;
1311 }
1312 let start = search_start.next_nth(cursor - start_cursor);
1313 while cursor < mark_data.len() {
1315 let mark = mark_data.get(cursor);
1316 if mark == unavail_state || mark == current_state {
1317 break;
1318 }
1319 cursor += 1;
1320 }
1321 let end = search_start.next_nth(cursor - start_cursor);
1322 debug_assert!(RegionIterator::<Line>::new(start, end)
1323 .all(|line| !line.is_marked(unavail_state) && !line.is_marked(current_state)));
1324 Some((start, end))
1325 }
1326
1327 pub fn is_last_gc_exhaustive(&self, did_defrag_for_last_gc: bool) -> bool {
1328 if self.is_defrag_enabled() {
1329 did_defrag_for_last_gc
1330 } else {
1331 true
1333 }
1334 }
1335
1336 pub(crate) fn get_mutator_recycled_lines_in_pages(&self) -> usize {
1337 debug_assert!(self.rc_enabled);
1338 self.reused_lines_consumed.load(Ordering::Relaxed)
1339 >> (LOG_BYTES_IN_PAGE - Line::LOG_BYTES as u8)
1340 }
1341
1342 pub(crate) fn get_pages_allocated(&self) -> usize {
1343 debug_assert!(!self.rc_enabled);
1344 self.lines_consumed.load(Ordering::Relaxed) >> (LOG_BYTES_IN_PAGE - Line::LOG_BYTES as u8)
1345 }
1346
1347 fn post_copy(&self, object: ObjectReference, _bytes: usize) {
1349 if self.rc_enabled {
1350 return;
1351 }
1352 VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.store_atomic::<VM, u8>(
1354 object,
1355 self.mark_state,
1356 None,
1357 Ordering::SeqCst,
1358 );
1359 if !super::MARK_LINE_AT_SCAN_TIME {
1361 self.mark_lines(object);
1362 }
1363 if self.common.unlog_traced_object {
1364 VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC
1365 .mark_byte_as_unlogged::<VM>(object, Ordering::Relaxed);
1366 }
1367 }
1368
1369 pub(crate) fn prefer_copy_on_nursery_gc(&self) -> bool {
1370 self.is_nursery_copy_enabled()
1371 }
1372
1373 pub(crate) fn is_nursery_copy_enabled(&self) -> bool {
1374 !self.space_args.never_move_objects && !cfg!(feature = "sticky_immix_non_moving_nursery")
1375 }
1376
1377 pub(crate) fn is_defrag_enabled(&self) -> bool {
1378 !self.space_args.never_move_objects
1379 }
1380}
1381
1382pub struct PrepareBlockState<VM: VMBinding> {
1385 #[allow(dead_code)]
1386 pub space: &'static ImmixSpace<VM>,
1387 pub chunk: Chunk,
1388 pub defrag_threshold: Option<usize>,
1389 pub unlog_bits_op: UnlogBitsOperation,
1390}
1391
1392impl<VM: VMBinding> PrepareBlockState<VM> {
1393 fn reset_object_mark(&self) {
1395 if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC {
1398 side.bzero_metadata(self.chunk.start(), Chunk::BYTES);
1399 }
1400 }
1401}
1402
1403impl<VM: VMBinding> GCWork<VM> for PrepareBlockState<VM> {
1404 fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
1405 self.reset_object_mark();
1407 for block in self.chunk.iter_region::<Block>() {
1409 let state = block.get_state();
1410 if state == BlockState::Unallocated {
1412 continue;
1413 }
1414 let is_defrag_source = if !self.space.is_defrag_enabled() {
1416 false
1418 } else if *mmtk.options.immix_defrag_every_block {
1419 true
1421 } else if let Some(defrag_threshold) = self.defrag_threshold {
1422 block.get_holes() > defrag_threshold
1424 } else {
1425 false
1427 };
1428 block.set_as_defrag_source(is_defrag_source);
1429 block.set_state(BlockState::Unmarked);
1431 debug_assert!(!block.get_state().is_reusable());
1432 debug_assert_ne!(block.get_state(), BlockState::Marked);
1433 }
1434
1435 self.unlog_bits_op
1436 .execute::<VM>(self.chunk.start(), Chunk::BYTES);
1437 }
1438}
1439
1440struct SweepChunk<VM: VMBinding> {
1442 space: &'static ImmixSpace<VM>,
1443 chunk: Chunk,
1444 unlog_bits_op: UnlogBitsOperation,
1445 epilogue: Arc<FlushPageResource<VM>>,
1447}
1448
1449impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
1450 fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
1451 assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
1452
1453 let mut histogram = self.space.defrag.new_histogram();
1454 let line_mark_state = if super::BLOCK_ONLY {
1455 None
1456 } else {
1457 Some(self.space.line_mark_state.load(Ordering::Acquire))
1458 };
1459 let is_moving_gc = mmtk.get_plan().current_gc_may_move_object();
1461 let is_defrag_gc = self.space.defrag.in_defrag();
1462
1463 let mut swept_blocks = 0;
1465 let mut reused_blocks = 0;
1467 let mut unreused_blocks = 0;
1469
1470 for block in self
1472 .chunk
1473 .iter_region::<Block>()
1474 .filter(|block| block.get_state() != BlockState::Unallocated)
1475 {
1476 if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC {
1484 if is_moving_gc {
1485 let objects_may_move = if is_defrag_gc {
1486 block.is_defrag_source()
1488 } else {
1489 true
1493 };
1494 if objects_may_move {
1495 side.bzero_metadata(block.start(), Block::BYTES);
1496 }
1497 }
1498 }
1499
1500 match block.sweep(self.space, &mut histogram, line_mark_state) {
1501 BlockSweepResult::Swept => swept_blocks += 1,
1502 BlockSweepResult::Reused => reused_blocks += 1,
1503 BlockSweepResult::NoReuse => unreused_blocks += 1,
1504 }
1505 }
1506
1507 probe!(
1508 mmtk,
1509 sweep_chunk_immix,
1510 swept_blocks,
1511 reused_blocks,
1512 unreused_blocks
1513 );
1514
1515 let allocated_blocks = reused_blocks + unreused_blocks;
1517
1518 if allocated_blocks == 0 {
1520 self.space.chunk_map.set_allocated(self.chunk, false)
1521 }
1522 self.space.defrag.add_completed_mark_histogram(histogram);
1523
1524 self.unlog_bits_op
1525 .execute::<VM>(self.chunk.start(), Chunk::BYTES);
1526
1527 self.epilogue.finish_one_work_packet();
1528 }
1529}
1530
1531struct FlushPageResource<VM: VMBinding> {
1533 space: &'static ImmixSpace<VM>,
1534 counter: AtomicUsize,
1535}
1536
1537impl<VM: VMBinding> FlushPageResource<VM> {
1538 fn finish_one_work_packet(&self) {
1540 if 1 == self.counter.fetch_sub(1, Ordering::SeqCst) {
1541 self.space.flush_page_resource()
1544 }
1545 }
1546}
1547
1548impl<VM: VMBinding> Drop for FlushPageResource<VM> {
1549 fn drop(&mut self) {
1550 epilogue::debug_assert_counter_zero(&self.counter, "FlushPageResource::counter");
1551 }
1552}
1553
1554use crate::policy::copy_context::PolicyCopyContext;
1555use crate::util::alloc::Allocator;
1556use crate::util::alloc::ImmixAllocator;
1557
1558pub struct ImmixCopyContext<VM: VMBinding> {
1561 allocator: ImmixAllocator<VM>,
1562}
1563
1564impl<VM: VMBinding> PolicyCopyContext for ImmixCopyContext<VM> {
1565 type VM = VM;
1566
1567 fn prepare(&mut self) {
1568 self.allocator.reset();
1569 }
1570 fn release(&mut self) {
1571 self.allocator.reset();
1572 }
1573 fn alloc_copy(
1574 &mut self,
1575 _original: ObjectReference,
1576 bytes: usize,
1577 align: usize,
1578 offset: usize,
1579 ) -> Address {
1580 self.allocator.alloc(bytes, align, offset)
1581 }
1582 fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1583 self.get_space().post_copy(obj, bytes)
1584 }
1585}
1586
1587impl<VM: VMBinding> ImmixCopyContext<VM> {
1588 pub(crate) fn new(
1589 tls: VMWorkerThread,
1590 context: Arc<AllocatorContext<VM>>,
1591 space: &'static ImmixSpace<VM>,
1592 ) -> Self {
1593 ImmixCopyContext {
1594 allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1595 }
1596 }
1597
1598 fn get_space(&self) -> &ImmixSpace<VM> {
1599 self.allocator.immix_space()
1600 }
1601}
1602
1603pub struct ImmixHybridCopyContext<VM: VMBinding> {
1607 copy_allocator: ImmixAllocator<VM>,
1608 defrag_allocator: ImmixAllocator<VM>,
1609}
1610
1611impl<VM: VMBinding> PolicyCopyContext for ImmixHybridCopyContext<VM> {
1612 type VM = VM;
1613
1614 fn prepare(&mut self) {
1615 self.copy_allocator.reset();
1616 self.defrag_allocator.reset();
1617 }
1618 fn release(&mut self) {
1619 self.copy_allocator.reset();
1620 self.defrag_allocator.reset();
1621 }
1622 fn alloc_copy(
1623 &mut self,
1624 _original: ObjectReference,
1625 bytes: usize,
1626 align: usize,
1627 offset: usize,
1628 ) -> Address {
1629 if self.get_space().in_defrag() {
1630 self.defrag_allocator.alloc(bytes, align, offset)
1631 } else {
1632 self.copy_allocator.alloc(bytes, align, offset)
1633 }
1634 }
1635 fn post_copy(&mut self, obj: ObjectReference, bytes: usize) {
1636 self.get_space().post_copy(obj, bytes)
1637 }
1638}
1639
1640impl<VM: VMBinding> ImmixHybridCopyContext<VM> {
1641 pub(crate) fn new(
1642 tls: VMWorkerThread,
1643 context: Arc<AllocatorContext<VM>>,
1644 space: &'static ImmixSpace<VM>,
1645 ) -> Self {
1646 ImmixHybridCopyContext {
1647 copy_allocator: ImmixAllocator::new(tls.0, Some(space), context.clone(), true),
1648 defrag_allocator: ImmixAllocator::new(tls.0, Some(space), context, true),
1649 }
1650 }
1651
1652 fn get_space(&self) -> &ImmixSpace<VM> {
1653 debug_assert_eq!(
1655 self.defrag_allocator.immix_space().common().descriptor,
1656 self.copy_allocator.immix_space().common().descriptor
1657 );
1658 self.defrag_allocator.immix_space()
1660 }
1661}
1662
1663#[cfg(feature = "vo_bit")]
1664#[derive(Clone, Copy)]
1665enum VOBitsClearingScope {
1666 FullGC,
1668 BlockOnly,
1670 Line { state: u8 },
1672}
1673
1674#[cfg(feature = "vo_bit")]
1676struct ClearVOBitsAfterPrepare {
1677 chunk: Chunk,
1678 scope: VOBitsClearingScope,
1679}
1680
1681#[cfg(feature = "vo_bit")]
1682impl<VM: VMBinding> GCWork<VM> for ClearVOBitsAfterPrepare {
1683 fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
1684 match self.scope {
1685 VOBitsClearingScope::FullGC => {
1686 vo_bit::bzero_vo_bit(self.chunk.start(), Chunk::BYTES);
1687 }
1688 VOBitsClearingScope::BlockOnly => {
1689 self.clear_blocks(None);
1690 }
1691 VOBitsClearingScope::Line { state } => {
1692 self.clear_blocks(Some(state));
1693 }
1694 }
1695 }
1696}
1697
1698#[cfg(feature = "vo_bit")]
1699impl ClearVOBitsAfterPrepare {
1700 fn clear_blocks(&mut self, line_mark_state: Option<u8>) {
1701 for block in self
1702 .chunk
1703 .iter_region::<Block>()
1704 .filter(|block| block.get_state() != BlockState::Unallocated)
1705 {
1706 block.clear_vo_bits_for_unmarked_regions(line_mark_state);
1707 }
1708 }
1709}