Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vm) Add Instance::offsets and InstanceHandle::vmoffsets #2040

Merged
merged 4 commits into from
Jan 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
223 changes: 15 additions & 208 deletions lib/vm/src/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,15 +29,14 @@ 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;
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::{
Expand Down Expand Up @@ -218,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()) }
Expand Down Expand Up @@ -767,212 +773,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<atomic::AtomicUsize>,

/// 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<Instance>,
}

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>, 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.
///
Expand Down Expand Up @@ -1141,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!
Comment on lines +944 to +946
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we need to document anything here or if this changes anything in regards to what is public / what we're saying we won't break, but something to keep in mind here!

pub fn vmoffsets(&self) -> &VMOffsets {
self.instance().as_ref().offsets()
}

/// Return a reference-counting pointer to a module.
pub fn module(&self) -> &Arc<ModuleInfo> {
self.instance().as_ref().module()
Expand Down
Loading