mmtk/util/heap/layout/mmapper/csm/
mod.rs

1use 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/// A range of whole chunks.  Always aligned.
20///
21/// This type is used internally by the chunk state mmapper and its storage backends.
22#[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
70/// The back-end storage of [`ChunkStateMmapper`].  It is responsible for holding the states of each
71/// chunk (eagerly or lazily) and transitioning the states in bulk.
72trait MapStateStorage {
73    /// The logarithm of the address space size this `MapStateStorage` can handle.
74    fn log_mappable_bytes(&self) -> u8;
75
76    /// Return the state of a given `chunk` (must be aligned).
77    ///
78    /// Note that all chunks are logically `MapState::Unmapped` before the states are stored.  They
79    /// include chunks outside the mappable address range.
80    fn get_state(&self, chunk: Address) -> MapState;
81
82    /// Set all chunks within `range` to `state`.
83    fn bulk_set_state(&self, range: ChunkRange, state: MapState);
84
85    /// Visit the chunk states within `range` and allow the `update_fn` callback to inspect and
86    /// change the states.
87    ///
88    /// It visits chunks from low to high addresses, and calls `update_fn(group_range, group_state)`
89    /// for each contiguous chunk range `group_range` that have the same state `group_state`.
90    /// `update_fn` can take actions accordingly and return one of the three values:
91    /// -   `Err(err)`: Stop visiting and return `Err(err)` from `bulk_transition_state`
92    ///     immediately.
93    /// -   `Ok(None)`: Continue visiting the next chunk range without changing chunk states.
94    /// -   `Ok(Some(new_state))`: Set the state of all chunks within `group_range` to `new_state`.
95    ///
96    /// Return `Ok(())` if finished visiting all chunks normally.
97    fn bulk_transition_state<F>(&self, range: ChunkRange, update_fn: F) -> MmapResult<()>
98    where
99        F: FnMut(ChunkRange, MapState) -> MmapResult<Option<MapState>>;
100}
101
102/// A [`Mmapper`] implementation based on a logical array of chunk states.
103///
104/// The [`ChunkStateMmapper::storage`] field holds the state of each chunk, and the
105/// [`ChunkStateMmapper`] itself actually makes system calls to manage the memory mapping.
106///
107/// As the name suggests, this implementation of [`Mmapper`] operates at the granularity of chunks.
108pub struct ChunkStateMmapper {
109    /// Lock for transitioning map states.
110    transition_lock: Mutex<()>,
111    /// This holds the [`MapState`] for each chunk.
112    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    /// Update the underlying storage to quarantined for the given range.
124    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/// The mmap state of a mmap chunk.
302#[repr(u8)]
303#[derive(Copy, Clone, PartialEq, Eq, Debug, NoUninit)]
304enum MapState {
305    /// The chunk is unmapped and not managed by MMTk.
306    Unmapped,
307    /// The chunk is reserved for future use. MMTk reserved the address range but hasn't used it yet.
308    /// We have reserved the addresss range with mmap_noreserve with PROT_NONE.
309    Quarantined,
310    /// The chunk is mapped by MMTk and is in use.
311    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}