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