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
  • Coverage
  • 100%
    51 out of 51 items documented0 out of 10 items with examples
  • Size
  • Source code size: 1.05 MB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 1.76 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 4s Average build duration of successful builds.
  • all releases: 3s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • PHPCraftdream/sefer-alloc
    1 0 1
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • PHPCraftdream

aligned-vmem

Crates.io Documentation License: MIT OR Apache-2.0

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.

[dependencies]
aligned-vmem = "0.2"
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_hugeMAP_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.
  • MIPSnot 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; 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 or Apache-2.0, at your option.