1- use atomic:: Atomic ;
2-
31use super :: metadata:: * ;
42use crate :: plan:: ObjectQueue ;
53use crate :: plan:: VectorObjectQueue ;
64use crate :: policy:: sft:: GCWorkerMutRef ;
75use crate :: policy:: sft:: SFT ;
86use crate :: policy:: space:: CommonSpace ;
97use crate :: scheduler:: GCWorkScheduler ;
8+ use crate :: util:: heap:: chunk_map:: Chunk ;
9+ use crate :: util:: heap:: chunk_map:: ChunkMap ;
1010use crate :: util:: heap:: gc_trigger:: GCTrigger ;
11+ use crate :: util:: heap:: space_descriptor:: SpaceDescriptor ;
1112use crate :: util:: heap:: PageResource ;
13+ use crate :: util:: linear_scan:: Region ;
1214use crate :: util:: malloc:: library:: { BYTES_IN_MALLOC_PAGE , LOG_BYTES_IN_MALLOC_PAGE } ;
1315use crate :: util:: malloc:: malloc_ms_util:: * ;
16+ use crate :: util:: metadata:: side_metadata;
1417use crate :: util:: metadata:: side_metadata:: {
1518 SideMetadataContext , SideMetadataSanity , SideMetadataSpec ,
1619} ;
@@ -30,7 +33,6 @@ use std::marker::PhantomData;
3033use std:: sync:: atomic:: AtomicU32 ;
3134use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
3235use std:: sync:: Arc ;
33- #[ cfg( debug_assertions) ]
3436use std:: sync:: Mutex ;
3537// If true, we will use a hashmap to store all the allocated memory from malloc, and use it
3638// to make sure our allocation is correct.
@@ -42,12 +44,13 @@ pub struct MallocSpace<VM: VMBinding> {
4244 phantom : PhantomData < VM > ,
4345 active_bytes : AtomicUsize ,
4446 active_pages : AtomicUsize ,
45- pub chunk_addr_min : Atomic < Address > ,
46- pub chunk_addr_max : Atomic < Address > ,
4747 metadata : SideMetadataContext ,
4848 /// Work packet scheduler
4949 scheduler : Arc < GCWorkScheduler < VM > > ,
5050 gc_trigger : Arc < GCTrigger < VM > > ,
51+ descriptor : SpaceDescriptor ,
52+ chunk_map : ChunkMap ,
53+ mmap_metadata_lock : Mutex < ( ) > ,
5154 // Mapping between allocated address and its size - this is used to check correctness.
5255 // Size will be set to zero when the memory is freed.
5356 #[ cfg( debug_assertions) ]
@@ -98,7 +101,7 @@ impl<VM: VMBinding> SFT for MallocSpace<VM> {
98101
99102 // For malloc space, we need to further check the VO bit.
100103 fn is_in_space ( & self , object : ObjectReference ) -> bool {
101- is_alloced_by_malloc ( object)
104+ self . is_alloced_by_malloc ( object)
102105 }
103106
104107 /// For malloc space, we just use the side metadata.
@@ -107,7 +110,7 @@ impl<VM: VMBinding> SFT for MallocSpace<VM> {
107110 debug_assert ! ( !addr. is_zero( ) ) ;
108111 // `addr` cannot be mapped by us. It should be mapped by the malloc library.
109112 debug_assert ! ( !addr. is_mapped( ) ) ;
110- has_object_alloced_by_malloc ( addr)
113+ self . has_object_alloced_by_malloc ( addr)
111114 }
112115
113116 #[ cfg( feature = "is_mmtk_object" ) ]
@@ -173,7 +176,7 @@ impl<VM: VMBinding> Space<VM> for MallocSpace<VM> {
173176 // We have assertions in a debug build. We allow this pattern for the release build.
174177 #[ allow( clippy:: let_and_return) ]
175178 fn in_space ( & self , object : ObjectReference ) -> bool {
176- let ret = is_alloced_by_malloc ( object) ;
179+ let ret = self . is_alloced_by_malloc ( object) ;
177180
178181 #[ cfg( debug_assertions) ]
179182 if ASSERT_ALLOCATION {
@@ -270,11 +273,6 @@ impl<VM: VMBinding> MallocSpace<VM> {
270273 if !cfg ! ( feature = "vo_bit" ) {
271274 specs. push ( crate :: util:: metadata:: vo_bit:: VO_BIT_SIDE_METADATA_SPEC ) ;
272275 }
273- // MallocSpace also need a global chunk metadata.
274- // TODO: I don't know why this is a global spec. Can we replace it with the chunk map (and the local spec used in the chunk map)?
275- // One reason could be that the address range in this space is not in our control, and it could be anywhere in the heap, thus we have
276- // to make it a global spec. I am not too sure about this.
277- specs. push ( ACTIVE_CHUNK_METADATA_SPEC ) ;
278276 }
279277
280278 pub fn new ( args : crate :: policy:: space:: PlanCreateSpaceArgs < VM > ) -> Self {
@@ -283,12 +281,12 @@ impl<VM: VMBinding> MallocSpace<VM> {
283281 // Besides we cannot meaningfully measure the live bytes vs total pages for MallocSpace.
284282 panic ! ( "count_live_bytes_in_gc is not supported by MallocSpace" ) ;
285283 }
284+ let descriptor = SpaceDescriptor :: create_descriptor ( ) ;
285+ let chunk_map = ChunkMap :: new ( descriptor. get_index ( ) ) ;
286286 MallocSpace {
287287 phantom : PhantomData ,
288288 active_bytes : AtomicUsize :: new ( 0 ) ,
289289 active_pages : AtomicUsize :: new ( 0 ) ,
290- chunk_addr_min : Atomic :: new ( Address :: MAX ) ,
291- chunk_addr_max : Atomic :: new ( Address :: ZERO ) ,
292290 metadata : SideMetadataContext {
293291 global : args. global_side_metadata_specs . clone ( ) ,
294292 local : metadata:: extract_side_metadata ( & [
@@ -299,6 +297,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
299297 } ,
300298 scheduler : args. scheduler . clone ( ) ,
301299 gc_trigger : args. gc_trigger ,
300+ descriptor,
301+ chunk_map,
302+ mmap_metadata_lock : Mutex :: new ( ( ) ) ,
302303 #[ cfg( debug_assertions) ]
303304 active_mem : Mutex :: new ( HashMap :: new ( ) ) ,
304305 #[ cfg( debug_assertions) ]
@@ -332,6 +333,15 @@ impl<VM: VMBinding> MallocSpace<VM> {
332333 }
333334 }
334335
336+ fn set_chunk_mark ( & self , start : Address , size : usize ) {
337+ let mut chunk = start. align_down ( BYTES_IN_CHUNK ) ;
338+ while chunk < start + size {
339+ self . chunk_map
340+ . set_allocated ( Chunk :: from_aligned_address ( chunk) , true ) ;
341+ chunk += BYTES_IN_CHUNK ;
342+ }
343+ }
344+
335345 /// Unset multiple pages, starting from the given address, for the given size, and decrease the active page count if we unset any page mark in the region
336346 ///
337347 /// # Safety
@@ -369,15 +379,17 @@ impl<VM: VMBinding> MallocSpace<VM> {
369379 if !address. is_zero ( ) {
370380 let actual_size = get_malloc_usable_size ( address, is_offset_malloc) ;
371381
372- // If the side metadata for the address has not yet been mapped, we will map all the side metadata for the range [address, address + actual_size).
373- if !is_meta_space_mapped ( address, actual_size) {
382+ if !self . is_meta_space_mapped ( address, actual_size) {
374383 // Map the metadata space for the associated chunk
375384 self . map_metadata_and_update_bound ( address, actual_size) ;
376385 // Update SFT
377386 assert ! ( crate :: mmtk:: SFT_MAP . has_sft_entry( address) ) ; // make sure the address is okay with our SFT map
378387 unsafe { crate :: mmtk:: SFT_MAP . update ( self , address, actual_size) } ;
379388 }
380389
390+ // Set chunk marks for the current object
391+ self . set_chunk_mark ( address, actual_size) ;
392+
381393 // Set page marks for current object
382394 self . set_page_mark ( address, actual_size) ;
383395 self . active_bytes . fetch_add ( actual_size, Ordering :: SeqCst ) ;
@@ -396,6 +408,43 @@ impl<VM: VMBinding> MallocSpace<VM> {
396408 address
397409 }
398410
411+ /// Check if metadata is mapped for a range [addr, addr + size). Metadata is mapped per chunk,
412+ /// we will go through all the chunks for [address, address + size), and check if they are mapped.
413+ /// If any of the chunks is not mapped, return false. Otherwise return true.
414+ fn is_meta_space_mapped ( & self , address : Address , size : usize ) -> bool {
415+ let mut chunk = address. align_down ( BYTES_IN_CHUNK ) ;
416+ while chunk < address + size {
417+ // is the chunk already mapped?
418+ if !self . is_meta_space_mapped_for_address ( chunk) {
419+ return false ;
420+ }
421+ chunk += BYTES_IN_CHUNK ;
422+ }
423+ true
424+ }
425+
426+ /// Check if metadata is mapped for a given address. We check with the chunk map: if the side metadata
427+ /// for the chunk map is mapped, and if it is allocated in the chunk map.
428+ fn is_meta_space_mapped_for_address ( & self , address : Address ) -> bool {
429+ let is_chunk_map_mapped = |chunk_start : Address | {
430+ const CHUNK_MAP_MAX_META_ADDRESS : Address =
431+ ChunkMap :: ALLOC_TABLE . upper_bound_address_for_contiguous ( ) ;
432+ let meta_address =
433+ side_metadata:: address_to_meta_address ( & ChunkMap :: ALLOC_TABLE , chunk_start) ;
434+ if meta_address < CHUNK_MAP_MAX_META_ADDRESS {
435+ meta_address. is_mapped ( )
436+ } else {
437+ false
438+ }
439+ } ;
440+ let chunk_start = address. align_down ( BYTES_IN_CHUNK ) ;
441+ is_chunk_map_mapped ( chunk_start)
442+ && self
443+ . chunk_map
444+ . get ( Chunk :: from_aligned_address ( chunk_start) )
445+ . is_some ( )
446+ }
447+
399448 pub fn free ( & self , addr : Address ) {
400449 let offset_malloc_bit = is_offset_malloc ( addr) ;
401450 let bytes = get_malloc_usable_size ( addr, offset_malloc_bit) ;
@@ -437,76 +486,76 @@ impl<VM: VMBinding> MallocSpace<VM> {
437486 ) ;
438487
439488 if !is_marked :: < VM > ( object, Ordering :: Relaxed ) {
440- let chunk_start = conversions:: chunk_align_down ( object. to_object_start :: < VM > ( ) ) ;
441489 set_mark_bit :: < VM > ( object, Ordering :: SeqCst ) ;
442- set_chunk_mark ( chunk_start) ;
443490 queue. enqueue ( object) ;
444491 }
445492
446493 object
447494 }
448495
449496 fn map_metadata_and_update_bound ( & self , addr : Address , size : usize ) {
450- // Map the metadata space for the range [addr, addr + size)
451- map_meta_space ( & self . metadata , addr, size, self . get_name ( ) ) ;
452-
453- // Update the bounds of the max and min chunk addresses seen -- this is used later in the sweep
454- // Lockless compare-and-swap loops perform better than a locking variant
455-
456- // Update chunk_addr_min, basing on the start of the allocation: addr.
457- {
458- let min_chunk_start = conversions:: chunk_align_down ( addr) ;
459- let mut min = self . chunk_addr_min . load ( Ordering :: Relaxed ) ;
460- while min_chunk_start < min {
461- match self . chunk_addr_min . compare_exchange_weak (
462- min,
463- min_chunk_start,
464- Ordering :: AcqRel ,
465- Ordering :: Relaxed ,
466- ) {
467- Ok ( _) => break ,
468- Err ( x) => min = x,
469- }
470- }
497+ // Acquire the lock before
498+ let _lock = self . mmap_metadata_lock . lock ( ) . unwrap ( ) ;
499+
500+ // Mmap metadata for each chunk
501+ let map_metadata_space_for_chunk = |start : Address | {
502+ debug_assert ! ( start. is_aligned_to( BYTES_IN_CHUNK ) ) ;
503+ // Attempt to map the local metadata for the policy.
504+ // Note that this might fail. For example, we have marked a chunk as active but later we freed all
505+ // the objects in it, and unset its chunk bit. However, we do not free its metadata. So for the chunk,
506+ // its chunk bit is mapped, but not marked, and all its local metadata is also mapped.
507+ let mmap_metadata_result =
508+ self . metadata
509+ . try_map_metadata_space ( start, BYTES_IN_CHUNK , self . get_name ( ) ) ;
510+ debug_assert ! (
511+ mmap_metadata_result. is_ok( ) ,
512+ "mmap sidemetadata failed for chunk_start ({})" ,
513+ start
514+ ) ;
515+ // Set the chunk mark at the end. So if we have chunk mark set, we know we have mapped side metadata
516+ // for the chunk.
517+ trace ! ( "set chunk mark bit for {}" , start) ;
518+ self . chunk_map
519+ . set_allocated ( Chunk :: from_aligned_address ( start) , true ) ;
520+ } ;
521+
522+ // Go through each chunk, and map for them.
523+ let mut chunk = conversions:: chunk_align_down ( addr) ;
524+ while chunk < addr + size {
525+ map_metadata_space_for_chunk ( chunk) ;
526+ chunk += BYTES_IN_CHUNK ;
471527 }
528+ }
472529
473- // Update chunk_addr_max, basing on the end of the allocation: addr + size.
474- {
475- let max_chunk_start = conversions:: chunk_align_down ( addr + size) ;
476- let mut max = self . chunk_addr_max . load ( Ordering :: Relaxed ) ;
477- while max_chunk_start > max {
478- match self . chunk_addr_max . compare_exchange_weak (
479- max,
480- max_chunk_start,
481- Ordering :: AcqRel ,
482- Ordering :: Relaxed ,
483- ) {
484- Ok ( _) => break ,
485- Err ( x) => max = x,
486- }
487- }
530+ /// Check if a given object was allocated by malloc
531+ pub fn is_alloced_by_malloc ( & self , object : ObjectReference ) -> bool {
532+ self . is_meta_space_mapped_for_address ( object. to_raw_address ( ) )
533+ && crate :: util:: metadata:: vo_bit:: is_vo_bit_set ( object)
534+ }
535+
536+ /// Check if there is an object allocated by malloc at the address.
537+ ///
538+ /// This function doesn't check if `addr` is aligned.
539+ /// If not, it will try to load the VO bit for the address rounded down to the metadata's granularity.
540+ #[ cfg( feature = "is_mmtk_object" ) ]
541+ pub fn has_object_alloced_by_malloc ( & self , addr : Address ) -> Option < ObjectReference > {
542+ if !self . is_meta_space_mapped_for_address ( addr) {
543+ return None ;
488544 }
545+ crate :: util:: metadata:: vo_bit:: is_vo_bit_set_for_addr ( addr)
489546 }
490547
491548 pub fn prepare ( & mut self , _full_heap : bool ) { }
492549
493550 pub fn release ( & mut self ) {
494551 use crate :: scheduler:: WorkBucketStage ;
495- let mut work_packets: Vec < Box < dyn GCWork < VM > > > = vec ! [ ] ;
496- let mut chunk = self . chunk_addr_min . load ( Ordering :: Relaxed ) ;
497- let end = self . chunk_addr_max . load ( Ordering :: Relaxed ) + BYTES_IN_CHUNK ;
498-
499- // Since only a single thread generates the sweep work packets as well as it is a Stop-the-World collector,
500- // we can assume that the chunk mark metadata is not being accessed by anything else and hence we use
501- // non-atomic accesses
502552 let space = unsafe { & * ( self as * const Self ) } ;
503- while chunk < end {
504- if is_chunk_mapped ( chunk) && unsafe { is_chunk_marked_unsafe ( chunk) } {
505- work_packets. push ( Box :: new ( MSSweepChunk { ms : space, chunk } ) ) ;
506- }
507-
508- chunk += BYTES_IN_CHUNK ;
509- }
553+ let work_packets = self . chunk_map . generate_tasks ( |chunk| {
554+ Box :: new ( MSSweepChunk {
555+ ms : space,
556+ chunk : chunk. start ( ) ,
557+ } )
558+ } ) ;
510559
511560 debug ! ( "Generated {} sweep work packets" , work_packets. len( ) ) ;
512561 #[ cfg( debug_assertions) ]
@@ -544,8 +593,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
544593
545594 /// Clean up for an empty chunk
546595 fn clean_up_empty_chunk ( & self , chunk_start : Address ) {
547- // Since the chunk mark metadata is a byte, we don't need synchronization
548- unsafe { unset_chunk_mark_unsafe ( chunk_start) } ;
596+ // Clear the chunk map
597+ self . chunk_map
598+ . set_allocated ( Chunk :: from_aligned_address ( chunk_start) , false ) ;
549599 // Clear the SFT entry
550600 unsafe { crate :: mmtk:: SFT_MAP . clear ( chunk_start) } ;
551601 // Clear the page marks - we are the only GC thread that is accessing this chunk
0 commit comments