Skip to main content

aligned_vmem/
lib.rs

1//! `aligned-vmem` — cross-platform **aligned anonymous virtual memory**.
2//!
3//! Reserve a span of `size` bytes whose base is aligned to an arbitrary
4//! power-of-two `align`, commit/decommit its pages, and release it — directly
5//! through the OS (`mmap`/`munmap`/`madvise` on Unix, `VirtualAlloc`/
6//! `VirtualFree` on Windows), with **no file-mapping machinery** and **no
7//! dependencies**. Under [miri](https://github.com/rust-lang/miri) it falls
8//! back to `std::alloc` so consumers stay miri-testable.
9//!
10//! This is the OS aperture extracted from
11//! [`sefer-alloc`](https://crates.io/crates/sefer-alloc). It is the one crate
12//! whose *entire purpose* is the `unsafe` OS calls — every `unsafe` block
13//! carries a `// SAFETY:` proof, and a safe API is exposed on top.
14//!
15//! # Why not `region` / `memmap2` / `mmap-rs`?
16//!
17//! Those crates are oriented around **file mappings** and **page-protection**.
18//! `aligned-vmem` does one different thing: hand you an *anonymous* span whose
19//! **base is aligned to a power of two you choose** (e.g. 2 MiB / 4 MiB for an
20//! allocator's segments) via the classic over-reserve + trim technique, plus
21//! page-granularity decommit/recommit so you can return physical memory to the
22//! OS while keeping the address-space reservation. If you are building an
23//! allocator, an arena, or a slab and need "give me a 4 MiB-aligned 4 MiB
24//! span", this is the small focused tool.
25//!
26//! # Example
27//!
28//! ```
29//! use aligned_vmem::{reserve_aligned, release};
30//!
31//! // Reserve 4 MiB aligned to 4 MiB.
32//! let span = 4 * 1024 * 1024;
33//! let r = reserve_aligned(span, span).expect("OOM");
34//! let base = r.as_ptr();
35//! assert_eq!(base as usize % span, 0); // base is `span`-aligned
36//!
37//! // SAFETY: `base` is valid for `r.len()` bytes; we own it exclusively.
38//! unsafe { base.write(0xAB); assert_eq!(base.read(), 0xAB); }
39//!
40//! // RAII release on drop, or take the parts for manual self-hosted release:
41//! let (raw, raw_len, raw_align) = r.into_parts();
42//! // SAFETY: the triple came from `into_parts` and is released exactly once.
43//! unsafe { release(raw, raw_len, raw_align) };
44//! ```
45//!
46//! # Alignment contract
47//!
48//! `align` must be a power of two and at least [`page_size`]. `size` must be a
49//! non-zero multiple of [`page_size`] (so decommit ranges land on page
50//! boundaries). Violations return `None` rather than panicking.
51
52#![allow(unsafe_code)]
53#![deny(missing_docs)]
54
55use core::ptr::NonNull;
56
57/// The page size this crate assumes for decommit/recommit granularity: 4 KiB,
58/// the smallest unit both `mmap` and `VirtualAlloc` will commit/decommit on the
59/// platforms this crate targets. Decommit/recommit offsets must be multiples of
60/// this value.
61pub const PAGE: usize = 1 << 12;
62
63/// Return the page size used for [`decommit`] / [`recommit`] granularity.
64///
65/// Currently a compile-time constant ([`PAGE`] = 4 KiB); exposed as a function
66/// so a future version can query the OS without a breaking change.
67#[must_use]
68#[inline]
69pub fn page_size() -> usize {
70    PAGE
71}
72
73/// An owning handle to one aligned span of anonymous virtual memory.
74///
75/// `as_ptr()` is non-null, aligned to the `align` requested at reservation, and
76/// valid for `len()` bytes for the lifetime of this handle. The span is **not**
77/// initialised. Dropping the handle returns the whole underlying OS reservation
78/// to the OS exactly once.
79///
80/// For a self-hosted allocator that records `(reservation, reservation_len)` in
81/// its own metadata rather than keeping a `Vec<Reservation>`, use
82/// [`into_parts`](Self::into_parts) to take the raw reservation (suppressing the
83/// `Drop`) and release it later with [`release`].
84///
85/// `Reservation` is `Send` (the span is owned exclusively) but not `Sync`
86/// (writes through the raw pointer are unsynchronised — that is the caller's
87/// concern).
88pub struct Reservation {
89    base: NonNull<u8>,
90    len: usize,
91    reservation: NonNull<u8>,
92    reservation_len: usize,
93    /// The alignment requested at reservation time. Carried so the `Drop` /
94    /// [`release`] path can reconstruct the exact `Layout` under miri (the
95    /// native `munmap` / `VirtualFree` paths ignore it). See [`into_parts`].
96    align: usize,
97}
98
99impl Reservation {
100    /// The aligned usable base of this span. Non-null, valid for [`len`](Self::len)
101    /// bytes, aligned to the `align` requested at reservation time.
102    #[must_use]
103    #[inline]
104    pub fn as_ptr(&self) -> *mut u8 {
105        self.base.as_ptr()
106    }
107
108    /// The number of usable bytes at [`as_ptr`](Self::as_ptr).
109    #[must_use]
110    #[inline]
111    pub const fn len(&self) -> usize {
112        self.len
113    }
114
115    /// Whether the usable span is empty (always `false` — [`reserve_aligned`]
116    /// rejects zero sizes; provided for lint-friendliness).
117    #[must_use]
118    #[inline]
119    pub const fn is_empty(&self) -> bool {
120        self.len == 0
121    }
122
123    /// The start of the underlying OS reservation (may sit below
124    /// [`as_ptr`](Self::as_ptr) due to the over-reserve + trim technique).
125    #[must_use]
126    #[inline]
127    pub fn reservation_ptr(&self) -> *mut u8 {
128        self.reservation.as_ptr()
129    }
130
131    /// The full size of the underlying OS reservation.
132    #[must_use]
133    #[inline]
134    pub const fn reservation_len(&self) -> usize {
135        self.reservation_len
136    }
137
138    /// Consume the handle WITHOUT releasing the OS reservation, returning the
139    /// `(reservation_ptr, reservation_len, align)` the caller must later hand to
140    /// [`release`] exactly once. Use this when your allocator records the
141    /// reservation in its own self-hosted metadata instead of relying on
142    /// `Drop`.
143    ///
144    /// `align` is the alignment originally requested; the native release paths
145    /// ignore it, but it is required for the miri fallback to reconstruct the
146    /// exact `Layout`. A self-hosting allocator that always uses one alignment
147    /// can pass that constant to [`release`] instead of storing this value.
148    #[must_use]
149    pub fn into_parts(self) -> (*mut u8, usize, usize) {
150        let parts = (self.reservation.as_ptr(), self.reservation_len, self.align);
151        core::mem::forget(self);
152        parts
153    }
154
155    /// Wrap a pre-existing OS reservation (e.g. one obtained from
156    /// `VirtualAllocExNuma` or another platform-specific allocator that
157    /// `reserve_aligned` does not call directly) in a [`Reservation`] handle.
158    ///
159    /// The handle then participates in the normal RAII lifecycle: on `Drop`
160    /// (or via [`release`]) the underlying reservation is returned to the OS
161    /// using the platform-appropriate release routine
162    /// (`VirtualFree(MEM_RELEASE)` on Windows, `munmap` on Unix,
163    /// `std::alloc::dealloc` on miri).
164    ///
165    /// This is the inverse of [`into_parts`](Self::into_parts) and exists for
166    /// the cross-crate handoff pattern: a sibling crate (`numa-shim` on
167    /// Windows) issues a platform-specific reservation call that `aligned-vmem`
168    /// itself does not wrap, then adopts the result via this constructor so
169    /// downstream code can hold a uniform [`Reservation`] regardless of which
170    /// syscall produced it.
171    ///
172    /// # Safety
173    ///
174    /// All five values must describe a **live, exclusively-owned OS
175    /// reservation** compatible with `aligned-vmem`'s release path:
176    ///
177    /// - `base` is the *aligned usable* start; non-null, valid for `len` bytes,
178    ///   aligned to `align`.
179    /// - `len` is the usable span size, a non-zero multiple of [`PAGE`].
180    /// - `reservation` is the *underlying OS reservation* start (often equal
181    ///   to `base`, but may be lower under the over-reserve + trim technique).
182    /// - `reservation_len` is the full size of the OS reservation, a non-zero
183    ///   multiple of [`PAGE`], `reservation_len >= len + (base - reservation)`.
184    /// - `align` is a power of two `>= PAGE` and matches the alignment the OS
185    ///   reservation was created with. The native release paths
186    ///   (`VirtualFree` / `munmap`) ignore it; the miri fallback uses it to
187    ///   reconstruct the exact `Layout`.
188    ///
189    /// The reservation must be released **exactly once** — by dropping this
190    /// handle, or by extracting via `into_parts` and calling [`release`]
191    /// manually. Constructing two `Reservation` handles over the same OS
192    /// reservation is undefined behaviour (double release).
193    ///
194    /// On Windows specifically, the reservation MUST have been created with
195    /// `MEM_RESERVE | MEM_COMMIT` so `VirtualFree(MEM_RELEASE)` accepts it.
196    /// (`VirtualAllocExNuma(..., MEM_RESERVE | MEM_COMMIT, ...)` satisfies
197    /// this — that is the intended call site.)
198    #[must_use]
199    pub unsafe fn from_raw_parts(
200        base: *mut u8,
201        len: usize,
202        reservation: *mut u8,
203        reservation_len: usize,
204        align: usize,
205    ) -> Self {
206        // The contract is enforced by the caller's `unsafe`. We only assert
207        // the non-null invariant: a null pointer here would corrupt the
208        // `Drop` path which would then call `release_reservation(null, ...)`.
209        // In a well-formed call this branch is dead.
210        let base_nn = NonNull::new(base).expect("from_raw_parts: base must be non-null");
211        let res_nn =
212            NonNull::new(reservation).expect("from_raw_parts: reservation must be non-null");
213        Self {
214            base: base_nn,
215            len,
216            reservation: res_nn,
217            reservation_len,
218            align,
219        }
220    }
221}
222
223impl Drop for Reservation {
224    fn drop(&mut self) {
225        // SAFETY: `self.reservation` was returned by `reserve_aligned` and is
226        // valid for `self.reservation_len` bytes; this handle owns it
227        // exclusively (no aliasing — `Reservation` is `Send` but not `Sync`).
228        // Dropping returns the entire reservation to the OS exactly once.
229        unsafe { release_reservation(self.reservation, self.reservation_len, self.align) };
230    }
231}
232
233// SAFETY (Send): a `Reservation` owns its OS reservation exclusively; moving it
234// to another thread moves ownership of every byte, leaving no aliasing on the
235// origin thread. The memory is plain uninitialised bytes (no `Rc`/`Cell`/TLS
236// affinity).
237unsafe impl Send for Reservation {}
238
239/// Reserve `size` bytes of anonymous virtual memory whose base is aligned to
240/// `align`, via the over-reserve + trim technique.
241///
242/// - `align` must be a power of two `>=` [`PAGE`].
243/// - `size` must be a non-zero multiple of [`PAGE`].
244///
245/// Returns `None` on a contract violation or if the OS refuses the reservation
246/// (OOM) — never panics, so it is safe to call from inside a `GlobalAlloc`
247/// implementation.
248#[must_use]
249pub fn reserve_aligned(size: usize, align: usize) -> Option<Reservation> {
250    if size == 0 || !align.is_power_of_two() || align < PAGE || !size.is_multiple_of(PAGE) {
251        return None;
252    }
253    reserve_aligned_raw(size, align).map(|(base, reservation, reservation_len)| Reservation {
254        base,
255        len: size,
256        reservation,
257        reservation_len,
258        align,
259    })
260}
261
262/// Release a whole OS reservation obtained from [`Reservation::into_parts`].
263///
264/// # Safety
265///
266/// `reservation`, `reservation_len` and `align` must be the three values
267/// returned by [`Reservation::into_parts`] (or, for a self-hosting caller that
268/// always uses one alignment, that same alignment constant), and the
269/// reservation must be released **exactly once**. A double release is a
270/// contract violation. The native (`munmap` / `VirtualFree`) paths ignore
271/// `align`; it is consulted only by the miri fallback to reconstruct the exact
272/// `Layout`.
273pub unsafe fn release(reservation: *mut u8, reservation_len: usize, align: usize) {
274    let nn = match NonNull::new(reservation) {
275        Some(n) => n,
276        None => return,
277    };
278    // SAFETY: forwarded from the caller's contract above.
279    unsafe { release_reservation(nn, reservation_len, align) };
280}
281
282/// Decommit pages `[base + start, base + end)`: return their physical backing
283/// to the OS while keeping the address-space reservation alive. Re-access after
284/// decommit produces fresh zero-filled pages (after [`recommit`] on Windows;
285/// implicitly on Unix).
286///
287/// `start` and `end` must be multiples of [`PAGE`] and within the span. A
288/// no-op if the range is empty.
289///
290/// # Safety
291///
292/// `base` must be the [`as_ptr`](Reservation::as_ptr) of a live reservation,
293/// and `[base+start, base+end)` must contain no data the caller still needs —
294/// its contents are discarded.
295pub unsafe fn decommit(base: *mut u8, start: usize, end: usize) {
296    if start >= end || !start.is_multiple_of(PAGE) || !end.is_multiple_of(PAGE) {
297        return;
298    }
299    // SAFETY: forwarded from the caller's contract; the per-OS routine touches
300    // only kernel page-state, never the bytes.
301    unsafe { decommit_pages_impl(base, start, end) };
302}
303
304/// Recommit pages `[base + start, base + end)` previously passed to
305/// [`decommit`]. On Windows this re-commits physical pages
306/// (`VirtualAlloc(MEM_COMMIT)`); on Unix re-access is implicit so this is a
307/// no-op.
308///
309/// `start` and `end` must be multiples of [`PAGE`] and within the span.
310///
311/// # Safety
312///
313/// `base` must be the [`as_ptr`](Reservation::as_ptr) of a live reservation
314/// whose `[base+start, base+end)` range was previously decommitted.
315pub unsafe fn recommit(base: *mut u8, start: usize, end: usize) {
316    if start >= end || !start.is_multiple_of(PAGE) || !end.is_multiple_of(PAGE) {
317        return;
318    }
319    // SAFETY: forwarded from the caller's contract.
320    unsafe { recommit_pages_impl(base, start, end) };
321}
322
323// ---------------------------------------------------------------------------
324// Windows path: VirtualAlloc / VirtualFree. Raw bindings declared locally so
325// the crate has NO winapi/windows-sys dependency. std always links kernel32.
326// ---------------------------------------------------------------------------
327
328#[cfg(all(windows, not(miri)))]
329fn reserve_aligned_raw(size: usize, align: usize) -> Option<(NonNull<u8>, NonNull<u8>, usize)> {
330    let over = size.checked_add(align)?;
331    let region = unsafe {
332        // SAFETY: `VirtualAlloc(NULL, over, MEM_RESERVE | MEM_COMMIT,
333        // PAGE_READWRITE)` reserves+commits `over` bytes, returning the base
334        // (granularity-aligned) or NULL on OOM. We check for NULL immediately.
335        let p = winapi_virtual_alloc(over);
336        NonNull::new(p as *mut u8)?
337    };
338    let region_ptr = region.as_ptr();
339    let region_addr = region_ptr as usize;
340    let base_addr = align_up_addr(region_addr, align);
341    debug_assert!(base_addr + size <= region_addr + over);
342    let base = unsafe {
343        // SAFETY: `base_addr` is non-null (>= region_addr) and within the
344        // committed `over`-byte region; aligned to `align`.
345        NonNull::new_unchecked(base_addr as *mut u8)
346    };
347    let head = base_addr - region_addr;
348    let tail_start = base_addr + size;
349    let tail_len = (region_addr + over) - tail_start;
350    if head > 0 {
351        unsafe {
352            // SAFETY: `[region_addr, region_addr + head)` is within the committed
353            // region; `MEM_DECOMMIT` returns its physical pages while keeping the
354            // address space reserved until `MEM_RELEASE` on drop.
355            winapi_virtual_decommit(region_ptr, head);
356        }
357    }
358    if tail_len > 0 {
359        unsafe {
360            // SAFETY: `[tail_start, tail_start + tail_len)` is within the committed
361            // region; same `MEM_DECOMMIT` contract as the head.
362            winapi_virtual_decommit(tail_start as *mut u8, tail_len);
363        }
364    }
365    Some((base, region, over))
366}
367
368#[cfg(all(windows, not(miri)))]
369unsafe fn release_reservation(reservation: NonNull<u8>, _reservation_len: usize, _align: usize) {
370    // SAFETY: `reservation` was returned by a prior `VirtualAlloc(.., MEM_RESERVE
371    // | MEM_COMMIT, ..)`. `VirtualFree(.., 0, MEM_RELEASE)` releases the ENTIRE
372    // region reserved in that one call — `dwSize` MUST be 0 in this mode.
373    winapi_virtual_release(reservation.as_ptr());
374}
375
376#[cfg(all(windows, not(miri)))]
377unsafe fn decommit_pages_impl(base: *mut u8, start: usize, end: usize) {
378    let len = end - start;
379    // SAFETY: caller guarantees `[base+start, base+start+len)` is within a
380    // committed reservation; `MEM_DECOMMIT` returns the physical pages.
381    let addr = unsafe { base.add(start) };
382    unsafe { winapi_virtual_decommit(addr, len) };
383}
384
385#[cfg(all(windows, not(miri)))]
386unsafe fn recommit_pages_impl(base: *mut u8, start: usize, end: usize) {
387    let len = end - start;
388    // SAFETY: caller guarantees `[base+start, +len)` is within an address-space
389    // reservation owned by them; `MEM_COMMIT` re-commits the physical pages.
390    let addr = unsafe { base.add(start) };
391    unsafe {
392        VirtualAlloc(
393            addr as *mut core::ffi::c_void,
394            len,
395            MEM_COMMIT,
396            PAGE_READWRITE,
397        );
398    }
399}
400
401#[cfg(all(windows, not(miri)))]
402extern "system" {
403    fn VirtualAlloc(
404        lp_address: *mut core::ffi::c_void,
405        dw_size: usize,
406        fl_allocation_type: u32,
407        fl_protect: u32,
408    ) -> *mut core::ffi::c_void;
409    fn VirtualFree(lp_address: *mut core::ffi::c_void, dw_size: usize, dw_free_type: u32) -> i32;
410}
411
412#[cfg(all(windows, not(miri)))]
413const MEM_COMMIT: u32 = 0x0000_1000;
414#[cfg(all(windows, not(miri)))]
415const MEM_RESERVE: u32 = 0x0000_2000;
416#[cfg(all(windows, not(miri)))]
417const MEM_DECOMMIT: u32 = 0x0000_4000;
418#[cfg(all(windows, not(miri)))]
419const MEM_RELEASE: u32 = 0x0000_8000;
420#[cfg(all(windows, not(miri)))]
421const PAGE_READWRITE: u32 = 0x04;
422
423#[cfg(all(windows, not(miri)))]
424unsafe fn winapi_virtual_alloc(over: usize) -> *mut core::ffi::c_void {
425    // SAFETY: non-zero `over` + documented reserve+commit + RW protection flags.
426    VirtualAlloc(
427        core::ptr::null_mut(),
428        over,
429        MEM_RESERVE | MEM_COMMIT,
430        PAGE_READWRITE,
431    )
432}
433
434#[cfg(all(windows, not(miri)))]
435unsafe fn winapi_virtual_decommit(addr: *mut u8, len: usize) {
436    // SAFETY: caller guarantees `[addr, addr+len)` is within a committed region.
437    VirtualFree(addr as *mut core::ffi::c_void, len, MEM_DECOMMIT);
438}
439
440#[cfg(all(windows, not(miri)))]
441unsafe fn winapi_virtual_release(addr: *mut u8) {
442    // SAFETY: caller guarantees `addr` is the base of a region reserved via
443    // `VirtualAlloc(.., MEM_RESERVE|MEM_COMMIT, ..)`; `MEM_RELEASE` + size 0
444    // releases the entire reservation.
445    VirtualFree(addr as *mut core::ffi::c_void, 0, MEM_RELEASE);
446}
447
448// ---------------------------------------------------------------------------
449// Unix path: mmap / munmap / madvise. Raw bindings declared locally — no libc
450// dependency.
451// ---------------------------------------------------------------------------
452
453#[cfg(all(unix, not(miri)))]
454fn reserve_aligned_raw(size: usize, align: usize) -> Option<(NonNull<u8>, NonNull<u8>, usize)> {
455    let over = size.checked_add(align)?;
456    let region_ptr = unsafe {
457        // SAFETY: `mmap(NULL, over, RW, PRIVATE|ANON, -1, 0)` requests an
458        // anonymous private mapping of `over` bytes; the kernel chooses the
459        // (page-aligned) address or returns MAP_FAILED.
460        let p = libc_mmap(over);
461        if p.is_null() {
462            return None;
463        }
464        p
465    };
466    let region_addr = region_ptr as usize;
467    let base_addr = align_up_addr(region_addr, align);
468    let base = unsafe {
469        // SAFETY: `base_addr` is non-null (>= region_addr) and `align`-aligned.
470        NonNull::new_unchecked(base_addr as *mut u8)
471    };
472    let head = base_addr - region_addr;
473    let tail_start = base_addr + size;
474    let tail_len = (region_addr + over) - tail_start;
475    if head > 0 {
476        unsafe {
477            // SAFETY: `[region_addr, region_addr + head)` is within the freshly
478            // mapped region; `munmap` returns it to the kernel.
479            libc_munmap(region_ptr as *mut u8, head);
480        }
481    }
482    if tail_len > 0 {
483        unsafe {
484            // SAFETY: `[tail_start, tail_start + tail_len)` is within the region
485            // and after the usable span; `munmap` returns it.
486            libc_munmap(tail_start as *mut u8, tail_len);
487        }
488    }
489    Some((base, base, size))
490}
491
492#[cfg(all(unix, not(miri)))]
493unsafe fn release_reservation(reservation: NonNull<u8>, reservation_len: usize, _align: usize) {
494    // SAFETY: on unix the head/tail are unmapped so `reservation` IS the start of
495    // the remaining mapping of length `reservation_len`; `munmap` returns it.
496    libc_munmap(reservation.as_ptr(), reservation_len);
497}
498
499#[cfg(all(unix, not(miri)))]
500unsafe fn decommit_pages_impl(base: *mut u8, start: usize, end: usize) {
501    let len = end - start;
502    // SAFETY: caller guarantees `[base+start, +len)` is within a live mapping;
503    // `madvise(MADV_DONTNEED)` discards the backing pages (re-access zero-fills).
504    let addr = unsafe { base.add(start) };
505    unsafe { libc_madvise_dontneed(addr, len) };
506}
507
508#[cfg(all(unix, not(miri)))]
509unsafe fn recommit_pages_impl(_base: *mut u8, _start: usize, _end: usize) {
510    // On unix, re-access after MADV_DONTNEED is implicit — the kernel provides
511    // fresh zeroed pages on demand. No syscall needed.
512}
513
514#[cfg(all(unix, not(miri)))]
515const PROT_READ: i32 = 0x1;
516#[cfg(all(unix, not(miri)))]
517const PROT_WRITE: i32 = 0x2;
518#[cfg(all(unix, not(miri)))]
519const MAP_PRIVATE: i32 = 0x02;
520// MAP_ANON value differs across BSD vs Linux. macOS / *BSD use 0x1000,
521// Linux uses 0x20. Wrong value silently turns mmap into a file-backed
522// mapping attempt (with fd=-1 → EBADF → MAP_FAILED → reserve_aligned
523// returns None). Tested in CI's macOS job.
524#[cfg(all(unix, not(miri), target_os = "linux"))]
525const MAP_ANON: i32 = 0x20;
526#[cfg(all(
527    unix,
528    not(miri),
529    any(
530        target_os = "macos",
531        target_os = "ios",
532        target_os = "tvos",
533        target_os = "watchos",
534        target_os = "freebsd",
535        target_os = "netbsd",
536        target_os = "openbsd",
537        target_os = "dragonfly",
538    )
539))]
540const MAP_ANON: i32 = 0x1000;
541#[cfg(all(unix, not(miri)))]
542const MAP_FAILED: usize = usize::MAX;
543#[cfg(all(unix, not(miri)))]
544const MADV_DONTNEED: i32 = 4;
545
546#[cfg(all(unix, not(miri)))]
547extern "C" {
548    fn mmap(
549        addr: *mut core::ffi::c_void,
550        length: usize,
551        prot: i32,
552        flags: i32,
553        fd: i32,
554        offset: i64,
555    ) -> *mut core::ffi::c_void;
556    fn munmap(addr: *mut core::ffi::c_void, length: usize) -> i32;
557    fn madvise(addr: *mut core::ffi::c_void, length: usize, advice: i32) -> i32;
558}
559
560#[cfg(all(unix, not(miri)))]
561unsafe fn libc_mmap(len: usize) -> *mut core::ffi::c_void {
562    // SAFETY: `mmap(NULL, len, RW, PRIVATE|ANON, -1, 0)` — anonymous private
563    // mapping; the kernel chooses the address.
564    let p = mmap(
565        core::ptr::null_mut(),
566        len,
567        PROT_READ | PROT_WRITE,
568        MAP_PRIVATE | MAP_ANON,
569        -1,
570        0,
571    );
572    if (p as usize) == MAP_FAILED {
573        core::ptr::null_mut()
574    } else {
575        p
576    }
577}
578
579#[cfg(all(unix, not(miri)))]
580unsafe fn libc_munmap(addr: *mut u8, len: usize) {
581    // SAFETY: caller guarantees `[addr, addr+len)` was returned by `mmap` and is
582    // unmapped exactly once.
583    let _ = munmap(addr as *mut core::ffi::c_void, len);
584}
585
586#[cfg(all(unix, not(miri)))]
587unsafe fn libc_madvise_dontneed(addr: *mut u8, len: usize) {
588    // SAFETY: caller guarantees `[addr, addr+len)` is within a live mmap region.
589    let _ = madvise(addr as *mut core::ffi::c_void, len, MADV_DONTNEED);
590}
591
592// ---------------------------------------------------------------------------
593// Miri aperture: miri cannot execute raw FFI, so fall back to `std::alloc` with
594// the requested alignment. Sound because under miri the consumer is NOT the
595// global allocator — the host allocator backs the test harness.
596// ---------------------------------------------------------------------------
597
598#[cfg(miri)]
599fn reserve_aligned_raw(size: usize, align: usize) -> Option<(NonNull<u8>, NonNull<u8>, usize)> {
600    use std::alloc::Layout;
601    let layout = Layout::from_size_align(size, align).ok()?;
602    let ptr = unsafe {
603        // SAFETY: `layout` has non-zero size and power-of-two alignment; under
604        // miri the consumer is not the global allocator, so no reentrancy.
605        std::alloc::alloc(layout)
606    };
607    let base = NonNull::new(ptr)?;
608    Some((base, base, size))
609}
610
611#[cfg(miri)]
612unsafe fn release_reservation(reservation: NonNull<u8>, reservation_len: usize, align: usize) {
613    use std::alloc::Layout;
614    // SAFETY: `reservation` was returned by `std::alloc::alloc` with exactly
615    // `Layout::from_size_align(reservation_len, align)` in `reserve_aligned_raw`
616    // (the `align` is threaded through `Reservation`/`into_parts`/`release` so
617    // the reconstructed layout matches the allocation), and is freed once.
618    let layout = Layout::from_size_align(reservation_len, align).expect("release: invalid layout");
619    std::alloc::dealloc(reservation.as_ptr(), layout);
620}
621
622#[cfg(miri)]
623unsafe fn decommit_pages_impl(_base: *mut u8, _start: usize, _end: usize) {
624    // Miri models no RSS; decommit is a no-op (pages stay accessible — the
625    // caller already proved nothing live remains in the range).
626}
627
628#[cfg(miri)]
629unsafe fn recommit_pages_impl(_base: *mut u8, _start: usize, _end: usize) {
630    // Miri: decommit was a no-op, so recommit is too.
631}
632
633/// Round `addr` up to the next multiple of `align` (a power of two).
634#[cfg(not(miri))]
635fn align_up_addr(addr: usize, align: usize) -> usize {
636    debug_assert!(align.is_power_of_two());
637    let mask = align - 1;
638    (addr + mask) & !mask
639}