1 //! Support for implementing the [`RuntimeLinearMemory`] trait in terms of a 2 //! platform memory allocation primitive (e.g. `malloc`) 3 //! 4 //! Note that memory is allocated here using `Vec::try_reserve` to explicitly 5 //! handle memory allocation failures. 6 7 use crate::prelude::*; 8 use crate::runtime::vm::SendSyncPtr; 9 use crate::runtime::vm::memory::{MemoryBase, RuntimeLinearMemory}; 10 use core::mem; 11 use core::ptr::NonNull; 12 use wasmtime_environ::Tunables; 13 14 #[repr(C, align(16))] 15 #[derive(Copy, Clone)] 16 pub struct Align16(u128); 17 18 /// An instance of linear memory backed by the default system allocator. 19 pub struct MallocMemory { 20 storage: Vec<Align16>, 21 base_ptr: SendSyncPtr<u8>, 22 byte_len: usize, 23 } 24 25 impl MallocMemory { 26 pub fn new( 27 _ty: &wasmtime_environ::Memory, 28 tunables: &Tunables, 29 minimum: usize, 30 ) -> Result<Self> { 31 if tunables.memory_guard_size > 0 { 32 bail!("malloc memory is only compatible if guard pages aren't used"); 33 } 34 if tunables.memory_reservation > 0 { 35 bail!("malloc memory is only compatible with no ahead-of-time memory reservation"); 36 } 37 if tunables.memory_init_cow { 38 bail!("malloc memory cannot be used with CoW images"); 39 } 40 41 let initial_allocation_byte_size = minimum 42 .checked_add(tunables.memory_reservation_for_growth.try_into()?) 43 .context("memory allocation size too large")?; 44 45 let initial_allocation_len = byte_size_to_element_len(initial_allocation_byte_size); 46 let mut storage = Vec::new(); 47 storage 48 .try_reserve(initial_allocation_len) 49 .with_context(|| { 50 format!( 51 "failed to allocate {initial_allocation_byte_size:#x} \ 52 bytes ({minimum:#x} minimum + {:#x} memory_reservation_for_growth)", 53 tunables.memory_reservation_for_growth, 54 ) 55 })?; 56 57 let initial_len = byte_size_to_element_len(minimum); 58 if initial_len > 0 { 59 grow_storage_to(&mut storage, initial_len); 60 } 61 Ok(MallocMemory { 62 base_ptr: SendSyncPtr::new(NonNull::new(storage.as_mut_ptr()).unwrap()).cast(), 63 storage, 64 byte_len: minimum, 65 }) 66 } 67 } 68 69 impl RuntimeLinearMemory for MallocMemory { 70 fn byte_size(&self) -> usize { 71 self.byte_len 72 } 73 74 fn byte_capacity(&self) -> usize { 75 self.storage.capacity() * mem::size_of::<Align16>() 76 } 77 78 fn grow_to(&mut self, new_size: usize) -> Result<()> { 79 let new_element_len = byte_size_to_element_len(new_size); 80 if new_element_len > self.storage.len() { 81 self.storage 82 .try_reserve(new_element_len - self.storage.len()) 83 .with_context(|| format!("failed to grow memory to {new_size:#x} bytes"))?; 84 grow_storage_to(&mut self.storage, new_element_len); 85 self.base_ptr = 86 SendSyncPtr::new(NonNull::new(self.storage.as_mut_ptr()).unwrap()).cast(); 87 } 88 self.byte_len = new_size; 89 Ok(()) 90 } 91 92 fn base(&self) -> MemoryBase { 93 MemoryBase::Raw(self.base_ptr) 94 } 95 96 fn vmmemory(&self) -> crate::vm::VMMemoryDefinition { 97 let base = self.base_ptr.as_non_null(); 98 crate::vm::VMMemoryDefinition { 99 base: base.into(), 100 current_length: self.byte_len.into(), 101 } 102 } 103 } 104 105 fn byte_size_to_element_len(byte_size: usize) -> usize { 106 let align = mem::align_of::<Align16>(); 107 108 // Round up the requested byte size to the size of each vector element. 109 let byte_size_rounded_up = 110 byte_size.checked_add(align - 1).unwrap_or(usize::MAX) & !(align - 1); 111 112 // Next divide this aligned size by the size of each element to get the 113 // element length of our vector. 114 byte_size_rounded_up / align 115 } 116 117 /// Helper that is the equivalent of `storage.resize(new_len, Align16(0))` 118 /// except it's also optimized to perform well in debug mode. Just using 119 /// `resize` leads to a per-element iteration which can be quite slow in debug 120 /// mode as it's not optimized to a memcpy, so it's manually optimized here 121 /// instead. 122 fn grow_storage_to(storage: &mut Vec<Align16>, new_len: usize) { 123 debug_assert!(new_len > storage.len()); 124 assert!(new_len <= storage.capacity()); 125 let capacity_to_set = new_len - storage.len(); 126 let slice_to_initialize = &mut storage.spare_capacity_mut()[..capacity_to_set]; 127 let byte_size = mem::size_of_val(slice_to_initialize); 128 129 // SAFETY: The `slice_to_initialize` is guaranteed to be in the capacity of 130 // the vector via the slicing above, so it's all owned memory by the 131 // vector. Additionally the `byte_size` is the exact size of the 132 // `slice_to_initialize` itself, so this `memset` should be in-bounds. 133 // Finally the `Align16` is a simple wrapper around `u128` for which 0 134 // is a valid byte pattern. This should make the initial `write_bytes` safe. 135 // 136 // Afterwards the `set_len` call should also be safe because we've 137 // initialized the tail end of the vector with zeros so it's safe to 138 // consider it having a new length now. 139 unsafe { 140 core::ptr::write_bytes(slice_to_initialize.as_mut_ptr().cast::<u8>(), 0, byte_size); 141 storage.set_len(new_len); 142 } 143 } 144 145 #[cfg(test)] 146 mod tests { 147 use super::*; 148 149 // This is currently required by the constructor but otherwise ignored in 150 // the creation of a `MallocMemory`, so just have a single one used in 151 // tests below. 152 const TY: wasmtime_environ::Memory = wasmtime_environ::Memory { 153 idx_type: wasmtime_environ::IndexType::I32, 154 limits: wasmtime_environ::Limits { min: 0, max: None }, 155 shared: false, 156 page_size_log2: 16, 157 }; 158 159 // Valid tunables that can be used to create a `MallocMemory`. 160 const TUNABLES: Tunables = Tunables { 161 memory_reservation: 0, 162 memory_guard_size: 0, 163 memory_init_cow: false, 164 ..Tunables::default_miri() 165 }; 166 167 #[test] 168 fn simple() { 169 let mut memory = MallocMemory::new(&TY, &TUNABLES, 10).unwrap(); 170 assert_eq!(memory.storage.len(), 1); 171 assert_valid(&memory); 172 173 memory.grow_to(11).unwrap(); 174 assert_eq!(memory.storage.len(), 1); 175 assert_valid(&memory); 176 177 memory.grow_to(16).unwrap(); 178 assert_eq!(memory.storage.len(), 1); 179 assert_valid(&memory); 180 181 memory.grow_to(17).unwrap(); 182 assert_eq!(memory.storage.len(), 2); 183 assert_valid(&memory); 184 185 memory.grow_to(65).unwrap(); 186 assert_eq!(memory.storage.len(), 5); 187 assert_valid(&memory); 188 } 189 190 #[test] 191 fn reservation_not_initialized() { 192 let tunables = Tunables { 193 memory_reservation_for_growth: 1 << 20, 194 ..TUNABLES 195 }; 196 let mut memory = MallocMemory::new(&TY, &tunables, 10).unwrap(); 197 assert_eq!(memory.storage.len(), 1); 198 assert_eq!( 199 memory.storage.capacity(), 200 (tunables.memory_reservation_for_growth / 16) as usize + 1, 201 ); 202 assert_valid(&memory); 203 204 memory.grow_to(100).unwrap(); 205 assert_eq!(memory.storage.len(), 7); 206 assert_eq!( 207 memory.storage.capacity(), 208 (tunables.memory_reservation_for_growth / 16) as usize + 1, 209 ); 210 assert_valid(&memory); 211 } 212 213 fn assert_valid(mem: &MallocMemory) { 214 assert_eq!(mem.storage.as_ptr().cast::<u8>(), mem.base_ptr.as_ptr()); 215 assert!(mem.byte_len <= mem.storage.len() * 16); 216 for slot in mem.storage.iter() { 217 assert_eq!(slot.0, 0); 218 } 219 } 220 } 221