Skip to content

Commit 27231b5

Browse files
committed
Use ChunkMap in MallocSpace
1 parent 74dadfd commit 27231b5

File tree

3 files changed

+122
-254
lines changed

3 files changed

+122
-254
lines changed

src/policy/marksweepspace/malloc_ms/global.rs

+122-71
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,14 @@ 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),
290+
// chunk_addr_min: Atomic::new(Address::MAX),
291+
// chunk_addr_max: Atomic::new(Address::ZERO),
292292
metadata: SideMetadataContext {
293293
global: args.global_side_metadata_specs.clone(),
294294
local: metadata::extract_side_metadata(&[
@@ -299,6 +299,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
299299
},
300300
scheduler: args.scheduler.clone(),
301301
gc_trigger: args.gc_trigger,
302+
descriptor,
303+
chunk_map,
304+
mmap_metadata_lock: Mutex::new(()),
302305
#[cfg(debug_assertions)]
303306
active_mem: Mutex::new(HashMap::new()),
304307
#[cfg(debug_assertions)]
@@ -332,6 +335,15 @@ impl<VM: VMBinding> MallocSpace<VM> {
332335
}
333336
}
334337

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+
335347
/// 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
336348
///
337349
/// # Safety
@@ -369,15 +381,16 @@ impl<VM: VMBinding> MallocSpace<VM> {
369381
if !address.is_zero() {
370382
let actual_size = get_malloc_usable_size(address, is_offset_malloc);
371383

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) {
374385
// Map the metadata space for the associated chunk
375386
self.map_metadata_and_update_bound(address, actual_size);
376387
// Update SFT
377388
assert!(crate::mmtk::SFT_MAP.has_sft_entry(address)); // make sure the address is okay with our SFT map
378389
unsafe { crate::mmtk::SFT_MAP.update(self, address, actual_size) };
379390
}
380391

392+
self.set_chunk_mark(address, actual_size);
393+
381394
// Set page marks for current object
382395
self.set_page_mark(address, actual_size);
383396
self.active_bytes.fetch_add(actual_size, Ordering::SeqCst);
@@ -396,6 +409,43 @@ impl<VM: VMBinding> MallocSpace<VM> {
396409
address
397410
}
398411

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+
399449
pub fn free(&self, addr: Address) {
400450
let offset_malloc_bit = is_offset_malloc(addr);
401451
let bytes = get_malloc_usable_size(addr, offset_malloc_bit);
@@ -437,76 +487,76 @@ impl<VM: VMBinding> MallocSpace<VM> {
437487
);
438488

439489
if !is_marked::<VM>(object, Ordering::Relaxed) {
440-
let chunk_start = conversions::chunk_align_down(object.to_object_start::<VM>());
441490
set_mark_bit::<VM>(object, Ordering::SeqCst);
442-
set_chunk_mark(chunk_start);
443491
queue.enqueue(object);
444492
}
445493

446494
object
447495
}
448496

449497
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;
471528
}
529+
}
472530

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;
488545
}
546+
crate::util::metadata::vo_bit::is_vo_bit_set_for_addr(addr)
489547
}
490548

491549
pub fn prepare(&mut self, _full_heap: bool) {}
492550

493551
pub fn release(&mut self) {
494552
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
502553
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+
});
510560

511561
debug!("Generated {} sweep work packets", work_packets.len());
512562
#[cfg(debug_assertions)]
@@ -544,8 +594,9 @@ impl<VM: VMBinding> MallocSpace<VM> {
544594

545595
/// Clean up for an empty chunk
546596
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);
549600
// Clear the SFT entry
550601
unsafe { crate::mmtk::SFT_MAP.clear(chunk_start) };
551602
// Clear the page marks - we are the only GC thread that is accessing this chunk

0 commit comments

Comments
 (0)