Revamp memory management of InstanceHandle (#1624)

* Revamp memory management of `InstanceHandle`

This commit fixes a known but in Wasmtime where an instance could still
be used after it was freed. Unfortunately the fix here is a bit of a
hammer, but it's the best that we can do for now. The changes made in
this commit are:

* A `Store` now stores all `InstanceHandle` objects it ever creates.
  This keeps all instances alive unconditionally (along with all host
  functions and such) until the `Store` is itself dropped. Note that a
  `Store` is reference counted so basically everything has to be dropped
  to drop anything, there's no longer any partial deallocation of instances.

* The `InstanceHandle` type's own reference counting has been removed.
  This is largely redundant with what's already happening in `Store`, so
  there's no need to manage two reference counts.

* Each `InstanceHandle` no longer tracks its dependencies in terms of
  instance handles. This set was actually inaccurate due to dynamic
  updates to tables and such, so we needed to revamp it anyway.

* Initialization of an `InstanceHandle` is now deferred until after
  `InstanceHandle::new`. This allows storing the `InstanceHandle` before
  side-effectful initialization, such as copying element segments or
  running the start function, to ensure that regardless of the result of
  instantiation the underlying `InstanceHandle` is still available to
  persist in storage.

Overall this should fix a known possible way to safely segfault Wasmtime
today (yay!) and it should also fix some flaikness I've seen on CI.
Turns out one of the spec tests
(bulk-memory-operations/partial-init-table-segment.wast) exercises this
functionality and we were hitting sporating use-after-free, but only on
Windows.

* Shuffle some APIs around

* Comment weak cycle
This commit is contained in:
Alex Crichton
2020-04-29 12:47:49 -05:00
committed by GitHub
parent 738e2742da
commit 654e953fbf
21 changed files with 315 additions and 256 deletions

View File

@@ -1,7 +1,8 @@
use crate::trampoline::{generate_global_export, generate_memory_export, generate_table_export}; use crate::trampoline::{
generate_global_export, generate_memory_export, generate_table_export, StoreInstanceHandle,
};
use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val}; use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val};
use crate::Mutability; use crate::{ExternType, GlobalType, MemoryType, Mutability, TableType, ValType};
use crate::{ExternType, GlobalType, MemoryType, TableType, ValType};
use crate::{Func, Store, Trap}; use crate::{Func, Store, Trap};
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use std::slice; use std::slice;
@@ -92,21 +93,20 @@ impl Extern {
pub(crate) fn from_wasmtime_export( pub(crate) fn from_wasmtime_export(
wasmtime_export: wasmtime_runtime::Export, wasmtime_export: wasmtime_runtime::Export,
store: &Store, instance: StoreInstanceHandle,
instance_handle: InstanceHandle,
) -> Extern { ) -> Extern {
match wasmtime_export { match wasmtime_export {
wasmtime_runtime::Export::Function(f) => { wasmtime_runtime::Export::Function(f) => {
Extern::Func(Func::from_wasmtime_function(f, store, instance_handle)) Extern::Func(Func::from_wasmtime_function(f, instance))
} }
wasmtime_runtime::Export::Memory(m) => { wasmtime_runtime::Export::Memory(m) => {
Extern::Memory(Memory::from_wasmtime_memory(m, store, instance_handle)) Extern::Memory(Memory::from_wasmtime_memory(m, instance))
} }
wasmtime_runtime::Export::Global(g) => { wasmtime_runtime::Export::Global(g) => {
Extern::Global(Global::from_wasmtime_global(g, store, instance_handle)) Extern::Global(Global::from_wasmtime_global(g, instance))
} }
wasmtime_runtime::Export::Table(t) => { wasmtime_runtime::Export::Table(t) => {
Extern::Table(Table::from_wasmtime_table(t, store, instance_handle)) Extern::Table(Table::from_wasmtime_table(t, instance))
} }
} }
} }
@@ -114,9 +114,9 @@ impl Extern {
pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool { pub(crate) fn comes_from_same_store(&self, store: &Store) -> bool {
let my_store = match self { let my_store = match self {
Extern::Func(f) => f.store(), Extern::Func(f) => f.store(),
Extern::Global(g) => &g.store, Extern::Global(g) => &g.instance.store,
Extern::Memory(m) => &m.store, Extern::Memory(m) => &m.instance.store,
Extern::Table(t) => &t.store, Extern::Table(t) => &t.instance.store,
}; };
Store::same(my_store, store) Store::same(my_store, store)
} }
@@ -163,9 +163,8 @@ impl From<Table> for Extern {
/// instances are equivalent in their functionality. /// instances are equivalent in their functionality.
#[derive(Clone)] #[derive(Clone)]
pub struct Global { pub struct Global {
store: Store, instance: StoreInstanceHandle,
wasmtime_export: wasmtime_runtime::ExportGlobal, wasmtime_export: wasmtime_runtime::ExportGlobal,
wasmtime_handle: InstanceHandle,
} }
impl Global { impl Global {
@@ -187,11 +186,10 @@ impl Global {
if val.ty() != *ty.content() { if val.ty() != *ty.content() {
bail!("value provided does not match the type of this global"); bail!("value provided does not match the type of this global");
} }
let (wasmtime_handle, wasmtime_export) = generate_global_export(store, &ty, val)?; let (instance, wasmtime_export) = generate_global_export(store, &ty, val)?;
Ok(Global { Ok(Global {
store: store.clone(), instance,
wasmtime_export, wasmtime_export,
wasmtime_handle,
}) })
} }
@@ -246,7 +244,7 @@ impl Global {
if val.ty() != ty { if val.ty() != ty {
bail!("global of type {:?} cannot be set to {:?}", ty, val.ty()); bail!("global of type {:?} cannot be set to {:?}", ty, val.ty());
} }
if !val.comes_from_same_store(&self.store) { if !val.comes_from_same_store(&self.instance.store) {
bail!("cross-`Store` values are not supported"); bail!("cross-`Store` values are not supported");
} }
unsafe { unsafe {
@@ -264,13 +262,11 @@ impl Global {
pub(crate) fn from_wasmtime_global( pub(crate) fn from_wasmtime_global(
wasmtime_export: wasmtime_runtime::ExportGlobal, wasmtime_export: wasmtime_runtime::ExportGlobal,
store: &Store, instance: StoreInstanceHandle,
wasmtime_handle: InstanceHandle,
) -> Global { ) -> Global {
Global { Global {
store: store.clone(), instance,
wasmtime_export, wasmtime_export,
wasmtime_handle,
} }
} }
} }
@@ -292,18 +288,17 @@ impl Global {
/// instances are equivalent in their functionality. /// instances are equivalent in their functionality.
#[derive(Clone)] #[derive(Clone)]
pub struct Table { pub struct Table {
store: Store, instance: StoreInstanceHandle,
wasmtime_handle: InstanceHandle,
wasmtime_export: wasmtime_runtime::ExportTable, wasmtime_export: wasmtime_runtime::ExportTable,
} }
fn set_table_item( fn set_table_item(
handle: &InstanceHandle, instance: &InstanceHandle,
table_index: wasm::DefinedTableIndex, table_index: wasm::DefinedTableIndex,
item_index: u32, item_index: u32,
item: wasmtime_runtime::VMCallerCheckedAnyfunc, item: wasmtime_runtime::VMCallerCheckedAnyfunc,
) -> Result<()> { ) -> Result<()> {
handle instance
.table_set(table_index, item_index, item) .table_set(table_index, item_index, item)
.map_err(|()| anyhow!("table element index out of bounds")) .map_err(|()| anyhow!("table element index out of bounds"))
} }
@@ -322,18 +317,17 @@ impl Table {
/// Returns an error if `init` does not match the element type of the table. /// Returns an error if `init` does not match the element type of the table.
pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Table> { pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Table> {
let item = into_checked_anyfunc(init, store)?; let item = into_checked_anyfunc(init, store)?;
let (wasmtime_handle, wasmtime_export) = generate_table_export(store, &ty)?; let (instance, wasmtime_export) = generate_table_export(store, &ty)?;
// Initialize entries with the init value. // Initialize entries with the init value.
let definition = unsafe { &*wasmtime_export.definition }; let definition = unsafe { &*wasmtime_export.definition };
let index = wasmtime_handle.table_index(definition); let index = instance.table_index(definition);
for i in 0..definition.current_elements { for i in 0..definition.current_elements {
set_table_item(&wasmtime_handle, index, i, item.clone())?; set_table_item(&instance, index, i, item.clone())?;
} }
Ok(Table { Ok(Table {
store: store.clone(), instance,
wasmtime_handle,
wasmtime_export, wasmtime_export,
}) })
} }
@@ -345,10 +339,7 @@ impl Table {
} }
fn wasmtime_table_index(&self) -> wasm::DefinedTableIndex { fn wasmtime_table_index(&self) -> wasm::DefinedTableIndex {
unsafe { unsafe { self.instance.table_index(&*self.wasmtime_export.definition) }
self.wasmtime_handle
.table_index(&*self.wasmtime_export.definition)
}
} }
/// Returns the table element value at `index`. /// Returns the table element value at `index`.
@@ -356,8 +347,8 @@ impl Table {
/// Returns `None` if `index` is out of bounds. /// Returns `None` if `index` is out of bounds.
pub fn get(&self, index: u32) -> Option<Val> { pub fn get(&self, index: u32) -> Option<Val> {
let table_index = self.wasmtime_table_index(); let table_index = self.wasmtime_table_index();
let item = self.wasmtime_handle.table_get(table_index, index)?; let item = self.instance.table_get(table_index, index)?;
Some(from_checked_anyfunc(item, &self.store)) Some(from_checked_anyfunc(item, &self.instance.store))
} }
/// Writes the `val` provided into `index` within this table. /// Writes the `val` provided into `index` within this table.
@@ -368,8 +359,8 @@ impl Table {
/// the right type to be stored in this table. /// the right type to be stored in this table.
pub fn set(&self, index: u32, val: Val) -> Result<()> { pub fn set(&self, index: u32, val: Val) -> Result<()> {
let table_index = self.wasmtime_table_index(); let table_index = self.wasmtime_table_index();
let item = into_checked_anyfunc(val, &self.store)?; let item = into_checked_anyfunc(val, &self.instance.store)?;
set_table_item(&self.wasmtime_handle, table_index, index, item) set_table_item(&self.instance, table_index, index, item)
} }
/// Returns the current size of this table. /// Returns the current size of this table.
@@ -387,12 +378,11 @@ impl Table {
/// error if `init` is not of the right type. /// error if `init` is not of the right type.
pub fn grow(&self, delta: u32, init: Val) -> Result<u32> { pub fn grow(&self, delta: u32, init: Val) -> Result<u32> {
let index = self.wasmtime_table_index(); let index = self.wasmtime_table_index();
let item = into_checked_anyfunc(init, &self.store)?; let item = into_checked_anyfunc(init, &self.instance.store)?;
if let Some(len) = self.wasmtime_handle.clone().table_grow(index, delta) { if let Some(len) = self.instance.table_grow(index, delta) {
let mut wasmtime_handle = self.wasmtime_handle.clone();
for i in 0..delta { for i in 0..delta {
let i = len - (delta - i); let i = len - (delta - i);
set_table_item(&mut wasmtime_handle, index, i, item.clone())?; set_table_item(&self.instance, index, i, item.clone())?;
} }
Ok(len) Ok(len)
} else { } else {
@@ -414,7 +404,7 @@ impl Table {
src_index: u32, src_index: u32,
len: u32, len: u32,
) -> Result<()> { ) -> Result<()> {
if !Store::same(&dst_table.store, &src_table.store) { if !Store::same(&dst_table.instance.store, &src_table.instance.store) {
bail!("cross-`Store` table copies are not supported"); bail!("cross-`Store` table copies are not supported");
} }
@@ -423,10 +413,10 @@ impl Table {
// come from different modules. // come from different modules.
let dst_table_index = dst_table.wasmtime_table_index(); let dst_table_index = dst_table.wasmtime_table_index();
let dst_table = dst_table.wasmtime_handle.get_defined_table(dst_table_index); let dst_table = dst_table.instance.get_defined_table(dst_table_index);
let src_table_index = src_table.wasmtime_table_index(); let src_table_index = src_table.wasmtime_table_index();
let src_table = src_table.wasmtime_handle.get_defined_table(src_table_index); let src_table = src_table.instance.get_defined_table(src_table_index);
runtime::Table::copy(dst_table, src_table, dst_index, src_index, len) runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)
.map_err(Trap::from_jit)?; .map_err(Trap::from_jit)?;
@@ -435,12 +425,10 @@ impl Table {
pub(crate) fn from_wasmtime_table( pub(crate) fn from_wasmtime_table(
wasmtime_export: wasmtime_runtime::ExportTable, wasmtime_export: wasmtime_runtime::ExportTable,
store: &Store, instance: StoreInstanceHandle,
wasmtime_handle: wasmtime_runtime::InstanceHandle,
) -> Table { ) -> Table {
Table { Table {
store: store.clone(), instance,
wasmtime_handle,
wasmtime_export, wasmtime_export,
} }
} }
@@ -654,8 +642,7 @@ impl Table {
/// [open an issue]: https://github.com/bytecodealliance/wasmtime/issues/new /// [open an issue]: https://github.com/bytecodealliance/wasmtime/issues/new
#[derive(Clone)] #[derive(Clone)]
pub struct Memory { pub struct Memory {
store: Store, instance: StoreInstanceHandle,
wasmtime_handle: InstanceHandle,
wasmtime_export: wasmtime_runtime::ExportMemory, wasmtime_export: wasmtime_runtime::ExportMemory,
} }
@@ -683,11 +670,10 @@ impl Memory {
/// # } /// # }
/// ``` /// ```
pub fn new(store: &Store, ty: MemoryType) -> Memory { pub fn new(store: &Store, ty: MemoryType) -> Memory {
let (wasmtime_handle, wasmtime_export) = let (instance, wasmtime_export) =
generate_memory_export(store, &ty).expect("generated memory"); generate_memory_export(store, &ty).expect("generated memory");
Memory { Memory {
store: store.clone(), instance,
wasmtime_handle,
wasmtime_export, wasmtime_export,
} }
} }
@@ -827,22 +813,19 @@ impl Memory {
/// ``` /// ```
pub fn grow(&self, delta: u32) -> Result<u32> { pub fn grow(&self, delta: u32) -> Result<u32> {
let index = self let index = self
.wasmtime_handle .instance
.memory_index(unsafe { &*self.wasmtime_export.definition }); .memory_index(unsafe { &*self.wasmtime_export.definition });
self.wasmtime_handle self.instance
.clone()
.memory_grow(index, delta) .memory_grow(index, delta)
.ok_or_else(|| anyhow!("failed to grow memory")) .ok_or_else(|| anyhow!("failed to grow memory"))
} }
pub(crate) fn from_wasmtime_memory( pub(crate) fn from_wasmtime_memory(
wasmtime_export: wasmtime_runtime::ExportMemory, wasmtime_export: wasmtime_runtime::ExportMemory,
store: &Store, instance: StoreInstanceHandle,
wasmtime_handle: wasmtime_runtime::InstanceHandle,
) -> Memory { ) -> Memory {
Memory { Memory {
store: store.clone(), instance,
wasmtime_handle,
wasmtime_export, wasmtime_export,
} }
} }

View File

@@ -1,3 +1,5 @@
use crate::runtime::StoreInner;
use crate::trampoline::StoreInstanceHandle;
use crate::{Extern, FuncType, Memory, Store, Trap, Val, ValType}; use crate::{Extern, FuncType, Memory, Store, Trap, Val, ValType};
use anyhow::{bail, ensure, Context as _, Result}; use anyhow::{bail, ensure, Context as _, Result};
use std::cmp::max; use std::cmp::max;
@@ -5,6 +7,7 @@ use std::fmt;
use std::mem; use std::mem;
use std::panic::{self, AssertUnwindSafe}; use std::panic::{self, AssertUnwindSafe};
use std::ptr; use std::ptr;
use std::rc::Weak;
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody}; use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
use wasmtime_runtime::{ExportFunction, VMTrampoline}; use wasmtime_runtime::{ExportFunction, VMTrampoline};
@@ -135,8 +138,7 @@ use wasmtime_runtime::{ExportFunction, VMTrampoline};
/// ``` /// ```
#[derive(Clone)] #[derive(Clone)]
pub struct Func { pub struct Func {
store: Store, instance: StoreInstanceHandle,
instance: InstanceHandle,
export: ExportFunction, export: ExportFunction,
trampoline: VMTrampoline, trampoline: VMTrampoline,
} }
@@ -176,7 +178,7 @@ macro_rules! getters {
// of the closure. Pass the export in so that we can call it. // of the closure. Pass the export in so that we can call it.
let instance = self.instance.clone(); let instance = self.instance.clone();
let export = self.export.clone(); let export = self.export.clone();
let max_wasm_stack = self.store.engine().config().max_wasm_stack; let max_wasm_stack = self.store().engine().config().max_wasm_stack;
// ... and then once we've passed the typechecks we can hand out our // ... and then once we've passed the typechecks we can hand out our
// object since our `transmute` below should be safe! // object since our `transmute` below should be safe!
@@ -236,7 +238,7 @@ impl Func {
ty: FuncType, ty: FuncType,
func: impl Fn(Caller<'_>, &[Val], &mut [Val]) -> Result<(), Trap> + 'static, func: impl Fn(Caller<'_>, &[Val], &mut [Val]) -> Result<(), Trap> + 'static,
) -> Self { ) -> Self {
let store_clone = store.clone(); let store_weak = store.weak();
let ty_clone = ty.clone(); let ty_clone = ty.clone();
// Create our actual trampoline function which translates from a bunch // Create our actual trampoline function which translates from a bunch
@@ -255,7 +257,7 @@ impl Func {
let mut returns = vec![Val::null(); ty_clone.results().len()]; let mut returns = vec![Val::null(); ty_clone.results().len()];
func( func(
Caller { Caller {
store: &store_clone, store: &store_weak,
caller_vmctx, caller_vmctx,
}, },
&args, &args,
@@ -281,7 +283,6 @@ impl Func {
let (instance, export, trampoline) = let (instance, export, trampoline) =
crate::trampoline::generate_func_export(&ty, func, store).expect("generated func"); crate::trampoline::generate_func_export(&ty, func, store).expect("generated func");
Func { Func {
store: store.clone(),
instance, instance,
export, export,
trampoline, trampoline,
@@ -483,6 +484,7 @@ impl Func {
// Signatures should always be registered in the store's registry of // Signatures should always be registered in the store's registry of
// shared signatures, so we should be able to unwrap safely here. // shared signatures, so we should be able to unwrap safely here.
let sig = self let sig = self
.instance
.store .store
.compiler() .compiler()
.signatures() .signatures()
@@ -498,6 +500,7 @@ impl Func {
/// Returns the number of parameters that this function takes. /// Returns the number of parameters that this function takes.
pub fn param_arity(&self) -> usize { pub fn param_arity(&self) -> usize {
let sig = self let sig = self
.instance
.store .store
.compiler() .compiler()
.signatures() .signatures()
@@ -509,6 +512,7 @@ impl Func {
/// Returns the number of results this function produces. /// Returns the number of results this function produces.
pub fn result_arity(&self) -> usize { pub fn result_arity(&self) -> usize {
let sig = self let sig = self
.instance
.store .store
.compiler() .compiler()
.signatures() .signatures()
@@ -549,7 +553,7 @@ impl Func {
if arg.ty() != *ty { if arg.ty() != *ty {
bail!("argument type mismatch"); bail!("argument type mismatch");
} }
if !arg.comes_from_same_store(&self.store) { if !arg.comes_from_same_store(&self.instance.store) {
bail!("cross-`Store` values are not currently supported"); bail!("cross-`Store` values are not currently supported");
} }
unsafe { unsafe {
@@ -561,7 +565,7 @@ impl Func {
if let Err(error) = unsafe { if let Err(error) = unsafe {
wasmtime_runtime::catch_traps( wasmtime_runtime::catch_traps(
self.export.vmctx, self.export.vmctx,
self.store.engine().config().max_wasm_stack, self.instance.store.engine().config().max_wasm_stack,
|| { || {
(self.trampoline)( (self.trampoline)(
self.export.vmctx, self.export.vmctx,
@@ -593,8 +597,7 @@ impl Func {
pub(crate) fn from_wasmtime_function( pub(crate) fn from_wasmtime_function(
export: wasmtime_runtime::ExportFunction, export: wasmtime_runtime::ExportFunction,
store: &Store, instance: StoreInstanceHandle,
instance: InstanceHandle,
) -> Self { ) -> Self {
// Each function signature in a module should have a trampoline stored // Each function signature in a module should have a trampoline stored
// on that module as well, so unwrap the result here since otherwise // on that module as well, so unwrap the result here since otherwise
@@ -607,7 +610,6 @@ impl Func {
instance, instance,
export, export,
trampoline, trampoline,
store: store.clone(),
} }
} }
@@ -734,7 +736,7 @@ impl Func {
} }
pub(crate) fn store(&self) -> &Store { pub(crate) fn store(&self) -> &Store {
&self.store &self.instance.store
} }
} }
@@ -970,7 +972,23 @@ pub trait IntoFunc<Params, Results> {
/// the caller's memory until interface types has been fully standardized and /// the caller's memory until interface types has been fully standardized and
/// implemented. /// implemented.
pub struct Caller<'a> { pub struct Caller<'a> {
store: &'a Store, // Note that this is a `Weak` pointer instead of a `&'a Store`,
// intentionally so. This allows us to break an `Rc` cycle which would
// otherwise look like this:
//
// * A `Store` object ...
// * ... owns all `InstanceHandle` objects ...
// * ... which are created in `Func::wrap` with custom host data ...
// * ... where the custom host data needs to point to `Store` to be stored
// here
//
// This `Rc` cycle means that we would never actually reclaim any memory or
// deallocate any instances. To break this cycle we use a weak pointer here
// which points back to `Store`. A `Caller` should only ever be usable
// when the original `Store` is alive, however, so this should always be an
// upgrade-able pointer. Alternative solutions or other ideas to break this
// cycle would be most welcome!
store: &'a Weak<StoreInner>,
caller_vmctx: *mut VMContext, caller_vmctx: *mut VMContext,
} }
@@ -1004,7 +1022,14 @@ impl Caller<'_> {
Some(Export::Memory(m)) => m, Some(Export::Memory(m)) => m,
_ => return None, _ => return None,
}; };
let mem = Memory::from_wasmtime_memory(export, self.store, instance); // Our `Weak` pointer is used only to break a cycle where `Store`
// stores instance handles which have this weak pointer as their
// custom host data. This function should only be invoke-able while
// the `Store` is active, so this upgrade should always succeed.
debug_assert!(self.store.upgrade().is_some());
let handle =
Store::from_inner(self.store.upgrade()?).existing_instance_handle(instance);
let mem = Memory::from_wasmtime_memory(export, handle);
Some(Extern::Memory(mem)) Some(Extern::Memory(mem))
} }
} }
@@ -1057,8 +1082,8 @@ macro_rules! impl_into_func {
// Double-check ourselves in debug mode, but we control // Double-check ourselves in debug mode, but we control
// the `Any` here so an unsafe downcast should also // the `Any` here so an unsafe downcast should also
// work. // work.
debug_assert!(state.is::<(F, Store)>()); debug_assert!(state.is::<(F, Weak<StoreInner>)>());
let (func, store) = &*(state as *const _ as *const (F, Store)); let (func, store) = &*(state as *const _ as *const (F, Weak<StoreInner>));
panic::catch_unwind(AssertUnwindSafe(|| { panic::catch_unwind(AssertUnwindSafe(|| {
func( func(
Caller { store, caller_vmctx }, Caller { store, caller_vmctx },
@@ -1102,7 +1127,7 @@ macro_rules! impl_into_func {
let mut ret = Vec::new(); let mut ret = Vec::new();
R::push(&mut ret); R::push(&mut ret);
let ty = FuncType::new(_args.into(), ret.into()); let ty = FuncType::new(_args.into(), ret.into());
let store_clone = store.clone(); let store_weak = store.weak();
unsafe { unsafe {
let trampoline = trampoline::<$($args,)* R>; let trampoline = trampoline::<$($args,)* R>;
let (instance, export) = crate::trampoline::generate_raw_func_export( let (instance, export) = crate::trampoline::generate_raw_func_export(
@@ -1113,11 +1138,10 @@ macro_rules! impl_into_func {
), ),
trampoline, trampoline,
store, store,
Box::new((self, store_clone)), Box::new((self, store_weak)),
) )
.expect("failed to generate export"); .expect("failed to generate export");
Func { Func {
store: store.clone(),
instance, instance,
export, export,
trampoline, trampoline,

View File

@@ -1,12 +1,9 @@
use crate::externals::{Export, Extern, Global, Memory, Table}; use crate::trampoline::StoreInstanceHandle;
use crate::func::Func; use crate::{Export, Extern, Func, Global, Memory, Module, Store, Table, Trap};
use crate::module::Module;
use crate::runtime::{Config, Store};
use crate::trap::Trap;
use anyhow::{bail, Error, Result}; use anyhow::{bail, Error, Result};
use std::any::Any; use std::any::Any;
use wasmtime_jit::{CompiledModule, Resolver}; use wasmtime_jit::{CompiledModule, Resolver};
use wasmtime_runtime::{InstanceHandle, InstantiationError, SignatureRegistry}; use wasmtime_runtime::{InstantiationError, SignatureRegistry};
struct SimpleResolver<'a> { struct SimpleResolver<'a> {
imports: &'a [Extern], imports: &'a [Extern],
@@ -21,22 +18,35 @@ impl Resolver for SimpleResolver<'_> {
} }
fn instantiate( fn instantiate(
config: &Config, store: &Store,
compiled_module: &CompiledModule, compiled_module: &CompiledModule,
imports: &[Extern], imports: &[Extern],
sig_registry: &SignatureRegistry, sig_registry: &SignatureRegistry,
host: Box<dyn Any>, host: Box<dyn Any>,
) -> Result<InstanceHandle, Error> { ) -> Result<StoreInstanceHandle, Error> {
let mut resolver = SimpleResolver { imports }; let mut resolver = SimpleResolver { imports };
unsafe { unsafe {
let instance = compiled_module let config = store.engine().config();
.instantiate( let instance = compiled_module.instantiate(
config.validating_config.operator_config.enable_bulk_memory,
&mut resolver, &mut resolver,
sig_registry, sig_registry,
config.memory_creator.as_ref().map(|a| a as _), config.memory_creator.as_ref().map(|a| a as _),
config.max_wasm_stack,
host, host,
)?;
// After we've created the `InstanceHandle` we still need to run
// initialization to set up data/elements/etc. We do this after adding
// the `InstanceHandle` to the store though. This is required for safety
// because the start function (for example) may trap, but element
// initializers may have run which placed elements into other instance's
// tables. This means that from this point on, regardless of whether
// initialization is successful, we need to keep the instance alive.
let instance = store.add_instance(instance);
instance
.initialize(
config.validating_config.operator_config.enable_bulk_memory,
config.max_wasm_stack,
&compiled_module.data_initializers(),
) )
.map_err(|e| -> Error { .map_err(|e| -> Error {
match e { match e {
@@ -68,7 +78,7 @@ fn instantiate(
/// call any code or execute anything! /// call any code or execute anything!
#[derive(Clone)] #[derive(Clone)]
pub struct Instance { pub struct Instance {
pub(crate) instance_handle: InstanceHandle, pub(crate) handle: StoreInstanceHandle,
module: Module, module: Module,
} }
@@ -137,9 +147,8 @@ impl Instance {
} }
let info = module.register_frame_info(); let info = module.register_frame_info();
let config = store.engine().config(); let handle = instantiate(
let instance_handle = instantiate( store,
config,
module.compiled_module(), module.compiled_module(),
imports, imports,
store.compiler().signatures(), store.compiler().signatures(),
@@ -147,7 +156,7 @@ impl Instance {
)?; )?;
Ok(Instance { Ok(Instance {
instance_handle, handle,
module: module.clone(), module: module.clone(),
}) })
} }
@@ -164,13 +173,9 @@ impl Instance {
pub fn exports<'instance>( pub fn exports<'instance>(
&'instance self, &'instance self,
) -> impl ExactSizeIterator<Item = Export<'instance>> + 'instance { ) -> impl ExactSizeIterator<Item = Export<'instance>> + 'instance {
let instance_handle = &self.instance_handle; self.handle.exports().map(move |(name, entity_index)| {
let store = self.module.store(); let export = self.handle.lookup_by_declaration(entity_index);
self.instance_handle let extern_ = Extern::from_wasmtime_export(export, self.handle.clone());
.exports()
.map(move |(name, entity_index)| {
let export = instance_handle.lookup_by_declaration(entity_index);
let extern_ = Extern::from_wasmtime_export(export, store, instance_handle.clone());
Export::new(name, extern_) Export::new(name, extern_)
}) })
} }
@@ -182,12 +187,8 @@ impl Instance {
/// ///
/// Returns `None` if there was no export named `name`. /// Returns `None` if there was no export named `name`.
pub fn get_export(&self, name: &str) -> Option<Extern> { pub fn get_export(&self, name: &str) -> Option<Extern> {
let export = self.instance_handle.lookup(&name)?; let export = self.handle.lookup(&name)?;
Some(Extern::from_wasmtime_export( Some(Extern::from_wasmtime_export(export, self.handle.clone()))
export,
self.module.store(),
self.instance_handle.clone(),
))
} }
/// Looks up an exported [`Func`] value by name. /// Looks up an exported [`Func`] value by name.
@@ -221,9 +222,4 @@ impl Instance {
pub fn get_global(&self, name: &str) -> Option<Global> { pub fn get_global(&self, name: &str) -> Option<Global> {
self.get_export(name)?.into_global() self.get_export(name)?.into_global()
} }
#[doc(hidden)]
pub fn handle(&self) -> &InstanceHandle {
&self.instance_handle
}
} }

View File

@@ -1,18 +1,18 @@
use crate::externals::MemoryCreator; use crate::externals::MemoryCreator;
use crate::trampoline::MemoryCreatorProxy; use crate::trampoline::{MemoryCreatorProxy, StoreInstanceHandle};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::cell::RefCell; use std::cell::RefCell;
use std::cmp::min; use std::cmp::min;
use std::fmt; use std::fmt;
use std::path::Path; use std::path::Path;
use std::rc::Rc; use std::rc::{Rc, Weak};
use std::sync::Arc; use std::sync::Arc;
use wasmparser::{OperatorValidatorConfig, ValidatingParserConfig}; use wasmparser::{OperatorValidatorConfig, ValidatingParserConfig};
use wasmtime_environ::settings::{self, Configurable}; use wasmtime_environ::settings::{self, Configurable};
use wasmtime_environ::{CacheConfig, Tunables}; use wasmtime_environ::{CacheConfig, Tunables};
use wasmtime_jit::{native, CompilationStrategy, Compiler}; use wasmtime_jit::{native, CompilationStrategy, Compiler};
use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent}; use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent};
use wasmtime_runtime::{debug_builtins, RuntimeMemoryCreator, VMInterrupts}; use wasmtime_runtime::{debug_builtins, InstanceHandle, RuntimeMemoryCreator, VMInterrupts};
// Runtime Environment // Runtime Environment
@@ -550,13 +550,13 @@ impl Engine {
/// ocnfiguration (see [`Config`] for more information). /// ocnfiguration (see [`Config`] for more information).
#[derive(Clone)] #[derive(Clone)]
pub struct Store { pub struct Store {
// FIXME(#777) should be `Arc` and this type should be thread-safe
inner: Rc<StoreInner>, inner: Rc<StoreInner>,
} }
struct StoreInner { pub(crate) struct StoreInner {
engine: Engine, engine: Engine,
compiler: RefCell<Compiler>, compiler: RefCell<Compiler>,
instances: RefCell<Vec<InstanceHandle>>,
} }
impl Store { impl Store {
@@ -573,10 +573,15 @@ impl Store {
inner: Rc::new(StoreInner { inner: Rc::new(StoreInner {
engine: engine.clone(), engine: engine.clone(),
compiler: RefCell::new(compiler), compiler: RefCell::new(compiler),
instances: RefCell::new(Vec::new()),
}), }),
} }
} }
pub(crate) fn from_inner(inner: Rc<StoreInner>) -> Store {
Store { inner }
}
/// Returns the [`Engine`] that this store is associated with. /// Returns the [`Engine`] that this store is associated with.
pub fn engine(&self) -> &Engine { pub fn engine(&self) -> &Engine {
&self.inner.engine &self.inner.engine
@@ -595,6 +600,31 @@ impl Store {
self.inner.compiler.borrow_mut() self.inner.compiler.borrow_mut()
} }
pub(crate) unsafe fn add_instance(&self, handle: InstanceHandle) -> StoreInstanceHandle {
self.inner.instances.borrow_mut().push(handle.clone());
StoreInstanceHandle {
store: self.clone(),
handle,
}
}
pub(crate) fn existing_instance_handle(&self, handle: InstanceHandle) -> StoreInstanceHandle {
debug_assert!(self
.inner
.instances
.borrow()
.iter()
.any(|i| i.vmctx_ptr() == handle.vmctx_ptr()));
StoreInstanceHandle {
store: self.clone(),
handle,
}
}
pub(crate) fn weak(&self) -> Weak<StoreInner> {
Rc::downgrade(&self.inner)
}
/// Returns whether the stores `a` and `b` refer to the same underlying /// Returns whether the stores `a` and `b` refer to the same underlying
/// `Store`. /// `Store`.
/// ///
@@ -703,6 +733,16 @@ impl Default for Store {
} }
} }
impl Drop for StoreInner {
fn drop(&mut self) {
for instance in self.instances.get_mut().iter() {
unsafe {
instance.dealloc();
}
}
}
}
/// A threadsafe handle used to interrupt instances executing within a /// A threadsafe handle used to interrupt instances executing within a
/// particular `Store`. /// particular `Store`.
/// ///

View File

@@ -1,9 +1,10 @@
//! Support for a calling of an imported function. //! Support for a calling of an imported function.
use crate::runtime::Store; use crate::trampoline::StoreInstanceHandle;
use crate::Store;
use anyhow::Result; use anyhow::Result;
use std::any::Any; use std::any::Any;
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::DefinedFuncIndex; use wasmtime_environ::wasm::DefinedFuncIndex;
@@ -18,15 +19,13 @@ pub(crate) fn create_handle(
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>, trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
state: Box<dyn Any>, state: Box<dyn Any>,
) -> Result<InstanceHandle> { ) -> Result<StoreInstanceHandle> {
let imports = Imports::new( let imports = Imports::new(
HashSet::new(),
PrimaryMap::new(), PrimaryMap::new(),
PrimaryMap::new(), PrimaryMap::new(),
PrimaryMap::new(), PrimaryMap::new(),
PrimaryMap::new(), PrimaryMap::new(),
); );
let data_initializers = Vec::new();
// Compute indices into the shared signature table. // Compute indices into the shared signature table.
let signatures = module let signatures = module
@@ -37,24 +36,17 @@ pub(crate) fn create_handle(
.collect::<PrimaryMap<_, _>>(); .collect::<PrimaryMap<_, _>>();
unsafe { unsafe {
Ok(InstanceHandle::new( let handle = InstanceHandle::new(
Arc::new(module), Arc::new(module),
finished_functions.into_boxed_slice(), finished_functions.into_boxed_slice(),
trampolines, trampolines,
imports, imports,
store.memory_creator(), store.memory_creator(),
&data_initializers,
signatures.into_boxed_slice(), signatures.into_boxed_slice(),
None, None,
store
.engine()
.config()
.validating_config
.operator_config
.enable_bulk_memory,
state, state,
store.compiler().interrupts().clone(), store.compiler().interrupts().clone(),
store.engine().config().max_wasm_stack, )?;
)?) Ok(store.add_instance(handle))
} }
} }

View File

@@ -1,6 +1,7 @@
//! Support for a calling of an imported function. //! Support for a calling of an imported function.
use super::create_handle::create_handle; use super::create_handle::create_handle;
use crate::trampoline::StoreInstanceHandle;
use crate::{FuncType, Store, Trap}; use crate::{FuncType, Store, Trap};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::any::Any; use std::any::Any;
@@ -203,7 +204,7 @@ pub fn create_handle_with_function(
ft: &FuncType, ft: &FuncType,
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>, func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>,
store: &Store, store: &Store,
) -> Result<(InstanceHandle, VMTrampoline)> { ) -> Result<(StoreInstanceHandle, VMTrampoline)> {
let isa = { let isa = {
let isa_builder = native::builder(); let isa_builder = native::builder();
let flag_builder = settings::builder(); let flag_builder = settings::builder();
@@ -267,7 +268,7 @@ pub unsafe fn create_handle_with_raw_function(
trampoline: VMTrampoline, trampoline: VMTrampoline,
store: &Store, store: &Store,
state: Box<dyn Any>, state: Box<dyn Any>,
) -> Result<InstanceHandle> { ) -> Result<StoreInstanceHandle> {
let isa = { let isa = {
let isa_builder = native::builder(); let isa_builder = native::builder();
let flag_builder = settings::builder(); let flag_builder = settings::builder();

View File

@@ -1,12 +1,12 @@
use super::create_handle::create_handle; use super::create_handle::create_handle;
use crate::trampoline::StoreInstanceHandle;
use crate::Store; use crate::Store;
use crate::{GlobalType, Mutability, Val}; use crate::{GlobalType, Mutability, Val};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::{wasm, EntityIndex, Module}; use wasmtime_environ::{wasm, EntityIndex, Module};
use wasmtime_runtime::InstanceHandle;
pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<InstanceHandle> { pub fn create_global(store: &Store, gt: &GlobalType, val: Val) -> Result<StoreInstanceHandle> {
let global = wasm::Global { let global = wasm::Global {
ty: match gt.content().get_wasmtime_type() { ty: match gt.content().get_wasmtime_type() {
Some(t) => t, Some(t) => t,

View File

@@ -1,17 +1,19 @@
use super::create_handle::create_handle; use super::create_handle::create_handle;
use crate::externals::{LinearMemory, MemoryCreator}; use crate::externals::{LinearMemory, MemoryCreator};
use crate::trampoline::StoreInstanceHandle;
use crate::Store; use crate::Store;
use crate::{Limits, MemoryType}; use crate::{Limits, MemoryType};
use anyhow::Result; use anyhow::Result;
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::{wasm, EntityIndex, MemoryPlan, Module, WASM_PAGE_SIZE}; use wasmtime_environ::{wasm, EntityIndex, MemoryPlan, Module, WASM_PAGE_SIZE};
use wasmtime_runtime::{ use wasmtime_runtime::{RuntimeLinearMemory, RuntimeMemoryCreator, VMMemoryDefinition};
InstanceHandle, RuntimeLinearMemory, RuntimeMemoryCreator, VMMemoryDefinition,
};
use std::sync::Arc; use std::sync::Arc;
pub fn create_handle_with_memory(store: &Store, memory: &MemoryType) -> Result<InstanceHandle> { pub fn create_handle_with_memory(
store: &Store,
memory: &MemoryType,
) -> Result<StoreInstanceHandle> {
let mut module = Module::new(); let mut module = Module::new();
let memory = wasm::Memory { let memory = wasm::Memory {

View File

@@ -15,14 +15,42 @@ use self::table::create_handle_with_table;
use crate::{FuncType, GlobalType, MemoryType, Store, TableType, Trap, Val}; use crate::{FuncType, GlobalType, MemoryType, Store, TableType, Trap, Val};
use anyhow::Result; use anyhow::Result;
use std::any::Any; use std::any::Any;
use wasmtime_runtime::{VMContext, VMFunctionBody, VMTrampoline}; use std::ops::Deref;
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody, VMTrampoline};
/// A wrapper around `wasmtime_runtime::InstanceHandle` which pairs it with the
/// `Store` that it's rooted within. The instance is deallocated when `Store` is
/// deallocated, so this is a safe handle in terms of memory management for the
/// `Store`.
pub struct StoreInstanceHandle {
pub store: Store,
pub handle: InstanceHandle,
}
impl Clone for StoreInstanceHandle {
fn clone(&self) -> StoreInstanceHandle {
StoreInstanceHandle {
store: self.store.clone(),
// Note should be safe because the lifetime of the instance handle
// is tied to the `Store` which this is paired with.
handle: unsafe { self.handle.clone() },
}
}
}
impl Deref for StoreInstanceHandle {
type Target = InstanceHandle;
fn deref(&self) -> &InstanceHandle {
&self.handle
}
}
pub fn generate_func_export( pub fn generate_func_export(
ft: &FuncType, ft: &FuncType,
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>, func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>,
store: &Store, store: &Store,
) -> Result<( ) -> Result<(
wasmtime_runtime::InstanceHandle, StoreInstanceHandle,
wasmtime_runtime::ExportFunction, wasmtime_runtime::ExportFunction,
VMTrampoline, VMTrampoline,
)> { )> {
@@ -42,10 +70,7 @@ pub unsafe fn generate_raw_func_export(
trampoline: VMTrampoline, trampoline: VMTrampoline,
store: &Store, store: &Store,
state: Box<dyn Any>, state: Box<dyn Any>,
) -> Result<( ) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportFunction)> {
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportFunction,
)> {
let instance = func::create_handle_with_raw_function(ft, func, trampoline, store, state)?; let instance = func::create_handle_with_raw_function(ft, func, trampoline, store, state)?;
match instance.lookup("trampoline").expect("trampoline export") { match instance.lookup("trampoline").expect("trampoline export") {
wasmtime_runtime::Export::Function(f) => Ok((instance, f)), wasmtime_runtime::Export::Function(f) => Ok((instance, f)),
@@ -57,10 +82,7 @@ pub fn generate_global_export(
store: &Store, store: &Store,
gt: &GlobalType, gt: &GlobalType,
val: Val, val: Val,
) -> Result<( ) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportGlobal)> {
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportGlobal,
)> {
let instance = create_global(store, gt, val)?; let instance = create_global(store, gt, val)?;
match instance.lookup("global").expect("global export") { match instance.lookup("global").expect("global export") {
wasmtime_runtime::Export::Global(g) => Ok((instance, g)), wasmtime_runtime::Export::Global(g) => Ok((instance, g)),
@@ -71,10 +93,7 @@ pub fn generate_global_export(
pub fn generate_memory_export( pub fn generate_memory_export(
store: &Store, store: &Store,
m: &MemoryType, m: &MemoryType,
) -> Result<( ) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportMemory)> {
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportMemory,
)> {
let instance = create_handle_with_memory(store, m)?; let instance = create_handle_with_memory(store, m)?;
match instance.lookup("memory").expect("memory export") { match instance.lookup("memory").expect("memory export") {
wasmtime_runtime::Export::Memory(m) => Ok((instance, m)), wasmtime_runtime::Export::Memory(m) => Ok((instance, m)),
@@ -85,10 +104,7 @@ pub fn generate_memory_export(
pub fn generate_table_export( pub fn generate_table_export(
store: &Store, store: &Store,
t: &TableType, t: &TableType,
) -> Result<( ) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportTable)> {
wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportTable,
)> {
let instance = create_handle_with_table(store, t)?; let instance = create_handle_with_table(store, t)?;
match instance.lookup("table").expect("table export") { match instance.lookup("table").expect("table export") {
wasmtime_runtime::Export::Table(t) => Ok((instance, t)), wasmtime_runtime::Export::Table(t) => Ok((instance, t)),

View File

@@ -1,12 +1,12 @@
use super::create_handle::create_handle; use super::create_handle::create_handle;
use crate::trampoline::StoreInstanceHandle;
use crate::Store; use crate::Store;
use crate::{TableType, ValType}; use crate::{TableType, ValType};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::{wasm, EntityIndex, Module}; use wasmtime_environ::{wasm, EntityIndex, Module};
use wasmtime_runtime::InstanceHandle;
pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<InstanceHandle> { pub fn create_handle_with_table(store: &Store, table: &TableType) -> Result<StoreInstanceHandle> {
let mut module = Module::new(); let mut module = Module::new();
let table = wasm::Table { let table = wasm::Table {

View File

@@ -26,6 +26,6 @@ impl InstanceExt for Instance {
where where
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool, H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool,
{ {
self.instance_handle.clone().set_signal_handler(handler); self.handle.set_signal_handler(handler);
} }
} }

View File

@@ -224,6 +224,7 @@ pub(crate) fn from_checked_anyfunc(
signature: item.type_index, signature: item.type_index,
vmctx: item.vmctx, vmctx: item.vmctx,
}; };
let f = Func::from_wasmtime_function(export, store, instance_handle); let instance = store.existing_instance_handle(instance_handle);
let f = Func::from_wasmtime_function(export, instance);
Val::FuncRef(f) Val::FuncRef(f)
} }

View File

@@ -26,6 +26,6 @@ impl InstanceExt for Instance {
where where
H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool, H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool,
{ {
self.instance_handle.clone().set_signal_handler(handler); self.handle.set_signal_handler(handler);
} }
} }

View File

@@ -2,14 +2,13 @@
use crate::resolver::Resolver; use crate::resolver::Resolver;
use more_asserts::assert_ge; use more_asserts::assert_ge;
use std::collections::HashSet;
use std::convert::TryInto; use std::convert::TryInto;
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::{Global, GlobalInit, Memory, Table, TableElementType}; use wasmtime_environ::wasm::{Global, GlobalInit, Memory, Table, TableElementType};
use wasmtime_environ::{EntityIndex, MemoryPlan, MemoryStyle, Module, TablePlan}; use wasmtime_environ::{EntityIndex, MemoryPlan, MemoryStyle, Module, TablePlan};
use wasmtime_runtime::{ use wasmtime_runtime::{
Export, Imports, InstanceHandle, LinkError, SignatureRegistry, VMFunctionImport, Export, Imports, LinkError, SignatureRegistry, VMFunctionImport, VMGlobalImport,
VMGlobalImport, VMMemoryImport, VMTableImport, VMMemoryImport, VMTableImport,
}; };
/// This function allows to match all imports of a `Module` with concrete definitions provided by /// This function allows to match all imports of a `Module` with concrete definitions provided by
@@ -21,8 +20,6 @@ pub fn resolve_imports(
signatures: &SignatureRegistry, signatures: &SignatureRegistry,
resolver: &mut dyn Resolver, resolver: &mut dyn Resolver,
) -> Result<Imports, LinkError> { ) -> Result<Imports, LinkError> {
let mut dependencies = HashSet::new();
let mut function_imports = PrimaryMap::with_capacity(module.local.num_imported_funcs); let mut function_imports = PrimaryMap::with_capacity(module.local.num_imported_funcs);
let mut table_imports = PrimaryMap::with_capacity(module.local.num_imported_tables); let mut table_imports = PrimaryMap::with_capacity(module.local.num_imported_tables);
let mut memory_imports = PrimaryMap::with_capacity(module.local.num_imported_memories); let mut memory_imports = PrimaryMap::with_capacity(module.local.num_imported_memories);
@@ -45,7 +42,6 @@ pub fn resolve_imports(
module_name, field_name, signature, import_signature module_name, field_name, signature, import_signature
))); )));
} }
dependencies.insert(unsafe { InstanceHandle::from_vmctx(f.vmctx) });
function_imports.push(VMFunctionImport { function_imports.push(VMFunctionImport {
body: f.address, body: f.address,
vmctx: f.vmctx, vmctx: f.vmctx,
@@ -73,7 +69,6 @@ pub fn resolve_imports(
module_name, field_name, module_name, field_name,
))); )));
} }
dependencies.insert(unsafe { InstanceHandle::from_vmctx(t.vmctx) });
table_imports.push(VMTableImport { table_imports.push(VMTableImport {
from: t.definition, from: t.definition,
vmctx: t.vmctx, vmctx: t.vmctx,
@@ -115,7 +110,6 @@ pub fn resolve_imports(
} }
assert_ge!(m.memory.offset_guard_size, import_memory.offset_guard_size); assert_ge!(m.memory.offset_guard_size, import_memory.offset_guard_size);
dependencies.insert(unsafe { InstanceHandle::from_vmctx(m.vmctx) });
memory_imports.push(VMMemoryImport { memory_imports.push(VMMemoryImport {
from: m.definition, from: m.definition,
vmctx: m.vmctx, vmctx: m.vmctx,
@@ -143,7 +137,6 @@ pub fn resolve_imports(
module_name, field_name module_name, field_name
))); )));
} }
dependencies.insert(unsafe { InstanceHandle::from_vmctx(g.vmctx) });
global_imports.push(VMGlobalImport { from: g.definition }); global_imports.push(VMGlobalImport { from: g.definition });
} }
(EntityIndex::Global(_), Some(_)) => { (EntityIndex::Global(_), Some(_)) => {
@@ -162,7 +155,6 @@ pub fn resolve_imports(
} }
Ok(Imports::new( Ok(Imports::new(
dependencies,
function_imports, function_imports,
table_imports, table_imports,
memory_imports, memory_imports,

View File

@@ -204,21 +204,11 @@ impl CompiledModule {
/// See `InstanceHandle::new` /// See `InstanceHandle::new`
pub unsafe fn instantiate( pub unsafe fn instantiate(
&self, &self,
is_bulk_memory: bool,
resolver: &mut dyn Resolver, resolver: &mut dyn Resolver,
sig_registry: &SignatureRegistry, sig_registry: &SignatureRegistry,
mem_creator: Option<&dyn RuntimeMemoryCreator>, mem_creator: Option<&dyn RuntimeMemoryCreator>,
max_wasm_stack: usize,
host_state: Box<dyn Any>, host_state: Box<dyn Any>,
) -> Result<InstanceHandle, InstantiationError> { ) -> Result<InstanceHandle, InstantiationError> {
let data_initializers = self
.data_initializers
.iter()
.map(|init| DataInitializer {
location: init.location.clone(),
data: &*init.data,
})
.collect::<Vec<_>>();
let imports = resolve_imports(&self.module, &sig_registry, resolver)?; let imports = resolve_imports(&self.module, &sig_registry, resolver)?;
InstanceHandle::new( InstanceHandle::new(
Arc::clone(&self.module), Arc::clone(&self.module),
@@ -226,16 +216,24 @@ impl CompiledModule {
self.trampolines.clone(), self.trampolines.clone(),
imports, imports,
mem_creator, mem_creator,
&data_initializers,
self.signatures.clone(), self.signatures.clone(),
self.dbg_jit_registration.as_ref().map(|r| Rc::clone(&r)), self.dbg_jit_registration.as_ref().map(|r| Rc::clone(&r)),
is_bulk_memory,
host_state, host_state,
self.interrupts.clone(), self.interrupts.clone(),
max_wasm_stack,
) )
} }
/// Returns data initializers to pass to `InstanceHandle::initialize`
pub fn data_initializers(&self) -> Vec<DataInitializer<'_>> {
self.data_initializers
.iter()
.map(|init| DataInitializer {
location: init.location.clone(),
data: &*init.data,
})
.collect()
}
/// Return a reference-counting pointer to a module. /// Return a reference-counting pointer to a module.
pub fn module(&self) -> &Arc<Module> { pub fn module(&self) -> &Arc<Module> {
&self.module &self.module

View File

@@ -1,15 +1,10 @@
use crate::instance::InstanceHandle;
use crate::vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport}; use crate::vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
use std::collections::HashSet;
use wasmtime_environ::entity::{BoxedSlice, PrimaryMap}; use wasmtime_environ::entity::{BoxedSlice, PrimaryMap};
use wasmtime_environ::wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex}; use wasmtime_environ::wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
/// Resolved import pointers. /// Resolved import pointers.
#[derive(Clone)] #[derive(Clone)]
pub struct Imports { pub struct Imports {
/// The set of instances that the imports depend on.
pub dependencies: HashSet<InstanceHandle>,
/// Resolved addresses for imported functions. /// Resolved addresses for imported functions.
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>, pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
@@ -26,14 +21,12 @@ pub struct Imports {
impl Imports { impl Imports {
/// Construct a new `Imports` instance. /// Construct a new `Imports` instance.
pub fn new( pub fn new(
dependencies: HashSet<InstanceHandle>,
function_imports: PrimaryMap<FuncIndex, VMFunctionImport>, function_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
table_imports: PrimaryMap<TableIndex, VMTableImport>, table_imports: PrimaryMap<TableIndex, VMTableImport>,
memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>, memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>,
global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>, global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>,
) -> Self { ) -> Self {
Self { Self {
dependencies,
functions: function_imports.into_boxed_slice(), functions: function_imports.into_boxed_slice(),
tables: table_imports.into_boxed_slice(), tables: table_imports.into_boxed_slice(),
memories: memory_imports.into_boxed_slice(), memories: memory_imports.into_boxed_slice(),
@@ -44,7 +37,6 @@ impl Imports {
/// Construct a new `Imports` instance with no imports. /// Construct a new `Imports` instance with no imports.
pub fn none() -> Self { pub fn none() -> Self {
Self { Self {
dependencies: HashSet::new(),
functions: PrimaryMap::new().into_boxed_slice(), functions: PrimaryMap::new().into_boxed_slice(),
tables: PrimaryMap::new().into_boxed_slice(), tables: PrimaryMap::new().into_boxed_slice(),
memories: PrimaryMap::new().into_boxed_slice(), memories: PrimaryMap::new().into_boxed_slice(),

View File

@@ -20,7 +20,7 @@ use more_asserts::assert_lt;
use std::alloc::{self, Layout}; use std::alloc::{self, Layout};
use std::any::Any; use std::any::Any;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@@ -39,7 +39,7 @@ cfg_if::cfg_if! {
impl InstanceHandle { impl InstanceHandle {
/// Set a custom signal handler /// Set a custom signal handler
pub fn set_signal_handler<H>(&mut self, handler: H) pub fn set_signal_handler<H>(&self, handler: H)
where where
H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool, H: 'static + Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool,
{ {
@@ -51,7 +51,7 @@ cfg_if::cfg_if! {
impl InstanceHandle { impl InstanceHandle {
/// Set a custom signal handler /// Set a custom signal handler
pub fn set_signal_handler<H>(&mut self, handler: H) pub fn set_signal_handler<H>(&self, handler: H)
where where
H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool, H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool,
{ {
@@ -66,14 +66,6 @@ cfg_if::cfg_if! {
/// This is repr(C) to ensure that the vmctx field is last. /// This is repr(C) to ensure that the vmctx field is last.
#[repr(C)] #[repr(C)]
pub(crate) struct Instance { pub(crate) struct Instance {
/// The number of references to this `Instance`.
refcount: Cell<usize>,
/// `Instance`s from which this `Instance` imports. These won't
/// create reference cycles because wasm instances can't cyclically
/// import from each other.
dependencies: HashSet<InstanceHandle>,
/// The `Module` this `Instance` was instantiated from. /// The `Module` this `Instance` was instantiated from.
module: Arc<Module>, module: Arc<Module>,
@@ -878,13 +870,10 @@ impl InstanceHandle {
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>, trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
imports: Imports, imports: Imports,
mem_creator: Option<&dyn RuntimeMemoryCreator>, mem_creator: Option<&dyn RuntimeMemoryCreator>,
data_initializers: &[DataInitializer<'_>],
vmshared_signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>, vmshared_signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
dbg_jit_registration: Option<Rc<GdbJitImageRegistration>>, dbg_jit_registration: Option<Rc<GdbJitImageRegistration>>,
is_bulk_memory: bool,
host_state: Box<dyn Any>, host_state: Box<dyn Any>,
interrupts: Arc<VMInterrupts>, interrupts: Arc<VMInterrupts>,
max_wasm_stack: usize,
) -> Result<Self, InstantiationError> { ) -> Result<Self, InstantiationError> {
let tables = create_tables(&module); let tables = create_tables(&module);
let memories = create_memories(&module, mem_creator.unwrap_or(&DefaultMemoryCreator {}))?; let memories = create_memories(&module, mem_creator.unwrap_or(&DefaultMemoryCreator {}))?;
@@ -909,8 +898,6 @@ impl InstanceHandle {
let handle = { let handle = {
let instance = Instance { let instance = Instance {
refcount: Cell::new(1),
dependencies: imports.dependencies,
module, module,
offsets, offsets,
memories, memories,
@@ -983,30 +970,44 @@ impl InstanceHandle {
); );
*instance.interrupts() = &*instance.interrupts; *instance.interrupts() = &*instance.interrupts;
// Ensure that our signal handlers are ready for action.
// TODO: Move these calls out of `InstanceHandle`.
traphandlers::init();
// Perform infallible initialization in this constructor, while fallible
// initialization is deferred to the `initialize` method.
initialize_passive_elements(instance);
initialize_globals(instance);
Ok(handle)
}
/// Finishes the instantiation process started by `Instance::new`.
///
/// Only safe to call immediately after instantiation.
pub unsafe fn initialize(
&self,
is_bulk_memory: bool,
max_wasm_stack: usize,
data_initializers: &[DataInitializer<'_>],
) -> Result<(), InstantiationError> {
// Check initializer bounds before initializing anything. Only do this // Check initializer bounds before initializing anything. Only do this
// when bulk memory is disabled, since the bulk memory proposal changes // when bulk memory is disabled, since the bulk memory proposal changes
// instantiation such that the intermediate results of failed // instantiation such that the intermediate results of failed
// initializations are visible. // initializations are visible.
if !is_bulk_memory { if !is_bulk_memory {
check_table_init_bounds(instance)?; check_table_init_bounds(self.instance())?;
check_memory_init_bounds(instance, data_initializers)?; check_memory_init_bounds(self.instance(), data_initializers)?;
} }
// Apply the initializers. // Apply fallible initializers. Note that this can "leak" state even if
initialize_tables(instance)?; // it fails.
initialize_passive_elements(instance); initialize_tables(self.instance())?;
initialize_memories(instance, data_initializers)?; initialize_memories(self.instance(), data_initializers)?;
initialize_globals(instance);
// Ensure that our signal handlers are ready for action. // And finally, invoke the start function.
// TODO: Move these calls out of `InstanceHandle`. self.instance().invoke_start_function(max_wasm_stack)?;
traphandlers::init(); Ok(())
// The WebAssembly spec specifies that the start function is
// invoked automatically at instantiation time.
instance.invoke_start_function(max_wasm_stack)?;
Ok(handle)
} }
/// Create a new `InstanceHandle` pointing at the instance /// Create a new `InstanceHandle` pointing at the instance
@@ -1017,7 +1018,6 @@ impl InstanceHandle {
/// be a `VMContext` allocated as part of an `Instance`. /// be a `VMContext` allocated as part of an `Instance`.
pub unsafe fn from_vmctx(vmctx: *mut VMContext) -> Self { pub unsafe fn from_vmctx(vmctx: *mut VMContext) -> Self {
let instance = (&mut *vmctx).instance(); let instance = (&mut *vmctx).instance();
instance.refcount.set(instance.refcount.get() + 1);
Self { Self {
instance: instance as *const Instance as *mut Instance, instance: instance as *const Instance as *mut Instance,
} }
@@ -1130,31 +1130,30 @@ impl InstanceHandle {
pub(crate) fn instance(&self) -> &Instance { pub(crate) fn instance(&self) -> &Instance {
unsafe { &*(self.instance as *const Instance) } unsafe { &*(self.instance as *const Instance) }
} }
}
impl Clone for InstanceHandle { /// Returns a clone of this instance.
fn clone(&self) -> Self { ///
let instance = self.instance(); /// This is unsafe because the returned handle here is just a cheap clone
instance.refcount.set(instance.refcount.get() + 1); /// of the internals, there's no lifetime tracking around its validity.
Self { /// You'll need to ensure that the returned handles all go out of scope at
/// the same time.
pub unsafe fn clone(&self) -> InstanceHandle {
InstanceHandle {
instance: self.instance, instance: self.instance,
} }
} }
}
impl Drop for InstanceHandle { /// Deallocates memory associated with this instance.
fn drop(&mut self) { ///
/// Note that this is unsafe because there might be other handles to this
/// `InstanceHandle` elsewhere, and there's nothing preventing usage of
/// this handle after this function is called.
pub unsafe fn dealloc(&self) {
let instance = self.instance(); let instance = self.instance();
let count = instance.refcount.get();
instance.refcount.set(count - 1);
if count == 1 {
let layout = instance.alloc_layout(); let layout = instance.alloc_layout();
unsafe {
ptr::drop_in_place(self.instance); ptr::drop_in_place(self.instance);
alloc::dealloc(self.instance.cast(), layout); alloc::dealloc(self.instance.cast(), layout);
} }
}
}
} }
fn check_table_init_bounds(instance: &Instance) -> Result<(), InstantiationError> { fn check_table_init_bounds(instance: &Instance) -> Result<(), InstantiationError> {

View File

@@ -39,6 +39,7 @@ fn dtor_runs() {
Func::wrap(&store, move || { Func::wrap(&store, move || {
drop(&a); drop(&a);
}); });
drop(store);
assert_eq!(HITS.load(SeqCst), 1); assert_eq!(HITS.load(SeqCst), 1);
} }
@@ -63,7 +64,7 @@ fn dtor_delayed() -> Result<()> {
let module = Module::new(&store, &wasm)?; let module = Module::new(&store, &wasm)?;
let instance = Instance::new(&module, &[func.into()])?; let instance = Instance::new(&module, &[func.into()])?;
assert_eq!(HITS.load(SeqCst), 0); assert_eq!(HITS.load(SeqCst), 0);
drop(instance); drop((instance, module, store));
assert_eq!(HITS.load(SeqCst), 1); assert_eq!(HITS.load(SeqCst), 1);
Ok(()) Ok(())
} }

View File

@@ -15,4 +15,5 @@ mod memory_creator;
mod name; mod name;
mod stack_overflow; mod stack_overflow;
mod traps; mod traps;
mod use_after_drop;
mod wast; mod wast;

View File

@@ -175,9 +175,9 @@ mod not_for_windows {
let tot_pages = *mem_creator.num_total_pages.lock().unwrap(); let tot_pages = *mem_creator.num_total_pages.lock().unwrap();
assert_eq!(tot_pages, 4); assert_eq!(tot_pages, 4);
drop(instance1); drop((instance1, instance2, store, module));
let tot_pages = *mem_creator.num_total_pages.lock().unwrap(); let tot_pages = *mem_creator.num_total_pages.lock().unwrap();
assert_eq!(tot_pages, 2); assert_eq!(tot_pages, 0);
Ok(()) Ok(())
} }

View File

@@ -0,0 +1,21 @@
use anyhow::Result;
use wasmtime::*;
#[test]
fn use_func_after_drop() -> Result<()> {
let table;
{
let store = Store::default();
let closed_over_data = String::from("abcd");
let func = Func::wrap(&store, move || {
assert_eq!(closed_over_data, "abcd");
});
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
table = Table::new(&store, ty, Val::AnyRef(AnyRef::Null))?;
table.set(0, func.into())?;
}
let func = table.get(0).unwrap().funcref().unwrap().clone();
let func = func.get0::<()>()?;
func()?;
Ok(())
}