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::memory::RuntimeLinearMemory; 9 use crate::runtime::vm::SendSyncPtr; 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( 43 tunables 44 .memory_reservation_for_growth 45 .try_into() 46 .err2anyhow()?, 47 ) 48 .context("memory allocation size too large")?; 49 50 let initial_allocation_len = byte_size_to_element_len(initial_allocation_byte_size); 51 let mut storage = Vec::new(); 52 storage.try_reserve(initial_allocation_len).err2anyhow()?; 53 54 let initial_len = byte_size_to_element_len(minimum); 55 if initial_len > 0 { 56 grow_storage_to(&mut storage, initial_len); 57 } 58 Ok(MallocMemory { 59 base_ptr: SendSyncPtr::new(NonNull::new(storage.as_mut_ptr()).unwrap()).cast(), 60 storage, 61 byte_len: minimum, 62 }) 63 } 64 } 65 66 impl RuntimeLinearMemory for MallocMemory { 67 fn byte_size(&self) -> usize { 68 self.byte_len 69 } 70 71 fn byte_capacity(&self) -> usize { 72 self.storage.capacity() * mem::size_of::<Align16>() 73 } 74 75 fn grow_to(&mut self, new_size: usize) -> Result<()> { 76 let new_element_len = byte_size_to_element_len(new_size); 77 if new_element_len > self.storage.len() { 78 self.storage 79 .try_reserve(new_element_len - self.storage.len()) 80 .err2anyhow()?; 81 grow_storage_to(&mut self.storage, new_element_len); 82 self.base_ptr = 83 SendSyncPtr::new(NonNull::new(self.storage.as_mut_ptr()).unwrap()).cast(); 84 } 85 self.byte_len = new_size; 86 Ok(()) 87 } 88 89 fn base_ptr(&self) -> *mut u8 { 90 self.base_ptr.as_ptr() 91 } 92 } 93 94 fn byte_size_to_element_len(byte_size: usize) -> usize { 95 let align = mem::align_of::<Align16>(); 96 97 // Round up the requested byte size to the size of each vector element. 98 let byte_size_rounded_up = 99 byte_size.checked_add(align - 1).unwrap_or(usize::MAX) & !(align - 1); 100 101 // Next divide this aligned size by the size of each element to get the 102 // element length of our vector. 103 byte_size_rounded_up / align 104 } 105 106 /// Helper that is the equivalent of `storage.resize(new_len, Align16(0))` 107 /// except it's also optimized to perform well in debug mode. Just using 108 /// `resize` leads to a per-element iteration which can be quite slow in debug 109 /// mode as it's not optimized to a memcpy, so it's manually optimized here 110 /// instead. 111 fn grow_storage_to(storage: &mut Vec<Align16>, new_len: usize) { 112 debug_assert!(new_len > storage.len()); 113 assert!(new_len <= storage.capacity()); 114 let capacity_to_set = new_len - storage.len(); 115 let slice_to_initialize = &mut storage.spare_capacity_mut()[..capacity_to_set]; 116 let byte_size = mem::size_of_val(slice_to_initialize); 117 118 // SAFETY: The `slice_to_initialize` is guaranteed to be in the capacity of 119 // the vector via the slicing above, so it's all owned memory by the 120 // vector. Additionally the `byte_size` is the exact size of the 121 // `slice_to_initialize` itself, so this `memset` should be in-bounds. 122 // Finally the `Align16` is a simple wrapper around `u128` for which 0 123 // is a valid byte pattern. This should make the initial `write_bytes` safe. 124 // 125 // Afterwards the `set_len` call should also be safe because we've 126 // initialized the tail end of the vector with zeros so it's safe to 127 // consider it having a new length now. 128 unsafe { 129 core::ptr::write_bytes(slice_to_initialize.as_mut_ptr().cast::<u8>(), 0, byte_size); 130 storage.set_len(new_len); 131 } 132 } 133 134 #[cfg(test)] 135 mod tests { 136 use super::*; 137 138 // This is currently required by the constructor but otherwise ignored in 139 // the creation of a `MallocMemory`, so just have a single one used in 140 // tests below. 141 const TY: wasmtime_environ::Memory = wasmtime_environ::Memory { 142 idx_type: wasmtime_environ::IndexType::I32, 143 limits: wasmtime_environ::Limits { min: 0, max: None }, 144 shared: false, 145 page_size_log2: 16, 146 }; 147 148 // Valid tunables that can be used to create a `MallocMemory`. 149 const TUNABLES: Tunables = Tunables { 150 memory_reservation: 0, 151 memory_guard_size: 0, 152 memory_init_cow: false, 153 ..Tunables::default_miri() 154 }; 155 156 #[test] 157 fn simple() { 158 let mut memory = MallocMemory::new(&TY, &TUNABLES, 10).unwrap(); 159 assert_eq!(memory.storage.len(), 1); 160 assert_valid(&memory); 161 162 memory.grow_to(11).unwrap(); 163 assert_eq!(memory.storage.len(), 1); 164 assert_valid(&memory); 165 166 memory.grow_to(16).unwrap(); 167 assert_eq!(memory.storage.len(), 1); 168 assert_valid(&memory); 169 170 memory.grow_to(17).unwrap(); 171 assert_eq!(memory.storage.len(), 2); 172 assert_valid(&memory); 173 174 memory.grow_to(65).unwrap(); 175 assert_eq!(memory.storage.len(), 5); 176 assert_valid(&memory); 177 } 178 179 #[test] 180 fn reservation_not_initialized() { 181 let tunables = Tunables { 182 memory_reservation_for_growth: 1 << 20, 183 ..TUNABLES 184 }; 185 let mut memory = MallocMemory::new(&TY, &tunables, 10).unwrap(); 186 assert_eq!(memory.storage.len(), 1); 187 assert_eq!( 188 memory.storage.capacity(), 189 (tunables.memory_reservation_for_growth / 16) as usize + 1, 190 ); 191 assert_valid(&memory); 192 193 memory.grow_to(100).unwrap(); 194 assert_eq!(memory.storage.len(), 7); 195 assert_eq!( 196 memory.storage.capacity(), 197 (tunables.memory_reservation_for_growth / 16) as usize + 1, 198 ); 199 assert_valid(&memory); 200 } 201 202 fn assert_valid(mem: &MallocMemory) { 203 assert_eq!(mem.storage.as_ptr().cast::<u8>(), mem.base_ptr.as_ptr()); 204 assert!(mem.byte_len <= mem.storage.len() * 16); 205 for slot in mem.storage.iter() { 206 assert_eq!(slot.0, 0); 207 } 208 } 209 } 210