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. A consumer that
9//! installs itself as `#[global_allocator]` cannot use this crate under miri,
10//! because the miri backend routes allocations through the global allocator and
11//! would create a reentrancy hazard (the same class of issue `numa-shim` hit
12//! in #777).
13//!
14//! This is the OS aperture extracted from
15//! [`sefer-alloc`](https://crates.io/crates/sefer-alloc). It is the one crate
16//! whose *entire purpose* is the `unsafe` OS calls — every `unsafe` block
17//! carries a `// SAFETY:` proof, and a safe API is exposed on top.
18//!
19//! # Why not `region` / `memmap2` / `mmap-rs`?
20//!
21//! Those crates are oriented around **file mappings** and **page-protection**.
22//! `aligned-vmem` does one different thing: hand you an *anonymous* span whose
23//! **base is aligned to a power of two you choose** (e.g. 2 MiB / 4 MiB for an
24//! allocator's segments). On 32-bit Unix, first tries an ordinary exact-size
25//! `mmap` and checks whether the kernel happened to place it at an
26//! `align`-aligned address (fast path; hit rate depends on the OS's placement
27//! heuristics, not on any hint this crate passes); on a miss (wrong
28//! alignment), over-reserves `size + align` bytes and keeps the full mapping.
29//! On 64-bit Unix, the exact-size fast path is compiled out entirely (see the
30//! module-level "bench-internals" section below and [`reserve_aligned`]'s own
31//! rustdoc), with ONE exception: on Linux AND Android, with the `huge-pages`
32//! feature on, a request for `align == LINUX_HUGE_PAGE_SIZE` (2 MiB) huge pages
33//! takes an exact-size `MAP_HUGETLB` attempt first, which when it succeeds
34//! reserves exactly `size`. The exception's gate is
35//! `any(target_os = "linux", target_os = "android")` + `feature = "huge-pages"` —
36//! it is NOT keyed on pointer width, which is why it survives on 64-bit; and it
37//! covers Android too, so do not describe it as Linux-only. When that exception
38//! does not apply, a 64-bit Unix reservation over-reserves `size + align` bytes
39//! in one `mmap` call. On Windows, uses one syscall (fast path
40//! for `align <= 64 KiB`, over-reserving nothing — base == region) or two
41//! syscalls (over-reserving `size + align` and keeping the full mapping). The
42//! `Reservation::reservation_ptr` / `reservation_len` fields expose the full
43//! reservation; `Reservation::as_ptr` / `len` expose the aligned usable span,
44//! plus page-granularity decommit/recommit so you can hint the OS to return
45//! physical memory while keeping the address-space reservation (on Linux,
46//! Android, and Windows this is guaranteed to return physical backing; on the
47//! Darwin family
48//! — macOS/iOS/tvOS/watchOS — and the BSDs, this reclaim is advisory-only and
49//! provides no zero-fill guarantee, see [`decommit`]'s Darwin caveat). If you are building an
50//! allocator, an arena, or a slab and need "give me a 4 MiB-aligned 4 MiB
51//! span", this is the small focused tool.
52//!
53//! # Fallible vs infallible API (0.2)
54//!
55//! Every reservation/commit entry point has two forms:
56//! - the historical infallible form returning `Option`/`bool`
57//! ([`reserve_aligned`], [`recommit`], …), and
58//! - a `try_*` form returning [`Result<_, VmemError>`] whose error carries the
59//! OS `errno` / `GetLastError` cause ([`try_reserve_aligned`],
60//! [`try_recommit`], …).
61//!
62//! For most of these pairs the infallible form forwards to the `try_*` form
63//! and discards the cause, so both stay in perfect lockstep. **The decommit
64//! family is the exception, in the OPPOSITE direction:** [`decommit`] and
65//! [`try_decommit`] are siblings, not a forward/wrap pair — both call the
66//! same per-OS backend directly (each discarding or keeping its own copy of
67//! the outcome), rather than one calling the other. `decommit`'s contract is
68//! deliberately silent on OS-level outcome (best-effort by nature; see
69//! [`decommit`]'s own rustdoc) — it discards the backend's answer.
70//! [`try_decommit`]'s outer `Result` still reports range-contract validity
71//! only (unchanged) — but since task #1180 its `Ok` payload is a
72//! [`DecommitOutcome`] (`Skipped` / `Advised` / `Refused`), which DOES
73//! observe what the SELECTED BACKEND did with a well-formed, non-empty
74//! range: whether a call was issued at all, and if so, whether it was
75//! accepted or refused. **`Advised` names what the call did, not
76//! necessarily a real OS syscall** — under the native backend it means the
77//! kernel accepted a real `madvise(2)`/`VirtualFree` call; under the
78//! `aligned_vmem_mock` cfg or miri, no syscall runs at all and `Advised` is
79//! the simulated backend's own unconditional answer (see
80//! [`DecommitOutcome::Advised`]'s own doc for the full three-way split).
81//! Before task #1180 this was a bare `Ok(())`, indistinguishable from every
82//! other well-formed outcome.
83//!
84//! # Example
85//!
86//! ```text
87//! use aligned_vmem::{reserve_aligned, release};
88//!
89//! // Reserve 4 MiB aligned to 4 MiB.
90//! let span = 4 * 1024 * 1024;
91//! let r = reserve_aligned(span, span).expect("OOM");
92//! let base = r.as_ptr();
93//! assert_eq!(base.addr() % span, 0); // base is `span`-aligned
94//!
95//! // SAFETY: `base` is valid for `r.len()` bytes; we own it exclusively.
96//! unsafe { base.write(0xAB); assert_eq!(base.read(), 0xAB); }
97//!
98//! // RAII release on drop, or take the parts for manual self-hosted release:
99//! let (raw, raw_len, raw_align) = r.into_parts();
100//! // SAFETY: the triple came from `into_parts` and is released exactly once.
101//! unsafe { release(raw, raw_len, raw_align) };
102//! ```
103//!
104//! Runnable form: `tests/smoke.rs`.
105//!
106//! # Alignment contract
107//!
108//! `align` must be a power of two and at least [`PAGE`]. `size` must be a
109//! non-zero multiple of [`PAGE`] (so decommit ranges land on page boundaries).
110//! Violations return `None` / `Err(VmemError::invalid_argument())` rather than
111//! panicking.
112//!
113//! # Page size ([`page_size`])
114//!
115//! [`PAGE`] (4 KiB) is the crate's *minimum decommit granularity* — the
116//! validation constant. [`page_size`] returns the **actual OS page size**
117//! queried once via `sysconf(_SC_PAGESIZE)` (Unix) / `GetSystemInfo` (Windows).
118//! On Apple Silicon macOS this is 16 KiB; callers computing decommit offsets
119//! must round to `page_size()`, not `PAGE`. The crate's own validation of
120//! both range endpoints against `page_size()` is the load-bearing guard: do
121//! not rely on the OS to reject a misaligned range — Linux `madvise(2)`
122//! rejects only a misaligned ADDRESS and rounds a misaligned LENGTH **up**
123//! past the requested range, and Windows `VirtualFree(MEM_DECOMMIT)` rejects
124//! nothing (it widens the range in both directions to whole pages). In the
125//! never-observed case where the one-time OS query fails, the crate fails
126//! closed rather than guessing — see [`page_size`]'s "If the one-time OS
127//! query fails" paragraph and [`try_page_size`].
128
129#![allow(unsafe_code)]
130#![deny(missing_docs)]
131#![cfg_attr(docsrs, feature(doc_cfg))]
132// Under `mock` the real platform syscalls (decommit/recommit/commit_range) are
133// bypassed by the recording backend, so their per-OS `*_impl` helpers become
134// legitimately unused. This used to be a crate-wide `allow(dead_code)`, which
135// made the whole crate structurally unable to report ANY unused item under
136// `--all-features` (task #646/F8). Narrowed to per-item
137// `#[cfg_attr(aligned_vmem_mock, allow(dead_code))]` on exactly the helpers
138// confirmed (by building `RUSTFLAGS="--cfg aligned_vmem_mock" cargo build
139// --features lazy-commit,huge-pages,fault-injection` on Windows, Unix
140// (`--target x86_64-unknown-linux-gnu`) and miri (`--cfg miri`)) to go dead
141// under `mock` alone: the per-OS `decommit_pages_impl` / `recommit_pages_impl`
142// / `commit_range_impl` / `reserve_aligned_lazy_raw` trio-plus-one on each
143// platform, plus the Windows-only `winapi_virtual_decommit` +
144// `MEM_DECOMMIT` and the Unix-only `libc_madvise` + `madv_free_advice` +
145// `MADV_DONTNEED` + `MADV_FREE` (all only reachable from the real decommit
146// path, which `mock` bypasses).
147// `fault_injection` carries two hooks with one call site each (task #1219
148// added the second). The COMMIT-side hook (`should_fail_commit`) is consulted
149// only from `try_commit_range`, which is itself gated on `lazy-commit`: a
150// caller who enables `fault-injection` without `lazy-commit` gets a
151// compiled-but-unreachable hook (harmless — the feature is additive and
152// test-only); suppress dead-code only in that specific combination, on the
153// single item it affects. The DECOMMIT-side hook (`should_fail_decommit`) is
154// consulted only from `dispatch_try_decommit`, which is NOT feature-gated
155// (decommit is core API), so its only orphaning combination is the mock cfg —
156// see its own `#[cfg_attr(aligned_vmem_mock, allow(dead_code))]`.
157//
158// Structural alternative considered and deferred for a future major release:
159// reorganize the three backends as separate `#[cfg]`-selected private modules
160// (`os_windows` / `os_unix` / `os_miri`) with one shared private signature,
161// allowing `mock` to be a fourth module selected by the same `#[cfg]` mechanism.
162// That would eliminate every `#[cfg_attr(aligned_vmem_mock, allow(dead_code))]`
163// attribute, but is a larger refactor than this crate's 0.2.0 release should
164// carry. The current partial-replacement shape (mock replaces decommit/recommit/
165// commit_range but not reserve/release) is explicitly chosen.
166//
167// Module layout (task #1055 / R7-10 / perf item 54): this file used to be one
168// 4656-line monolith. It is now the crate's re-export surface only — every
169// item lives in a module named after it (or, where the crate itself already
170// documents two functions as one feature in two forms — an infallible/`try_*`
171// pair, or a family of per-platform bench-internals counters — grouped into
172// one file per that established pairing, not atomized further).
173
174pub mod error;
175pub use error::VmemError;
176
177mod decommit_outcome;
178pub use decommit_outcome::DecommitOutcome;
179
180#[cfg(aligned_vmem_mock)]
181#[cfg_attr(docsrs, doc(cfg(aligned_vmem_mock)))]
182pub mod mock;
183
184#[cfg(feature = "fault-injection")]
185#[cfg_attr(docsrs, doc(cfg(feature = "fault-injection")))]
186pub mod fault_injection;
187
188#[cfg(aligned_vmem_page_size_override)]
189pub mod page_size_override;
190#[cfg(aligned_vmem_page_size_override)]
191pub mod page_size_query_override;
192
193mod min_page;
194mod page;
195mod page_size;
196mod try_page_size;
197#[cfg(feature = "bench-internals")]
198mod validate_page_size;
199
200pub use min_page::MIN_PAGE;
201pub use page::PAGE;
202pub use page_size::page_size;
203pub use try_page_size::try_page_size;
204#[cfg(feature = "bench-internals")]
205pub use validate_page_size::validate_page_size;
206
207#[cfg(feature = "bench-internals")]
208mod bench_internals;
209#[cfg(feature = "bench-internals")]
210pub use bench_internals::{
211 huge_decommit_attempts, reset_bench_internals_counters, unix_exact_reserve_attempts,
212 unix_exact_reserve_hits, unix_madvise_attempts, unix_madvise_successes, unix_munmap_attempts,
213 unix_munmap_failures, windows_large_page_alignment_failures,
214 windows_large_page_plain_fallback_successes, windows_large_page_retry_failures,
215 windows_reserve_commit_calls, windows_reserve_commit_single_calls,
216 windows_reserve_commit_two_call_pairs, windows_virtualfree_decommit_attempts,
217 windows_virtualfree_decommit_failures, windows_virtualfree_release_attempts,
218 windows_virtualfree_release_failures,
219};
220
221mod reservation;
222pub use reservation::Reservation;
223
224#[cfg(feature = "lazy-commit")]
225mod lazy_commit_is_honored;
226#[cfg(feature = "lazy-commit")]
227mod lazy_reservation;
228#[cfg(feature = "lazy-commit")]
229pub use lazy_commit_is_honored::lazy_commit_is_honored;
230#[cfg(feature = "lazy-commit")]
231pub use lazy_reservation::LazyReservation;
232
233mod reservation_parts;
234pub use reservation_parts::ReservationParts;
235
236mod reservation_full_parts;
237pub use reservation_full_parts::ReservationFullParts;
238
239mod api;
240#[cfg(feature = "lazy-commit")]
241pub use api::{commit_range, reserve_aligned_lazy, try_commit_range, try_reserve_aligned_lazy};
242pub use api::{
243 decommit, decommit_lazy, leak_zeroed_pages, recommit, release, release_parts, reserve_aligned,
244 try_decommit, try_recommit, try_reserve_aligned,
245};
246#[cfg(feature = "huge-pages")]
247pub use api::{reserve_aligned_huge, try_reserve_aligned_huge};
248
249mod os;