From 5c9d0230232cd6683e915f3c12747d501ee985ec Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 22 Jan 2021 10:51:46 +0100 Subject: [PATCH 1/4] chore(vm) Extract `InstanceRef` into its own module. --- lib/vm/src/instance/mod.rs | 211 +------------------------------------ lib/vm/src/instance/ref.rs | 210 ++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 208 deletions(-) create mode 100644 lib/vm/src/instance/ref.rs diff --git a/lib/vm/src/instance/mod.rs b/lib/vm/src/instance/mod.rs index 44a14cf560e..ed3fea8def0 100644 --- a/lib/vm/src/instance/mod.rs +++ b/lib/vm/src/instance/mod.rs @@ -8,8 +8,10 @@ //! wrapper around an `InstanceRef`. mod allocator; +mod r#ref; pub use allocator::InstanceAllocator; +pub use r#ref::InstanceRef; use crate::export::VMExport; use crate::global::Global; @@ -27,7 +29,6 @@ use crate::{FunctionBodyPtr, ModuleInfo, VMOffsets}; use crate::{VMExportFunction, VMExportGlobal, VMExportMemory, VMExportTable}; use memoffset::offset_of; use more_asserts::assert_lt; -use std::alloc::Layout; use std::any::Any; use std::cell::{Cell, RefCell}; use std::collections::HashMap; @@ -35,7 +36,7 @@ use std::convert::{TryFrom, TryInto}; use std::ffi; use std::fmt; use std::ptr::NonNull; -use std::sync::{atomic, Arc}; +use std::sync::Arc; use std::{mem, ptr, slice}; use wasmer_types::entity::{packed_option::ReservedValue, BoxedSlice, EntityRef, PrimaryMap}; use wasmer_types::{ @@ -767,212 +768,6 @@ impl Instance { } } -/// An `InstanceRef` is responsible to properly deallocate, -/// and to give access to an `Instance`, in such a way that `Instance` -/// is unique, can be shared, safely, across threads, without -/// duplicating the pointer in multiple locations. `InstanceRef` -/// must be the only “owner” of an `Instance`. -/// -/// Consequently, one must not share `Instance` but -/// `InstanceRef`. It acts like an Atomically Reference Counter -/// to `Instance`. In short, `InstanceRef` is roughly a -/// simplified version of `std::sync::Arc`. -/// -/// This `InstanceRef` must be freed with [`InstanceRef::deallocate_instance`] -/// if and only if it has been set correctly. The `Drop` implementation of -/// [`InstanceRef`] calls its `deallocate_instance` method without -/// checking if this property holds, only when `Self.strong` is equal to 1. -/// -/// Note for the curious reader: [`InstanceAllocator::new`] -/// and [`InstanceHandle::new`] will respectively allocate a proper -/// `Instance` and will fill it correctly. -/// -/// A little bit of background: The initial goal was to be able to -/// share an [`Instance`] between an [`InstanceHandle`] and the module -/// exports, so that one can drop a [`InstanceHandle`] but still being -/// able to use the exports properly. -/// -/// This structure has a C representation because `Instance` is -/// dynamically-sized, and the `instance` field must be last. -#[derive(Debug)] -#[repr(C)] -pub struct InstanceRef { - /// Number of `Self` in the nature. It increases when `Self` is - /// cloned, and it decreases when `Self` is dropped. - strong: Arc, - - /// The layout of `Instance` (which can vary). - instance_layout: Layout, - - /// The `Instance` itself. It must be the last field of - /// `InstanceRef` since `Instance` is dyamically-sized. - /// - /// `Instance` must not be dropped manually by Rust, because it's - /// allocated manually with `alloc` and a specific layout (Rust - /// would be able to drop `Instance` itself but it will imply a - /// memory leak because of `alloc`). - /// - /// No one in the code has a copy of the `Instance`'s - /// pointer. `Self` is the only one. - instance: NonNull, -} - -impl InstanceRef { - /// Create a new `InstanceRef`. It allocates nothing. It fills - /// nothing. The `Instance` must be already valid and - /// filled. - /// - /// # Safety - /// - /// `instance` must a non-null, non-dangling, properly aligned, - /// and correctly initialized pointer to `Instance`. See - /// [`InstanceAllocator`] for an example of how to correctly use - /// this API. - pub(self) unsafe fn new(instance: NonNull, instance_layout: Layout) -> Self { - Self { - strong: Arc::new(atomic::AtomicUsize::new(1)), - instance_layout, - instance, - } - } - - /// A soft limit on the amount of references that may be made to an `InstanceRef`. - /// - /// Going above this limit will make the program to panic at exactly - /// `MAX_REFCOUNT` references. - const MAX_REFCOUNT: usize = std::usize::MAX - 1; - - /// Deallocate `Instance`. - /// - /// # Safety - /// - /// `Self.instance` must be correctly set and filled before being - /// dropped and deallocated. - unsafe fn deallocate_instance(&mut self) { - let instance_ptr = self.instance.as_ptr(); - - ptr::drop_in_place(instance_ptr); - std::alloc::dealloc(instance_ptr as *mut u8, self.instance_layout); - } - - /// Get the number of strong references pointing to this - /// `InstanceRef`. - pub fn strong_count(&self) -> usize { - self.strong.load(atomic::Ordering::SeqCst) - } - - /// Get a reference to the `Instance`. - #[inline] - pub(crate) fn as_ref<'a>(&'a self) -> &'a Instance { - // SAFETY: The pointer is properly aligned, it is - // “dereferencable”, it points to an initialized memory of - // `Instance`, and the reference has the lifetime `'a`. - unsafe { self.instance.as_ref() } - } - - #[inline] - unsafe fn as_mut<'a>(&'a mut self) -> &'a mut Instance { - self.instance.as_mut() - } -} - -/// TODO: Review this super carefully. -unsafe impl Send for InstanceRef {} -unsafe impl Sync for InstanceRef {} - -impl Clone for InstanceRef { - /// Makes a clone of `InstanceRef`. - /// - /// This creates another `InstanceRef` using the same - /// `instance` pointer, increasing the strong reference count. - #[inline] - fn clone(&self) -> Self { - // Using a relaxed ordering is alright here, as knowledge of - // the original reference prevents other threads from - // erroneously deleting the object. - // - // As explained in the [Boost documentation][1]: - // - // > Increasing the reference counter can always be done with - // > `memory_order_relaxed`: New references to an object can - // > only be formed from an existing reference, and passing an - // > existing reference from one thread to another must already - // > provide any required synchronization. - // - // [1]: https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html - let old_size = self.strong.fetch_add(1, atomic::Ordering::Relaxed); - - // However we need to guard against massive refcounts in case - // someone is `mem::forget`ing `InstanceRef`. If we - // don't do this the count can overflow and users will - // use-after free. We racily saturate to `isize::MAX` on the - // assumption that there aren't ~2 billion threads - // incrementing the reference count at once. This branch will - // never be taken in any realistic program. - // - // We abort because such a program is incredibly degenerate, - // and we don't care to support it. - - if old_size > Self::MAX_REFCOUNT { - panic!("Too many references of `InstanceRef`"); - } - - Self { - strong: self.strong.clone(), - instance_layout: self.instance_layout, - instance: self.instance.clone(), - } - } -} - -impl PartialEq for InstanceRef { - /// Two `InstanceRef` are equal if and only if - /// `Self.instance` points to the same location. - fn eq(&self, other: &Self) -> bool { - self.instance == other.instance - } -} - -impl Drop for InstanceRef { - /// Drop the `InstanceRef`. - /// - /// This will decrement the strong reference count. If it reaches - /// 1, then the `Self.instance` will be deallocated with - /// `Self::deallocate_instance`. - fn drop(&mut self) { - // Because `fetch_sub` is already atomic, we do not need to - // synchronize with other thread. - if self.strong.fetch_sub(1, atomic::Ordering::Release) != 1 { - return; - } - - // This fence is needed to prevent reordering of use of the data and - // deletion of the data. Because it is marked `Release`, the decreasing - // of the reference count synchronizes with this `Acquire` fence. This - // means that use of the data happens before decreasing the reference - // count, which happens before this fence, which happens before the - // deletion of the data. - // - // As explained in the [Boost documentation][1]: - // - // > It is important to enforce any possible access to the object in one - // > thread (through an existing reference) to *happen before* deleting - // > the object in a different thread. This is achieved by a "release" - // > operation after dropping a reference (any access to the object - // > through this reference must obviously happened before), and an - // > "acquire" operation before deleting the object. - // - // [1]: https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html - atomic::fence(atomic::Ordering::Acquire); - - // Now we can deallocate the instance. Note that we don't - // check the pointer to `Instance` is correctly initialized, - // but the way `InstanceHandle` creates the - // `InstanceRef` ensures that. - unsafe { Self::deallocate_instance(self) }; - } -} - /// A handle holding an `InstanceRef`, which holds an `Instance` /// of a WebAssembly module. /// diff --git a/lib/vm/src/instance/ref.rs b/lib/vm/src/instance/ref.rs new file mode 100644 index 00000000000..f69c940720a --- /dev/null +++ b/lib/vm/src/instance/ref.rs @@ -0,0 +1,210 @@ +use super::Instance; +use std::alloc::Layout; +use std::ptr::{self, NonNull}; +use std::sync::{atomic, Arc}; + +/// An `InstanceRef` is responsible to properly deallocate, +/// and to give access to an `Instance`, in such a way that `Instance` +/// is unique, can be shared, safely, across threads, without +/// duplicating the pointer in multiple locations. `InstanceRef` +/// must be the only “owner” of an `Instance`. +/// +/// Consequently, one must not share `Instance` but +/// `InstanceRef`. It acts like an Atomically Reference Counter +/// to `Instance`. In short, `InstanceRef` is roughly a +/// simplified version of `std::sync::Arc`. +/// +/// This `InstanceRef` must be freed with [`InstanceRef::deallocate_instance`] +/// if and only if it has been set correctly. The `Drop` implementation of +/// [`InstanceRef`] calls its `deallocate_instance` method without +/// checking if this property holds, only when `Self.strong` is equal to 1. +/// +/// Note for the curious reader: [`InstanceAllocator::new`] +/// and [`InstanceHandle::new`] will respectively allocate a proper +/// `Instance` and will fill it correctly. +/// +/// A little bit of background: The initial goal was to be able to +/// share an [`Instance`] between an [`InstanceHandle`] and the module +/// exports, so that one can drop a [`InstanceHandle`] but still being +/// able to use the exports properly. +/// +/// This structure has a C representation because `Instance` is +/// dynamically-sized, and the `instance` field must be last. +#[derive(Debug)] +#[repr(C)] +pub struct InstanceRef { + /// Number of `Self` in the nature. It increases when `Self` is + /// cloned, and it decreases when `Self` is dropped. + strong: Arc, + + /// The layout of `Instance` (which can vary). + instance_layout: Layout, + + /// The `Instance` itself. It must be the last field of + /// `InstanceRef` since `Instance` is dyamically-sized. + /// + /// `Instance` must not be dropped manually by Rust, because it's + /// allocated manually with `alloc` and a specific layout (Rust + /// would be able to drop `Instance` itself but it will imply a + /// memory leak because of `alloc`). + /// + /// No one in the code has a copy of the `Instance`'s + /// pointer. `Self` is the only one. + instance: NonNull, +} + +impl InstanceRef { + /// Create a new `InstanceRef`. It allocates nothing. It fills + /// nothing. The `Instance` must be already valid and + /// filled. + /// + /// # Safety + /// + /// `instance` must a non-null, non-dangling, properly aligned, + /// and correctly initialized pointer to `Instance`. See + /// [`InstanceAllocator`] for an example of how to correctly use + /// this API. + pub(super) unsafe fn new(instance: NonNull, instance_layout: Layout) -> Self { + Self { + strong: Arc::new(atomic::AtomicUsize::new(1)), + instance_layout, + instance, + } + } + + /// A soft limit on the amount of references that may be made to an `InstanceRef`. + /// + /// Going above this limit will make the program to panic at exactly + /// `MAX_REFCOUNT` references. + const MAX_REFCOUNT: usize = std::usize::MAX - 1; + + /// Deallocate `Instance`. + /// + /// # Safety + /// + /// `Self.instance` must be correctly set and filled before being + /// dropped and deallocated. + unsafe fn deallocate_instance(&mut self) { + let instance_ptr = self.instance.as_ptr(); + + ptr::drop_in_place(instance_ptr); + std::alloc::dealloc(instance_ptr as *mut u8, self.instance_layout); + } + + /// Get the number of strong references pointing to this + /// `InstanceRef`. + pub fn strong_count(&self) -> usize { + self.strong.load(atomic::Ordering::SeqCst) + } + + /// Get a reference to the `Instance`. + #[inline] + pub(crate) fn as_ref<'a>(&'a self) -> &'a Instance { + // SAFETY: The pointer is properly aligned, it is + // “dereferencable”, it points to an initialized memory of + // `Instance`, and the reference has the lifetime `'a`. + unsafe { self.instance.as_ref() } + } + + #[inline] + pub(super) unsafe fn as_mut<'a>(&'a mut self) -> &'a mut Instance { + self.instance.as_mut() + } +} + +/// TODO: Review this super carefully. +unsafe impl Send for InstanceRef {} +unsafe impl Sync for InstanceRef {} + +impl Clone for InstanceRef { + /// Makes a clone of `InstanceRef`. + /// + /// This creates another `InstanceRef` using the same + /// `instance` pointer, increasing the strong reference count. + #[inline] + fn clone(&self) -> Self { + // Using a relaxed ordering is alright here, as knowledge of + // the original reference prevents other threads from + // erroneously deleting the object. + // + // As explained in the [Boost documentation][1]: + // + // > Increasing the reference counter can always be done with + // > `memory_order_relaxed`: New references to an object can + // > only be formed from an existing reference, and passing an + // > existing reference from one thread to another must already + // > provide any required synchronization. + // + // [1]: https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html + let old_size = self.strong.fetch_add(1, atomic::Ordering::Relaxed); + + // However we need to guard against massive refcounts in case + // someone is `mem::forget`ing `InstanceRef`. If we + // don't do this the count can overflow and users will + // use-after free. We racily saturate to `isize::MAX` on the + // assumption that there aren't ~2 billion threads + // incrementing the reference count at once. This branch will + // never be taken in any realistic program. + // + // We abort because such a program is incredibly degenerate, + // and we don't care to support it. + + if old_size > Self::MAX_REFCOUNT { + panic!("Too many references of `InstanceRef`"); + } + + Self { + strong: self.strong.clone(), + instance_layout: self.instance_layout, + instance: self.instance.clone(), + } + } +} + +impl PartialEq for InstanceRef { + /// Two `InstanceRef` are equal if and only if + /// `Self.instance` points to the same location. + fn eq(&self, other: &Self) -> bool { + self.instance == other.instance + } +} + +impl Drop for InstanceRef { + /// Drop the `InstanceRef`. + /// + /// This will decrement the strong reference count. If it reaches + /// 1, then the `Self.instance` will be deallocated with + /// `Self::deallocate_instance`. + fn drop(&mut self) { + // Because `fetch_sub` is already atomic, we do not need to + // synchronize with other thread. + if self.strong.fetch_sub(1, atomic::Ordering::Release) != 1 { + return; + } + + // This fence is needed to prevent reordering of use of the data and + // deletion of the data. Because it is marked `Release`, the decreasing + // of the reference count synchronizes with this `Acquire` fence. This + // means that use of the data happens before decreasing the reference + // count, which happens before this fence, which happens before the + // deletion of the data. + // + // As explained in the [Boost documentation][1]: + // + // > It is important to enforce any possible access to the object in one + // > thread (through an existing reference) to *happen before* deleting + // > the object in a different thread. This is achieved by a "release" + // > operation after dropping a reference (any access to the object + // > through this reference must obviously happened before), and an + // > "acquire" operation before deleting the object. + // + // [1]: https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html + atomic::fence(atomic::Ordering::Acquire); + + // Now we can deallocate the instance. Note that we don't + // check the pointer to `Instance` is correctly initialized, + // but the way `InstanceHandle` creates the + // `InstanceRef` ensures that. + unsafe { Self::deallocate_instance(self) }; + } +} From b802d08d79dcebb117e7e51629b6d49966c85f97 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 22 Jan 2021 10:52:49 +0100 Subject: [PATCH 2/4] chore(vm) Remove a warning. --- lib/vm/src/trap/traphandlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vm/src/trap/traphandlers.rs b/lib/vm/src/trap/traphandlers.rs index 24062f8ffdc..be2b68ff5e7 100644 --- a/lib/vm/src/trap/traphandlers.rs +++ b/lib/vm/src/trap/traphandlers.rs @@ -221,7 +221,7 @@ cfg_if::cfg_if! { pub __pc: u64, /* Program counter */ pub __cpsr: u32, /* Current program status register */ pub __pad: u32, /* Same size for 32-bit or 64-bit clients */ - }; + } let cx = &*(cx as *const libc::ucontext_t); let uc_mcontext = mem::transmute::<_, *const __darwin_arm_thread_state64>(&(*cx.uc_mcontext).__ss); From 6fcb7de4c673f41e40475af560e40f502978d580 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 22 Jan 2021 11:04:29 +0100 Subject: [PATCH 3/4] feat(vm) Add `Instance::offsets` and `InstanceHandle::vmoffsets`. The idea is to share the `VMOffsets` of a given `Instance` through `InstanceHandle` for advanced users. See https://github.com/wasmerio/wasmer/issues/2032. --- lib/vm/src/instance/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/vm/src/instance/mod.rs b/lib/vm/src/instance/mod.rs index ed3fea8def0..c20964ba5d4 100644 --- a/lib/vm/src/instance/mod.rs +++ b/lib/vm/src/instance/mod.rs @@ -219,6 +219,11 @@ impl Instance { &*self.module } + /// Offsets in the `vmctx` region. + fn offsets(&self) -> &VMOffsets { + &self.offsets + } + /// Return a pointer to the `VMSharedSignatureIndex`s. fn signature_ids_ptr(&self) -> *mut VMSharedSignatureIndex { unsafe { self.vmctx_plus_offset(self.offsets.vmctx_signature_ids_begin()) } @@ -936,6 +941,13 @@ impl InstanceHandle { self.instance().as_ref().vmctx_ptr() } + /// Return a reference to the `VMOffsets` to get offsets in the + /// `Self::vmctx_ptr` region. Be careful when doing pointer + /// arithmetic! + pub fn vmoffsets(&self) -> &VMOffsets { + self.instance().as_ref().offsets() + } + /// Return a reference-counting pointer to a module. pub fn module(&self) -> &Arc { self.instance().as_ref().module() From 29ae67da996d3c60bd18776f8dffd201ea508e4c Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 22 Jan 2021 11:08:21 +0100 Subject: [PATCH 4/4] doc(changelog) Add #2040. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 774342db242..b94a5e29b12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## **[Unreleased]** ### Added +- [#2040](https://github.com/wasmerio/wasmer/pull/2040) Add `InstanceHandle::vmoffsets` to expose the offsets of the `vmctx` region. - [#2026](https://github.com/wasmerio/wasmer/pull/2010) Expose trap code of a `RuntimeError`, if it's a `Trap`. ### Changed