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,14 @@ 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 ) ,
290
+ // chunk_addr_min: Atomic::new(Address::MAX),
291
+ // chunk_addr_max: Atomic::new(Address::ZERO),
292
292
metadata : SideMetadataContext {
293
293
global : args. global_side_metadata_specs . clone ( ) ,
294
294
local : metadata:: extract_side_metadata ( & [
@@ -299,6 +299,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
299
299
} ,
300
300
scheduler : args. scheduler . clone ( ) ,
301
301
gc_trigger : args. gc_trigger ,
302
+ descriptor,
303
+ chunk_map,
304
+ mmap_metadata_lock : Mutex :: new ( ( ) ) ,
302
305
#[ cfg( debug_assertions) ]
303
306
active_mem : Mutex :: new ( HashMap :: new ( ) ) ,
304
307
#[ cfg( debug_assertions) ]
@@ -332,6 +335,15 @@ impl<VM: VMBinding> MallocSpace<VM> {
332
335
}
333
336
}
334
337
338
+ fn set_chunk_mark ( & self , start : Address , size : usize ) {
339
+ let mut chunk = start. align_down ( BYTES_IN_CHUNK ) ;
340
+ while chunk < start + size {
341
+ self . chunk_map
342
+ . set_allocated ( Chunk :: from_aligned_address ( chunk) , true ) ;
343
+ chunk += BYTES_IN_CHUNK ;
344
+ }
345
+ }
346
+
335
347
/// 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
348
///
337
349
/// # Safety
@@ -369,15 +381,16 @@ impl<VM: VMBinding> MallocSpace<VM> {
369
381
if !address. is_zero ( ) {
370
382
let actual_size = get_malloc_usable_size ( address, is_offset_malloc) ;
371
383
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) {
384
+ if !self . is_meta_space_mapped ( address, actual_size) {
374
385
// Map the metadata space for the associated chunk
375
386
self . map_metadata_and_update_bound ( address, actual_size) ;
376
387
// Update SFT
377
388
assert ! ( crate :: mmtk:: SFT_MAP . has_sft_entry( address) ) ; // make sure the address is okay with our SFT map
378
389
unsafe { crate :: mmtk:: SFT_MAP . update ( self , address, actual_size) } ;
379
390
}
380
391
392
+ self . set_chunk_mark ( address, actual_size) ;
393
+
381
394
// Set page marks for current object
382
395
self . set_page_mark ( address, actual_size) ;
383
396
self . active_bytes . fetch_add ( actual_size, Ordering :: SeqCst ) ;
@@ -396,6 +409,43 @@ impl<VM: VMBinding> MallocSpace<VM> {
396
409
address
397
410
}
398
411
412
+ /// Check if metadata is mapped for a range [addr, addr + size). Metadata is mapped per chunk,
413
+ /// we will go through all the chunks for [address, address + size), and check if they are mapped.
414
+ /// If any of the chunks is not mapped, return false. Otherwise return true.
415
+ fn is_meta_space_mapped ( & self , address : Address , size : usize ) -> bool {
416
+ let mut chunk = address. align_down ( BYTES_IN_CHUNK ) ;
417
+ while chunk < address + size {
418
+ // is the chunk already mapped?
419
+ if !self . is_meta_space_mapped_for_address ( chunk) {
420
+ return false ;
421
+ }
422
+ chunk += BYTES_IN_CHUNK ;
423
+ }
424
+ true
425
+ }
426
+
427
+ /// Check if metadata is mapped for a given address. We check with the chunk map: if the side metadata
428
+ /// for the chunk map is mapped, and if it is allocated in the chunk map.
429
+ fn is_meta_space_mapped_for_address ( & self , address : Address ) -> bool {
430
+ let is_chunk_map_mapped = |chunk_start : Address | {
431
+ const CHUNK_MAP_MAX_META_ADDRESS : Address =
432
+ ChunkMap :: ALLOC_TABLE . upper_bound_address_for_contiguous ( ) ;
433
+ let meta_address =
434
+ side_metadata:: address_to_meta_address ( & ChunkMap :: ALLOC_TABLE , chunk_start) ;
435
+ if meta_address < CHUNK_MAP_MAX_META_ADDRESS {
436
+ meta_address. is_mapped ( )
437
+ } else {
438
+ false
439
+ }
440
+ } ;
441
+ let chunk_start = address. align_down ( BYTES_IN_CHUNK ) ;
442
+ is_chunk_map_mapped ( chunk_start)
443
+ && self
444
+ . chunk_map
445
+ . get ( Chunk :: from_aligned_address ( chunk_start) )
446
+ . is_some ( )
447
+ }
448
+
399
449
pub fn free ( & self , addr : Address ) {
400
450
let offset_malloc_bit = is_offset_malloc ( addr) ;
401
451
let bytes = get_malloc_usable_size ( addr, offset_malloc_bit) ;
@@ -437,76 +487,76 @@ impl<VM: VMBinding> MallocSpace<VM> {
437
487
) ;
438
488
439
489
if !is_marked :: < VM > ( object, Ordering :: Relaxed ) {
440
- let chunk_start = conversions:: chunk_align_down ( object. to_object_start :: < VM > ( ) ) ;
441
490
set_mark_bit :: < VM > ( object, Ordering :: SeqCst ) ;
442
- set_chunk_mark ( chunk_start) ;
443
491
queue. enqueue ( object) ;
444
492
}
445
493
446
494
object
447
495
}
448
496
449
497
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
- }
498
+ // Acquire the lock before
499
+ let _lock = self . mmap_metadata_lock . lock ( ) . unwrap ( ) ;
500
+
501
+ // Mmap metadata for each chunk
502
+ let map_metadata_space_for_chunk = |start : Address | {
503
+ debug_assert ! ( start. is_aligned_to( BYTES_IN_CHUNK ) ) ;
504
+ // Attempt to map the local metadata for the policy.
505
+ // Note that this might fail. For example, we have marked a chunk as active but later we freed all
506
+ // the objects in it, and unset its chunk bit. However, we do not free its metadata. So for the chunk,
507
+ // its chunk bit is mapped, but not marked, and all its local metadata is also mapped.
508
+ let mmap_metadata_result =
509
+ self . metadata
510
+ . try_map_metadata_space ( start, BYTES_IN_CHUNK , self . get_name ( ) ) ;
511
+ debug_assert ! (
512
+ mmap_metadata_result. is_ok( ) ,
513
+ "mmap sidemetadata failed for chunk_start ({})" ,
514
+ start
515
+ ) ;
516
+ // Set the chunk mark at the end. So if we have chunk mark set, we know we have mapped side metadata
517
+ // for the chunk.
518
+ trace ! ( "set chunk mark bit for {}" , start) ;
519
+ self . chunk_map
520
+ . set_allocated ( Chunk :: from_aligned_address ( start) , true ) ;
521
+ } ;
522
+
523
+ // Go through each chunk, and map for them.
524
+ let mut chunk = conversions:: chunk_align_down ( addr) ;
525
+ while chunk < addr + size {
526
+ map_metadata_space_for_chunk ( chunk) ;
527
+ chunk += BYTES_IN_CHUNK ;
471
528
}
529
+ }
472
530
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
- }
531
+ /// Check if a given object was allocated by malloc
532
+ pub fn is_alloced_by_malloc ( & self , object : ObjectReference ) -> bool {
533
+ self . is_meta_space_mapped_for_address ( object. to_raw_address ( ) )
534
+ && crate :: util:: metadata:: vo_bit:: is_vo_bit_set ( object)
535
+ }
536
+
537
+ /// Check if there is an object allocated by malloc at the address.
538
+ ///
539
+ /// This function doesn't check if `addr` is aligned.
540
+ /// If not, it will try to load the VO bit for the address rounded down to the metadata's granularity.
541
+ #[ cfg( feature = "is_mmtk_object" ) ]
542
+ pub fn has_object_alloced_by_malloc ( & self , addr : Address ) -> Option < ObjectReference > {
543
+ if !self . is_meta_space_mapped_for_address ( addr) {
544
+ return None ;
488
545
}
546
+ crate :: util:: metadata:: vo_bit:: is_vo_bit_set_for_addr ( addr)
489
547
}
490
548
491
549
pub fn prepare ( & mut self , _full_heap : bool ) { }
492
550
493
551
pub fn release ( & mut self ) {
494
552
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
553
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
- }
554
+ let work_packets = self . chunk_map . generate_tasks ( |chunk| {
555
+ Box :: new ( MSSweepChunk {
556
+ ms : space,
557
+ chunk : chunk. start ( ) ,
558
+ } )
559
+ } ) ;
510
560
511
561
debug ! ( "Generated {} sweep work packets" , work_packets. len( ) ) ;
512
562
#[ cfg( debug_assertions) ]
@@ -544,8 +594,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
544
594
545
595
/// Clean up for an empty chunk
546
596
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) } ;
597
+ // Clear the chunk map
598
+ self . chunk_map
599
+ . set_allocated ( Chunk :: from_aligned_address ( chunk_start) , false ) ;
549
600
// Clear the SFT entry
550
601
unsafe { crate :: mmtk:: SFT_MAP . clear ( chunk_start) } ;
551
602
// Clear the page marks - we are the only GC thread that is accessing this chunk
0 commit comments