mmtk/util/heap/layout/mmapper/csm/
mod.rs1use crate::util::constants::LOG_BYTES_IN_PAGE;
2use crate::util::conversions::raw_is_aligned;
3use crate::util::heap::layout::vm_layout::*;
4use crate::util::heap::layout::Mmapper;
5use crate::util::os::*;
6use crate::util::Address;
7use bytemuck::NoUninit;
8use std::sync::Mutex;
9
10mod byte_map_storage;
11#[cfg(target_pointer_width = "64")]
12mod two_level_storage;
13
14#[cfg(target_pointer_width = "32")]
15type ChosenMapStateStorage = byte_map_storage::ByteMapStateStorage;
16#[cfg(target_pointer_width = "64")]
17type ChosenMapStateStorage = two_level_storage::TwoLevelStateStorage;
18
19#[derive(Clone, Copy)]
23struct ChunkRange {
24 start: Address,
25 bytes: usize,
26}
27
28impl ChunkRange {
29 fn new_aligned(start: Address, bytes: usize) -> Self {
30 debug_assert!(
31 start.is_aligned_to(BYTES_IN_CHUNK),
32 "start {start} is not chunk-aligned"
33 );
34 debug_assert!(
35 raw_is_aligned(bytes, BYTES_IN_CHUNK),
36 "bytes 0x{bytes:x} is not a multiple of chunks"
37 );
38 Self { start, bytes }
39 }
40
41 fn new_unaligned(start: Address, bytes: usize) -> Self {
42 let start_aligned = start.align_down(BYTES_IN_CHUNK);
43 let end_aligned = (start + bytes).align_up(BYTES_IN_CHUNK);
44 Self::new_aligned(start_aligned, end_aligned - start_aligned)
45 }
46
47 fn limit(&self) -> Address {
48 self.start + self.bytes
49 }
50
51 fn is_within_limit(&self, limit: Address) -> bool {
52 self.limit() <= limit
53 }
54
55 fn is_empty(&self) -> bool {
56 self.bytes == 0
57 }
58
59 fn is_single_chunk(&self) -> bool {
60 self.bytes == BYTES_IN_CHUNK
61 }
62}
63
64impl std::fmt::Display for ChunkRange {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 write!(f, "{}-{}", self.start, self.limit())
67 }
68}
69
70trait MapStateStorage {
73 fn log_mappable_bytes(&self) -> u8;
75
76 fn get_state(&self, chunk: Address) -> MapState;
81
82 fn bulk_set_state(&self, range: ChunkRange, state: MapState);
84
85 fn bulk_transition_state<F>(&self, range: ChunkRange, update_fn: F) -> MmapResult<()>
98 where
99 F: FnMut(ChunkRange, MapState) -> MmapResult<Option<MapState>>;
100}
101
102pub struct ChunkStateMmapper {
109 transition_lock: Mutex<()>,
111 storage: ChosenMapStateStorage,
113}
114
115impl ChunkStateMmapper {
116 pub fn new() -> Self {
117 Self {
118 transition_lock: Default::default(),
119 storage: ChosenMapStateStorage::new(),
120 }
121 }
122
123 fn record_quarantined_range(
125 &self,
126 start: Address,
127 bytes: usize,
128 anno: &MmapAnnotation,
129 ) -> MmapResult<()> {
130 let range = ChunkRange::new_aligned(start, bytes);
131 let mappable_limit = self.mappable_limit();
132 if !range.is_within_limit(mappable_limit) {
133 let _ = OS::munmap(start, bytes);
134 return Err(MmapError::new(
135 start,
136 bytes,
137 anno,
138 std::io::Error::other("quarantined range is outside the mappable address space"),
139 ));
140 }
141
142 self.storage
143 .bulk_transition_state(range, |group_range, state| match state {
144 MapState::Unmapped => Ok(Some(MapState::Quarantined)),
145 MapState::Quarantined => {
146 panic!("Attempted to quarantine already quarantined range {group_range}")
147 }
148 MapState::Mapped => {
149 panic!("Quarantine returned already mapped range {group_range}")
150 }
151 })
152 }
153
154 fn mappable_limit(&self) -> Address {
155 let log_mappable = self.storage.log_mappable_bytes() as u32;
156 if log_mappable < usize::BITS {
157 unsafe { Address::from_usize(1usize << log_mappable) }
158 } else {
159 Address::MAX
160 }
161 }
162
163 #[cfg(test)]
164 fn get_state(&self, chunk: Address) -> MapState {
165 self.storage.get_state(chunk)
166 }
167}
168
169impl Mmapper for ChunkStateMmapper {
170 fn log_granularity(&self) -> u8 {
171 LOG_BYTES_IN_CHUNK as u8
172 }
173
174 fn log_mappable_bytes(&self) -> u8 {
175 self.storage.log_mappable_bytes()
176 }
177
178 fn mark_as_mapped(&self, start: Address, bytes: usize) {
179 let _guard = self.transition_lock.lock().unwrap();
180
181 let range = ChunkRange::new_unaligned(start, bytes);
182 self.storage.bulk_set_state(range, MapState::Mapped);
183 }
184
185 fn quarantine_address_range(
186 &self,
187 start: Address,
188 pages: usize,
189 huge_page_option: HugePageSupport,
190 anno: &MmapAnnotation,
191 ) -> MmapResult<()> {
192 let _guard = self.transition_lock.lock().unwrap();
193
194 let bytes = pages << LOG_BYTES_IN_PAGE;
195 let range = ChunkRange::new_unaligned(start, bytes);
196
197 self.storage
198 .bulk_transition_state(range, |group_range, state| {
199 let group_start: Address = group_range.start;
200 let group_bytes = group_range.bytes;
201
202 match state {
203 MapState::Unmapped => {
204 trace!("Trying to quarantine {group_range}");
205 let mmap_strategy = MmapStrategy::QUARANTINE.huge_page(huge_page_option);
206 OS::dzmmap(group_start, group_bytes, mmap_strategy, anno)?;
207 Ok(Some(MapState::Quarantined))
208 }
209 MapState::Quarantined => {
210 panic!("Attempted to quarantine already quarantined range {group_range}")
211 }
212 MapState::Mapped => {
213 trace!("Already mapped {group_range}");
214 Ok(None)
215 }
216 }
217 })
218 }
219
220 fn quarantine_address_range_anywhere(
221 &self,
222 pages: usize,
223 align: Option<usize>,
224 huge_page_option: HugePageSupport,
225 anno: &MmapAnnotation,
226 ) -> MmapResult<Address> {
227 let _guard = self.transition_lock.lock().unwrap();
228
229 let bytes = pages << LOG_BYTES_IN_PAGE;
230 let align = align.unwrap_or(BYTES_IN_CHUNK);
231 let mmap_strategy = MmapStrategy::QUARANTINE.huge_page(huge_page_option);
232 let start = OS::dzmmap_anywhere(bytes, align, mmap_strategy, anno)?;
233 self.record_quarantined_range(start, bytes, anno)?;
234 Ok(start)
235 }
236
237 fn quarantine_address_range_preferred(
238 &self,
239 start: Address,
240 pages: usize,
241 align: Option<usize>,
242 huge_page_option: HugePageSupport,
243 anno: &MmapAnnotation,
244 ) -> MmapResult<Address> {
245 let _guard = self.transition_lock.lock().unwrap();
246
247 let bytes = pages << LOG_BYTES_IN_PAGE;
248 let align = align.unwrap_or(BYTES_IN_CHUNK);
249 assert!(
250 start.is_aligned_to(align),
251 "Preferred start {start} is not aligned to {align}"
252 );
253 let mmap_strategy = MmapStrategy::QUARANTINE.huge_page(huge_page_option);
254 let actual_start = OS::dzmmap_preferred(start, bytes, align, mmap_strategy, anno)?;
255 self.record_quarantined_range(actual_start, bytes, anno)?;
256 Ok(actual_start)
257 }
258
259 fn ensure_mapped(
260 &self,
261 start: Address,
262 pages: usize,
263 huge_page_option: HugePageSupport,
264 prot: MmapProtection,
265 anno: &MmapAnnotation,
266 ) -> MmapResult<()> {
267 let _guard = self.transition_lock.lock().unwrap();
268
269 let bytes = pages << LOG_BYTES_IN_PAGE;
270 let range = ChunkRange::new_unaligned(start, bytes);
271
272 let mmap_strategy = MmapStrategy::default()
273 .huge_page(huge_page_option)
274 .prot(prot)
275 .reserve(true);
276
277 self.storage
278 .bulk_transition_state(range, |group_range, state| {
279 let group_start: Address = group_range.start;
280 let group_bytes = group_range.bytes;
281
282 match state {
283 MapState::Unmapped => {
284 OS::dzmmap(group_start, group_bytes, mmap_strategy.replace(false), anno)?;
285 Ok(Some(MapState::Mapped))
286 }
287 MapState::Quarantined => {
288 OS::dzmmap(group_start, group_bytes, mmap_strategy.replace(true), anno)?;
289 Ok(Some(MapState::Mapped))
290 }
291 MapState::Mapped => Ok(None),
292 }
293 })
294 }
295
296 fn is_mapped_address(&self, addr: Address) -> bool {
297 self.storage.get_state(addr) == MapState::Mapped
298 }
299}
300
301#[repr(u8)]
303#[derive(Copy, Clone, PartialEq, Eq, Debug, NoUninit)]
304enum MapState {
305 Unmapped,
307 Quarantined,
310 Mapped,
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::mmap_anno_test;
318 use crate::util::constants::LOG_BYTES_IN_PAGE;
319 use crate::util::test_util::CHUNK_STATE_MMAPPER_TEST_REGION;
320 use crate::util::test_util::{serial_test, with_cleanup};
321 use crate::util::{conversions, Address};
322
323 const FIXED_ADDRESS: Address = CHUNK_STATE_MMAPPER_TEST_REGION.start;
324 const MAX_BYTES: usize = CHUNK_STATE_MMAPPER_TEST_REGION.size;
325
326 fn pages_to_chunks_up(pages: usize) -> usize {
327 conversions::raw_align_up(pages, BYTES_IN_CHUNK) / BYTES_IN_CHUNK
328 }
329
330 fn get_chunk_map_state(mmapper: &ChunkStateMmapper, chunk: Address) -> MapState {
331 chunk.is_aligned_to(BYTES_IN_CHUNK);
332 mmapper.get_state(chunk)
333 }
334
335 #[test]
336 fn ensure_mapped_1page() {
337 serial_test(|| {
338 let pages = 1;
339 with_cleanup(
340 || {
341 let mmapper = ChunkStateMmapper::new();
342 mmapper
343 .ensure_mapped(
344 FIXED_ADDRESS,
345 pages,
346 HugePageSupport::No,
347 MmapProtection::ReadWrite,
348 mmap_anno_test!(),
349 )
350 .unwrap();
351
352 let chunks = pages_to_chunks_up(pages);
353 for i in 0..chunks {
354 assert_eq!(
355 get_chunk_map_state(
356 &mmapper,
357 FIXED_ADDRESS + (i << LOG_BYTES_IN_CHUNK)
358 ),
359 MapState::Mapped
360 );
361 }
362 },
363 || {
364 OS::munmap(FIXED_ADDRESS, MAX_BYTES).unwrap();
365 },
366 )
367 })
368 }
369 #[test]
370 fn ensure_mapped_1chunk() {
371 serial_test(|| {
372 let pages = BYTES_IN_CHUNK >> LOG_BYTES_IN_PAGE as usize;
373 with_cleanup(
374 || {
375 let mmapper = ChunkStateMmapper::new();
376 mmapper
377 .ensure_mapped(
378 FIXED_ADDRESS,
379 pages,
380 HugePageSupport::No,
381 MmapProtection::ReadWrite,
382 mmap_anno_test!(),
383 )
384 .unwrap();
385
386 let chunks = pages_to_chunks_up(pages);
387 for i in 0..chunks {
388 assert_eq!(
389 get_chunk_map_state(
390 &mmapper,
391 FIXED_ADDRESS + (i << LOG_BYTES_IN_CHUNK)
392 ),
393 MapState::Mapped
394 );
395 }
396 },
397 || {
398 OS::munmap(FIXED_ADDRESS, MAX_BYTES).unwrap();
399 },
400 )
401 })
402 }
403
404 #[test]
405 fn ensure_mapped_more_than_1chunk() {
406 serial_test(|| {
407 let pages = (BYTES_IN_CHUNK + BYTES_IN_CHUNK / 2) >> LOG_BYTES_IN_PAGE as usize;
408 with_cleanup(
409 || {
410 let mmapper = ChunkStateMmapper::new();
411 mmapper
412 .ensure_mapped(
413 FIXED_ADDRESS,
414 pages,
415 HugePageSupport::No,
416 MmapProtection::ReadWrite,
417 mmap_anno_test!(),
418 )
419 .unwrap();
420
421 let chunks = pages_to_chunks_up(pages);
422 for i in 0..chunks {
423 assert_eq!(
424 get_chunk_map_state(
425 &mmapper,
426 FIXED_ADDRESS + (i << LOG_BYTES_IN_CHUNK)
427 ),
428 MapState::Mapped
429 );
430 }
431 },
432 || {
433 OS::munmap(FIXED_ADDRESS, MAX_BYTES).unwrap();
434 },
435 )
436 })
437 }
438}