Skip to main content

aligned_vmem/api/
reserve.rs

1use crate::error::VmemError;
2#[cfg(aligned_vmem_mock)]
3use crate::mock;
4use crate::os::reserve_aligned_raw;
5use crate::Reservation;
6
7use super::internal::{finish_reservation, validate_size_align, RawReservation};
8
9/// Reserve `size` bytes of anonymous virtual memory whose base is aligned to
10/// `align`.
11///
12/// - `align` must be a power of two `>=` [`PAGE`](crate::page::PAGE).
13/// - `size` must be a non-zero multiple of [`PAGE`](crate::page::PAGE) —
14///   the COMPILE-TIME 4 KiB constant, not the runtime
15///   [`page_size()`](crate::page_size::page_size). **On a host where those
16///   differ (e.g. Apple Silicon macOS, 16 KiB pages)**, a `size` that is a
17///   `PAGE` multiple but not also a `page_size()` multiple is accepted here
18///   but produces a reservation whose span can never be fully decommitted:
19///   `Reservation::decommit`/the free [`decommit`](crate::api::decommit)
20///   validate against the runtime `page_size()`, so `decommit(0, size)` on
21///   such a reservation is a debug-build panic (`debug_assert!`) and a
22///   silent permanent no-op in release. This is the SAME fail-closed
23///   contract [`try_reserve_aligned_lazy`](crate::try_reserve_aligned_lazy)'s
24///   `initial_commit` parameter already enforces at the runtime granularity
25///   (task #1256/OH13-F3) — this eager constructor does not, by design, to
26///   avoid a runtime `page_size()` read on every call; validate against
27///   `page_size()` yourself first if your `size` is not already a multiple
28///   of the platform's largest supported page size.
29///
30/// On 32-bit Unix, first tries an ordinary exact-size `mmap` and checks
31/// whether the kernel happened to place it at an `align`-aligned address
32/// (fast path; hit rate depends on the OS's placement heuristics, not on any
33/// hint this crate passes); on a miss (wrong alignment), over-reserves
34/// `size + align` bytes and keeps the full mapping. On 64-bit Unix, the fast
35/// path is compiled out (`target_pointer_width = "32"` — see task #944,
36/// finding P-1), with ONE exception: on Linux AND Android, with the `huge-pages`
37/// feature on, a request for `align == LINUX_HUGE_PAGE_SIZE` (2 MiB) huge pages
38/// takes an exact-size `MAP_HUGETLB` attempt first, which when it succeeds
39/// reserves exactly `size`. That exception is gated on
40/// `any(target_os = "linux", target_os = "android")` + `feature = "huge-pages"`,
41/// NOT on pointer width — which is both why it still fires on 64-bit and why
42/// calling it Linux-only would be wrong. When it does not apply, a 64-bit Unix
43/// reservation over-reserves `size + align`
44/// bytes in one `mmap` call. On Windows, uses one syscall (fast path
45/// for `align <= 64 KiB`, over-reserving nothing — base == region) or two
46/// syscalls (over-reserving `size + align` and keeping the full mapping). The `Reservation::reservation_ptr` / `reservation_len` fields
47/// expose the full reservation; `Reservation::as_ptr` / `len` expose the
48/// aligned usable span.
49///
50/// **Cost on 32-bit Unix fast-path miss:** the reservation holds `size + align`
51/// bytes of virtual address space for its lifetime (measured hit rate: 34.4% at
52/// 64 KiB align, 46.7% at 1 MiB, 56.7% at 4 MiB — commit `35d51e6`, task #849;
53/// measured on WSL2/Linux, x86_64; 30-run aggregate; scope: 32-bit only — the
54/// hit rate is kernel- and ASLR-dependent and is not expected to transfer to
55/// other Unix platforms). **On 64-bit Unix these numbers do not apply**: the
56/// fast path never runs, so every reservation pays the "miss" cost of
57/// `size + align` bytes held for the reservation's lifetime, unconditionally.
58///
59/// Returns `None` on a contract violation or if the OS refuses the reservation
60/// (OOM) — never panics, so it is safe to call from inside a `GlobalAlloc`
61/// implementation. For the failure cause use [`try_reserve_aligned`].
62#[must_use]
63pub fn reserve_aligned(size: usize, align: usize) -> Option<Reservation> {
64    try_reserve_aligned(size, align).ok()
65}
66/// Fallible [`reserve_aligned`]: returns a [`VmemError`] carrying the OS cause
67/// (`errno` / `GetLastError`) on failure instead of a bare `None`.
68///
69/// A contract violation (bad `size`/`align`) returns
70/// [`VmemError::invalid_argument`] without touching the OS.
71pub fn try_reserve_aligned(size: usize, align: usize) -> Result<Reservation, VmemError> {
72    validate_size_align(size, align)?;
73    // Mock fault-injection: honour a scripted reserve failure first.
74    #[cfg(aligned_vmem_mock)]
75    if let Some(e) = mock::take_reserve_fault() {
76        mock::record(mock::Call::Reserve { size, align });
77        return Err(e);
78    }
79    #[cfg(aligned_vmem_mock)]
80    mock::record(mock::Call::Reserve { size, align });
81
82    // task #713: `reserve_aligned_raw` now captures its own `VmemError`
83    // immediately at the point of failure (before any cleanup FFI); this
84    // just propagates it rather than re-deriving a possibly-stale one here.
85    finish_reservation(
86        size,
87        align,
88        reserve_aligned_raw(size, align).map(|(base, reservation, reservation_len)| {
89            RawReservation {
90                base,
91                reservation,
92                reservation_len,
93                granted_huge: false,
94            }
95        }),
96    )
97}