userspace-pagefault 0.0.8

Manage user-allocated virtual memory by handling page faults in user space on *nix platforms.
Documentation
use nix::libc::ucontext_t;
use std::os::fd::OwnedFd;

#[allow(unused_macros)]
macro_rules! bits_set {
    ($val:expr, $($bit:expr),+ $(,)?) => {{
        let mask: u64 = 0 $(| (1 << $bit))+;
        (u64::from($val) & mask) == mask
    }};
}

#[allow(unused_macros)]
macro_rules! not_bits_set {
    ($val:expr, $($bit:expr),+ $(,)?) => {{
        let mask: u64 = 0 $(| (1 << $bit))+;
        (u64::from($val) & mask) == 0
    }};
}

/*
    check_page_fault_rw_flag_from_context returns whether the page fault was triggered by R/W memory op.
    Return 1(write) is safe because the upper layer uses the result as a hint to decide whether to do the write back, and write back is always correct.
    However for the performance purpose we want to hint it right so the upper layer only writes back the memory has been touched.
*/
#[inline]
pub fn check_page_fault_rw_flag_from_context(ctx: ucontext_t) -> u8 {
    /* If REG_ERR does not exist/available, we can check pc */
    #[cfg(all(target_arch = "aarch64", any(target_os = "linux", target_os = "macos")))]
    let flag = {
        let mut val = 1;
        // https://developer.arm.com/documentation/ddi0487/latest
        #[cfg(target_os = "macos")]
        let i = unsafe { *((*ctx.uc_mcontext).__ss.__pc as *const u32) };
        #[cfg(target_os = "linux")]
        let i = unsafe { *((ctx.uc_mcontext).pc as *const u32) };
        // Organize the matching order based on op hit rate
        // C4.1 A64 instruction set encoding, C4-492
        // x1x0 -> C4.1.96 Loads and Stores, C4-753
        if bits_set!(i, 27) && not_bits_set!(i, 25) {
            // op0: xx11 - series
            if bits_set!(i, 29, 28) {
                /*
                Load/store register (unscaled immediate)
                Load/store register (immediate postindexed)
                Load/store register (unprivileged)
                Load/store register (immediate preindexed)
                --OR--
                Load/store register (register offset)
                --OR--
                Load/store register (pac)
                --OR--
                Load/store register (unsigned immediate)
                */
                if bits_set!(i, 24) ||
                    (not_bits_set!(i, 24) && bits_set!(i, 21, 10)) ||
                    (not_bits_set!(i, 24, 10) && bits_set!(i, 21, 11)) ||
                    not_bits_set!(i, 24, 21)
                {
                    // VR indicates SIMD&FP
                    if bits_set!(i, 26) {
                        // opc=x1 is load
                        if bits_set!(i, 22) {
                            val = 0;
                        }
                    } else {
                        // opc=00 is store, others are load
                        if !not_bits_set!(i, 23, 22) {
                            val = 0;
                        }
                    }
                }
                // Atomic memory operations
                // Treat them all as W because lots of them are doing cmpxchg
                //if (not_bits_set!(i, 24, 11, 10)) && bits_set!(i, 21) {
                //}
            }
        }
        val
    };
    #[cfg(all(target_arch = "aarch64", not(any(target_os = "linux", target_os = "macos"))))]
    let flag = 1;
    #[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "netbsd")))]
    let flag = ((ctx.uc_mcontext.gregs[nix::libc::REG_ERR as usize] & 0x2) >> 1) as u8;
    #[cfg(all(target_arch = "x86_64", target_os = "freebsd"))]
    let flag = ((ctx.uc_mcontext.mc_err & 0x2) >> 1) as u8;
    #[cfg(all(
        target_arch = "x86_64",
        not(any(target_os = "freebsd", target_os = "linux", target_os = "netbsd"))
    ))]
    let flag = 1;
    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
    let flag = 1;

    return flag;
}

#[inline]
pub fn get_shared_memory() -> Result<OwnedFd, crate::Error> {
    #[cfg(target_os = "linux")]
    match nix::sys::memfd::memfd_create(c"userspace-paging", nix::sys::memfd::MFdFlags::empty()) {
        Ok(fd) => Ok(fd),
        Err(e) => Err(crate::Error::UnixError(e)),
    }
    #[cfg(any(target_os = "macos", target_os = "ios"))]
    {
        let mut failed = 0;
        let mut result = None;
        loop {
            if failed > 10 {
                break;
            }
            use nix::fcntl::OFlag;
            use nix::sys::stat::Mode;
            use rand::distr::{Alphanumeric, SampleString};

            // macOS shared memory name is maximum 31 characters.
            let name = format!("/usp-{}", Alphanumeric.sample_string(&mut rand::rng(), 16));
            if let Ok(fd) = nix::sys::mman::shm_open(
                &*name,
                OFlag::O_RDWR | OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_NOFOLLOW,
                Mode::S_IRUSR | Mode::S_IWUSR,
            ) {
                result = Some(fd);
                break;
            }
            failed += 1;
        }
        match result {
            None => return Err(crate::Error::Unsupported),
            Some(fd) => return Ok(fd),
        }
    };
}