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