Path: blob/main/cranelift/jit/src/memory/system.rs
3067 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, JITMemoryKind, JITMemoryProvider};1213/// A simple struct consisting of a pointer and length.14struct PtrLen {15#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]16map: Option<MmapMut>,1718ptr: *mut u8,19len: usize,20}2122impl PtrLen {23/// Create a new empty `PtrLen`.24fn new() -> Self {25Self {26#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]27map: None,2829ptr: ptr::null_mut(),30len: 0,31}32}3334/// Create a new `PtrLen` pointing to at least `size` bytes of memory,35/// suitably sized and aligned for memory protection.36#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]37fn with_size(size: usize) -> io::Result<Self> {38let alloc_size = region::page::ceil(size as *const ()) as usize;39MmapMut::map_anon(alloc_size).map(|mut mmap| {40// The order here is important; we assign the pointer first to get41// around compile time borrow errors.42Self {43ptr: mmap.as_mut_ptr(),44map: Some(mmap),45len: alloc_size,46}47})48}4950#[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]51fn with_size(size: usize) -> io::Result<Self> {52assert_ne!(size, 0);53let page_size = region::page::size();54let alloc_size = region::page::ceil(size as *const ()) as usize;55let layout = alloc::Layout::from_size_align(alloc_size, page_size).unwrap();56// Safety: We assert that the size is non-zero above.57let ptr = unsafe { alloc::alloc(layout) };5859if !ptr.is_null() {60Ok(Self {61ptr,62len: alloc_size,63})64} else {65Err(io::Error::from(io::ErrorKind::OutOfMemory))66}67}6869#[cfg(target_os = "windows")]70fn with_size(size: usize) -> io::Result<Self> {71use windows_sys::Win32::System::Memory::{72MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, VirtualAlloc,73};7475// VirtualAlloc always rounds up to the next multiple of the page size76let ptr = unsafe {77VirtualAlloc(78ptr::null_mut(),79size,80MEM_COMMIT | MEM_RESERVE,81PAGE_READWRITE,82)83};84if !ptr.is_null() {85Ok(Self {86ptr: ptr as *mut u8,87len: region::page::ceil(size as *const ()) as usize,88})89} else {90Err(io::Error::last_os_error())91}92}93}9495// `MMapMut` from `cfg(feature = "selinux-fix")` already deallocates properly.96#[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]97impl Drop for PtrLen {98fn drop(&mut self) {99if !self.ptr.is_null() {100let page_size = region::page::size();101let layout = alloc::Layout::from_size_align(self.len, page_size).unwrap();102unsafe {103region::protect(self.ptr, self.len, region::Protection::READ_WRITE)104.expect("unable to unprotect memory");105alloc::dealloc(self.ptr, layout)106}107}108}109}110111// TODO: add a `Drop` impl for `cfg(target_os = "windows")`112113/// JIT memory manager. This manages pages of suitably aligned and114/// accessible memory. Memory will be leaked by default to have115/// function pointers remain valid for the remainder of the116/// program's life.117pub(crate) struct Memory {118allocations: Vec<PtrLen>,119already_protected: usize,120current: PtrLen,121position: usize,122}123124unsafe impl Send for Memory {}125126impl Memory {127pub(crate) fn new() -> Self {128Self {129allocations: Vec::new(),130already_protected: 0,131current: PtrLen::new(),132position: 0,133}134}135136fn finish_current(&mut self) {137self.allocations138.push(mem::replace(&mut self.current, PtrLen::new()));139self.position = 0;140}141142pub(crate) fn allocate(&mut self, size: usize, align: u64) -> io::Result<*mut u8> {143let align = usize::try_from(align).expect("alignment too big");144if self.position % align != 0 {145self.position += align - self.position % align;146debug_assert!(self.position % align == 0);147}148149if size <= self.current.len - self.position {150// TODO: Ensure overflow is not possible.151let ptr = unsafe { self.current.ptr.add(self.position) };152self.position += size;153return Ok(ptr);154}155156self.finish_current();157158// TODO: Allocate more at a time.159self.current = PtrLen::with_size(size)?;160self.position = size;161162Ok(self.current.ptr)163}164165/// Set all memory allocated in this `Memory` up to now as readable and executable.166pub(crate) fn set_readable_and_executable(167&mut self,168branch_protection: BranchProtection,169) -> ModuleResult<()> {170self.finish_current();171172for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() {173super::set_readable_and_executable(ptr, len, branch_protection)?;174}175176// Flush any in-flight instructions from the pipeline177wasmtime_jit_icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush");178179self.already_protected = self.allocations.len();180Ok(())181}182183/// Set all memory allocated in this `Memory` up to now as readonly.184pub(crate) fn set_readonly(&mut self) -> ModuleResult<()> {185self.finish_current();186187for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() {188unsafe {189region::protect(ptr, len, region::Protection::READ).map_err(|e| {190ModuleError::Backend(191anyhow::Error::new(e).context("unable to make memory readonly"),192)193})?;194}195}196197self.already_protected = self.allocations.len();198Ok(())199}200201/// Iterates non protected memory allocations that are of not zero bytes in size.202fn non_protected_allocations_iter(&self) -> impl Iterator<Item = &PtrLen> {203let iter = self.allocations[self.already_protected..].iter();204205#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]206return iter.filter(|&PtrLen { map, len, .. }| *len != 0 && map.is_some());207208#[cfg(any(target_os = "windows", not(feature = "selinux-fix")))]209return iter.filter(|&PtrLen { len, .. }| *len != 0);210}211212/// Frees all allocated memory regions that would be leaked otherwise.213/// Likely to invalidate existing function pointers, causing unsafety.214pub(crate) unsafe fn free_memory(&mut self) {215self.allocations.clear();216self.already_protected = 0;217}218}219220impl Drop for Memory {221fn drop(&mut self) {222// leak memory to guarantee validity of function pointers223mem::replace(&mut self.allocations, Vec::new())224.into_iter()225.for_each(mem::forget);226}227}228229/// A memory provider that allocates memory on-demand using the system230/// allocator.231///232/// Note: Memory will be leaked by default unless233/// [`JITMemoryProvider::free_memory`] is called to ensure function pointers234/// remain valid for the remainder of the program's life.235pub struct SystemMemoryProvider {236code: Memory,237readonly: Memory,238writable: Memory,239}240241impl SystemMemoryProvider {242/// Create a new memory handle with the given branch protection.243pub fn new() -> Self {244Self {245code: Memory::new(),246readonly: Memory::new(),247writable: Memory::new(),248}249}250}251252impl JITMemoryProvider for SystemMemoryProvider {253unsafe fn free_memory(&mut self) {254self.code.free_memory();255self.readonly.free_memory();256self.writable.free_memory();257}258259fn finalize(&mut self, branch_protection: BranchProtection) -> ModuleResult<()> {260self.readonly.set_readonly()?;261self.code.set_readable_and_executable(branch_protection)262}263264fn allocate(&mut self, size: usize, align: u64, kind: JITMemoryKind) -> io::Result<*mut u8> {265match kind {266JITMemoryKind::Executable => self.code.allocate(size, align),267JITMemoryKind::Writable => self.writable.allocate(size, align),268JITMemoryKind::ReadOnly => self.readonly.allocate(size, align),269}270}271}272273274