diff --git a/lib/engine/src/artifact.rs b/lib/engine/src/artifact.rs index 83294c2a2..f0632a048 100644 --- a/lib/engine/src/artifact.rs +++ b/lib/engine/src/artifact.rs @@ -12,7 +12,7 @@ use wasmer_types::{ SignatureIndex, TableIndex, }; use wasmer_vm::{ - FunctionBodyPtr, InstanceHandle, MemoryStyle, ModuleInfo, TableStyle, UnpreparedInstance, + FunctionBodyPtr, InstanceBuilder, InstanceHandle, MemoryStyle, ModuleInfo, TableStyle, VMSharedSignatureIndex, VMTrampoline, }; @@ -116,7 +116,7 @@ pub trait Artifact: Send + Sync + Upcastable { // Get pointers to where metadata about local tables should live in VM memory. let (unprepared, memory_definition_locations, table_definition_locations) = - UnpreparedInstance::new(&*module); + InstanceBuilder::new(&*module); let finished_memories = tunables .create_memories(&module, self.memory_styles(), &memory_definition_locations) .map_err(InstantiationError::Link)? diff --git a/lib/vm/src/instance/builder.rs b/lib/vm/src/instance/builder.rs new file mode 100644 index 000000000..8ea8bafcd --- /dev/null +++ b/lib/vm/src/instance/builder.rs @@ -0,0 +1,197 @@ +use super::{Instance, InstanceAllocator}; +use crate::vmcontext::{VMMemoryDefinition, VMTableDefinition}; +use crate::{ModuleInfo, VMOffsets}; +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::{LocalMemoryIndex, LocalTableIndex}; + +/// 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. +/// +/// The [`InstanceBuilder::instance_layout`] computes the correct +/// layout to represent the wanted [`Instance`]. +/// +/// Then we use this layout to allocate an empty `Instance` properly. +pub struct InstanceBuilder { + /// The buffer that will contain the [`Instance`] and dynamic fields. + instance_ptr: NonNull, + /// 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 InstanceBuilder { + 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 InstanceBuilder { + /// Allocates instance data for use with [`InstanceHandle::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 [`InstanceHandle::new`]. + pub fn new( + module: &ModuleInfo, + ) -> ( + InstanceBuilder, + Vec>, + Vec>, + ) { + let offsets = VMOffsets::new(mem::size_of::() 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 unprepared = 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 { unprepared.memory_definition_locations() }; + let tables = unsafe { unprepared.table_definition_locations() }; + + (unprepared, 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::(vmctx_size).expect("Failed to create a layout for `VMContext`"); + + let (instance_layout, _offset) = Layout::new::() + .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 + /// - `instance_ptr` must point to enough memory that all of the + /// offsets in `offsets` point to valid locations in memory, + /// i.e. `instance_ptr` must have been allocated by + /// `Self::new`. + unsafe fn memory_definition_locations(&self) -> Vec> { + 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::().as_ptr(); + let base_ptr = ptr.add(mem::size_of::()); + + 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 + /// - `instance_ptr` must point to enough memory that all of the + /// offsets in `offsets` point to valid locations in memory, + /// i.e. `instance_ptr` must have been allocated by + /// `Self::new`. + unsafe fn table_definition_locations(&self) -> Vec> { + 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::().as_ptr(); + let base_ptr = ptr.add(std::mem::size_of::()); + + 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. + pub(crate) fn write_instance(mut self, instance: Instance) -> InstanceAllocator { + // 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 `instance_ptr`. This pointer has + // been allocated by `Self::allocate_instance` (so by + // `InstanceAllocator::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. + unsafe { InstanceAllocator::new(instance, instance_layout) } + } + + /// Get the [`VMOffsets`] for the allocated buffer. + pub(crate) fn offsets(&self) -> &VMOffsets { + &self.offsets + } +} diff --git a/lib/vm/src/instance.rs b/lib/vm/src/instance/mod.rs similarity index 88% rename from lib/vm/src/instance.rs rename to lib/vm/src/instance/mod.rs index 9fc7cc493..db92d9510 100644 --- a/lib/vm/src/instance.rs +++ b/lib/vm/src/instance/mod.rs @@ -7,6 +7,10 @@ //! how it is allocated and deallocated. An `InstanceHandle` is a //! wrapper around an `InstanceAllocator`. +mod builder; + +pub use builder::InstanceBuilder; + use crate::export::VMExport; use crate::global::Global; use crate::imports::Imports; @@ -23,7 +27,7 @@ use crate::{FunctionBodyPtr, ModuleInfo, VMOffsets}; use crate::{VMExportFunction, VMExportGlobal, VMExportMemory, VMExportTable}; use memoffset::offset_of; use more_asserts::assert_lt; -use std::alloc::{self, Layout}; +use std::alloc::Layout; use std::any::Any; use std::cell::{Cell, RefCell}; use std::collections::HashMap; @@ -688,189 +692,7 @@ impl Instance { } } -/// 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. -/// -/// The [`UnpreparedInstance::instance_layout`] computes the correct -/// layout to represent the wanted [`Instance`]. -/// -/// Then we use this layout to allocate an empty `Instance` properly. -pub struct UnpreparedInstance { - /// The buffer that will contain the [`Instance`] and dynamic fields. - instance_ptr: NonNull, - /// 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 UnpreparedInstance { - 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 UnpreparedInstance { - /// Allocates instance data for use with [`InstanceHandle::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 [`InstanceHandle::new`]. - pub fn new( - module: &ModuleInfo, - ) -> ( - UnpreparedInstance, - Vec>, - Vec>, - ) { - let offsets = VMOffsets::new(mem::size_of::<*const u8>() 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 unprepared = Self { - instance_ptr, - instance_layout, - offsets, - consumed: false, - }; - - let memories = unsafe { unprepared.memory_definition_locations() }; - let tables = unsafe { unprepared.table_definition_locations() }; - - (unprepared, 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::(vmctx_size).expect("Failed to create a layout for `VMContext`"); - - let (instance_layout, _offset) = Layout::new::() - .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 - /// - `instance_ptr` must point to enough memory that all of the - /// offsets in `offsets` point to valid locations in memory, - /// i.e. `instance_ptr` must have been allocated by - /// `Self::new`. - unsafe fn memory_definition_locations(&self) -> Vec> { - 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::().as_ptr(); - let base_ptr = ptr.add(mem::size_of::()); - - 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 - /// - `instance_ptr` must point to enough memory that all of the - /// offsets in `offsets` point to valid locations in memory, - /// i.e. `instance_ptr` must have been allocated by - /// `Self::new`. - unsafe fn table_definition_locations(&self) -> Vec> { - 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::().as_ptr(); - let base_ptr = ptr.add(std::mem::size_of::()); - - 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. - pub(crate) fn write_instance(mut self, instance: Instance) -> InstanceAllocator { - unsafe { - // `instance` is moved at `instance_ptr`. This pointer has - // been allocated by `Self::allocate_instance` (so by - // `InstanceAllocator::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; - // prevent the old state's drop logic from being called as we - // transition into the new state. - self.consumed = true; - - // This is correct because of the invariants of `Self` and - // because we write `Instance` to the pointer in this function. - unsafe { InstanceAllocator::new(instance, instance_layout) } - } - - /// Get the [`VMOffsets`] for the allocated buffer. - pub(crate) fn offsets(&self) -> VMOffsets { - self.offsets.clone() - } -} - +/// TODO: update these docs /// An `InstanceAllocator` is responsible to allocate, to deallocate, /// and to give access to an `Instance`, in such a way that `Instance` /// is unique, can be shared, safely, across threads, without @@ -895,7 +717,7 @@ impl UnpreparedInstance { /// [`InstanceAllocator`] calls its `deallocate_instance` method without /// checking if this property holds, only when `Self.strong` is equal to 1. /// -/// Note for the curious reader: [`UnpreparedInstance::new`] +/// Note for the curious reader: [`InstanceBuilder::new`] /// and [`InstanceHandle::new`] will respectively allocate a proper /// `Instance` and will fill it correctly. /// @@ -940,11 +762,11 @@ impl InstanceAllocator { /// /// `instance` must a non-null, non-dangling, properly aligned, /// and correctly initialized pointer to `Instance`. See - /// [`UnpreparedInstance::new`] for an example of how to correctly use + /// [`InstanceBuilder::new`] for an example of how to correctly use /// this API. /// TODO: update docs pub(crate) unsafe fn new(instance: NonNull, instance_layout: Layout) -> Self { - InstanceAllocator { + Self { strong: Arc::new(atomic::AtomicUsize::new(1)), instance_layout, instance, @@ -1123,7 +945,7 @@ impl InstanceHandle { /// all the local memories. #[allow(clippy::too_many_arguments)] pub unsafe fn new( - unprepared: UnpreparedInstance, + unprepared: InstanceBuilder, module: Arc, finished_functions: BoxedSlice, finished_function_call_trampolines: BoxedSlice, @@ -1143,7 +965,7 @@ impl InstanceHandle { let passive_data = RefCell::new(module.passive_data.clone()); let handle = { - let offsets = unprepared.offsets(); + let offsets = unprepared.offsets().clone(); // Create the `Instance`. The unique, the One. let instance = Instance { module, diff --git a/lib/vm/src/lib.rs b/lib/vm/src/lib.rs index 28670f4e4..db1e648be 100644 --- a/lib/vm/src/lib.rs +++ b/lib/vm/src/lib.rs @@ -39,7 +39,7 @@ pub mod libcalls; pub use crate::export::*; pub use crate::global::*; pub use crate::imports::Imports; -pub use crate::instance::{ImportInitializerFuncPtr, InstanceHandle, UnpreparedInstance}; +pub use crate::instance::{ImportInitializerFuncPtr, InstanceBuilder, InstanceHandle}; pub use crate::memory::{LinearMemory, Memory, MemoryError, MemoryStyle}; pub use crate::mmap::Mmap; pub use crate::module::{ExportsIterator, ImportsIterator, ModuleInfo};