Skip to main content

cranelift_jit/memory/
system.rs

1use cranelift_module::{ModuleError, ModuleResult};
2
3#[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
4use memmap2::MmapMut;
5
6#[cfg(not(any(feature = "selinux-fix", windows)))]
7use std::alloc;
8use std::io;
9use std::mem;
10use std::ptr;
11
12use super::{BranchProtection, JITMemoryKind, JITMemoryProvider};
13
14/// A simple struct consisting of a pointer and length.
15struct PtrLen {
16    #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
17    map: Option<MmapMut>,
18
19    ptr: *mut u8,
20    len: usize,
21}
22
23impl PtrLen {
24    /// Create a new empty `PtrLen`.
25    fn new() -> Self {
26        Self {
27            #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
28            map: None,
29
30            ptr: ptr::null_mut(),
31            len: 0,
32        }
33    }
34
35    /// 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"))]
38    fn with_size(size: usize) -> io::Result<Self> {
39        let alloc_size = region::page::ceil(size as *const ()) as usize;
40        MmapMut::map_anon(alloc_size).map(|mut mmap| {
41            // The order here is important; we assign the pointer first to get
42            // around compile time borrow errors.
43            Self {
44                ptr: mmap.as_mut_ptr(),
45                map: Some(mmap),
46                len: alloc_size,
47            }
48        })
49    }
50
51    #[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]
52    fn with_size(size: usize) -> io::Result<Self> {
53        assert_ne!(size, 0);
54        let page_size = region::page::size();
55        let alloc_size = region::page::ceil(size as *const ()) as usize;
56        let layout = alloc::Layout::from_size_align(alloc_size, page_size).unwrap();
57        // Safety: We assert that the size is non-zero above.
58        let ptr = unsafe { alloc::alloc(layout) };
59
60        if !ptr.is_null() {
61            Ok(Self {
62                ptr,
63                len: alloc_size,
64            })
65        } else {
66            Err(io::Error::from(io::ErrorKind::OutOfMemory))
67        }
68    }
69
70    #[cfg(target_os = "windows")]
71    fn with_size(size: usize) -> io::Result<Self> {
72        use windows_sys::Win32::System::Memory::{
73            MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, VirtualAlloc,
74        };
75
76        // VirtualAlloc always rounds up to the next multiple of the page size
77        let ptr = unsafe {
78            VirtualAlloc(
79                ptr::null_mut(),
80                size,
81                MEM_COMMIT | MEM_RESERVE,
82                PAGE_READWRITE,
83            )
84        };
85        if !ptr.is_null() {
86            Ok(Self {
87                ptr: ptr as *mut u8,
88                len: region::page::ceil(size as *const ()) as usize,
89            })
90        } else {
91            Err(io::Error::last_os_error())
92        }
93    }
94}
95
96// `MMapMut` from `cfg(feature = "selinux-fix")` already deallocates properly.
97#[cfg(all(not(target_os = "windows"), not(feature = "selinux-fix")))]
98impl Drop for PtrLen {
99    fn drop(&mut self) {
100        if !self.ptr.is_null() {
101            let page_size = region::page::size();
102            let layout = alloc::Layout::from_size_align(self.len, page_size).unwrap();
103            unsafe {
104                region::protect(self.ptr, self.len, region::Protection::READ_WRITE)
105                    .expect("unable to unprotect memory");
106                alloc::dealloc(self.ptr, layout)
107            }
108        }
109    }
110}
111
112// TODO: add a `Drop` impl for `cfg(target_os = "windows")`
113
114/// JIT memory manager. This manages pages of suitably aligned and
115/// accessible memory. Memory will be leaked by default to have
116/// function pointers remain valid for the remainder of the
117/// program's life.
118pub(crate) struct Memory {
119    allocations: Vec<PtrLen>,
120    already_protected: usize,
121    current: PtrLen,
122    position: usize,
123}
124
125unsafe impl Send for Memory {}
126
127impl Memory {
128    pub(crate) fn new() -> Self {
129        Self {
130            allocations: Vec::new(),
131            already_protected: 0,
132            current: PtrLen::new(),
133            position: 0,
134        }
135    }
136
137    fn finish_current(&mut self) {
138        self.allocations
139            .push(mem::replace(&mut self.current, PtrLen::new()));
140        self.position = 0;
141    }
142
143    pub(crate) fn allocate(&mut self, size: usize, align: u64) -> io::Result<*mut u8> {
144        let align = usize::try_from(align).expect("alignment too big");
145        if self.position % align != 0 {
146            self.position += align - self.position % align;
147            debug_assert!(self.position % align == 0);
148        }
149
150        if size <= self.current.len - self.position {
151            // TODO: Ensure overflow is not possible.
152            let ptr = unsafe { self.current.ptr.add(self.position) };
153            self.position += size;
154            return Ok(ptr);
155        }
156
157        self.finish_current();
158
159        // TODO: Allocate more at a time.
160        self.current = PtrLen::with_size(size)?;
161        self.position = size;
162
163        Ok(self.current.ptr)
164    }
165
166    /// Set all memory allocated in this `Memory` up to now as readable and executable.
167    pub(crate) fn set_readable_and_executable(
168        &mut self,
169        branch_protection: BranchProtection,
170    ) -> ModuleResult<()> {
171        self.finish_current();
172
173        for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() {
174            super::set_readable_and_executable(ptr, len, branch_protection)?;
175        }
176
177        // Flush any in-flight instructions from the pipeline
178        wasmtime_jit_icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush");
179
180        self.already_protected = self.allocations.len();
181        Ok(())
182    }
183
184    /// Set all memory allocated in this `Memory` up to now as readonly.
185    pub(crate) fn set_readonly(&mut self) -> ModuleResult<()> {
186        self.finish_current();
187
188        for &PtrLen { ptr, len, .. } in self.non_protected_allocations_iter() {
189            unsafe {
190                region::protect(ptr, len, region::Protection::READ).map_err(|e| {
191                    ModuleError::Backend(
192                        anyhow::Error::new(e).context("unable to make memory readonly"),
193                    )
194                })?;
195            }
196        }
197
198        self.already_protected = self.allocations.len();
199        Ok(())
200    }
201
202    /// Iterates non protected memory allocations that are of not zero bytes in size.
203    fn non_protected_allocations_iter(&self) -> impl Iterator<Item = &PtrLen> {
204        let iter = self.allocations[self.already_protected..].iter();
205
206        #[cfg(all(not(target_os = "windows"), feature = "selinux-fix"))]
207        return iter.filter(|&PtrLen { map, len, .. }| *len != 0 && map.is_some());
208
209        #[cfg(any(target_os = "windows", not(feature = "selinux-fix")))]
210        return iter.filter(|&PtrLen { len, .. }| *len != 0);
211    }
212
213    /// Frees all allocated memory regions that would be leaked otherwise.
214    /// Likely to invalidate existing function pointers, causing unsafety.
215    pub(crate) unsafe fn free_memory(&mut self) {
216        self.allocations.clear();
217        self.already_protected = 0;
218    }
219}
220
221impl Drop for Memory {
222    fn drop(&mut self) {
223        // leak memory to guarantee validity of function pointers
224        mem::replace(&mut self.allocations, Vec::new())
225            .into_iter()
226            .for_each(mem::forget);
227    }
228}
229
230/// A memory provider that allocates memory on-demand using the system
231/// allocator.
232///
233/// Note: Memory will be leaked by default unless
234/// [`JITMemoryProvider::free_memory`] is called to ensure function pointers
235/// remain valid for the remainder of the program's life.
236pub struct SystemMemoryProvider {
237    code: Memory,
238    readonly: Memory,
239    writable: Memory,
240}
241
242impl SystemMemoryProvider {
243    /// Create a new memory handle with the given branch protection.
244    pub fn new() -> Self {
245        Self {
246            code: Memory::new(),
247            readonly: Memory::new(),
248            writable: Memory::new(),
249        }
250    }
251}
252
253impl JITMemoryProvider for SystemMemoryProvider {
254    unsafe fn free_memory(&mut self) {
255        self.code.free_memory();
256        self.readonly.free_memory();
257        self.writable.free_memory();
258    }
259
260    fn finalize(&mut self, branch_protection: BranchProtection) -> ModuleResult<()> {
261        self.readonly.set_readonly()?;
262        self.code.set_readable_and_executable(branch_protection)
263    }
264
265    fn allocate(&mut self, size: usize, align: u64, kind: JITMemoryKind) -> io::Result<*mut u8> {
266        match kind {
267            JITMemoryKind::Executable => self.code.allocate(size, align),
268            JITMemoryKind::Writable => self.writable.allocate(size, align),
269            JITMemoryKind::ReadOnly => self.readonly.allocate(size, align),
270        }
271    }
272}