1
- use atomic:: Atomic ;
2
-
3
1
use super :: metadata:: * ;
4
2
use crate :: plan:: ObjectQueue ;
5
3
use crate :: plan:: VectorObjectQueue ;
6
4
use crate :: policy:: sft:: GCWorkerMutRef ;
7
5
use crate :: policy:: sft:: SFT ;
8
6
use crate :: policy:: space:: CommonSpace ;
9
7
use crate :: scheduler:: GCWorkScheduler ;
8
+ use crate :: util:: heap:: chunk_map:: Chunk ;
9
+ use crate :: util:: heap:: chunk_map:: ChunkMap ;
10
10
use crate :: util:: heap:: gc_trigger:: GCTrigger ;
11
+ use crate :: util:: heap:: space_descriptor:: SpaceDescriptor ;
11
12
use crate :: util:: heap:: PageResource ;
13
+ use crate :: util:: linear_scan:: Region ;
12
14
use crate :: util:: malloc:: library:: { BYTES_IN_MALLOC_PAGE , LOG_BYTES_IN_MALLOC_PAGE } ;
13
15
use crate :: util:: malloc:: malloc_ms_util:: * ;
16
+ use crate :: util:: metadata:: side_metadata;
14
17
use crate :: util:: metadata:: side_metadata:: {
15
18
SideMetadataContext , SideMetadataSanity , SideMetadataSpec ,
16
19
} ;
@@ -30,7 +33,6 @@ use std::marker::PhantomData;
30
33
use std:: sync:: atomic:: AtomicU32 ;
31
34
use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
32
35
use std:: sync:: Arc ;
33
- #[ cfg( debug_assertions) ]
34
36
use std:: sync:: Mutex ;
35
37
// If true, we will use a hashmap to store all the allocated memory from malloc, and use it
36
38
// to make sure our allocation is correct.
@@ -42,12 +44,13 @@ pub struct MallocSpace<VM: VMBinding> {
42
44
phantom : PhantomData < VM > ,
43
45
active_bytes : AtomicUsize ,
44
46
active_pages : AtomicUsize ,
45
- pub chunk_addr_min : Atomic < Address > ,
46
- pub chunk_addr_max : Atomic < Address > ,
47
47
metadata : SideMetadataContext ,
48
48
/// Work packet scheduler
49
49
scheduler : Arc < GCWorkScheduler < VM > > ,
50
50
gc_trigger : Arc < GCTrigger < VM > > ,
51
+ descriptor : SpaceDescriptor ,
52
+ chunk_map : ChunkMap ,
53
+ mmap_metadata_lock : Mutex < ( ) > ,
51
54
// Mapping between allocated address and its size - this is used to check correctness.
52
55
// Size will be set to zero when the memory is freed.
53
56
#[ cfg( debug_assertions) ]
@@ -98,7 +101,7 @@ impl<VM: VMBinding> SFT for MallocSpace<VM> {
98
101
99
102
// For malloc space, we need to further check the VO bit.
100
103
fn is_in_space ( & self , object : ObjectReference ) -> bool {
101
- is_alloced_by_malloc ( object)
104
+ self . is_alloced_by_malloc ( object)
102
105
}
103
106
104
107
/// For malloc space, we just use the side metadata.
@@ -107,7 +110,7 @@ impl<VM: VMBinding> SFT for MallocSpace<VM> {
107
110
debug_assert ! ( !addr. is_zero( ) ) ;
108
111
// `addr` cannot be mapped by us. It should be mapped by the malloc library.
109
112
debug_assert ! ( !addr. is_mapped( ) ) ;
110
- has_object_alloced_by_malloc ( addr)
113
+ self . has_object_alloced_by_malloc ( addr)
111
114
}
112
115
113
116
#[ cfg( feature = "is_mmtk_object" ) ]
@@ -173,7 +176,7 @@ impl<VM: VMBinding> Space<VM> for MallocSpace<VM> {
173
176
// We have assertions in a debug build. We allow this pattern for the release build.
174
177
#[ allow( clippy:: let_and_return) ]
175
178
fn in_space ( & self , object : ObjectReference ) -> bool {
176
- let ret = is_alloced_by_malloc ( object) ;
179
+ let ret = self . is_alloced_by_malloc ( object) ;
177
180
178
181
#[ cfg( debug_assertions) ]
179
182
if ASSERT_ALLOCATION {
@@ -270,11 +273,6 @@ impl<VM: VMBinding> MallocSpace<VM> {
270
273
if !cfg ! ( feature = "vo_bit" ) {
271
274
specs. push ( crate :: util:: metadata:: vo_bit:: VO_BIT_SIDE_METADATA_SPEC ) ;
272
275
}
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 ) ;
278
276
}
279
277
280
278
pub fn new ( args : crate :: policy:: space:: PlanCreateSpaceArgs < VM > ) -> Self {
@@ -283,12 +281,12 @@ impl<VM: VMBinding> MallocSpace<VM> {
283
281
// Besides we cannot meaningfully measure the live bytes vs total pages for MallocSpace.
284
282
panic ! ( "count_live_bytes_in_gc is not supported by MallocSpace" ) ;
285
283
}
284
+ let descriptor = SpaceDescriptor :: create_descriptor ( ) ;
285
+ let chunk_map = ChunkMap :: new ( descriptor. get_index ( ) ) ;
286
286
MallocSpace {
287
287
phantom : PhantomData ,
288
288
active_bytes : AtomicUsize :: new ( 0 ) ,
289
289
active_pages : AtomicUsize :: new ( 0 ) ,
290
- chunk_addr_min : Atomic :: new ( Address :: MAX ) ,
291
- chunk_addr_max : Atomic :: new ( Address :: ZERO ) ,
292
290
metadata : SideMetadataContext {
293
291
global : args. global_side_metadata_specs . clone ( ) ,
294
292
local : metadata:: extract_side_metadata ( & [
@@ -299,6 +297,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
299
297
} ,
300
298
scheduler : args. scheduler . clone ( ) ,
301
299
gc_trigger : args. gc_trigger ,
300
+ descriptor,
301
+ chunk_map,
302
+ mmap_metadata_lock : Mutex :: new ( ( ) ) ,
302
303
#[ cfg( debug_assertions) ]
303
304
active_mem : Mutex :: new ( HashMap :: new ( ) ) ,
304
305
#[ cfg( debug_assertions) ]
@@ -332,6 +333,15 @@ impl<VM: VMBinding> MallocSpace<VM> {
332
333
}
333
334
}
334
335
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
+
335
345
/// 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
336
346
///
337
347
/// # Safety
@@ -369,15 +379,17 @@ impl<VM: VMBinding> MallocSpace<VM> {
369
379
if !address. is_zero ( ) {
370
380
let actual_size = get_malloc_usable_size ( address, is_offset_malloc) ;
371
381
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) {
374
383
// Map the metadata space for the associated chunk
375
384
self . map_metadata_and_update_bound ( address, actual_size) ;
376
385
// Update SFT
377
386
assert ! ( crate :: mmtk:: SFT_MAP . has_sft_entry( address) ) ; // make sure the address is okay with our SFT map
378
387
unsafe { crate :: mmtk:: SFT_MAP . update ( self , address, actual_size) } ;
379
388
}
380
389
390
+ // Set chunk marks for the current object
391
+ self . set_chunk_mark ( address, actual_size) ;
392
+
381
393
// Set page marks for current object
382
394
self . set_page_mark ( address, actual_size) ;
383
395
self . active_bytes . fetch_add ( actual_size, Ordering :: SeqCst ) ;
@@ -396,6 +408,43 @@ impl<VM: VMBinding> MallocSpace<VM> {
396
408
address
397
409
}
398
410
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
+
399
448
pub fn free ( & self , addr : Address ) {
400
449
let offset_malloc_bit = is_offset_malloc ( addr) ;
401
450
let bytes = get_malloc_usable_size ( addr, offset_malloc_bit) ;
@@ -437,76 +486,76 @@ impl<VM: VMBinding> MallocSpace<VM> {
437
486
) ;
438
487
439
488
if !is_marked :: < VM > ( object, Ordering :: Relaxed ) {
440
- let chunk_start = conversions:: chunk_align_down ( object. to_object_start :: < VM > ( ) ) ;
441
489
set_mark_bit :: < VM > ( object, Ordering :: SeqCst ) ;
442
- set_chunk_mark ( chunk_start) ;
443
490
queue. enqueue ( object) ;
444
491
}
445
492
446
493
object
447
494
}
448
495
449
496
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 ;
471
527
}
528
+ }
472
529
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 ;
488
544
}
545
+ crate :: util:: metadata:: vo_bit:: is_vo_bit_set_for_addr ( addr)
489
546
}
490
547
491
548
pub fn prepare ( & mut self , _full_heap : bool ) { }
492
549
493
550
pub fn release ( & mut self ) {
494
551
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
502
552
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
+ } ) ;
510
559
511
560
debug ! ( "Generated {} sweep work packets" , work_packets. len( ) ) ;
512
561
#[ cfg( debug_assertions) ]
@@ -544,8 +593,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
544
593
545
594
/// Clean up for an empty chunk
546
595
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 ) ;
549
599
// Clear the SFT entry
550
600
unsafe { crate :: mmtk:: SFT_MAP . clear ( chunk_start) } ;
551
601
// Clear the page marks - we are the only GC thread that is accessing this chunk
0 commit comments