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 {
new( _ty: &wasmtime_environ::Memory, tunables: &Tunables, minimum: usize, ) -> Result<Self>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 {
byte_size(&self) -> usize70 fn byte_size(&self) -> usize {
71 self.byte_len
72 }
73
byte_capacity(&self) -> usize74 fn byte_capacity(&self) -> usize {
75 self.storage.capacity() * mem::size_of::<Align16>()
76 }
77
grow_to(&mut self, new_size: usize) -> Result<()>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
base(&self) -> MemoryBase92 fn base(&self) -> MemoryBase {
93 MemoryBase::Raw(self.base_ptr)
94 }
95
vmmemory(&self) -> crate::vm::VMMemoryDefinition96 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
byte_size_to_element_len(byte_size: usize) -> usize105 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.
grow_storage_to(storage: &mut Vec<Align16>, new_len: usize)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`.
tunables() -> Tunables160 fn tunables() -> Tunables {
161 Tunables {
162 memory_reservation: 0,
163 memory_guard_size: 0,
164 memory_init_cow: false,
165 ..Tunables::default_miri()
166 }
167 }
168
169 #[test]
simple()170 fn simple() {
171 let mut memory = MallocMemory::new(&TY, &tunables(), 10).unwrap();
172 assert_eq!(memory.storage.len(), 1);
173 assert_valid(&memory);
174
175 memory.grow_to(11).unwrap();
176 assert_eq!(memory.storage.len(), 1);
177 assert_valid(&memory);
178
179 memory.grow_to(16).unwrap();
180 assert_eq!(memory.storage.len(), 1);
181 assert_valid(&memory);
182
183 memory.grow_to(17).unwrap();
184 assert_eq!(memory.storage.len(), 2);
185 assert_valid(&memory);
186
187 memory.grow_to(65).unwrap();
188 assert_eq!(memory.storage.len(), 5);
189 assert_valid(&memory);
190 }
191
192 #[test]
reservation_not_initialized()193 fn reservation_not_initialized() {
194 let tunables = Tunables {
195 memory_reservation_for_growth: 1 << 20,
196 ..tunables()
197 };
198 let mut memory = MallocMemory::new(&TY, &tunables, 10).unwrap();
199 assert_eq!(memory.storage.len(), 1);
200 assert_eq!(
201 memory.storage.capacity(),
202 (tunables.memory_reservation_for_growth / 16) as usize + 1,
203 );
204 assert_valid(&memory);
205
206 memory.grow_to(100).unwrap();
207 assert_eq!(memory.storage.len(), 7);
208 assert_eq!(
209 memory.storage.capacity(),
210 (tunables.memory_reservation_for_growth / 16) as usize + 1,
211 );
212 assert_valid(&memory);
213 }
214
assert_valid(mem: &MallocMemory)215 fn assert_valid(mem: &MallocMemory) {
216 assert_eq!(mem.storage.as_ptr().cast::<u8>(), mem.base_ptr.as_ptr());
217 assert!(mem.byte_len <= mem.storage.len() * 16);
218 for slot in mem.storage.iter() {
219 assert_eq!(slot.0, 0);
220 }
221 }
222 }
223