aligned-vmem
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.
[]
= "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_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 aslet mut r = reserve_aligned(...)at the call site. If aReservationis held behind a shared reference (inside a struct whose method takes&self, inside anRc, or captured by aFnclosure), that pattern no longer compiles and needsRefCell/Mutex(single-threaded / cross-thread respectively) or a&mut selfmethod instead. This closes a real soundness gap: a leaked&Reservationused to let 100%-safe code decommit or recommit memory behind aLazyReservation's watermark without it knowing.reserve_aligned_lazy/try_reserve_aligned_lazynow returnLazyReservation, notReservation. A caller that keeps its own commit bookkeeping and wants the plainReservationback 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
alignmust be a power of two>=PAGE(4 KiB).sizemust be a non-zero multiple ofPAGE.decommit/decommit_lazyoffsets must be multiples of the runtime page size (page_size()).recommit/commit_rangeoffsets must be multiples of the runtime page size (page_size()).- Lazy reservations (feature
lazy-commit):reserve_aligned_lazy'ssizeandinitial_commitmust BOTH be multiples of the runtime page size (page_size()), not justPAGE. This is required becausecommit_rangeoperates on whole runtime pages, and asizenot aligned topage_size()would create an unwritable tail that cannot be committed via the public API. reserve_aligned(eager) does NOT enforce thepage_size()half of this contract, unlike the lazy constructor above. Asizethat is aPAGEmultiple but not also apage_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. Roundsizeup to apage_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-pagesenabled,reserve_aligned_huge/try_reserve_aligned_hugeadditionally requiresizeandalignto 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_DECOMMITgenuinely unmaps the pages, so writing into[base+start, base+end)before callingrecommitraises aSTATUS_ACCESS_VIOLATIONon Windows. On Linux and Android,MADV_DONTNEEDkeeps 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_lazyreturns aLazyReservation, which tracks the committed prefix as a watermark (LazyReservation::committed_len()); on the real Windows backend only theinitial_commitbytes are actually committed at reservation time (seelazy_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 viaLazyReservation::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 aSTATUS_ACCESS_VIOLATION. This is different from eager reservations (reserve_aligned), where the entirelen()span is writable from the start. A caller that keeps its own commit bookkeeping instead of usingLazyReservation's watermark can call.into_reservation()and drivecommit_range/try_commit_rangedirectly on the rawReservation. - 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.
VirtualFreewithMEM_DECOMMITfails 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 thatMADV_DONTNEEDgained HugeTLB support in Linux 5.18, with the same 2-MiB-alignment requirement this crate already imposes onreserve_aligned_huge's ownsize/alignon Linux/Android — so decommitting an entire huge reservation, or any 2-MiB-granular sub-range of it, IS such an eligible range. Apage_size()-granular (e.g. 4 KiB) but not 2-MiB-granular offset still getsEINVALand does nothing, as does EVERY range on a pre-5.18 kernel.Reservation::decommit/Reservation::try_decommit(the safe methods) check bothis_huge()and the range before deciding whether to call the backend at all; the freedecommitfunction has nois_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: thealigned-vmem-hugetlb-realjob (.github/workflows/ci.yml) configures a realnr_hugepagespool, 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 realMADV_DONTNEEDbackend 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: underbench-internals,libc_madvise(src/os/unix.rs) records whether the syscall itself returned0(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 acceptedMADV_DONTNEEDon this realMAP_HUGETLBmapping, 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, callsdecommit, 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 logsHugePages_Free(from/proc/meminfo) around the decommit call for a human to read, but does NOT gate on it —HugePages_Freeis 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 owndecommit()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_lazyis NOT part of this change:MADV_FREEhas no documented HugeTLB support (unlikeMADV_DONTNEEDabove), so it remains an unconditional no-op on huge reservations on every platform. - Use
reserve_alignedinstead ofreserve_aligned_hugeif you need decommit to work unconditionally, regardless of range shape or kernel version. Note: Huge-page support on Linux and Android (reserve_aligned_hugewith thehuge-pagesfeature) requires kernel >= 3.8 for correctMAP_HUGE_2MBsize 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.
- Windows: unconditionally does nothing.
- Darwin (macOS/iOS/tvOS/watchOS): no zero-fill, no RSS return, on ordinary
reservations too.
MADV_DONTNEEDis advisory-only for anonymous memory on Darwin, so unlike Linux and Android it does not reliably unmap the physical pages: a decommit +recommitround 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. UseReservation::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
decommitcaveat as Darwin, but lazydecommit_lazygenuinely reclaims.MADV_DONTNEEDis advisory-only for anonymous memory on the four BSDs too — like Darwin, and unlike Linux and Android, eagerdecommitdoes not reliably unmap the physical pages there, so the same "no zero-fill, no RSS return" gap applies. Unlike eagerdecommit, though,decommit_lazy'sMADV_FREEadvice on BSD (as on Darwin'sMADV_FREE_REUSABLE) DOES do something real: it drops the physical footprint rather than being a no-op — seedecommit's own rustdoc for the precise wording this caveat mirrors. UseReservation::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 conservativePAGEfloor, 4 KiB) so it never panics, but from that point on:decommit/decommit_lazybecome no-ops,recommit/commit_rangereturnfalse, and thetry_*family (try_decommit,try_recommit,try_commit_range, the lazy reservation constructor, andtry_page_sizeitself) reportsErr(VmemError::os_refusal_unknown_code()). Reserving, using, and releasing memory are unaffected — only page-granular decommit/ recommit state operations are refused. Usetry_page_size()to detect this upfront rather than discovering it through a latertry_decommiterror. Seepage_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-gnuandcargo check --target i686-unknown-linux-musl) — musl uses 64-bitoff_ton all architectures, so 32-bit musl targets (including i686) are safe with this crate's dual-archOffTtype.
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, andMADV_*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_tby 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_tis correctly declared as 64-bit on all musl architectures (musl uses 64-bitoff_ton all architectures), so the crate's dual-archOffTtype 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
mipsandmips64) uses differentMAP_ANON/MAP_HUGETLBconstant values than theasm-generic/mman-common.hvalues this crate hardcodes for Linux/Android. MIPS definesMAP_ANONYMOUS = 0x0800andMAP_HUGETLB = 0x80000, while this crate uses0x20and0x40000respectively. With the wrong constants, everyreserve_alignedcall fails closed at runtime withEBADF(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 = 0x20arm: Linux and Android (any architecture except MIPS — see below). - Unix,
MAP_ANON = 0x1000arm: 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_ANONdefinition —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.