wasmer_vm/instance/
allocator.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
use super::{Instance, VMInstance};
use crate::vmcontext::VMTableDefinition;
use crate::VMMemoryDefinition;
use std::alloc::{self, Layout};
use std::convert::TryFrom;
use std::mem;
use std::ptr::{self, NonNull};
use wasmer_types::entity::EntityRef;
use wasmer_types::VMOffsets;
use wasmer_types::{LocalMemoryIndex, LocalTableIndex, ModuleInfo};

/// This is an intermediate type that manages the raw allocation and
/// metadata when creating an [`Instance`].
///
/// This type will free the allocated memory if it's dropped before
/// being used.
///
/// It is important to remind that [`Instance`] is dynamically-sized
/// based on `VMOffsets`: The `Instance.vmctx` field represents a
/// dynamically-sized array that extends beyond the nominal end of the
/// type. So in order to create an instance of it, we must:
///
/// 1. Define the correct layout for `Instance` (size and alignment),
/// 2. Allocate it properly.
///
/// The [`InstanceAllocator::instance_layout`] computes the correct
/// layout to represent the wanted [`Instance`].
///
/// Then we use this layout to allocate an empty `Instance` properly.
pub struct InstanceAllocator {
    /// The buffer that will contain the [`Instance`] and dynamic fields.
    instance_ptr: NonNull<Instance>,

    /// The layout of the `instance_ptr` buffer.
    instance_layout: Layout,

    /// Information about the offsets into the `instance_ptr` buffer for
    /// the dynamic fields.
    offsets: VMOffsets,

    /// Whether or not this type has transferred ownership of the
    /// `instance_ptr` buffer. If it has not when being dropped,
    /// the buffer should be freed.
    consumed: bool,
}

impl Drop for InstanceAllocator {
    fn drop(&mut self) {
        if !self.consumed {
            // If `consumed` has not been set, then we still have ownership
            // over the buffer and must free it.
            let instance_ptr = self.instance_ptr.as_ptr();

            unsafe {
                std::alloc::dealloc(instance_ptr as *mut u8, self.instance_layout);
            }
        }
    }
}

impl InstanceAllocator {
    /// Allocates instance data for use with [`VMInstance::new`].
    ///
    /// Returns a wrapper type around the allocation and 2 vectors of
    /// pointers into the allocated buffer. These lists of pointers
    /// correspond to the location in memory for the local memories and
    /// tables respectively. These pointers should be written to before
    /// calling [`VMInstance::new`].
    ///
    /// [`VMInstance::new`]: super::VMInstance::new
    pub fn new(
        module: &ModuleInfo,
    ) -> (
        Self,
        Vec<NonNull<VMMemoryDefinition>>,
        Vec<NonNull<VMTableDefinition>>,
    ) {
        let offsets = VMOffsets::new(mem::size_of::<usize>() as u8, module);
        let instance_layout = Self::instance_layout(&offsets);

        #[allow(clippy::cast_ptr_alignment)]
        let instance_ptr = unsafe { alloc::alloc(instance_layout) as *mut Instance };

        let instance_ptr = if let Some(ptr) = NonNull::new(instance_ptr) {
            ptr
        } else {
            alloc::handle_alloc_error(instance_layout);
        };

        let allocator = Self {
            instance_ptr,
            instance_layout,
            offsets,
            consumed: false,
        };

        // # Safety
        // Both of these calls are safe because we allocate the pointer
        // above with the same `offsets` that these functions use.
        // Thus there will be enough valid memory for both of them.
        let memories = unsafe { allocator.memory_definition_locations() };
        let tables = unsafe { allocator.table_definition_locations() };

        (allocator, memories, tables)
    }

