wasmer_vm/instance/
allocator.rs

1use super::{Instance, VMInstance};
2use crate::VMMemoryDefinition;
3use crate::vmcontext::VMTableDefinition;
4use std::alloc::{self, Layout};
5use std::convert::TryFrom;
6use std::mem;
7use std::ptr::{self, NonNull};
8use wasmer_types::VMOffsets;
9use wasmer_types::entity::EntityRef;
10use wasmer_types::{LocalMemoryIndex, LocalTableIndex, ModuleInfo};
11
12/// This is an intermediate type that manages the raw allocation and
13/// metadata when creating a [`VMInstance`].
14///
15/// This type will free the allocated memory if it's dropped before
16/// being used.
17///
18/// It is important to remind that [`VMInstance`] is dynamically-sized
19/// based on `VMOffsets`: The `Instance.vmctx` field represents a
20/// dynamically-sized array that extends beyond the nominal end of the
21/// type. So in order to create an instance of it, we must:
22///
23/// 1. Define the correct layout for `Instance` (size and alignment),
24/// 2. Allocate it properly.
25///
26/// The `InstanceAllocator::instance_layout` helper computes the correct
27/// layout to represent the wanted [`VMInstance`].
28///
29/// Then we use this layout to allocate an empty `Instance` properly.
30pub struct InstanceAllocator {
31    /// The buffer that will contain the [`VMInstance`] and dynamic fields.
32    instance_ptr: NonNull<Instance>,
33
34    /// The layout of the `instance_ptr` buffer.
35    instance_layout: Layout,
36
37    /// Information about the offsets into the `instance_ptr` buffer for
38    /// the dynamic fields.
39    offsets: VMOffsets,
40
41    /// Whether or not this type has transferred ownership of the
42    /// `instance_ptr` buffer. If it has not when being dropped,
43    /// the buffer should be freed.
44    consumed: bool,
45}
46
47impl Drop for InstanceAllocator {
48    fn drop(&mut self) {
49        if !self.consumed {
50            // If `consumed` has not been set, then we still have ownership
51            // over the buffer and must free it.
52            let instance_ptr = self.instance_ptr.as_ptr();
53
54            unsafe {
55                std::alloc::dealloc(instance_ptr as *mut u8, self.instance_layout);
56            }
57        }
58    }
59}
60
61impl InstanceAllocator {
62    /// Allocates instance data for use with [`VMInstance::new`].
63    ///
64    /// Returns a wrapper type around the allocation and 2 vectors of
65    /// pointers into the allocated buffer. These lists of pointers
66    /// correspond to the location in memory for the local memories and
67    /// tables respectively. These pointers should be written to before
68    /// calling [`VMInstance::new`].
69    ///
70    /// [`VMInstance::new`]: super::VMInstance::new
71    pub fn new(
72        module: &ModuleInfo,
73    ) -> (
74        Self,
75        Vec<NonNull<VMMemoryDefinition>>,
76        Vec<NonNull<VMTableDefinition>>,
77    ) {
78        let offsets = VMOffsets::new(mem::size_of::<usize>() as u8, module);
79        let instance_layout = Self::instance_layout(&offsets);
80
81        #[allow(clippy::cast_ptr_alignment)]
82        let instance_ptr = unsafe { alloc::alloc(instance_layout) as *mut Instance };
83
84        let instance_ptr = if let Some(ptr) = NonNull::new(instance_ptr) {
85            ptr
86        } else {
87            alloc::handle_alloc_error(instance_layout);
88        };
89
90        let allocator = Self {
91            instance_ptr,
92            instance_layout,
93            offsets,
94            consumed: false,
95        };
96
97        // # Safety
98        // Both of these calls are safe because we allocate the pointer
99        // above with the same `offsets` that these functions use.
100        // Thus there will be enough valid memory for both of them.
101        let memories = unsafe { allocator.memory_definition_locations() };
102        let tables = unsafe { allocator.table_definition_locations() };
103
104        (allocator, memories, tables)
105    }
106
107    /// Calculate the appropriate layout for the internal `Instance` structure.
108    fn instance_layout(offsets: &VMOffsets) -> Layout {
109        let vmctx_size = usize::try_from(offsets.size_of_vmctx())
110            .expect("Failed to convert the size of `vmctx` to a `usize`");
111
112        let instance_vmctx_layout =
113            Layout::array::<u8>(vmctx_size).expect("Failed to create a layout for `VMContext`");
114
115        let (instance_layout, _offset) = Layout::new::<Instance>()
116            .extend(instance_vmctx_layout)
117            .expect("Failed to extend to `Instance` layout to include `VMContext`");
118
119        instance_layout.pad_to_align()
120    }
121
122    /// Get the locations of where the local [`VMMemoryDefinition`]s should be stored.
123    ///
124    /// This function lets us create `Memory` objects on the host with backing
125    /// memory in the VM.
126    ///
127    /// # Safety
128    ///
129    /// - `Self.instance_ptr` must point to enough memory that all of
130    ///   the offsets in `Self.offsets` point to valid locations in
131    ///   memory, i.e. `Self.instance_ptr` must have been allocated by
132    ///   `Self::new`.
133    unsafe fn memory_definition_locations(&self) -> Vec<NonNull<VMMemoryDefinition>> {
134        unsafe {
135            let num_memories = self.offsets.num_local_memories();
136            let num_memories = usize::try_from(num_memories).unwrap();
137            let mut out = Vec::with_capacity(num_memories);
138
139            // We need to do some pointer arithmetic now. The unit is `u8`.
140            let ptr = self.instance_ptr.cast::<u8>().as_ptr();
141            let base_ptr = ptr.add(mem::size_of::<Instance>());
142
143            for i in 0..num_memories {
144                let mem_offset = self
145                    .offsets
146                    .vmctx_vmmemory_definition(LocalMemoryIndex::new(i));
147                let mem_offset = usize::try_from(mem_offset).unwrap();
148
149                let new_ptr = NonNull::new_unchecked(base_ptr.add(mem_offset));
150
151                out.push(new_ptr.cast());
152            }
153
154            out
155        }
156    }
157
158    /// Get the locations of where the [`VMTableDefinition`]s should be stored.
159    ///
160    /// This function lets us create [`Table`] objects on the host with backing
161    /// memory in the VM.
162    ///
163    /// # Safety
164    ///
165    /// - `Self.instance_ptr` must point to enough memory that all of
166    ///   the offsets in `Self.offsets` point to valid locations in
167    ///   memory, i.e. `Self.instance_ptr` must have been allocated by
168    ///   `Self::new`.
169    unsafe fn table_definition_locations(&self) -> Vec<NonNull<VMTableDefinition>> {
170        unsafe {
171            let num_tables = self.offsets.num_local_tables();
172            let num_tables = usize::try_from(num_tables).unwrap();
173            let mut out = Vec::with_capacity(num_tables);
174
175            // We need to do some pointer arithmetic now. The unit is `u8`.
176            let ptr = self.instance_ptr.cast::<u8>().as_ptr();
177            let base_ptr = ptr.add(std::mem::size_of::<Instance>());
178
179            for i in 0..num_tables {
180                let table_offset = self
181                    .offsets
182                    .vmctx_vmtable_definition(LocalTableIndex::new(i));
183                let table_offset = usize::try_from(table_offset).unwrap();
184
185                let new_ptr = NonNull::new_unchecked(base_ptr.add(table_offset));
186
187                out.push(new_ptr.cast());
188            }
189            out
190        }
191    }
192
193    /// Finish preparing by writing the internal `Instance` into memory, and
194    /// consume this `InstanceAllocator`.
195    pub(crate) fn into_vminstance(mut self, instance: Instance) -> VMInstance {
196        // Prevent the old state's drop logic from being called as we
197        // transition into the new state.
198        self.consumed = true;
199
200        unsafe {
201            // `instance` is moved at `Self.instance_ptr`. This
202            // pointer has been allocated by `Self::allocate_instance`
203            // (so by `VMInstance::allocate_instance`).
204            ptr::write(self.instance_ptr.as_ptr(), instance);
205            // Now `instance_ptr` is correctly initialized!
206        }
207        let instance = self.instance_ptr;
208        let instance_layout = self.instance_layout;
209
210        // This is correct because of the invariants of `Self` and
211        // because we write `Instance` to the pointer in this function.
212        VMInstance {
213            instance,
214            instance_layout,
215        }
216    }
217
218    /// Get the [`VMOffsets`] for the allocated buffer.
219    pub(crate) fn offsets(&self) -> &VMOffsets {
220        &self.offsets
221    }
222}