Path: blob/main/cranelift/jit/src/memory/system.rs
1692 views
use cranelift_module::{ModuleError, ModuleResult};12#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]3use memmap2::MmapMut;45#[cfg(not(any(feature = "selinux-fix", windows)))]6use std::alloc;7use std::io;8use std::mem;9use std::ptr;1011use super::BranchProtection;12use super::JITMemoryProvider;1314/// A simple struct consisting of a pointer and length.15struct PtrLen {16#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]17map: Option<MmapMut>,1819ptr: *mut u8,20len: usize,21}2223impl PtrLen {24/// Create a new empty `PtrLen`.25fn new() -> Self {26Self {27#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]28map: None,2930ptr: ptr::null_mut(),31len: 0,32}33}3435/// Create a new `PtrLen` pointing to at least `size` bytes of memory,36/// suitably sized and aligned for memory protection.37#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]38fn with_size(size: usize) -> io::Result<Self> {39let alloc_size = region::page::ceil(size as *const ()) as usize;40MmapMut::map_anon(alloc_size).map(|mut mmap| {41// The order here is important; we assign the pointer first to get42// around compile time borrow errors.43Self {44ptr: mmap.as_mut_ptr(),45map: Some(mmap),46len: alloc_size,47}48})49}5051#[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]52fn with_size(size: usize) -> io::Result<Self> {53assert_ne!(size, 0);54let page_size = region::page::size();55let alloc_size = region::page::ceil(size as *const ()) as usize;56let layout = alloc::Layout::from_size_align(alloc_size, page_size).unwrap();57// Safety: We assert that the size is non-zero above.58let ptr = unsafe { alloc::alloc(layout) };5960if !ptr.is_null() {61Ok(Self {62ptr,63len: alloc_size,64})65} else {66Err(io::Error::from(io::ErrorKind::OutOfMemory))67}68}6970#[cfg(target_os = "windows")]71fn with_size(size: usize) -> io::Result<Self> {72use windows_sys::Win32::System::Memory::{73MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, VirtualAlloc,74};7576// VirtualAlloc always rounds up to the next multiple of the page size77let ptr = unsafe {78VirtualAlloc(79ptr::null_mut(),80size,81MEM_COMMIT | MEM_RESERVE,82PAGE_READWRITE,83)84};85if !ptr.is_null() {86Ok(Self {87ptr: ptr as *mut u8,88len: region::page::ceil(size as *const ()) as usize,89})90} else {91Err(io::Error::last_os_error())92}93}94}9596// `MMapMut` from `cfg(feature = "selinux-fix")` already deallocates properly.97#[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]98impl Drop for PtrLen {99fn drop(&mut self) {100if !self.ptr.is_null() {101let page_size = region::page::size();102let layout = alloc::Layout::from_size_align(self.len, page_size).unwrap();103unsafe {104region::protect(self.ptr, self.len, region::Protection::READ_WRITE)105.expect("unable to unprotect memory");106alloc::dealloc(self.ptr, layout)107}108}109}110}111112// TODO: add a `Drop` impl for `cfg(target_os = "windows")`113114/// JIT memory manager. This manages pages of suitably aligned and115/// accessible memory. Memory will be leaked by default to have116/// function pointers remain valid for the remainder of the117/// program's life.118pub(crate) struct Memory {119allocations: Vec<PtrLen>,120already_protected: usize,121current: PtrLen,122position: usize,123}124125unsafe impl Send for Memory {}126127impl Memory {128pub(crate) fn new() -> Self {129Self {130allocations: Vec::new(),131already_protected: 0,132current: PtrLen::new(),133position: 0,134}135}136137fn finish_current(&mut self) {138self.allocations139.push(mem::replace(&mut self.current, PtrLen::new()));140self.position = 0;141}142143pub(crate) fn allocate(&mut self, size: usize, align: u64) -> io::Result<*mut u8> {144let align = usize::try_from(align).expect("alignment too big");145if self.position % align != 0 {146self.position += align - self.position % align;147debug_assert!(self.position % align == 0);148}149150if size <= self.current.len - self.position {151// TODO: Ensure overflow is not possible.152let ptr = unsafe { self.current.ptr.add(self.position) };153self.position += size;154return Ok(ptr);155}156157self.finish_current();158159// TODO: Allocate more at a time.160self.current = PtrLen::with_size(size)?;161self.position = size;162163Ok(self.current.ptr)164}165166/// Set all memory allocated in this `Memory` up to now as readable and executable.167pub(crate) fn set_readable_and_executable(168&mut self,169branch_protection: BranchProtection,170) -> ModuleResult<()> {171self.finish_current();172173for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() {174super::set_readable_and_executable(ptr, len, branch_protection)?;175}176177// Flush any in-flight instructions from the pipeline178wasmtime_jit_icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush");179180self.already_protected = self.allocations.len();181Ok(())182}183184/// Set all memory allocated in this `Memory` up to now as readonly.185pub(crate) fn set_readonly(&mut self) -> ModuleResult<()> {186self.finish_current();187188for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() {189unsafe {190region::protect(ptr, len, region::Protection::READ).map_err(|e| {191ModuleError::Backend(192anyhow::Error::new(e).context("unable to make memory readonly"),193)194})?;195}196}197198self.already_protected = self.allocations.len();199Ok(())200}201202/// Iterates non protected memory allocations that are of not zero bytes in size.203fn non_protected_allocations_iter(&self) -> impl Iterator<Item = &PtrLen> {204let iter = self.allocations[self.already_protected..].iter();205206#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]207return iter.filter(|&PtrLen { map, len, .. }| *len != 0 && map.is_some());208209#[cfg(any(target_os = "windows", not(feature = "selinux-fix")))]210return iter.filter(|&PtrLen { len, .. }| *len != 0);211}212213/// Frees all allocated memory regions that would be leaked otherwise.214/// Likely to invalidate existing function pointers, causing unsafety.215pub(crate) unsafe fn free_memory(&mut self) {216self.allocations.clear();217self.already_protected = 0;218}219}220221impl Drop for Memory {222fn drop(&mut self) {223// leak memory to guarantee validity of function pointers224mem::replace(&mut self.allocations, Vec::new())225.into_iter()226.for_each(mem::forget);227}228}229230/// A memory provider that allocates memory on-demand using the system231/// allocator.232///233/// Note: Memory will be leaked by default unless234/// [`JITMemoryProvider::free_memory`] is called to ensure function pointers235/// remain valid for the remainder of the program's life.236pub struct SystemMemoryProvider {237code: Memory,238readonly: Memory,239writable: Memory,240}241242impl SystemMemoryProvider {243/// Create a new memory handle with the given branch protection.244pub fn new() -> Self {245Self {246code: Memory::new(),247readonly: Memory::new(),248writable: Memory::new(),249}250}251}252253impl JITMemoryProvider for SystemMemoryProvider {254unsafe fn free_memory(&mut self) {255self.code.free_memory();256self.readonly.free_memory();257self.writable.free_memory();258}259260fn finalize(&mut self, branch_protection: BranchProtection) -> ModuleResult<()> {261self.readonly.set_readonly()?;262self.code.set_readable_and_executable(branch_protection)263}264265fn allocate_readexec(&mut self, size: usize, align: u64) -> io::Result<*mut u8> {266self.code.allocate(size, align)267}268269fn allocate_readwrite(&mut self, size: usize, align: u64) -> io::Result<*mut u8> {270self.writable.allocate(size, align)271}272273fn allocate_readonly(&mut self, size: usize, align: u64) -> io::Result<*mut u8> {274self.readonly.allocate(size, align)275}276}277278279