wasmer_vm/instance/allocator.rs
1use super::{Instance, VMInstance};
2use crate::vmcontext::VMTableDefinition;
3use crate::{VMGlobalDefinition, VMMemoryDefinition};
4use std::alloc::{self, Layout};
5use std::convert::TryFrom;
6use std::mem;
7use std::ptr::{self, NonNull};
8use wasmer_types::entity::EntityRef;
9use wasmer_types::{LocalGlobalIndex, VMOffsets};
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 #[allow(clippy::type_complexity)]
72 pub fn new(
73 module: &ModuleInfo,
74 ) -> (
75 Self,
76 Vec<NonNull<VMMemoryDefinition>>,
77 Vec<NonNull<VMTableDefinition>>,
78 Vec<NonNull<VMGlobalDefinition>>,
79 ) {
80 let offsets = VMOffsets::new(mem::size_of::<usize>() as u8, module);
81 let instance_layout = Self::instance_layout(&offsets);
82
83 #[allow(clippy::cast_ptr_alignment)]
84 let instance_ptr = unsafe { alloc::alloc(instance_layout) as *mut Instance };
85
86 let instance_ptr = if let Some(ptr) = NonNull::new(instance_ptr) {
87 ptr
88 } else {
89 alloc::handle_alloc_error(instance_layout);
90 };
91
92 let allocator = Self {
93 instance_ptr,
94 instance_layout,
95 offsets,
96 consumed: false,
97 };
98
99 // # Safety
100 // Both of these calls are safe because we allocate the pointer
101 // above with the same `offsets` that these functions use.
102 // Thus there will be enough valid memory for both of them.
103 let memories = unsafe { allocator.memory_definition_locations() };
104 let tables = unsafe { allocator.table_definition_locations() };
105 let globals = unsafe { allocator.global_definition_locations() };
106
107 (allocator, memories, tables, globals)
108 }
109
110 /// Calculate the appropriate layout for the internal `Instance` structure.
111 fn instance_layout(offsets: &VMOffsets) -> Layout {
112 let vmctx_size = usize::try_from(offsets.size_of_vmctx())
113 .expect("Failed to convert the size of `vmctx` to a `usize`");
114
115 let instance_vmctx_layout =
116 Layout::array::<u8>(vmctx_size).expect("Failed to create a layout for `VMContext`");
117
118 let (instance_layout, _offset) = Layout::new::<Instance>()
119 .extend(instance_vmctx_layout)
120 .expect("Failed to extend to `Instance` layout to include `VMContext`");
121
122 instance_layout.pad_to_align()
123 }
124
125 /// Get the locations of where the local [`VMMemoryDefinition`]s should be stored.
126 ///
127 /// This function lets us create `Memory` objects on the host with backing
128 /// memory in the VM.
129 ///
130 /// # Safety
131 ///
132 /// - `Self.instance_ptr` must point to enough memory that all of
133 /// the offsets in `Self.offsets` point to valid locations in
134 /// memory, i.e. `Self.instance_ptr` must have been allocated by
135 /// `Self::new`.
136 unsafe fn memory_definition_locations(&self) -> Vec<NonNull<VMMemoryDefinition>> {
137 unsafe {
138 let num_memories = self.offsets.num_local_memories();
139 let num_memories = usize::try_from(num_memories).unwrap();
140 let mut out = Vec::with_capacity(num_memories);
141
142 // We need to do some pointer arithmetic now. The unit is `u8`.
143 let ptr = self.instance_ptr.cast::<u8>().as_ptr();
144 let base_ptr = ptr.add(mem::size_of::<Instance>());
145
146 for i in 0..num_memories {
147 let mem_offset = self
148 .offsets
149 .vmctx_vmmemory_definition(LocalMemoryIndex::new(i));
150 let mem_offset = usize::try_from(mem_offset).unwrap();
151
152 let new_ptr = NonNull::new_unchecked(base_ptr.add(mem_offset));
153
154 out.push(new_ptr.cast());
155 }
156
157 out
158 }
159 }
160
161 /// Get the locations of where the [`VMTableDefinition`]s should be stored.
162 ///
163 /// This function lets us create [`Table`] objects on the host with backing
164 /// memory in the VM.
165 ///
166 /// # Safety
167 ///
168 /// - `Self.instance_ptr` must point to enough memory that all of
169 /// the offsets in `Self.offsets` point to valid locations in
170 /// memory, i.e. `Self.instance_ptr` must have been allocated by
171 /// `Self::new`.
172 unsafe fn table_definition_locations(&self) -> Vec<NonNull<VMTableDefinition>> {
173 unsafe {
174 let num_tables = self.offsets.num_local_tables();
175 let num_tables = usize::try_from(num_tables).unwrap();
176 let mut out = Vec::with_capacity(num_tables);
177
178 // We need to do some pointer arithmetic now. The unit is `u8`.
179 let ptr = self.instance_ptr.cast::<u8>().as_ptr();
180 let base_ptr = ptr.add(std::mem::size_of::<Instance>());
181
182 for i in 0..num_tables {
183 let table_offset = self
184 .offsets
185 .vmctx_vmtable_definition(LocalTableIndex::new(i));
186 let table_offset = usize::try_from(table_offset).unwrap();
187
188 let new_ptr = NonNull::new_unchecked(base_ptr.add(table_offset));
189
190 out.push(new_ptr.cast());
191 }
192 out
193 }
194 }
195
196 /// Get the locations of where the [`VMGlobalDefinition`]s should be stored.
197 ///
198 /// This function lets us create [`Global`] objects on the host with backing
199 /// memory in the VM.
200 ///
201 /// # Safety
202 ///
203 /// - `Self.instance_ptr` must point to enough memory that all of
204 /// the offsets in `Self.offsets` point to valid locations in
205 /// memory, i.e. `Self.instance_ptr` must have been allocated by
206 /// `Self::new`.
207 unsafe fn global_definition_locations(&self) -> Vec<NonNull<VMGlobalDefinition>> {
208 unsafe {
209 let num_globals = self.offsets.num_local_globals();
210 let num_globals = usize::try_from(num_globals).unwrap();
211 let mut out = Vec::with_capacity(num_globals);
212
213 let ptr = self.instance_ptr.cast::<u8>().as_ptr();
214 let base_ptr = ptr.add(std::mem::size_of::<Instance>());
215
216 for i in 0..num_globals {
217 let global_offset = self
218 .offsets
219 .vmctx_vmglobal_definition(LocalGlobalIndex::new(i));
220 let global_offset = usize::try_from(global_offset).unwrap();
221
222 let new_ptr = NonNull::new_unchecked(base_ptr.add(global_offset));
223 out.push(new_ptr.cast());
224 }
225
226 out
227 }
228 }
229
230 /// Finish preparing by writing the internal `Instance` into memory, and
231 /// consume this `InstanceAllocator`.
232 pub(crate) fn into_vminstance(mut self, instance: Instance) -> VMInstance {
233 // Prevent the old state's drop logic from being called as we
234 // transition into the new state.
235 self.consumed = true;
236
237 unsafe {
238 // `instance` is moved at `Self.instance_ptr`. This
239 // pointer has been allocated by `Self::allocate_instance`
240 // (so by `VMInstance::allocate_instance`).
241 ptr::write(self.instance_ptr.as_ptr(), instance);
242 // Now `instance_ptr` is correctly initialized!
243 }
244 let instance = self.instance_ptr;
245 let instance_layout = self.instance_layout;
246
247 // This is correct because of the invariants of `Self` and
248 // because we write `Instance` to the pointer in this function.
249 VMInstance {
250 instance,
251 instance_layout,
252 }
253 }
254
255 /// Get the [`VMOffsets`] for the allocated buffer.
256 pub(crate) fn offsets(&self) -> &VMOffsets {
257 &self.offsets
258 }
259}