Skip to content

Commit f5179fb

Browse files
authored
Use ChunkMap in MallocSpace (#1312)
This PR changes `MallocSpace` to remove its own global side metadata `MS_ACTIVE_CHUNK`, and use `ChunkMap` instead. This PR resolves the discussion in #1304 (comment).
1 parent 74dadfd commit f5179fb

File tree

3 files changed

+121
-254
lines changed

3 files changed

+121
-254
lines changed

src/policy/marksweepspace/malloc_ms/global.rs

Lines changed: 121 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
use atomic::Atomic;
2-
31
use super::metadata::*;
42
use crate::plan::ObjectQueue;
53
use crate::plan::VectorObjectQueue;
64
use crate::policy::sft::GCWorkerMutRef;
75
use crate::policy::sft::SFT;
86
use crate::policy::space::CommonSpace;
97
use crate::scheduler::GCWorkScheduler;
8+
use crate::util::heap::chunk_map::Chunk;
9+
use crate::util::heap::chunk_map::ChunkMap;
1010
use crate::util::heap::gc_trigger::GCTrigger;
11+
use crate::util::heap::space_descriptor::SpaceDescriptor;
1112
use crate::util::heap::PageResource;
13+
use crate::util::linear_scan::Region;
1214
use crate::util::malloc::library::{BYTES_IN_MALLOC_PAGE, LOG_BYTES_IN_MALLOC_PAGE};
1315
use crate::util::malloc::malloc_ms_util::*;
16+
use crate::util::metadata::side_metadata;
1417
use crate::util::metadata::side_metadata::{
1518
SideMetadataContext, SideMetadataSanity, SideMetadataSpec,
1619
};
@@ -30,7 +33,6 @@ use std::marker::PhantomData;
3033
use std::sync::atomic::AtomicU32;
3134
use std::sync::atomic::{AtomicUsize, Ordering};
3235
use std::sync::Arc;
33-
#[cfg(debug_assertions)]
3436
use 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

Comments
 (0)