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