mmap-io 0.9.8

Zero-copy memory-mapped file I/O for Rust. Safe concurrent reads, writes, and atomic views across Linux, macOS, and Windows. Built for databases, log structures, game runtimes, caches, and IPC.
Documentation
//! Utility helpers for alignment, page size, and safe range calculations.

use crate::errors::{MmapIoError, Result};
use std::sync::OnceLock;

/// Cached page size. Initialized on first call to [`page_size`].
///
/// The page size cannot change at runtime, so caching after the first
/// syscall removes a per-call cost from every hot path that asks for
/// it (microflush optimization, page-aligned operations, touch_pages).
static PAGE_SIZE: OnceLock<usize> = OnceLock::new();

/// Get the system page size in bytes.
///
/// The value is computed once via a platform syscall (`sysconf` on
/// Unix, `GetSystemInfo` on Windows) and cached for the lifetime of
/// the process.
#[must_use]
pub fn page_size() -> usize {
    *PAGE_SIZE.get_or_init(query_page_size)
}

/// Query the platform for the current page size. Called at most once
/// per process via [`PAGE_SIZE`].
fn query_page_size() -> usize {
    cfg_if::cfg_if! {
        if #[cfg(target_os = "windows")] {
            windows_page_size()
        } else {
            unix_page_size()
        }
    }
}

#[cfg(target_os = "windows")]
fn windows_page_size() -> usize {
    use std::mem::MaybeUninit;
    #[allow(non_snake_case)]
    #[repr(C)]
    struct SYSTEM_INFO {
        wProcessorArchitecture: u16,
        wReserved: u16,
        dwPageSize: u32,
        lpMinimumApplicationAddress: *mut core::ffi::c_void,
        lpMaximumApplicationAddress: *mut core::ffi::c_void,
        dwActiveProcessorMask: usize,
        dwNumberOfProcessors: u32,
        dwProcessorType: u32,
        dwAllocationGranularity: u32,
        wProcessorLevel: u16,
        wProcessorRevision: u16,
    }
    extern "system" {
        fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
    }
    let mut sysinfo = MaybeUninit::<SYSTEM_INFO>::uninit();
    // SAFETY: `GetSystemInfo` (kernel32.dll, documented on MSDN) accepts
    // a pointer to caller-allocated SYSTEM_INFO storage. We pass a
    // pointer to our MaybeUninit slot, which has the correct size and
    // alignment for the struct. The function unconditionally populates
    // every field of the SYSTEM_INFO struct on return (no failure
    // mode), so `assume_init` is sound. The returned `dwPageSize` is a
    // u32 representing the system page size in bytes; casting to usize
    // is lossless on every supported Windows target (page sizes are
    // <= 64 KiB on every documented architecture).
    // Reference: https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo
    unsafe {
        GetSystemInfo(sysinfo.as_mut_ptr());
        let s = sysinfo.assume_init();
        s.dwPageSize as usize
    }
}

#[cfg(not(target_os = "windows"))]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn unix_page_size() -> usize {
    // SAFETY: `sysconf` (POSIX.1-2001) with `_SC_PAGESIZE` is a
    // documented query that takes no pointer arguments and has no
    // failure mode that requires inspection on supported platforms.
    // It returns a long that is always positive on the platforms we
    // support (Linux, macOS, FreeBSD, OpenBSD, illumos). On the rare
    // chance it returns -1 (POSIX permits this only for queries
    // sysconf doesn't recognize, which is impossible for _SC_PAGESIZE),
    // we clamp to 0 via `.max(0)` and the resulting `as usize` cast
    // produces 0. Callers can detect this degenerate value, but no
    // documented platform exhibits it.
    // Reference: https://man7.org/linux/man-pages/man3/sysconf.3.html
    unsafe {
        let page_size = libc::sysconf(libc::_SC_PAGESIZE);
        page_size.max(0) as usize
    }
}

/// Align a value up to the nearest multiple of `alignment`.
///
/// Returns the original value unchanged when `alignment == 0` (a
/// permissive convention rather than a panic; callers passing 0 are
/// presumed to mean "no alignment requested").
#[inline]
#[must_use]
pub fn align_up(value: u64, alignment: u64) -> u64 {
    if alignment == 0 {
        return value;
    }
    // Fast path for power-of-2 alignments (common case for page sizes)
    if alignment.is_power_of_two() {
        let mask = alignment - 1;
        (value + mask) & !mask
    } else {
        value.div_ceil(alignment) * alignment
    }
}

/// Ensure the requested [offset, offset+len) range is within [0, total).
/// Returns `Ok(())` if valid; otherwise an `OutOfBounds` error.
///
/// This function is called from every bounds-checked public method in
/// the crate (`as_slice`, `as_slice_mut`, `read_into`, `update_region`,
/// `flush_range`, `touch_pages_range`, `prefetch_range`, advise, lock,
/// segment access). It is marked `#[inline]` so the compiler can fuse
/// the check into the calling stack frame and avoid a function-call
/// boundary on the hot path.
///
/// # Errors
///
/// Returns `MmapIoError::OutOfBounds` if the range exceeds bounds.
#[inline]
pub fn ensure_in_bounds(offset: u64, len: u64, total: u64) -> Result<()> {
    // Use a single saturating-add comparison rather than two branches.
    // `offset > total` is implied by `offset + len > total` when
    // `len == 0` is paired with `offset > total`; in the common case
    // (len > 0) the saturating_add catches both overflow and OOB.
    let end = offset.saturating_add(len);
    if end > total || offset > total {
        return Err(MmapIoError::OutOfBounds { offset, len, total });
    }
    Ok(())
}

/// Compute a safe byte slice range for a given total length, returning start..end as usize tuple.
///
/// `#[inline]` because this is on every read/write hot path; the
/// function body is small (one bounds check + two casts) and inlining
/// removes the call/return overhead.
///
/// # Errors
///
/// Returns `MmapIoError::OutOfBounds` if the requested range exceeds the total length.
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub fn slice_range(offset: u64, len: u64, total: u64) -> Result<(usize, usize)> {
    ensure_in_bounds(offset, len, total)?;
    // Safe to cast because we've already validated bounds against total
    // which itself must fit in memory (and thus usize)
    let start = offset as usize;
    let end = (offset + len) as usize;
    Ok((start, end))
}