mmtk/util/os/
memory.rs

1use bytemuck::NoUninit;
2use std::io::Result;
3
4use crate::util::os::*;
5use crate::vm::*;
6use crate::{
7    util::{address::Address, VMThread},
8    vm::VMBinding,
9};
10
11/// Error returned by mmap-related operations in MMTk.
12#[derive(Debug)]
13pub struct MmapError {
14    /// The start address of the mmap operation that failed.
15    pub error_address: Address,
16    /// The size (in bytes) of the mmap operation that failed.
17    pub bytes: usize,
18    /// Human-readable annotation for the mmap operation.
19    ///
20    /// This is derived from [`MmapAnnotation`] at the call site.
21    pub annotation: String,
22    /// The underlying OS I/O error.
23    pub error: std::io::Error,
24}
25
26impl MmapError {
27    /// Create a new [`MmapError`].
28    pub fn new(
29        error_address: Address,
30        bytes: usize,
31        annotation: &MmapAnnotation<'_>,
32        error: std::io::Error,
33    ) -> Self {
34        Self {
35            error_address,
36            bytes,
37            annotation: annotation.to_string(),
38            error,
39        }
40    }
41}
42
43impl std::fmt::Display for MmapError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(
46            f,
47            "mmap {} (size {}, annotation {}) failed: {}",
48            self.error_address, self.bytes, self.annotation, self.error
49        )
50    }
51}
52
53impl std::error::Error for MmapError {
54    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
55        Some(&self.error)
56    }
57}
58
59/// Result type for mmap operations that can return [`MmapError`].
60pub type MmapResult<T> = std::result::Result<T, MmapError>;
61
62/// Abstraction for OS memory operations.
63pub trait OSMemory {
64    /// log2 of the page size of the OS. This is the granularity of OS memory operations such as `mprotect`.
65    /// MMTk uses the same page size ([`crate::util::constants::LOG_BYTES_IN_PAGE`]).
66    const LOG_BYTES_IN_PAGE: u8;
67    /// The page size of the OS in bytes.
68    const BYTES_IN_PAGE: usize = 1 << Self::LOG_BYTES_IN_PAGE;
69
70    /// Perform a demand-zero mmap.
71    ///
72    /// Fallback: `annotation` is only used for debugging. For platforms that do not support mmap annotations, this parameter can be ignored.
73    /// Fallback: see [`crate::util::os::MmapStrategy`] for fallbacks for `strategy`.
74    fn dzmmap(
75        start: Address,
76        size: usize,
77        strategy: MmapStrategy,
78        annotation: &MmapAnnotation<'_>,
79    ) -> MmapResult<Address>;
80
81    /// Perform a no-reserve mmap at any available address, aligned to `align`.
82    ///
83    /// This API is used for reserving address ranges (typically with `PROT_NONE`) before committing.
84    fn dzmmap_anywhere(
85        size: usize,
86        align: usize,
87        strategy: MmapStrategy,
88        annotation: &MmapAnnotation<'_>,
89    ) -> MmapResult<Address>;
90
91    /// Perform a mmap with `start` as the preferred address. The OS may return a different address.
92    /// The returned range is aligned to `align`.
93    fn dzmmap_preferred(
94        start: Address,
95        size: usize,
96        align: usize,
97        strategy: MmapStrategy,
98        annotation: &MmapAnnotation<'_>,
99    ) -> MmapResult<Address>;
100
101    /// Handle mmap errors, possibly by signaling the VM about an out-of-memory condition.
102    fn handle_mmap_error<VM: VMBinding>(mmap_error: MmapError, tls: VMThread) {
103        use crate::util::alloc::AllocationError;
104        use std::io::ErrorKind;
105
106        eprintln!(
107            "Failed to mmap from {} to {} (size {}), annotation {}",
108            mmap_error.error_address,
109            mmap_error.error_address.wrapping_add(mmap_error.bytes),
110            mmap_error.bytes,
111            mmap_error.annotation
112        );
113        eprintln!("{}", OS::get_process_memory_maps().unwrap());
114
115        let call_binding_oom = || {
116            // Signal `MmapOutOfMemory`. Expect the VM to abort immediately.
117            trace!("Signal MmapOutOfMemory!");
118            VM::VMCollection::out_of_memory(tls, AllocationError::MmapOutOfMemory);
119            unreachable!()
120        };
121
122        match mmap_error.error.kind() {
123            // From Rust nightly 2021-05-12, we started to see Rust added this ErrorKind.
124            ErrorKind::OutOfMemory => {
125                call_binding_oom();
126            }
127            // Before Rust had ErrorKind::OutOfMemory, this is how we capture OOM from OS calls.
128            // TODO: We may be able to remove this now.
129            ErrorKind::Other => {
130                // further check the error
131                if let Some(os_errno) = mmap_error.error.raw_os_error() {
132                    if OS::is_mmap_oom(os_errno) {
133                        call_binding_oom();
134                    }
135                }
136            }
137            ErrorKind::AlreadyExists => {
138                panic!("Failed to mmap, the address is already mapped. Should MMTk quarantine the address range first?");
139            }
140            _ => {
141                if let Some(os_errno) = mmap_error.error.raw_os_error() {
142                    if OS::is_mmap_oom(os_errno) {
143                        call_binding_oom();
144                    }
145                }
146            }
147        }
148        panic!("Unexpected mmap failure: {:?}", mmap_error.error)
149    }
150
151    /// Check whether the given OS error number indicates an out-of-memory condition.
152    fn is_mmap_oom(os_errno: i32) -> bool;
153
154    /// Unmap a memory region.
155    fn munmap(start: Address, size: usize) -> Result<()>;
156
157    /// Change the protection of a memory region to the specified protection.
158    fn set_memory_access(start: Address, size: usize, prot: MmapProtection) -> Result<()>;
159
160    /// Checks if the memory has already been mapped. If not, we panic.
161    ///
162    /// Note that the checking may have a side effect that it will map the memory if it was unmapped. So we panic if it was unmapped.
163    /// Be very careful about using this function.
164    ///
165    /// Fallback: As the function is only used for assertions, it can be a no-op, and MMTk will still run and never panics in this function.
166    fn panic_if_unmapped(start: Address, size: usize);
167
168    /// Get the total memory of the system in bytes.
169    fn get_system_total_memory() -> Result<u64> {
170        use sysinfo::MemoryRefreshKind;
171        use sysinfo::{RefreshKind, System};
172
173        // TODO: Note that if we want to get system info somewhere else in the future, we should
174        // refactor this instance into some global struct. sysinfo recommends sharing one instance of
175        // `System` instead of making multiple instances.
176        // See https://docs.rs/sysinfo/0.29.0/sysinfo/index.html#usage for more info
177        //
178        // If we refactor the `System` instance to use it for other purposes, please make sure start-up
179        // time is not affected.  It takes a long time to load all components in sysinfo (e.g. by using
180        // `System::new_all()`).  Some applications, especially short-running scripts, are sensitive to
181        // start-up time.  During start-up, MMTk core only needs the total memory to initialize the
182        // `Options`.  If we only load memory-related components on start-up, it should only take <1ms
183        // to initialize the `System` instance.
184        let sys = System::new_with_specifics(
185            RefreshKind::nothing().with_memory(MemoryRefreshKind::nothing().with_ram()),
186        );
187        Ok(sys.total_memory())
188    }
189}
190
191/// Strategy for performing mmap
192#[derive(Debug, Copy, Clone)]
193pub struct MmapStrategy {
194    /// Whether we should use huge page for this mmapping.
195    /// Fallback: for platforms that do not support huge pages, this option can be ignored.
196    pub huge_page: HugePageSupport,
197    /// The protection flags for mmap.
198    pub prot: MmapProtection,
199    /// Whether this mmap allows replacing existing mappings.
200    /// Fallback: for platforms that cannot replace existing mappings, or always replace existing mappings, this option can be ignored.
201    pub replace: bool,
202    /// Whether this mmap allows reserve/commit physical memory.
203    /// This has to be implemented properly for a platform. Otherwise, we will see huge unrealistic memory consumption.
204    pub reserve: bool,
205}
206
207impl std::default::Default for MmapStrategy {
208    fn default() -> Self {
209        Self {
210            huge_page: HugePageSupport::No,
211            prot: MmapProtection::ReadWrite,
212            replace: false,
213            reserve: true,
214        }
215    }
216}
217
218impl MmapStrategy {
219    /// Create a new strategy
220    pub fn new(
221        huge_page: HugePageSupport,
222        prot: MmapProtection,
223        replace: bool,
224        reserve: bool,
225    ) -> Self {
226        Self {
227            huge_page,
228            prot,
229            replace,
230            reserve,
231        }
232    }
233
234    // Builder methods
235
236    /// Set huge page option.
237    pub fn huge_page(self, huge_page: HugePageSupport) -> Self {
238        Self { huge_page, ..self }
239    }
240
241    /// Set huge page option by a boolean flag.
242    pub fn transparent_hugepages(self, enable: bool) -> Self {
243        let huge_page = if enable {
244            HugePageSupport::TransparentHugePages
245        } else {
246            HugePageSupport::No
247        };
248        Self { huge_page, ..self }
249    }
250
251    /// Set protection option.
252    pub fn prot(self, prot: MmapProtection) -> Self {
253        Self { prot, ..self }
254    }
255
256    /// Set the replace flag.
257    pub fn replace(self, replace: bool) -> Self {
258        Self { replace, ..self }
259    }
260
261    /// Set the reserve flag.
262    pub fn reserve(self, reserve: bool) -> Self {
263        Self { reserve, ..self }
264    }
265
266    #[cfg(test)] // In test mode, we use test settings which allows replacing existing mappings.
267    /// The strategy for MMTk's own internal memory (test)
268    pub const INTERNAL_MEMORY: Self = Self::TEST;
269    #[cfg(not(test))]
270    /// The strategy for MMTk's own internal memory
271    pub const INTERNAL_MEMORY: Self = Self {
272        huge_page: HugePageSupport::No,
273        prot: MmapProtection::ReadWrite,
274        replace: false,
275        reserve: true,
276    };
277
278    /// The strategy for raw memory freelist
279    pub const RAW_MEMORY_FREELIST: Self = Self {
280        huge_page: HugePageSupport::No,
281        prot: MmapProtection::ReadWrite,
282        // Raw memory freelist will mmap the address ranges quarantined for the spaces. So we have to allow replace.
283        replace: true,
284        reserve: true,
285    };
286
287    /// The strategy for quarantining address ranges.
288    pub const QUARANTINE: Self = Self {
289        huge_page: HugePageSupport::No,
290        prot: MmapProtection::NoAccess,
291        // In test mode, we allow replacing existing mappings for quarantine,
292        // so that we can reuse the same address range for multiple test cases.
293        replace: cfg!(test),
294        reserve: false,
295    };
296
297    /// The strategy for MMTk's test memory
298    #[cfg(test)]
299    pub const TEST: Self = Self {
300        huge_page: HugePageSupport::No,
301        prot: MmapProtection::ReadWrite,
302        replace: true,
303        reserve: true,
304    };
305}
306
307/// The protection flags for Mmap
308#[repr(i32)]
309#[derive(Debug, Copy, Clone)]
310pub enum MmapProtection {
311    /// Allow read + write
312    ReadWrite,
313    /// Allow read + write + code execution
314    ReadWriteExec,
315    /// Do not allow any access
316    NoAccess,
317}
318
319/// Support for huge pages
320#[repr(u8)]
321#[derive(Debug, Copy, Clone, NoUninit)]
322pub enum HugePageSupport {
323    /// No support for huge page
324    No,
325    /// Enable transparent huge pages for the pages that are mapped. This option is only for linux.
326    TransparentHugePages,
327}
328
329/// Annotation for an mmap entry.
330///
331/// Invocations of `mmap_fixed` and other functions that may transitively call `mmap_fixed`
332/// require an annotation that indicates the purpose of the memory mapping.
333///
334/// This is for debugging.  On Linux, mmtk-core will use `prctl` with `PR_SET_VMA` to set the
335/// human-readable name for the given mmap region.  The annotation is ignored on other platforms.
336///
337/// Note that when using `Map32` (even when running on 64-bit architectures), the discontiguous
338/// memory range is shared between different spaces. Spaces may use `mmap` to map new chunks, but
339/// the same chunk may later be reused by other spaces. The annotation only applies when `mmap` is
340/// called for a chunk for the first time, which reflects which space first attempted the mmap, not
341/// which space is currently using the chunk.  Use `crate::policy::space::print_vm_map` to print a
342/// more accurate mapping between address ranges and spaces.
343///
344/// On 32-bit architecture, side metadata are allocated in a chunked fasion.  One single `mmap`
345/// region will contain many different metadata.  In that case, we simply annotate the whole region
346/// with a `MmapAnnotation::SideMeta` where `meta` is `"all"`.
347pub enum MmapAnnotation<'a> {
348    /// The mmap is for a space.
349    Space {
350        /// The name of the space.
351        name: &'a str,
352    },
353    /// The mmap is for a side metadata.
354    SideMeta {
355        /// The name of the space.
356        space: &'a str,
357        /// The name of the side metadata.
358        meta: &'a str,
359    },
360    /// The mmap is for a test case.  Usually constructed using the [`mmap_anno_test!`] macro.
361    Test {
362        /// The source file.
363        file: &'a str,
364        /// The line number.
365        line: u32,
366    },
367    /// For all other use cases.
368    Misc {
369        /// A human-readable descriptive name.
370        name: &'a str,
371    },
372}
373
374/// Construct an `MmapAnnotation::Test` with the current file name and line number.
375#[macro_export]
376macro_rules! mmap_anno_test {
377    () => {
378        &$crate::util::os::MmapAnnotation::Test {
379            file: file!(),
380            line: line!(),
381        }
382    };
383}
384
385// Export this to external crates
386pub use mmap_anno_test;
387
388impl std::fmt::Display for MmapAnnotation<'_> {
389    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390        match self {
391            MmapAnnotation::Space { name } => write!(f, "mmtk:space:{name}"),
392            MmapAnnotation::SideMeta { space, meta } => write!(f, "mmtk:sidemeta:{space}:{meta}"),
393            MmapAnnotation::Test { file, line } => write!(f, "mmtk:test:{file}:{line}"),
394            MmapAnnotation::Misc { name } => write!(f, "mmtk:misc:{name}"),
395        }
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402    use crate::util::os::OS;
403
404    #[test]
405    fn os_page_size_matches_system() {
406        let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
407        assert_eq!(OS::BYTES_IN_PAGE, page_size as usize);
408    }
409}