aligned-vmem 0.2.0

Cross-platform aligned anonymous virtual memory: reserve, commit, decommit, and release power-of-two-aligned spans via mmap/VirtualAlloc. Zero dependencies, miri-friendly.
Documentation
# aligned-vmem

[![Crates.io](https://img.shields.io/crates/v/aligned-vmem.svg)](https://crates.io/crates/aligned-vmem)
[![Documentation](https://docs.rs/aligned-vmem/badge.svg)](https://docs.rs/aligned-vmem)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)

Cross-platform **aligned anonymous virtual memory** — reserve a span whose base
is aligned to an arbitrary power of two, commit/decommit its pages, release it.
Directly through the OS, no file-mapping machinery, **zero dependencies, 100 %
Rust** (no C / C++ libraries pulled in — the OS syscalls are declared locally
through `extern "system"` / `extern "C"`, the same way `std` itself links
`kernel32` / `libc`), miri-friendly.

```toml
[dependencies]
aligned-vmem = "0.2"
```

```text
use aligned_vmem::{reserve_aligned, release};

// Reserve 4 MiB aligned to 4 MiB — e.g. one allocator segment.
let span = 4 * 1024 * 1024;
let r = reserve_aligned(span, span).expect("OOM");
let base = r.as_ptr();
assert_eq!(base.addr() % span, 0);

// SAFETY: base is valid for r.len() bytes (except decommitted ranges), owned exclusively.
unsafe { base.write(0xAB); assert_eq!(base.read(), 0xAB); }

// RAII release on drop — or take the parts for self-hosted manual release:
let (raw, raw_len, raw_align) = r.into_parts();
unsafe { release(raw, raw_len, raw_align) };
```

Runnable form: `tests/readme_example.rs`.

**Reservation ownership:** `Reservation` is a single-owner handle over its whole span with no built-in sub-span/re-derivation API. Sub-allocation on top of this crate is expected via raw pointer arithmetic within the reservation's bounds, done by the consumer.

## What it does

| API | Purpose |
|---|---|
| `reserve_aligned(size, align) -> Option<Reservation>` | Reserve `size` bytes whose base is `align`-aligned. On 32-bit Unix, first tries an exact-size mmap fast path; on a miss, or on 64-bit Unix (where the fast path is compiled out — see P-1 below), over-reserves `size + align` and keeps the full mapping. On Windows, over-reserves `size + align` and keeps the full mapping when `align > WIN_ALLOCATION_GRANULARITY` (typically 64 KiB); for `align <= WIN_ALLOCATION_GRANULARITY` uses a single-call fast path with no over-reserve (threshold widens to `GetLargePageMinimum()` when requesting large pages). On 32-bit Unix, a fast-path miss holds `size + align` bytes of virtual address space for the reservation's lifetime (measured hit rate: 34.4% at 64 KiB align, 46.7% at 1 MiB, 56.7% at 4 MiB — commit `35d51e6`, task #849; measured on WSL2/Linux, x86_64; 30-run aggregate; scope: 32-bit only — the hit rate is kernel- and ASLR-dependent and is not expected to transfer to other Unix platforms). On 64-bit Unix, every reservation usually over-reserves in one `mmap` call — the exact-size fast path never runs, so these hit-rate numbers do not apply there (task #944, finding P-1: `try_reserve_aligned_exact` is gated `target_pointer_width = "32"`). Exception on Linux and Android: when `align == LINUX_HUGE_PAGE_SIZE` with huge pages requested, an exact-size `MAP_HUGETLB` fast path avoids the over-reserve (kernel guarantees huge-page-aligned base). |
| `Reservation::as_ptr / len / reservation_ptr / reservation_len` | The usable span and the requested reservation length (not necessarily the actual OS reservation size). |
| `Reservation::into_parts() -> (*mut u8, usize, usize)` | Take the raw reservation, suppress `Drop`, for self-hosted release (legacy tuple form). |
| `Reservation::into_reservation_parts() -> ReservationParts` | Take the raw reservation, suppress `Drop`, for self-hosted release (typed form). |
| `Reservation::into_full_parts() -> ReservationFullParts` | Take the raw reservation, suppress `Drop`, lossless six-field form (also preserves `base`, usable `len`, and `granted_huge`, which `into_reservation_parts` discards). |
| `release(ptr, len, align)` (unsafe) | Release a reservation taken via `into_parts`, exactly once (legacy tuple form). |
| `release_parts(ReservationParts)` (unsafe) | Release a reservation taken via `into_reservation_parts`, exactly once (typed form). |
| `Reservation::is_huge() -> bool` | Detect whether a reservation actually got large/huge pages on either platform. |
| `impl From<VmemError> for std::io::Error` | Convert `VmemError` to `std::io::Error` for error-propagation convenience. |
| `Reservation::decommit(start, end)` / `Reservation::recommit(start, end)` (`&mut self`) | Safe, bounds-checked methods for callers already holding a `Reservation` — hint the OS to return page-granular physical backing / re-commit it. Forward to the free functions below with the reservation's own base and length. |
| `Reservation::try_decommit(start, end)` / `Reservation::try_recommit(start, end)` / `Reservation::commit_range(start, end)` / `Reservation::try_commit_range(start, end)` (`&mut self`) | Fallible/boolean safe-method twins of the above, reporting a contract-violating range instead of silently no-oping (`decommit`) or panicking only in debug (see "Alignment contract" below). |
| `decommit(base, start, end)` / `recommit(base, start, end)` (unsafe) | Hint the OS to return page-granular physical backing (guaranteed on Linux/Android/Windows, best-effort on Darwin/BSDs with no zero-fill guarantee) / re-commit it. |
| `try_decommit(base, start, end)` (unsafe) `-> Result<DecommitOutcome, VmemError>` | Fallible twin of `decommit`. The outer `Result` reports only caller-contract validity: `Err` for a violated range (misaligned, or `start > end`), on every build profile — `decommit` itself only reports a violation via a DEBUG-only `debug_assert!` and stays silent in release. An OS refusal is never `Err`. The `Ok` payload is a `DecommitOutcome` (`#[non_exhaustive]`) distinguishing what actually happened: `Skipped` (no backend call — an empty range, or a huge-page reservation's Rust-level skip), `Advised` (the backend call was made and the selected backend accepted it — under the native backend the kernel/OS accepted a real syscall, but under the `aligned_vmem_mock` cfg or miri no syscall runs at all and this is the simulated backend's unconditional answer; does **not** by itself mean the physical pages were reclaimed), or `Refused(VmemError)` (the backend call was made and the OS/kernel refused it). |
| `decommit_lazy(base, start, end)` (unsafe) | Cheaper lazy reclaim — Linux/Android `MADV_FREE`, macOS/iOS `MADV_FREE_REUSABLE`, BSD (FreeBSD/DragonFly/NetBSD/OpenBSD) `MADV_FREE`, Windows falls back to `decommit` (eager `MEM_DECOMMIT`: a write before `recommit` is a hard crash there, not a re-fault). |
| `page_size() -> usize` | Real OS page size, queried once (`sysconf`/`GetSystemInfo`) — 16 KiB on Apple Silicon, not the 4 KiB `PAGE` minimum. Infallible: if the one-time OS query itself fails (not observed on any supported platform), this still returns the conservative `PAGE` floor rather than panicking — see `try_page_size` and the platform-caveats section below for what that degraded state means for other entry points. |
| `try_page_size() -> Result<usize, VmemError>` | Fallible twin of `page_size()`: `Err(VmemError::os_refusal_unknown_code())` if the one-time OS page-size query failed, `Ok` with the identical value `page_size()` would return otherwise. Lets a caller detect the degraded state upfront instead of discovering it through `try_decommit`/`try_recommit` errors later. |
| `PAGE` | Minimum decommit granularity constant (4 KiB) — superseded by `page_size()` on hosts with larger pages (see `MIN_PAGE` for the underlying constant). |
| `MIN_PAGE` | Underlying minimum page size constant (4 KiB). |
| `leak_zeroed_pages(size) -> Option<NonNull<u8>>` | Reserve zeroed, process-lifetime-leaked pages (for pre-main / `GlobalAlloc` bookkeeping). |
| `try_reserve_aligned` / `try_recommit` / `try_commit_range` … `-> Result<_, VmemError>` | Fallible forms carrying the OS `errno`/`GetLastError` cause. |
| `reserve_aligned_lazy(size, align, initial_commit) -> Option<LazyReservation>` (feature `lazy-commit`) | Reserve `size` bytes, committing only the first `initial_commit` bytes up front (genuinely partial on the Windows native backend; Unix/miri/mock commit the whole span regardless — see `lazy_commit_is_honored()`). Returns `LazyReservation`, **not** `Reservation` — see the migration note below. |
| `LazyReservation` (feature `lazy-commit`) | A `Reservation` that also tracks how much of itself is committed: a single watermark, `[0, committed_len())`. Replaces the pre-0.2.0 raw-primitive shape where callers tracked the watermark themselves. |
| `LazyReservation::committed_len() -> usize` | Bytes from the base that are committed and writable right now. |
| `LazyReservation::ensure_committed(&mut self, len) -> Result<(), VmemError>` | Idempotent, monotone: commit up to at least `len` bytes from the base. A call at or below the current watermark issues no syscall and no error; rounds UP to a page, so `len` need not be page-aligned. |
| `LazyReservation::shrink_committed(&mut self, len)` | Lower the watermark to `len` (rounded UP to a page, so a page holding bytes you asked to keep is never released), asking the OS to release the dropped range. |
| `LazyReservation::into_reservation(self) -> Reservation` | Give up tracking and take the plain `Reservation`, for a caller that keeps its own commit bookkeeping. |
| `lazy_commit_is_honored() -> bool` (feature `lazy-commit`, `const fn`) | Whether the current platform's backend genuinely honors `initial_commit` (`true` only on the real Windows native backend; Unix, miri, and the mock backend all commit the whole span up front, making "lazy" a no-op there). |

Every fallible entry point has an infallible `Option`/`bool` counterpart that
discards the cause. Optional features: `lazy-commit` (incremental commit:
`reserve_aligned_lazy` + `commit_range`, and the `LazyReservation` watermark
type above; the `alloc-lazy-commit` name is kept
as a compat alias for one release and will be removed in 0.3.0/1.0.0 — migrate
to `lazy-commit` now), `huge-pages` (`reserve_aligned_huge` — `MAP_HUGETLB` /
`MEM_LARGE_PAGES`, best-effort with fallback — **on Linux and Android,
`size` and `align`
must both additionally be multiples of the huge-page size (2 MiB), or the
request is rejected up front**; **on Windows, large pages (`MEM_LARGE_PAGES`) are only ever requested and possibly granted via the single-call fast path; the two-call path never requests large pages, so `is_huge()` is always `false` for a reservation that takes it**; otherwise the request falls back
to ordinary pages — see the function's own rustdoc for the full technical
explanation; use `Reservation::is_huge` to detect whether a reservation actually
got large/huge pages on either platform), and `fault-injection` — a PUBLIC,
process-global, opt-in Cargo feature with armed hooks on TWO real syscall
paths: `fault_injection::arm_fail_next` / `arm_fail_at` on the REAL
`try_commit_range` syscall path, and (since task #1219)
`fault_injection::arm_fail_next_decommit` on the REAL decommit dispatch path
(`try_decommit` / `Reservation::try_decommit`) — DISTINCT from the mock
backend: it changes nothing about which backend runs, it only forces a
specific real commit or decommit call to report failure, for a consumer that
needs the genuine OS backend under test. The mock backend (recording call log +
`fail_next_reserve` / `fail_next_commit` fault injection for deterministic
OOM-path tests on any target — **replaces** the commit/decommit/recommit
backend with a stub) is enabled via the `aligned_vmem_mock` cfg flag
(`RUSTFLAGS="--cfg aligned_vmem_mock" cargo test ...`), following the same
pattern as this repo's `cfg(loom)`/`cfg(kani)` flags. A `--cfg` flag cannot be
silently unified into a build by another crate downstream — exactly why it
was converted (task #962).

## Migrating to 0.2.0 (breaking changes)

- **`Reservation`'s seven OS-state mutators now take `&mut self`**: `decommit`,
  `try_decommit`, `decommit_lazy`, `recommit`, `try_recommit`, `commit_range`,
  `try_commit_range`. Bind the reservation as `let mut r = reserve_aligned(...)`
  at the call site. If a `Reservation` is held behind a shared reference
  (inside a struct whose method takes `&self`, inside an `Rc`, or captured by a
  `Fn` closure), that pattern no longer compiles and needs `RefCell`/`Mutex`
  (single-threaded / cross-thread respectively) or a `&mut self` method
  instead. This closes a real soundness gap: a leaked `&Reservation` used to
  let 100%-safe code decommit or recommit memory behind a `LazyReservation`'s
  watermark without it knowing.
- **`reserve_aligned_lazy` / `try_reserve_aligned_lazy` now return
  `LazyReservation`, not `Reservation`.** A caller that keeps its own commit
  bookkeeping and wants the plain `Reservation` back calls
  `.into_reservation()` on the result.

Backends: `mmap`/`munmap`/`madvise` on Unix,
`VirtualAlloc`/`VirtualFree(MEM_DECOMMIT/MEM_RELEASE)` on Windows, `std::alloc`
fallback under miri (so consumers stay miri-testable).

## Why not `region` / `memmap2` / `mmap-rs`?

Those are excellent for **file mappings** and **page-protection changes**.
`aligned-vmem` does one different, narrow thing: hand you an **anonymous span
aligned to a power of two you choose** plus page-granular decommit/recommit.
That is exactly what an **allocator / arena / slab** needs ("give me a 4
MiB-aligned 4 MiB span, let me hand pages back to the OS, keep the address
reservation"), and what the file-mapping crates don't directly offer.

## Alignment contract

- `align` must be a power of two `>=` `PAGE` (4 KiB).
- `size` must be a non-zero multiple of `PAGE`.
- `decommit`/`decommit_lazy` offsets must be multiples of the runtime page size
  (`page_size()`).
- `recommit`/`commit_range` offsets must be multiples of the runtime page size
  (`page_size()`).
- **Lazy reservations (feature `lazy-commit`):** `reserve_aligned_lazy`'s
  `size` and `initial_commit` must BOTH be multiples of the runtime page size
  (`page_size()`), not just `PAGE`. This is required because `commit_range`
  operates on whole runtime pages, and a `size` not aligned to `page_size()` would
  create an unwritable tail that cannot be committed via the public API.
- **`reserve_aligned` (eager) does NOT enforce the `page_size()` half of this
  contract, unlike the lazy constructor above.** A `size` that is a `PAGE`
  multiple but not also a `page_size()` multiple is accepted — on a host
  where those differ (e.g. Apple Silicon macOS, 16 KiB pages), the resulting
  reservation's span can never be fully decommitted: `decommit(0, size)` on
  it is a debug-build panic and a silent permanent no-op in release. Round
  `size` up to a `page_size()` multiple yourself if it is not already one.

Note: `decommit`/`decommit_lazy` and `recommit`/`commit_range` validate the
same granularity, but respond differently to a violated range — this
asymmetry is intentional. `decommit`/`decommit_lazy` have an infallible
`()` return with no write-permitting sentinel to misuse, so silently skipping
on a violated range is safe; `recommit`/`commit_range`'s boolean/`Result`
return means a silent no-op could hide a real OOM, so they reject violations
instead. Since task #1051 the eager `decommit` additionally trips a
`debug_assert!` on a violated range in DEBUG builds (zero cost — and still a
silent no-op — in release); `decommit_lazy` silently skips on every profile,
and `try_decommit` reports the violation as `Err` on every profile.
- On Linux and Android with `huge-pages` enabled, `reserve_aligned_huge`/
  `try_reserve_aligned_huge` additionally require `size` and `align` to both
  be multiples of the huge-page size (2 MiB) — see that function's own
  rustdoc.

Most violations return `None` / `false` / `Err(_)` — never a panic, so this
is safe to call from inside a `GlobalAlloc::alloc` body: `reserve_aligned`
and its siblings, and `decommit_lazy` (which silently no-ops on a violated
range on every profile, an intentional asymmetry with
`recommit`/`commit_range` below — since the `()` return has no
write-permitting sentinel to misuse, silently skipping is safe, whereas
`recommit`/`commit_range`'s boolean/`Result` return previously clamped a
contract violation to the same value a genuine success reports, which
crashed an in-repo consumer — see `recommit`'s own rustdoc). The eager
`decommit` is the one debug-build exception: since task #1051 a violated
range trips its `debug_assert!` there (release builds keep the silent
no-op; `try_decommit` reports the violation as `Err` on every profile).

**Behavior change:** `recommit` and `commit_range` (and their fallible
`try_*` forms) now **reject**, rather than silently accept, a violated offset
range (`start > end` or misaligned) — they still don't panic, but callers
relying on the old silent-no-op shape should check the return value.

**Two panic exceptions:** `Reservation::from_raw_parts` (an `unsafe fn` for
adopting a foreign OS reservation, not part of the ordinary reservation
flow) panics immediately on a contract-violating `align`/`reservation_len`
pair; and `release` panics on a contract-violating `(reservation_len, align)`
pair (a null pointer remains a documented no-op) — see `release`'s own rustdoc
`# Panics` section for the full detail. A third, debug-only panic surface
exists: `decommit`'s task-#1051 `debug_assert!` on a violated range,
compiled out of release builds (see above).

## Platform caveats

The table entry above (`decommit`/`recommit`) hides six platform and failure-mode divergences worth knowing before you rely on it — see each function's own rustdoc for the full technical explanation:

- **Windows: a write before recommit is a hard crash, not a soft re-fault.**
  `MEM_DECOMMIT` genuinely unmaps the pages, so writing into
  `[base+start, base+end)` before calling `recommit` raises a
  `STATUS_ACCESS_VIOLATION` on Windows. On Linux and Android, `MADV_DONTNEED` keeps the
  mapping resident and transparently re-faults a fresh zero page on the next
  write, so code that is safe on Linux or Android can crash on Windows. This exact
  divergence has already crashed an in-repo consumer — see
  <https://github.com/PHPCraftdream/sefer-alloc/blob/main/docs/CORRECTNESS_OPEN_ITEMS.md>
  item 6 for the incident record.
- **Lazy reservations on Windows: only the committed prefix is writable.**
  `reserve_aligned_lazy` returns a `LazyReservation`, which tracks the
  committed prefix as a watermark (`LazyReservation::committed_len()`); on the
  real Windows backend only the `initial_commit` bytes are actually committed
  at reservation time (see `lazy_commit_is_honored()` — Unix, miri, and the
  mock backend commit the whole span up front regardless of what was
  requested, so this distinction is Windows-only in practice). The tail
  `[committed_len(), len())` must be committed via
  `LazyReservation::ensure_committed(len)` before it becomes writable — this
  call is idempotent and monotone, so it is safe to call before every write
  without separately tracking what was already committed. Writing to the
  uncommitted tail raises a `STATUS_ACCESS_VIOLATION`. This is different from
  eager reservations (`reserve_aligned`), where the entire `len()` span is
  writable from the start. A caller that keeps its own commit bookkeeping
  instead of using `LazyReservation`'s watermark can call
  `.into_reservation()` and drive `commit_range`/`try_commit_range` directly
  on the raw `Reservation`.
- **Huge pages: decommit's behavior is platform- AND range-dependent, not a
  blanket no-op.** When a reservation came from `reserve_aligned_huge`
  (`Reservation::is_huge() == true`):
  - **Windows: unconditionally does nothing.** `VirtualFree` with
    `MEM_DECOMMIT` fails on large-page regions regardless of the requested
    range.
  - **Linux/Android: the eligible-range/post-5.18-kernel case is FORWARDED to
    the kernel, the kernel is observed to ACCEPT that call, AND — since task
    #1174 — the crate's own CI now reads memory content back and confirms the
    zero-fill.** `madvise(2)` documents that `MADV_DONTNEED` gained HugeTLB
    support in Linux 5.18, with the same 2-MiB-alignment requirement this
    crate already imposes on `reserve_aligned_huge`'s own `size`/`align` on
    Linux/Android — so decommitting an entire huge reservation, or any
    2-MiB-granular sub-range of it, IS such an eligible range. A
    `page_size()`-granular (e.g. 4 KiB) but not 2-MiB-granular offset still
    gets `EINVAL` and does nothing, as does EVERY range on a pre-5.18 kernel.
    `Reservation::decommit`/`Reservation::try_decommit` (the safe methods)
    check both `is_huge()` and the range before deciding whether to call the
    backend at all; the free `decommit` function has no `is_huge()` to
    consult and always issues the syscall, letting the kernel itself turn an
    ineligible range into a no-op. Documented per the man page cited above,
    and — since task #1152 (F1) — empirically exercised under a real hugetlb
    pool by this crate's own CI: the `aligned-vmem-hugetlb-real` job
    (`.github/workflows/ci.yml`) configures a real `nr_hugepages` pool,
    hard-asserts (via a path-activation oracle) that a huge-page grant was
    actually obtained, and then drives both an eligible and an ineligible
    range through the safe methods, confirming the eligible case **reaches
    the real `MADV_DONTNEED` backend call** rather than silently taking the
    Rust-level skip path. **Since task #1164 (strengthened task #1166,
    F5), that job additionally hard-asserts the kernel's own response:**
    under `bench-internals`, `libc_madvise` (`src/os/unix.rs`) records
    whether the syscall itself returned `0` (accepted) or `-1` (rejected)
    into a counter pair, and the job now asserts that the successes counter
    equals the attempts counter for the eligible-range call (`assert_eq!`,
    not merely `>`) — i.e. the kernel genuinely accepted `MADV_DONTNEED` on
    this real `MAP_HUGETLB` mapping, not merely that the crate dispatched to
    it, and not merely that at least one of possibly several calls
    succeeded. **Since task #1174, the CI job goes one step further and
    proves the outcome, not just the dispatch:**
    `ci_hugetlb_real_pool_decommit_actually_zeroes_memory_on_reaccess`
    (`tests/decommit_capability.rs`) writes a non-zero byte pattern across the
    whole eligible range, calls `decommit`, then reads every byte back and
    hard-asserts it is zero — closing the "accepted the call" vs. "actually
    zero-fills on next access" gap this section used to leave open.
    **What still remains NOT proven, and is deliberately kept separate from
    the zero-fill result above:** that the kernel physically returned the
    huge pages to the pool. The CI job logs `HugePages_Free` (from
    `/proc/meminfo`) around the decommit call for a human to read, but does
    NOT gate on it — `HugePages_Free` is a kernel-global counter shared with
    every other huge-page reservation the job's other targets make, so it
    cannot be attributed to one test's own `decommit()` call without racing
    them, and is observation-only by design, not a pass/fail assertion. So:
    zero-fill-on-next-access is empirically proven for the eligible-range
    case; physical reclaim of the pages back to the hugetlb pool is not.
  - Either way — Windows, or an ineligible range/kernel on Linux/Android —
    the effect is indistinguishable from a silent no-op: RSS does not drop
    and reads return the old data. `decommit_lazy` is NOT part of this
    change: `MADV_FREE` has no documented HugeTLB support (unlike
    `MADV_DONTNEED` above), so it remains an unconditional no-op on huge
    reservations on every platform.
  - Use `reserve_aligned` instead of `reserve_aligned_huge` if you need
    decommit to work unconditionally, regardless of range shape or kernel
    version.
  **Note:** Huge-page support on Linux and Android (`reserve_aligned_huge` with the
  `huge-pages` feature) requires kernel >= 3.8 for correct `MAP_HUGE_2MB`
  size encoding. On kernels older than 3.8, the size bits are ignored and
  the system's configured default huge-page size is used instead of the
  expected 2 MiB; the crate does not detect this mismatch.
- **Darwin (macOS/iOS/tvOS/watchOS): no zero-fill, no RSS return, on ordinary
  reservations too.**
  `MADV_DONTNEED` is advisory-only for anonymous memory on Darwin, so unlike
  Linux and Android it does not reliably unmap the physical pages: a decommit +
  `recommit` round trip on any Darwin target can still observe the old data
  instead of a fresh zero page, even for a non-huge reservation. Confirmed as a real,
  failing-test-level gap by this crate's first real-macOS CI run on
  2026-08-13 (the underlying hazard was already documented elsewhere in this
  repository since Round 9, before this crate was extracted); no fix is
  implemented yet — see
  <https://github.com/PHPCraftdream/sefer-alloc/blob/main/docs/CORRECTNESS_OPEN_ITEMS.md>
  item 48 for the open item. Use `Reservation::decommit_reclaims_and_zeroes()` to
  programmatically query whether the current platform guarantees reclaim+zero-fill
  semantics.
- **BSD (FreeBSD/DragonFly/NetBSD/OpenBSD): the same advisory-only eager
  `decommit` caveat as Darwin, but lazy `decommit_lazy` genuinely reclaims.**
  `MADV_DONTNEED` is advisory-only for anonymous memory on the four BSDs too
  — like Darwin, and unlike Linux and Android, eager `decommit` does not reliably unmap
  the physical pages there, so the same "no zero-fill, no RSS return" gap
  applies. Unlike eager `decommit`, though, `decommit_lazy`'s `MADV_FREE`
  advice on BSD (as on Darwin's `MADV_FREE_REUSABLE`) DOES do something real:
  it drops the physical footprint rather than being a no-op — see
  `decommit`'s own rustdoc for the precise wording this caveat mirrors.
  Use `Reservation::decommit_reclaims_and_zeroes()` to programmatically query
  whether the current platform guarantees reclaim+zero-fill semantics.
  REASONED-FROM-SPEC only (no BSD CI runner in this crate to verify against
  empirically); not independently confirmed the way the Darwin gap above was
  by a real CI run.
- **Degraded state: if the one-time OS page-size query itself fails, every
  page-granular state operation fails closed for the process lifetime — not
  observed on any supported platform, but a documented user-visible
  contract.** `page_size()` stays infallible (it returns the conservative
  `PAGE` floor, 4 KiB) so it never panics, but from that point on:
  `decommit`/`decommit_lazy` become no-ops, `recommit`/`commit_range` return
  `false`, and the `try_*` family (`try_decommit`, `try_recommit`,
  `try_commit_range`, the lazy reservation constructor, and `try_page_size`
  itself) reports `Err(VmemError::os_refusal_unknown_code())`. Reserving,
  using, and releasing memory are unaffected — only page-granular decommit/
  recommit state operations are refused. Use `try_page_size()` to detect this
  upfront rather than discovering it through a later `try_decommit` error.
  See `page_size()`'s own rustdoc ("If the one-time OS query fails") for the
  full enumeration and rationale.

## Supported targets

This crate's platform support falls into two categories:

### CI-verified targets

The following platforms are verified by this project's CI and are tested on every commit:

- **x86_64 Linux** (Ubuntu, glibc)
- **x86_64 Windows** (Windows Server)
- **AArch64 macOS** (Apple Silicon, macos-latest CI)
- **i686 Linux** (compile-only check via `cargo check --target i686-unknown-linux-gnu` and `cargo check --target i686-unknown-linux-musl`) — musl uses 64-bit `off_t` on all architectures, so 32-bit musl targets (including i686) are safe with this crate's dual-arch `OffT` type.

### Reasoned-from-spec targets

The following targets are supported but have not been empirically verified in CI. Support is based on published specifications and OS documentation:

- **AArch64 Linux** — the crate's FFI constants (`MAP_ANON`, `MADV_*`, `_SC_PAGESIZE`) are architecture-agnostic standard Linux values, not x86_64-specific, so the same 64-bit code path used by x86_64 Linux applies, but this has not been empirically CI-verified. (The project's CI has a cross-compilation matrix for aarch64, but no job runs on an ARM64 runner; AArch64 macOS is CI-verified — see the CI-verified list above.)
- **BSD family** (FreeBSD, DragonFly BSD, NetBSD, OpenBSD) — `MAP_ANON`, `_SC_PAGESIZE`, and `MADV_*` constants are derived from each OS's header files; no BSD CI runner exists for this project. `MADV_DONTNEED`'s advisory-only behavior is confirmed by BSD documentation but not empirically verified on BSD hardware.
- **Android (bionic)** — 32-bit targets use a 32-bit `off_t` by default (matching glibc behavior without `_FILE_OFFSET_BITS=64`); this is reasoned from bionic's design documentation, not CI-verified.
- **32-bit musl Linux** (e.g., `armv7-unknown-linux-musleabihf`) — `off_t` is correctly declared as 64-bit on all musl architectures (musl uses 64-bit `off_t` on all architectures), so the crate's dual-arch `OffT` type is safe; the i686-unknown-linux-musl target is compile-checked in CI (see the CI-verified list above), but other 32-bit musl targets are not.
- **Apple platforms beyond macOS** (iOS, tvOS, watchOS) — constants and behavior are derived from Apple's unified documentation; no CI runner currently tests these targets.
- **MIPS** — **not supported (compile_error!)**. MIPS (both `mips` and `mips64`) uses different `MAP_ANON`/`MAP_HUGETLB` constant values than the `asm-generic/mman-common.h` values this crate hardcodes for Linux/Android. MIPS defines `MAP_ANONYMOUS = 0x0800` and `MAP_HUGETLB = 0x80000`, while this crate uses `0x20` and `0x40000` respectively. With the wrong constants, every `reserve_aligned` call fails closed at runtime with `EBADF` (invalid file descriptor) but the failure is silent (no diagnostic points to the constant error). Rather than compile a buildable-but-broken crate, MIPS targets fail compilation with a clear diagnostic. This is a release decision; see <https://github.com/PHPCraftdream/sefer-alloc/blob/main/docs/CORRECTNESS_OPEN_ITEMS.md> item 62 for the decision record. Adding support requires adding a `#[cfg(any(target_arch = "mips", target_arch = "mips64"))]` arm with the correct MIPS-specific constant values.

**Note:** The absence of CI verification for a target means only that this project's own test suite has not executed on that platform. The platform-specific constants (e.g., `MADV_DONTNEED = 4`, Linux's `_SC_PAGESIZE = 30`, macOS's `_SC_PAGESIZE = 29`) are sourced from the respective OS's official documentation and header files. If you use this crate on a reasoned-from-spec target and encounter issues, please file a bug report.

**The exact support matrix** (task #1106/L3 — this enumeration, not a blanket family claim, is the contract; each arm is enforced by `compile_error!` in `src/os/unix.rs` for anything outside it):

- **Unix, `MAP_ANON = 0x20` arm**: Linux and Android (any architecture except MIPS — see below).
- **Unix, `MAP_ANON = 0x1000` arm**: macOS, iOS, tvOS, watchOS, FreeBSD, NetBSD, OpenBSD, DragonFly BSD.
- **Unix, everything else** (e.g. Solaris, illumos, AIX, Haiku): **fails compilation** with a diagnostic naming the missing `MAP_ANON` definition — `cfg(unix)` alone is NOT a promise of support.
- **MIPS** (any Unix OS): **fails compilation** (see the MIPS entry above).
- **Windows**: the Win32 backend (`VirtualAlloc`/`VirtualFree`); see the CI-verified list for which targets are tested.

Only the targets in the CI-verified list above have had the test suite actually run on them; everything else in the matrix is reasoned-from-spec.

## Provenance & safety

Every `unsafe` block carries a `// SAFETY:` proof. The crate is the OS aperture
extracted from [`sefer-alloc`](https://crates.io/crates/sefer-alloc); it is
deliberately the one place where the raw OS calls live, so consumers can stay
`#![forbid(unsafe_code)]` above it. The returned pointers preserve provenance
(no exposed-address `as usize` casts in the public API — the mock backend's
diagnostic-only call recorder stores addresses as `usize` for
comparison/logging, obtained via the non-exposing `.addr()`, and none of
those values is ever cast back into a pointer; this crate's own `tests/`
files, which are not part of the public API, still use `as usize` at a
few sites).

## License

Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your
option.