wasmtime/runtime/vm/memory/
malloc.rs

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