1use std::sync::{
2 atomic::{AtomicUsize, Ordering},
3 Arc,
4};
5
6use crate::{
7 policy::{marksweepspace::native_ms::*, sft::GCWorkerMutRef},
8 scheduler::{GCWorkScheduler, GCWorker, WorkBucketStage},
9 util::{
10 copy::CopySemantics,
11 epilogue,
12 heap::{BlockPageResource, PageResource},
13 metadata::{self, side_metadata::SideMetadataSpec, MetadataSpec},
14 object_enum::{self, ObjectEnumerator},
15 ObjectReference,
16 },
17 vm::{ActivePlan, VMBinding},
18};
19
20#[cfg(feature = "vo_bit")]
21use crate::util::Address;
22
23use crate::plan::tracing::{ObjectQueue, OptionObjectQueue};
24use crate::policy::sft::SFT;
25use crate::policy::space::{CommonSpace, Space};
26use crate::util::alloc::allocator::AllocationOptions;
27use crate::util::constants::LOG_BYTES_IN_PAGE;
28use crate::util::heap::chunk_map::*;
29use crate::util::linear_scan::Region;
30use crate::util::VMThread;
31use crate::vm::ObjectModel;
32use crate::vm::Scanning;
33use std::sync::Mutex;
34
35pub enum BlockAcquireResult {
39 Exhausted,
40 Fresh(Block),
42 AbandonedAvailable(Block),
44 AbandonedUnswept(Block),
46}
47
48pub struct MarkSweepSpace<VM: VMBinding> {
70 pub common: CommonSpace<VM>,
71 pr: BlockPageResource<VM, Block>,
72 chunk_map: ChunkMap,
74 scheduler: Arc<GCWorkScheduler<VM>>,
76 abandoned: Mutex<AbandonedBlockLists>,
80 abandoned_in_gc: Mutex<AbandonedBlockLists>,
85 pending_release_packets: AtomicUsize,
88}
89
90unsafe impl<VM: VMBinding> Sync for MarkSweepSpace<VM> {}
91
92pub struct AbandonedBlockLists {
93 pub available: BlockLists,
94 pub unswept: BlockLists,
95 pub consumed: BlockLists,
96}
97
98impl AbandonedBlockLists {
99 fn new() -> Self {
100 Self {
101 available: new_empty_block_lists(),
102 unswept: new_empty_block_lists(),
103 consumed: new_empty_block_lists(),
104 }
105 }
106
107 fn sweep_later<VM: VMBinding>(&mut self, space: &MarkSweepSpace<VM>) {
108 for i in 0..MI_BIN_FULL {
109 self.available[i].release_blocks(space);
111 self.consumed[i].release_blocks(space);
112 if cfg!(not(feature = "eager_sweeping")) {
113 self.unswept[i].release_blocks(space);
114 } else {
115 debug_assert!(self.unswept[i].is_empty());
117 }
118
119 if cfg!(not(feature = "eager_sweeping")) {
125 self.unswept[i].append(&mut self.available[i]);
126 self.unswept[i].append(&mut self.consumed[i]);
127 }
128 }
129 }
130
131 fn recycle_blocks(&mut self) {
132 for i in 0..MI_BIN_FULL {
133 for block in self.consumed[i].iter() {
134 if block.has_free_cells() {
135 self.consumed[i].remove(block);
136 self.available[i].push(block);
137 }
138 }
139 }
140 }
141
142 fn merge(&mut self, other: &mut Self) {
143 for i in 0..MI_BIN_FULL {
144 self.available[i].append(&mut other.available[i]);
145 self.unswept[i].append(&mut other.unswept[i]);
146 self.consumed[i].append(&mut other.consumed[i]);
147 }
148 }
149
150 #[cfg(debug_assertions)]
151 fn assert_empty(&self) {
152 for i in 0..MI_BIN_FULL {
153 assert!(self.available[i].is_empty());
154 assert!(self.unswept[i].is_empty());
155 assert!(self.consumed[i].is_empty());
156 }
157 }
158}
159
160impl<VM: VMBinding> SFT for MarkSweepSpace<VM> {
161 fn name(&self) -> &'static str {
162 self.common.name
163 }
164
165 fn is_live(&self, object: crate::util::ObjectReference) -> bool {
166 VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_marked::<VM>(object, Ordering::SeqCst)
167 }
168
169 #[cfg(feature = "object_pinning")]
170 fn pin_object(&self, _object: ObjectReference) -> bool {
171 false
172 }
173
174 #[cfg(feature = "object_pinning")]
175 fn unpin_object(&self, _object: ObjectReference) -> bool {
176 false
177 }
178
179 #[cfg(feature = "object_pinning")]
180 fn is_object_pinned(&self, _object: ObjectReference) -> bool {
181 false
182 }
183
184 fn is_movable(&self) -> bool {
185 false
186 }
187
188 #[cfg(feature = "sanity")]
189 fn is_sane(&self) -> bool {
190 true
191 }
192
193 fn initialize_object_metadata(&self, _object: crate::util::ObjectReference, _bytes: usize) {
194 #[cfg(feature = "vo_bit")]
195 crate::util::metadata::vo_bit::set_vo_bit(_object);
196 }
197
198 #[cfg(feature = "vo_bit")]
199 fn is_mmtk_object(&self, addr: Address) -> Option<ObjectReference> {
200 crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
201 }
202
203 #[cfg(feature = "vo_bit")]
204 fn find_object_from_internal_pointer(
205 &self,
206 ptr: Address,
207 max_search_bytes: usize,
208 ) -> Option<ObjectReference> {
209 let search_bytes = usize::min(MAX_OBJECT_SIZE, max_search_bytes);
211 crate::util::metadata::vo_bit::find_object_from_internal_pointer::<VM>(ptr, search_bytes)
212 }
213
214 fn sft_trace_object(
215 &self,
216 queue: &mut OptionObjectQueue,
217 object: ObjectReference,
218 _worker: GCWorkerMutRef,
219 ) -> ObjectReference {
220 self.trace_object(queue, object)
221 }
222}
223
224impl<VM: VMBinding> Space<VM> for MarkSweepSpace<VM> {
225 fn as_space(&self) -> &dyn Space<VM> {
226 self
227 }
228
229 fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
230 self
231 }
232
233 fn get_page_resource(&self) -> &dyn crate::util::heap::PageResource<VM> {
234 &self.pr
235 }
236
237 fn maybe_get_page_resource_mut(&mut self) -> Option<&mut dyn PageResource<VM>> {
238 Some(&mut self.pr)
239 }
240
241 fn initialize_sft(&self, sft_map: &mut dyn crate::policy::sft_map::SFTMap) {
242 self.common().initialize_sft(self.as_sft(), sft_map)
243 }
244
245 fn common(&self) -> &CommonSpace<VM> {
246 &self.common
247 }
248
249 fn release_multiple_pages(&mut self, _start: crate::util::Address) {
250 todo!()
251 }
252
253 fn enumerate_objects(&self, enumerator: &mut dyn ObjectEnumerator) {
254 object_enum::enumerate_blocks_from_chunk_map::<Block>(enumerator, &self.chunk_map);
255 }
256
257 fn clear_side_log_bits(&self) {
258 let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
259 for chunk in self.chunk_map.all_chunks() {
260 log_bit.bzero_metadata(chunk.start(), Chunk::BYTES);
261 }
262 }
263
264 fn set_side_log_bits(&self) {
265 let log_bit = VM::VMObjectModel::GLOBAL_LOG_BIT_SPEC.extract_side_spec();
266 for chunk in self.chunk_map.all_chunks() {
267 log_bit.bset_metadata(chunk.start(), Chunk::BYTES);
268 }
269 }
270}
271
272impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for MarkSweepSpace<VM> {
273 fn trace_object<Q: ObjectQueue, const KIND: crate::policy::gc_work::TraceKind>(
274 &self,
275 queue: &mut Q,
276 object: ObjectReference,
277 _copy: Option<CopySemantics>,
278 _worker: &mut GCWorker<VM>,
279 ) -> ObjectReference {
280 self.trace_object(queue, object)
281 }
282
283 fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
284 false
285 }
286}
287
288#[allow(dead_code)]
290pub const MAX_OBJECT_SIZE: usize = crate::policy::marksweepspace::native_ms::MI_LARGE_OBJ_SIZE_MAX;
291
292impl<VM: VMBinding> MarkSweepSpace<VM> {
293 #[allow(clippy::ptr_arg)]
295 pub fn extend_global_side_metadata_specs(_specs: &mut Vec<SideMetadataSpec>) {
296 }
299
300 pub fn new(args: crate::policy::space::PlanCreateSpaceArgs<VM>) -> MarkSweepSpace<VM> {
301 let scheduler = args.scheduler.clone();
302 let vm_map = args.vm_map;
303 let is_discontiguous = args.vmrequest.is_discontiguous();
304 let local_specs = {
305 metadata::extract_side_metadata(&[
306 MetadataSpec::OnSide(Block::NEXT_BLOCK_TABLE),
307 MetadataSpec::OnSide(Block::PREV_BLOCK_TABLE),
308 MetadataSpec::OnSide(Block::FREE_LIST_TABLE),
309 MetadataSpec::OnSide(Block::SIZE_TABLE),
310 #[cfg(feature = "malloc_native_mimalloc")]
311 MetadataSpec::OnSide(Block::LOCAL_FREE_LIST_TABLE),
312 #[cfg(feature = "malloc_native_mimalloc")]
313 MetadataSpec::OnSide(Block::THREAD_FREE_LIST_TABLE),
314 MetadataSpec::OnSide(Block::BLOCK_LIST_TABLE),
315 MetadataSpec::OnSide(Block::TLS_TABLE),
316 MetadataSpec::OnSide(Block::MARK_TABLE),
317 *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
318 ])
319 };
320 let common = CommonSpace::new(args.into_policy_args(false, false, local_specs));
321 let space_index = common.descriptor.get_index();
322 MarkSweepSpace {
323 pr: if is_discontiguous {
324 BlockPageResource::new_discontiguous(
325 Block::LOG_PAGES,
326 vm_map,
327 scheduler.num_workers(),
328 )
329 } else {
330 BlockPageResource::new_contiguous(
331 Block::LOG_PAGES,
332 common.start,
333 common.extent,
334 vm_map,
335 scheduler.num_workers(),
336 )
337 },
338 common,
339 chunk_map: ChunkMap::new(space_index),
340 scheduler,
341 abandoned: Mutex::new(AbandonedBlockLists::new()),
342 abandoned_in_gc: Mutex::new(AbandonedBlockLists::new()),
343 pending_release_packets: AtomicUsize::new(0),
344 }
345 }
346
347 fn attempt_mark_non_atomic(&self, object: ObjectReference) -> bool {
350 if !VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.is_marked::<VM>(object, Ordering::SeqCst) {
351 VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.mark::<VM>(object, Ordering::SeqCst);
352 true
353 } else {
354 false
355 }
356 }
357
358 fn attempt_mark_atomic(&self, object: ObjectReference) -> bool {
360 let mark_state = 1u8;
361
362 loop {
363 let old_value = VM::VMObjectModel::LOCAL_MARK_BIT_SPEC.load_atomic::<VM, u8>(
364 object,
365 None,
366 Ordering::SeqCst,
367 );
368 if old_value == mark_state {
369 return false;
370 }
371
372 if VM::VMObjectModel::LOCAL_MARK_BIT_SPEC
373 .compare_exchange_metadata::<VM, u8>(
374 object,
375 old_value,
376 mark_state,
377 None,
378 Ordering::SeqCst,
379 Ordering::SeqCst,
380 )
381 .is_ok()
382 {
383 break;
384 }
385 }
386 true
387 }
388
389 fn attempt_mark(&self, object: ObjectReference) -> bool {
392 if VM::VMScanning::UNIQUE_OBJECT_ENQUEUING {
393 self.attempt_mark_atomic(object)
394 } else {
395 self.attempt_mark_non_atomic(object)
396 }
397 }
398
399 fn trace_object<Q: ObjectQueue>(
400 &self,
401 queue: &mut Q,
402 object: ObjectReference,
403 ) -> ObjectReference {
404 debug_assert!(
405 self.in_space(object),
406 "Cannot mark an object {} that was not alloced by free list allocator.",
407 object,
408 );
409 if self.attempt_mark(object) {
410 let block = Block::containing(object);
411 block.set_state(BlockState::Marked);
412 queue.enqueue(object);
413 }
414 object
415 }
416
417 pub fn record_new_block(&self, block: Block) {
418 block.init();
419 self.chunk_map.set_allocated(block.chunk(), true);
420 }
421
422 pub fn prepare(&mut self, _full_heap: bool) {
423 #[cfg(debug_assertions)]
424 self.abandoned_in_gc.lock().unwrap().assert_empty();
425
426 let space = unsafe { &*(self as *const Self) };
428 let work_packets = self
429 .chunk_map
430 .generate_tasks(|chunk| Box::new(PrepareChunkMap { space, chunk }));
431 self.scheduler.work_buckets[crate::scheduler::WorkBucketStage::Prepare]
432 .bulk_add(work_packets);
433 }
434
435 pub fn release(&mut self) {
436 let num_mutators = VM::VMActivePlan::number_of_mutators();
437 self.pending_release_packets
439 .store(num_mutators + 1, Ordering::SeqCst);
440
441 let space = unsafe { &*(self as *const Self) };
444 let work_packet = ReleaseMarkSweepSpace { space };
445 self.scheduler.work_buckets[crate::scheduler::WorkBucketStage::Release].add(work_packet);
446 }
447
448 pub fn end_of_gc(&mut self) {
449 epilogue::debug_assert_counter_zero(
450 &self.pending_release_packets,
451 "pending_release_packets",
452 );
453 }
454
455 pub fn release_block(&self, block: Block) {
457 self.block_clear_metadata(block);
458
459 block.deinit();
460 self.pr.release_block(block);
461 }
462
463 pub fn block_clear_metadata(&self, block: Block) {
464 for metadata_spec in Block::METADATA_SPECS {
465 metadata_spec.set_zero_atomic(block.start(), Ordering::SeqCst);
466 }
467 #[cfg(feature = "vo_bit")]
468 crate::util::metadata::vo_bit::bzero_vo_bit(block.start(), Block::BYTES);
469 }
470
471 pub fn acquire_block(
472 &self,
473 tls: VMThread,
474 size: usize,
475 align: usize,
476 alloc_options: AllocationOptions,
477 ) -> BlockAcquireResult {
478 {
479 let mut abandoned = self.abandoned.lock().unwrap();
480 let bin = mi_bin::<VM>(size, align);
481
482 {
483 let abandoned_available = &mut abandoned.available;
484 if !abandoned_available[bin].is_empty() {
485 let block = abandoned_available[bin].pop().unwrap();
486 return BlockAcquireResult::AbandonedAvailable(block);
487 }
488 }
489
490 {
491 let abandoned_unswept = &mut abandoned.unswept;
492 if !abandoned_unswept[bin].is_empty() {
493 let block = abandoned_unswept[bin].pop().unwrap();
494 return BlockAcquireResult::AbandonedUnswept(block);
495 }
496 }
497 }
498
499 let acquired = self.acquire(tls, Block::BYTES >> LOG_BYTES_IN_PAGE, alloc_options);
500 if acquired.is_zero() {
501 BlockAcquireResult::Exhausted
502 } else {
503 BlockAcquireResult::Fresh(Block::from_unaligned_address(acquired))
504 }
505 }
506
507 pub fn get_abandoned_block_lists(&self) -> &Mutex<AbandonedBlockLists> {
508 &self.abandoned
509 }
510
511 pub fn get_abandoned_block_lists_in_gc(&self) -> &Mutex<AbandonedBlockLists> {
512 &self.abandoned_in_gc
513 }
514
515 pub fn release_packet_done(&self) {
516 let old = self.pending_release_packets.fetch_sub(1, Ordering::SeqCst);
517 if old == 1 {
518 if cfg!(feature = "eager_sweeping") {
519 let work_packets = self.generate_sweep_tasks();
522 self.scheduler.work_buckets[WorkBucketStage::Release].bulk_add(work_packets);
523 } else {
524 self.recycle_blocks();
526 }
527 }
528 }
529
530 fn generate_sweep_tasks(&self) -> Vec<Box<dyn GCWork<VM>>> {
531 let space = unsafe { &*(self as *const Self) };
532 let epilogue = Arc::new(RecycleBlocks {
533 space,
534 counter: AtomicUsize::new(0),
535 });
536 let tasks = self.chunk_map.generate_tasks(|chunk| {
537 Box::new(SweepChunk {
538 space,
539 chunk,
540 epilogue: epilogue.clone(),
541 })
542 });
543 epilogue.counter.store(tasks.len(), Ordering::SeqCst);
544 tasks
545 }
546
547 fn recycle_blocks(&self) {
548 {
549 let mut abandoned = self.abandoned.try_lock().unwrap();
550 let mut abandoned_in_gc = self.abandoned_in_gc.try_lock().unwrap();
551
552 if cfg!(feature = "eager_sweeping") {
553 abandoned.recycle_blocks();
556 abandoned_in_gc.recycle_blocks();
557 }
558
559 abandoned.merge(&mut abandoned_in_gc);
560
561 #[cfg(debug_assertions)]
562 abandoned_in_gc.assert_empty();
563 }
564
565 self.pr.flush_all();
569 }
570}
571
572use crate::scheduler::GCWork;
573use crate::MMTK;
574
575struct PrepareChunkMap<VM: VMBinding> {
576 space: &'static MarkSweepSpace<VM>,
577 chunk: Chunk,
578}
579
580impl<VM: VMBinding> GCWork<VM> for PrepareChunkMap<VM> {
581 fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
582 debug_assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
583 let mut n_occupied_blocks = 0;
585 self.chunk
586 .iter_region::<Block>()
587 .filter(|block| block.get_state() != BlockState::Unallocated)
588 .for_each(|block| {
589 block.set_state(BlockState::Unmarked);
591 n_occupied_blocks += 1
593 });
594 if n_occupied_blocks == 0 {
595 self.space.chunk_map.set_allocated(self.chunk, false)
597 } else {
598 if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC {
600 side.bzero_metadata(self.chunk.start(), Chunk::BYTES);
601 }
602 }
603 }
604}
605
606struct ReleaseMarkSweepSpace<VM: VMBinding> {
607 space: &'static MarkSweepSpace<VM>,
608}
609
610impl<VM: VMBinding> GCWork<VM> for ReleaseMarkSweepSpace<VM> {
611 fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
612 {
613 let mut abandoned = self.space.abandoned.lock().unwrap();
614 abandoned.sweep_later(self.space);
615 }
616
617 self.space.release_packet_done();
618 }
619}
620
621struct SweepChunk<VM: VMBinding> {
624 space: &'static MarkSweepSpace<VM>,
625 chunk: Chunk,
626 epilogue: Arc<RecycleBlocks<VM>>,
628}
629
630impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
631 fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
632 assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
633
634 let mut allocated_blocks = 0;
636 for block in self
638 .chunk
639 .iter_region::<Block>()
640 .filter(|block| block.get_state() != BlockState::Unallocated)
641 {
642 debug_assert_eq!(block.get_state(), BlockState::Marked);
645 block.sweep::<VM>();
646 allocated_blocks += 1;
647 }
648 probe!(mmtk, sweep_chunk_ms, allocated_blocks);
649 if allocated_blocks == 0 {
651 self.space.chunk_map.set_allocated(self.chunk, false);
652 }
653 self.epilogue.finish_one_work_packet();
654 }
655}
656
657struct RecycleBlocks<VM: VMBinding> {
658 space: &'static MarkSweepSpace<VM>,
659 counter: AtomicUsize,
660}
661
662impl<VM: VMBinding> RecycleBlocks<VM> {
663 fn finish_one_work_packet(&self) {
664 if 1 == self.counter.fetch_sub(1, Ordering::SeqCst) {
665 self.space.recycle_blocks()
666 }
667 }
668}
669
670impl<VM: VMBinding> Drop for RecycleBlocks<VM> {
671 fn drop(&mut self) {
672 epilogue::debug_assert_counter_zero(&self.counter, "RecycleBlocks::counter");
673 }
674}