pd-vm 0.22.4

RustScript bytecode compiler and VM
Documentation
#![allow(dead_code)]

use super::super::{VmError, VmResult};

pub(crate) struct ExecutableBuffer {
    ptr: *mut u8,
    len: usize,
}

unsafe impl Send for ExecutableBuffer {}
unsafe impl Sync for ExecutableBuffer {}

#[cfg(any(windows, unix))]
impl ExecutableBuffer {
    pub(crate) fn new(code: &[u8]) -> VmResult<Self> {
        if code.is_empty() {
            return Err(VmError::JitNative(
                "native trace produced empty machine code".to_string(),
            ));
        }

        let ptr = unsafe { alloc_executable(code.len()) }?;
        unsafe {
            with_jit_write_protection_disabled(|| {
                std::ptr::copy_nonoverlapping(code.as_ptr(), ptr, code.len());
            });
            flush_instruction_cache(ptr, code.len());
            prepare_for_execution();
        }

        Ok(Self {
            ptr,
            len: code.len(),
        })
    }

    pub(crate) fn entry(&self) -> *const u8 {
        self.ptr.cast_const()
    }
}

#[cfg(not(any(windows, unix)))]
impl ExecutableBuffer {
    pub(crate) fn new(_code: &[u8]) -> VmResult<Self> {
        Err(VmError::JitNative(
            "executable trace buffers are unavailable on this target".to_string(),
        ))
    }

    pub(crate) fn entry(&self) -> *const u8 {
        self.ptr.cast_const()
    }
}

#[cfg(any(windows, unix))]
impl Drop for ExecutableBuffer {
    fn drop(&mut self) {
        if self.ptr.is_null() || self.len == 0 {
            return;
        }
        unsafe {
            free_executable(self.ptr, self.len);
        }
    }
}

#[cfg(windows)]
unsafe fn alloc_executable(len: usize) -> VmResult<*mut u8> {
    use windows_sys::Win32::System::Memory::{
        MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE, VirtualAlloc,
    };

    let ptr = unsafe {
        VirtualAlloc(
            std::ptr::null_mut(),
            len,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE,
        )
    } as *mut u8;
    if ptr.is_null() {
        return Err(VmError::JitNative(
            "VirtualAlloc failed for executable trace buffer".to_string(),
        ));
    }
    Ok(ptr)
}

#[cfg(windows)]
unsafe fn free_executable(ptr: *mut u8, _len: usize) {
    use windows_sys::Win32::System::Memory::{MEM_RELEASE, VirtualFree};

    let _ = unsafe { VirtualFree(ptr.cast(), 0, MEM_RELEASE) };
}

#[cfg(windows)]
unsafe fn flush_instruction_cache(ptr: *mut u8, len: usize) {
    use windows_sys::Win32::System::Diagnostics::Debug::FlushInstructionCache;
    use windows_sys::Win32::System::Threading::GetCurrentProcess;

    let process = unsafe { GetCurrentProcess() };
    let _ = unsafe { FlushInstructionCache(process, ptr.cast(), len) };
}

#[cfg(unix)]
unsafe fn alloc_executable(len: usize) -> VmResult<*mut u8> {
    let ptr = unsafe {
        libc::mmap(
            std::ptr::null_mut(),
            len,
            libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
            libc::MAP_PRIVATE | map_anon_flag() | map_jit_flag(),
            -1,
            0,
        )
    };
    if ptr == libc::MAP_FAILED {
        return Err(VmError::JitNative(format!(
            "mmap failed for executable trace buffer: {}",
            std::io::Error::last_os_error()
        )));
    }
    Ok(ptr.cast())
}

#[cfg(unix)]
unsafe fn free_executable(ptr: *mut u8, len: usize) {
    let _ = unsafe { libc::munmap(ptr.cast(), len) };
}

#[cfg(unix)]
unsafe fn flush_instruction_cache(ptr: *mut u8, len: usize) {
    let _ = (ptr, len);

    #[cfg(all(target_arch = "aarch64", target_os = "macos"))]
    {
        unsafe extern "C" {
            fn sys_icache_invalidate(start: *mut core::ffi::c_void, len: usize);
        }

        unsafe { sys_icache_invalidate(ptr.cast(), len) };
    }

    #[cfg(all(target_arch = "aarch64", not(target_os = "macos")))]
    {
        unsafe extern "C" {
            fn __clear_cache(start: *mut u8, end: *mut u8);
        }

        unsafe { __clear_cache(ptr, ptr.add(len)) };
    }
}

#[cfg(unix)]
const fn map_anon_flag() -> i32 {
    #[cfg(any(target_os = "linux", target_os = "android"))]
    {
        libc::MAP_ANONYMOUS
    }
    #[cfg(not(any(target_os = "linux", target_os = "android")))]
    {
        libc::MAP_ANON
    }
}

#[cfg(all(unix, target_os = "macos", target_arch = "aarch64"))]
const fn map_jit_flag() -> i32 {
    libc::MAP_JIT
}

#[cfg(not(all(unix, target_os = "macos", target_arch = "aarch64")))]
const fn map_jit_flag() -> i32 {
    0
}

#[cfg(all(unix, target_os = "macos", target_arch = "aarch64"))]
pub(crate) unsafe fn prepare_for_execution() {
    if unsafe { libc::pthread_jit_write_protect_supported_np() } != 0 {
        unsafe { libc::pthread_jit_write_protect_np(1) };
    }
}

#[cfg(not(all(unix, target_os = "macos", target_arch = "aarch64")))]
pub(crate) unsafe fn prepare_for_execution() {}

#[cfg(all(unix, target_os = "macos", target_arch = "aarch64"))]
unsafe fn with_jit_write_protection_disabled<R>(f: impl FnOnce() -> R) -> R {
    struct WriteProtectionGuard {
        restore: bool,
    }

    impl Drop for WriteProtectionGuard {
        fn drop(&mut self) {
            if self.restore {
                unsafe { libc::pthread_jit_write_protect_np(1) };
            }
        }
    }

    let restore = unsafe { libc::pthread_jit_write_protect_supported_np() } != 0;
    let _guard = if restore {
        unsafe { libc::pthread_jit_write_protect_np(0) };
        Some(WriteProtectionGuard { restore: true })
    } else {
        None
    };
    f()
}

#[cfg(not(all(unix, target_os = "macos", target_arch = "aarch64")))]
unsafe fn with_jit_write_protection_disabled<R>(f: impl FnOnce() -> R) -> R {
    f()
}