Skip to content

Commit 2569dcd

Browse files
authored
Make chunk map as global side metadata, and each space only lists its own chunks using chunk map (#1304)
The chunk map records the lowest chunk and the highest chunk used by a space, and accesses all the chunk states (local side metadata) in the range. There are multiple issues when we use discontiguous spaces. 1. Chunks in the range may be ~~used~~ unused, thus the local side metadata may be unmapped. 2. Chunks in the range may belong to other spaces. If other spaces also use the chunk map, they will list all the chunks even when the chunks do not belong to the space. This PR makes the chunk state side metadata as a global side metadata. We need the chunk map for plans that use the chunk map. As we will use mark sweep or nonmoving immix as the non moving policy, they both use the chunk map, and the non moving policy is included in every plan, we just include the side metadata for every plan. This PR fixes #1197.
1 parent 5c1c8b1 commit 2569dcd

File tree

8 files changed

+156
-59
lines changed

8 files changed

+156
-59
lines changed

src/policy/immix/immixspace.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,6 @@ impl<VM: VMBinding> ImmixSpace<VM> {
254254
vec![
255255
MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
256256
MetadataSpec::OnSide(Block::MARK_TABLE),
257-
MetadataSpec::OnSide(ChunkMap::ALLOC_TABLE),
258257
*VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
259258
*VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
260259
*VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
@@ -266,7 +265,6 @@ impl<VM: VMBinding> ImmixSpace<VM> {
266265
MetadataSpec::OnSide(Line::MARK_TABLE),
267266
MetadataSpec::OnSide(Block::DEFRAG_STATE_TABLE),
268267
MetadataSpec::OnSide(Block::MARK_TABLE),
269-
MetadataSpec::OnSide(ChunkMap::ALLOC_TABLE),
270268
*VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
271269
*VM::VMObjectModel::LOCAL_FORWARDING_BITS_SPEC,
272270
*VM::VMObjectModel::LOCAL_FORWARDING_POINTER_SPEC,
@@ -315,6 +313,7 @@ impl<VM: VMBinding> ImmixSpace<VM> {
315313
let scheduler = args.scheduler.clone();
316314
let common =
317315
CommonSpace::new(args.into_policy_args(true, false, Self::side_metadata_specs()));
316+
let space_index = common.descriptor.get_index();
318317
ImmixSpace {
319318
pr: if common.vmrequest.is_discontiguous() {
320319
BlockPageResource::new_discontiguous(
@@ -332,7 +331,7 @@ impl<VM: VMBinding> ImmixSpace<VM> {
332331
)
333332
},
334333
common,
335-
chunk_map: ChunkMap::new(),
334+
chunk_map: ChunkMap::new(space_index),
336335
line_mark_state: AtomicU8::new(Line::RESET_MARK_STATE),
337336
line_unavail_state: AtomicU8::new(Line::RESET_MARK_STATE),
338337
lines_consumed: AtomicUsize::new(0),
@@ -541,7 +540,7 @@ impl<VM: VMBinding> ImmixSpace<VM> {
541540
self.defrag.notify_new_clean_block(copy);
542541
let block = Block::from_aligned_address(block_address);
543542
block.init(copy);
544-
self.chunk_map.set(block.chunk(), ChunkState::Allocated);
543+
self.chunk_map.set_allocated(block.chunk(), true);
545544
self.lines_consumed
546545
.fetch_add(Block::LINES, Ordering::SeqCst);
547546
Some(block)
@@ -928,7 +927,7 @@ struct SweepChunk<VM: VMBinding> {
928927

929928
impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
930929
fn do_work(&mut self, _worker: &mut GCWorker<VM>, mmtk: &'static MMTK<VM>) {
931-
assert_eq!(self.space.chunk_map.get(self.chunk), ChunkState::Allocated);
930+
assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
932931

933932
let mut histogram = self.space.defrag.new_histogram();
934933
let line_mark_state = if super::BLOCK_ONLY {
@@ -979,7 +978,7 @@ impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
979978
probe!(mmtk, sweep_chunk, allocated_blocks);
980979
// Set this chunk as free if there is not live blocks.
981980
if allocated_blocks == 0 {
982-
self.space.chunk_map.set(self.chunk, ChunkState::Free)
981+
self.space.chunk_map.set_allocated(self.chunk, false)
983982
}
984983
self.space.defrag.add_completed_mark_histogram(histogram);
985984
self.epilogue.finish_one_work_packet();

src/policy/marksweepspace/native_ms/global.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ impl<VM: VMBinding> MarkSweepSpace<VM> {
288288
let vm_map = args.vm_map;
289289
let is_discontiguous = args.vmrequest.is_discontiguous();
290290
let local_specs = {
291-
metadata::extract_side_metadata(&vec![
291+
metadata::extract_side_metadata(&[
292292
MetadataSpec::OnSide(Block::NEXT_BLOCK_TABLE),
293293
MetadataSpec::OnSide(Block::PREV_BLOCK_TABLE),
294294
MetadataSpec::OnSide(Block::FREE_LIST_TABLE),
@@ -300,11 +300,11 @@ impl<VM: VMBinding> MarkSweepSpace<VM> {
300300
MetadataSpec::OnSide(Block::BLOCK_LIST_TABLE),
301301
MetadataSpec::OnSide(Block::TLS_TABLE),
302302
MetadataSpec::OnSide(Block::MARK_TABLE),
303-
MetadataSpec::OnSide(ChunkMap::ALLOC_TABLE),
304303
*VM::VMObjectModel::LOCAL_MARK_BIT_SPEC,
305304
])
306305
};
307306
let common = CommonSpace::new(args.into_policy_args(false, false, local_specs));
307+
let space_index = common.descriptor.get_index();
308308
MarkSweepSpace {
309309
pr: if is_discontiguous {
310310
BlockPageResource::new_discontiguous(
@@ -322,7 +322,7 @@ impl<VM: VMBinding> MarkSweepSpace<VM> {
322322
)
323323
},
324324
common,
325-
chunk_map: ChunkMap::new(),
325+
chunk_map: ChunkMap::new(space_index),
326326
scheduler,
327327
abandoned: Mutex::new(AbandonedBlockLists::new()),
328328
abandoned_in_gc: Mutex::new(AbandonedBlockLists::new()),
@@ -402,7 +402,7 @@ impl<VM: VMBinding> MarkSweepSpace<VM> {
402402

403403
pub fn record_new_block(&self, block: Block) {
404404
block.init();
405-
self.chunk_map.set(block.chunk(), ChunkState::Allocated);
405+
self.chunk_map.set_allocated(block.chunk(), true);
406406
}
407407

408408
pub fn prepare(&mut self, full_heap: bool) {
@@ -567,7 +567,7 @@ struct PrepareChunkMap<VM: VMBinding> {
567567

568568
impl<VM: VMBinding> GCWork<VM> for PrepareChunkMap<VM> {
569569
fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
570-
debug_assert!(self.space.chunk_map.get(self.chunk) == ChunkState::Allocated);
570+
debug_assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
571571
// number of allocated blocks.
572572
let mut n_occupied_blocks = 0;
573573
self.chunk
@@ -581,7 +581,7 @@ impl<VM: VMBinding> GCWork<VM> for PrepareChunkMap<VM> {
581581
});
582582
if n_occupied_blocks == 0 {
583583
// Set this chunk as free if there is no live blocks.
584-
self.space.chunk_map.set(self.chunk, ChunkState::Free)
584+
self.space.chunk_map.set_allocated(self.chunk, false)
585585
} else {
586586
// Otherwise this chunk is occupied, and we reset the mark bit if it is on the side.
587587
if let MetadataSpec::OnSide(side) = *VM::VMObjectModel::LOCAL_MARK_BIT_SPEC {
@@ -617,7 +617,7 @@ struct SweepChunk<VM: VMBinding> {
617617

618618
impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
619619
fn do_work(&mut self, _worker: &mut GCWorker<VM>, _mmtk: &'static MMTK<VM>) {
620-
assert_eq!(self.space.chunk_map.get(self.chunk), ChunkState::Allocated);
620+
assert!(self.space.chunk_map.get(self.chunk).unwrap().is_allocated());
621621

622622
// number of allocated blocks.
623623
let mut allocated_blocks = 0;
@@ -636,7 +636,7 @@ impl<VM: VMBinding> GCWork<VM> for SweepChunk<VM> {
636636
probe!(mmtk, sweep_chunk, allocated_blocks);
637637
// Set this chunk as free if there is not live blocks.
638638
if allocated_blocks == 0 {
639-
self.space.chunk_map.set(self.chunk, ChunkState::Free)
639+
self.space.chunk_map.set_allocated(self.chunk, false);
640640
}
641641
self.epilogue.finish_one_work_packet();
642642
}

src/util/heap/chunk_map.rs

+94-34
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,68 @@ impl Chunk {
4444
}
4545
}
4646

47-
/// Chunk allocation state
48-
#[repr(u8)]
49-
#[derive(Debug, PartialEq, Clone, Copy)]
50-
pub enum ChunkState {
51-
/// The chunk is not allocated.
52-
Free = 0,
53-
/// The chunk is allocated.
54-
Allocated = 1,
47+
/// The allocation state for a chunk in the chunk map. It includes whether each chunk is allocated or free, and the space the chunk belongs to.
48+
/// Highest bit: 0 = free, 1 = allocated
49+
/// Lower 4 bits: Space index (0-15) if the chunk is allocated.
50+
#[repr(transparent)]
51+
#[derive(PartialEq, Clone, Copy)]
52+
pub struct ChunkState(u8);
53+
54+
impl ChunkState {
55+
const ALLOC_BIT_MASK: u8 = 0x80;
56+
const SPACE_INDEX_MASK: u8 = 0x0F;
57+
58+
/// Create a new ChunkState that represents being allocated in the given space
59+
pub fn allocated(space_index: usize) -> ChunkState {
60+
debug_assert!(space_index < crate::util::heap::layout::heap_parameters::MAX_SPACES);
61+
let mut encode = space_index as u8;
62+
encode |= Self::ALLOC_BIT_MASK;
63+
ChunkState(encode)
64+
}
65+
/// Create a new ChunkState that represents being free
66+
pub fn free() -> ChunkState {
67+
ChunkState(0u8)
68+
}
69+
/// Is the chunk free?
70+
pub fn is_free(&self) -> bool {
71+
self.0 == 0
72+
}
73+
/// Is the chunk allocated?
74+
pub fn is_allocated(&self) -> bool {
75+
!self.is_free()
76+
}
77+
/// Get the space index of the chunk
78+
pub fn get_space_index(&self) -> usize {
79+
debug_assert!(self.is_allocated());
80+
let index = (self.0 & Self::SPACE_INDEX_MASK) as usize;
81+
debug_assert!(index < crate::util::heap::layout::heap_parameters::MAX_SPACES);
82+
index
83+
}
84+
}
85+
86+
impl std::fmt::Debug for ChunkState {
87+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88+
if self.is_free() {
89+
write!(f, "Free")
90+
} else {
91+
write!(f, "Allocated({})", self.get_space_index())
92+
}
93+
}
5594
}
5695

5796
/// A byte-map to record all the allocated chunks.
5897
/// A plan can use this to maintain records for the chunks that they used, and the states of the chunks.
59-
/// Any plan that uses the chunk map should include the `ALLOC_TABLE` spec in their local sidemetadata specs
98+
/// Any plan that uses the chunk map should include the `ALLOC_TABLE` spec in their global sidemetadata specs.
99+
///
100+
/// A chunk map is created for a space (identified by the space index), and will only update or list chunks for that space.
60101
pub struct ChunkMap {
102+
/// The space that uses this chunk map.
103+
space_index: usize,
104+
/// The range of chunks that are used by the space. The range only records the lowest chunk and the highest chunk.
105+
/// All the chunks that are used for the space are within the range, but not necessarily that all the chunks in the range
106+
/// are used for the space. Spaces may be discontiguous, thus the range may include chunks that do not belong to the space.
107+
/// We need to use the space index in the chunk map and the space index encoded with the chunk state to know if
108+
/// the chunk belongs to the current space.
61109
chunk_range: Mutex<Range<Chunk>>,
62110
}
63111

@@ -66,22 +114,40 @@ impl ChunkMap {
66114
pub const ALLOC_TABLE: SideMetadataSpec =
67115
crate::util::metadata::side_metadata::spec_defs::CHUNK_MARK;
68116

69-
pub fn new() -> Self {
117+
pub fn new(space_index: usize) -> Self {
70118
Self {
119+
space_index,
71120
chunk_range: Mutex::new(Chunk::ZERO..Chunk::ZERO),
72121
}
73122
}
74123

75-
/// Set chunk state
76-
pub fn set(&self, chunk: Chunk, state: ChunkState) {
124+
/// Set a chunk as allocated, or as free.
125+
pub fn set_allocated(&self, chunk: Chunk, allocated: bool) {
126+
let state = if allocated {
127+
ChunkState::allocated(self.space_index)
128+
} else {
129+
ChunkState::free()
130+
};
77131
// Do nothing if the chunk is already in the expected state.
78-
if self.get(chunk) == state {
132+
if self.get_internal(chunk) == state {
79133
return;
80134
}
135+
#[cfg(debug_assertions)]
136+
{
137+
let old_state = self.get_internal(chunk);
138+
// If a chunk is free, any space may use it. If a chunk is not free, only the current space may update its state.
139+
assert!(
140+
old_state.is_free() || old_state.get_space_index() == self.space_index,
141+
"Chunk {:?}: old state {:?}, new state {:?}. Cannot set to new state.",
142+
chunk,
143+
old_state,
144+
state
145+
);
146+
}
81147
// Update alloc byte
82-
unsafe { Self::ALLOC_TABLE.store::<u8>(chunk.start(), state as u8) };
148+
unsafe { Self::ALLOC_TABLE.store::<u8>(chunk.start(), state.0) };
83149
// If this is a newly allcoated chunk, then expand the chunk range.
84-
if state == ChunkState::Allocated {
150+
if allocated {
85151
debug_assert!(!chunk.start().is_zero());
86152
let mut range = self.chunk_range.lock();
87153
if range.start == Chunk::ZERO {
@@ -96,20 +162,23 @@ impl ChunkMap {
96162
}
97163
}
98164

99-
/// Get chunk state
100-
pub fn get(&self, chunk: Chunk) -> ChunkState {
165+
/// Get chunk state. Return None if the chunk does not belong to the space.
166+
pub fn get(&self, chunk: Chunk) -> Option<ChunkState> {
167+
let state = self.get_internal(chunk);
168+
(state.is_allocated() && state.get_space_index() == self.space_index).then_some(state)
169+
}
170+
171+
/// Get chunk state, regardless of the space. This should always be private.
172+
fn get_internal(&self, chunk: Chunk) -> ChunkState {
101173
let byte = unsafe { Self::ALLOC_TABLE.load::<u8>(chunk.start()) };
102-
match byte {
103-
0 => ChunkState::Free,
104-
1 => ChunkState::Allocated,
105-
_ => unreachable!(),
106-
}
174+
ChunkState(byte)
107175
}
108176

109-
/// A range of all chunks in the heap.
110-
pub fn all_chunks(&self) -> RegionIterator<Chunk> {
177+
/// A range of all allocated chunks by this space in the heap.
178+
pub fn all_chunks(&self) -> impl Iterator<Item = Chunk> + '_ {
111179
let chunk_range = self.chunk_range.lock();
112180
RegionIterator::<Chunk>::new(chunk_range.start, chunk_range.end)
181+
.filter(|c| self.get(*c).is_some())
113182
}
114183

115184
/// Helper function to create per-chunk processing work packets for each allocated chunks.
@@ -118,18 +187,9 @@ impl ChunkMap {
118187
func: impl Fn(Chunk) -> Box<dyn GCWork<VM>>,
119188
) -> Vec<Box<dyn GCWork<VM>>> {
120189
let mut work_packets: Vec<Box<dyn GCWork<VM>>> = vec![];
121-
for chunk in self
122-
.all_chunks()
123-
.filter(|c| self.get(*c) == ChunkState::Allocated)
124-
{
190+
for chunk in self.all_chunks() {
125191
work_packets.push(func(chunk));
126192
}
127193
work_packets
128194
}
129195
}
130-
131-
impl Default for ChunkMap {
132-
fn default() -> Self {
133-
Self::new()
134-
}
135-
}

src/util/metadata/side_metadata/global.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,11 @@ impl SideMetadataContext {
13451345
}
13461346
}
13471347

1348+
// Any plan that uses the chunk map needs to reserve the chunk map table.
1349+
// As we use either the mark sweep or (non moving) immix as the non moving space,
1350+
// and both policies use the chunk map, we just add the chunk map table globally.
1351+
ret.push(crate::util::heap::chunk_map::ChunkMap::ALLOC_TABLE);
1352+
13481353
ret.extend_from_slice(specs);
13491354
ret
13501355
}

src/util/metadata/side_metadata/spec_defs.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ define_side_metadata_specs!(
6060
MS_ACTIVE_CHUNK = (global: true, log_num_of_bits: 3, log_bytes_in_region: LOG_BYTES_IN_CHUNK),
6161
// Track the index in SFT map for a chunk (only used for SFT sparse chunk map)
6262
SFT_DENSE_CHUNK_MAP_INDEX = (global: true, log_num_of_bits: 3, log_bytes_in_region: LOG_BYTES_IN_CHUNK),
63+
// Mark chunks (any plan that uses the chunk map should include this spec in their global sidemetadata specs)
64+
CHUNK_MARK = (global: true, log_num_of_bits: 3, log_bytes_in_region: crate::util::heap::chunk_map::Chunk::LOG_BYTES),
6365
);
6466

6567
// This defines all LOCAL side metadata used by mmtk-core.
@@ -75,8 +77,6 @@ define_side_metadata_specs!(
7577
IX_BLOCK_DEFRAG = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::policy::immix::block::Block::LOG_BYTES),
7678
// Mark blocks by immix
7779
IX_BLOCK_MARK = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::policy::immix::block::Block::LOG_BYTES),
78-
// Mark chunks (any plan that uses the chunk map should include this spec in their local sidemetadata specs)
79-
CHUNK_MARK = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::util::heap::chunk_map::Chunk::LOG_BYTES),
8080
// Mark blocks by (native mimalloc) marksweep
8181
MS_BLOCK_MARK = (global: false, log_num_of_bits: 3, log_bytes_in_region: crate::policy::marksweepspace::native_ms::Block::LOG_BYTES),
8282
// Next block in list for native mimalloc

src/util/object_enum.rs

+4-9
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ use std::marker::PhantomData;
55
use crate::vm::VMBinding;
66

77
use super::{
8-
heap::{
9-
chunk_map::{ChunkMap, ChunkState},
10-
MonotonePageResource,
11-
},
8+
heap::{chunk_map::ChunkMap, MonotonePageResource},
129
linear_scan::Region,
1310
metadata::{side_metadata::spec_defs::VO_BIT, vo_bit},
1411
Address, ObjectReference,
@@ -85,11 +82,9 @@ pub(crate) fn enumerate_blocks_from_chunk_map<B>(
8582
B: BlockMayHaveObjects,
8683
{
8784
for chunk in chunk_map.all_chunks() {
88-
if chunk_map.get(chunk) == ChunkState::Allocated {
89-
for block in chunk.iter_region::<B>() {
90-
if block.may_have_objects() {
91-
enumerator.visit_address_range(block.start(), block.end());
92-
}
85+
for block in chunk.iter_region::<B>() {
86+
if block.may_have_objects() {
87+
enumerator.visit_address_range(block.start(), block.end());
9388
}
9489
}
9590
}

0 commit comments

Comments
 (0)