    /// Calculate the appropriate layout for the [`Instance`].
    fn instance_layout(offsets: &VMOffsets) -> Layout {
        let vmctx_size = usize::try_from(offsets.size_of_vmctx())
            .expect("Failed to convert the size of `vmctx` to a `usize`");

        let instance_vmctx_layout =
            Layout::array::<u8>(vmctx_size).expect("Failed to create a layout for `VMContext`");

        let (instance_layout, _offset) = Layout::new::<Instance>()
            .extend(instance_vmctx_layout)
            .expect("Failed to extend to `Instance` layout to include `VMContext`");

        instance_layout.pad_to_align()
    }

    /// Get the locations of where the local [`VMMemoryDefinition`]s should be stored.
    ///
    /// This function lets us create `Memory` objects on the host with backing
    /// memory in the VM.
    ///
    /// # Safety
    ///
    /// - `Self.instance_ptr` must point to enough memory that all of
    ///   the offsets in `Self.offsets` point to valid locations in
    ///   memory, i.e. `Self.instance_ptr` must have been allocated by
    ///   `Self::new`.
    unsafe fn memory_definition_locations(&self) -> Vec<NonNull<VMMemoryDefinition>> {
        let num_memories = self.offsets.num_local_memories();
        let num_memories = usize::try_from(num_memories).unwrap();
        let mut out = Vec::with_capacity(num_memories);

        // We need to do some pointer arithmetic now. The unit is `u8`.
        let ptr = self.instance_ptr.cast::<u8>().as_ptr();
        let base_ptr = ptr.add(mem::size_of::<Instance>());

        for i in 0..num_memories {
            let mem_offset = self
                .offsets
                .vmctx_vmmemory_definition(LocalMemoryIndex::new(i));
            let mem_offset = usize::try_from(mem_offset).unwrap();

            let new_ptr = NonNull::new_unchecked(base_ptr.add(mem_offset));

            out.push(new_ptr.cast());
        }

        out
    }

    /// Get the locations of where the [`VMTableDefinition`]s should be stored.
    ///
    /// This function lets us create [`Table`] objects on the host with backing
    /// memory in the VM.
    ///
    /// # Safety
    ///
    /// - `Self.instance_ptr` must point to enough memory that all of
    ///   the offsets in `Self.offsets` point to valid locations in
    ///   memory, i.e. `Self.instance_ptr` must have been allocated by
    ///   `Self::new`.
    unsafe fn table_definition_locations(&self) -> Vec<NonNull<VMTableDefinition>> {
        let num_tables = self.offsets.num_local_tables();
        let num_tables = usize::try_from(num_tables).unwrap();
        let mut out = Vec::with_capacity(num_tables);

        // We need to do some pointer arithmetic now. The unit is `u8`.
        let ptr = self.instance_ptr.cast::<u8>().as_ptr();
        let base_ptr = ptr.add(std::mem::size_of::<Instance>());

        for i in 0..num_tables {
            let table_offset = self
                .offsets
                .vmctx_vmtable_definition(LocalTableIndex::new(i));
            let table_offset = usize::try_from(table_offset).unwrap();

            let new_ptr = NonNull::new_unchecked(base_ptr.add(table_offset));

            out.push(new_ptr.cast());
        }
        out
    }

    /// Finish preparing by writing the [`Instance`] into memory, and
    /// consume this `InstanceAllocator`.
    pub(crate) fn into_vminstance(mut self, instance: Instance) -> VMInstance {
        // Prevent the old state's drop logic from being called as we
        // transition into the new state.
        self.consumed = true;

        unsafe {
            // `instance` is moved at `Self.instance_ptr`. This
            // pointer has been allocated by `Self::allocate_instance`
            // (so by `VMInstance::allocate_instance`).
            ptr::write(self.instance_ptr.as_ptr(), instance);
            // Now `instance_ptr` is correctly initialized!
        }
        let instance = self.instance_ptr;
        let instance_layout = self.instance_layout;

        // This is correct because of the invariants of `Self` and
        // because we write `Instance` to the pointer in this function.
        VMInstance {
            instance,
            instance_layout,
        }
    }

    /// Get the [`VMOffsets`] for the allocated buffer.
    pub(crate) fn offsets(&self) -> &VMOffsets {
        &self.offsets
    }
}