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:
@@ -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::Mutability;
|
||||
use crate::{ExternType, GlobalType, MemoryType, TableType, ValType};
|
||||
use crate::{ExternType, GlobalType, MemoryType, Mutability, TableType, ValType};
|
||||
use crate::{Func, Store, Trap};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use std::slice;
|
||||
@@ -92,21 +93,20 @@ impl Extern {
|
||||
|
||||
pub(crate) fn from_wasmtime_export(
|
||||
wasmtime_export: wasmtime_runtime::Export,
|
||||
store: &Store,
|
||||
instance_handle: InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
) -> Extern {
|
||||
match wasmtime_export {
|
||||
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) => {
|
||||
Extern::Memory(Memory::from_wasmtime_memory(m, store, instance_handle))
|
||||
Extern::Memory(Memory::from_wasmtime_memory(m, instance))
|
||||
}
|
||||
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) => {
|
||||
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 {
|
||||
let my_store = match self {
|
||||
Extern::Func(f) => f.store(),
|
||||
Extern::Global(g) => &g.store,
|
||||
Extern::Memory(m) => &m.store,
|
||||
Extern::Table(t) => &t.store,
|
||||
Extern::Global(g) => &g.instance.store,
|
||||
Extern::Memory(m) => &m.instance.store,
|
||||
Extern::Table(t) => &t.instance.store,
|
||||
};
|
||||
Store::same(my_store, store)
|
||||
}
|
||||
@@ -163,9 +163,8 @@ impl From<Table> for Extern {
|
||||
/// instances are equivalent in their functionality.
|
||||
#[derive(Clone)]
|
||||
pub struct Global {
|
||||
store: Store,
|
||||
instance: StoreInstanceHandle,
|
||||
wasmtime_export: wasmtime_runtime::ExportGlobal,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
}
|
||||
|
||||
impl Global {
|
||||
@@ -187,11 +186,10 @@ impl Global {
|
||||
if val.ty() != *ty.content() {
|
||||
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 {
|
||||
store: store.clone(),
|
||||
instance,
|
||||
wasmtime_export,
|
||||
wasmtime_handle,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -246,7 +244,7 @@ impl Global {
|
||||
if val.ty() != 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");
|
||||
}
|
||||
unsafe {
|
||||
@@ -264,13 +262,11 @@ impl Global {
|
||||
|
||||
pub(crate) fn from_wasmtime_global(
|
||||
wasmtime_export: wasmtime_runtime::ExportGlobal,
|
||||
store: &Store,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
) -> Global {
|
||||
Global {
|
||||
store: store.clone(),
|
||||
instance,
|
||||
wasmtime_export,
|
||||
wasmtime_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,18 +288,17 @@ impl Global {
|
||||
/// instances are equivalent in their functionality.
|
||||
#[derive(Clone)]
|
||||
pub struct Table {
|
||||
store: Store,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
wasmtime_export: wasmtime_runtime::ExportTable,
|
||||
}
|
||||
|
||||
fn set_table_item(
|
||||
handle: &InstanceHandle,
|
||||
instance: &InstanceHandle,
|
||||
table_index: wasm::DefinedTableIndex,
|
||||
item_index: u32,
|
||||
item: wasmtime_runtime::VMCallerCheckedAnyfunc,
|
||||
) -> Result<()> {
|
||||
handle
|
||||
instance
|
||||
.table_set(table_index, item_index, item)
|
||||
.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.
|
||||
pub fn new(store: &Store, ty: TableType, init: Val) -> Result<Table> {
|
||||
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.
|
||||
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 {
|
||||
set_table_item(&wasmtime_handle, index, i, item.clone())?;
|
||||
set_table_item(&instance, index, i, item.clone())?;
|
||||
}
|
||||
|
||||
Ok(Table {
|
||||
store: store.clone(),
|
||||
wasmtime_handle,
|
||||
instance,
|
||||
wasmtime_export,
|
||||
})
|
||||
}
|
||||
@@ -345,10 +339,7 @@ impl Table {
|
||||
}
|
||||
|
||||
fn wasmtime_table_index(&self) -> wasm::DefinedTableIndex {
|
||||
unsafe {
|
||||
self.wasmtime_handle
|
||||
.table_index(&*self.wasmtime_export.definition)
|
||||
}
|
||||
unsafe { self.instance.table_index(&*self.wasmtime_export.definition) }
|
||||
}
|
||||
|
||||
/// Returns the table element value at `index`.
|
||||
@@ -356,8 +347,8 @@ impl Table {
|
||||
/// Returns `None` if `index` is out of bounds.
|
||||
pub fn get(&self, index: u32) -> Option<Val> {
|
||||
let table_index = self.wasmtime_table_index();
|
||||
let item = self.wasmtime_handle.table_get(table_index, index)?;
|
||||
Some(from_checked_anyfunc(item, &self.store))
|
||||
let item = self.instance.table_get(table_index, index)?;
|
||||
Some(from_checked_anyfunc(item, &self.instance.store))
|
||||
}
|
||||
|
||||
/// Writes the `val` provided into `index` within this table.
|
||||
@@ -368,8 +359,8 @@ impl Table {
|
||||
/// the right type to be stored in this table.
|
||||
pub fn set(&self, index: u32, val: Val) -> Result<()> {
|
||||
let table_index = self.wasmtime_table_index();
|
||||
let item = into_checked_anyfunc(val, &self.store)?;
|
||||
set_table_item(&self.wasmtime_handle, table_index, index, item)
|
||||
let item = into_checked_anyfunc(val, &self.instance.store)?;
|
||||
set_table_item(&self.instance, table_index, index, item)
|
||||
}
|
||||
|
||||
/// Returns the current size of this table.
|
||||
@@ -387,12 +378,11 @@ impl Table {
|
||||
/// error if `init` is not of the right type.
|
||||
pub fn grow(&self, delta: u32, init: Val) -> Result<u32> {
|
||||
let index = self.wasmtime_table_index();
|
||||
let item = into_checked_anyfunc(init, &self.store)?;
|
||||
if let Some(len) = self.wasmtime_handle.clone().table_grow(index, delta) {
|
||||
let mut wasmtime_handle = self.wasmtime_handle.clone();
|
||||
let item = into_checked_anyfunc(init, &self.instance.store)?;
|
||||
if let Some(len) = self.instance.table_grow(index, delta) {
|
||||
for i in 0..delta {
|
||||
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)
|
||||
} else {
|
||||
@@ -414,7 +404,7 @@ impl Table {
|
||||
src_index: u32,
|
||||
len: u32,
|
||||
) -> 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");
|
||||
}
|
||||
|
||||
@@ -423,10 +413,10 @@ impl Table {
|
||||
// come from different modules.
|
||||
|
||||
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 = 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)
|
||||
.map_err(Trap::from_jit)?;
|
||||
@@ -435,12 +425,10 @@ impl Table {
|
||||
|
||||
pub(crate) fn from_wasmtime_table(
|
||||
wasmtime_export: wasmtime_runtime::ExportTable,
|
||||
store: &Store,
|
||||
wasmtime_handle: wasmtime_runtime::InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
) -> Table {
|
||||
Table {
|
||||
store: store.clone(),
|
||||
wasmtime_handle,
|
||||
instance,
|
||||
wasmtime_export,
|
||||
}
|
||||
}
|
||||
@@ -654,8 +642,7 @@ impl Table {
|
||||
/// [open an issue]: https://github.com/bytecodealliance/wasmtime/issues/new
|
||||
#[derive(Clone)]
|
||||
pub struct Memory {
|
||||
store: Store,
|
||||
wasmtime_handle: InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
wasmtime_export: wasmtime_runtime::ExportMemory,
|
||||
}
|
||||
|
||||
@@ -683,11 +670,10 @@ impl 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");
|
||||
Memory {
|
||||
store: store.clone(),
|
||||
wasmtime_handle,
|
||||
instance,
|
||||
wasmtime_export,
|
||||
}
|
||||
}
|
||||
@@ -827,22 +813,19 @@ impl Memory {
|
||||
/// ```
|
||||
pub fn grow(&self, delta: u32) -> Result<u32> {
|
||||
let index = self
|
||||
.wasmtime_handle
|
||||
.instance
|
||||
.memory_index(unsafe { &*self.wasmtime_export.definition });
|
||||
self.wasmtime_handle
|
||||
.clone()
|
||||
self.instance
|
||||
.memory_grow(index, delta)
|
||||
.ok_or_else(|| anyhow!("failed to grow memory"))
|
||||
}
|
||||
|
||||
pub(crate) fn from_wasmtime_memory(
|
||||
wasmtime_export: wasmtime_runtime::ExportMemory,
|
||||
store: &Store,
|
||||
wasmtime_handle: wasmtime_runtime::InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
) -> Memory {
|
||||
Memory {
|
||||
store: store.clone(),
|
||||
wasmtime_handle,
|
||||
instance,
|
||||
wasmtime_export,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::runtime::StoreInner;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::{Extern, FuncType, Memory, Store, Trap, Val, ValType};
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use std::cmp::max;
|
||||
@@ -5,6 +7,7 @@ use std::fmt;
|
||||
use std::mem;
|
||||
use std::panic::{self, AssertUnwindSafe};
|
||||
use std::ptr;
|
||||
use std::rc::Weak;
|
||||
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
|
||||
use wasmtime_runtime::{ExportFunction, VMTrampoline};
|
||||
|
||||
@@ -135,8 +138,7 @@ use wasmtime_runtime::{ExportFunction, VMTrampoline};
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct Func {
|
||||
store: Store,
|
||||
instance: InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
export: ExportFunction,
|
||||
trampoline: VMTrampoline,
|
||||
}
|
||||
@@ -176,7 +178,7 @@ macro_rules! getters {
|
||||
// of the closure. Pass the export in so that we can call it.
|
||||
let instance = self.instance.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
|
||||
// object since our `transmute` below should be safe!
|
||||
@@ -236,7 +238,7 @@ impl Func {
|
||||
ty: FuncType,
|
||||
func: impl Fn(Caller<'_>, &[Val], &mut [Val]) -> Result<(), Trap> + 'static,
|
||||
) -> Self {
|
||||
let store_clone = store.clone();
|
||||
let store_weak = store.weak();
|
||||
let ty_clone = ty.clone();
|
||||
|
||||
// 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()];
|
||||
func(
|
||||
Caller {
|
||||
store: &store_clone,
|
||||
store: &store_weak,
|
||||
caller_vmctx,
|
||||
},
|
||||
&args,
|
||||
@@ -281,7 +283,6 @@ impl Func {
|
||||
let (instance, export, trampoline) =
|
||||
crate::trampoline::generate_func_export(&ty, func, store).expect("generated func");
|
||||
Func {
|
||||
store: store.clone(),
|
||||
instance,
|
||||
export,
|
||||
trampoline,
|
||||
@@ -483,6 +484,7 @@ impl Func {
|
||||
// Signatures should always be registered in the store's registry of
|
||||
// shared signatures, so we should be able to unwrap safely here.
|
||||
let sig = self
|
||||
.instance
|
||||
.store
|
||||
.compiler()
|
||||
.signatures()
|
||||
@@ -498,6 +500,7 @@ impl Func {
|
||||
/// Returns the number of parameters that this function takes.
|
||||
pub fn param_arity(&self) -> usize {
|
||||
let sig = self
|
||||
.instance
|
||||
.store
|
||||
.compiler()
|
||||
.signatures()
|
||||
@@ -509,6 +512,7 @@ impl Func {
|
||||
/// Returns the number of results this function produces.
|
||||
pub fn result_arity(&self) -> usize {
|
||||
let sig = self
|
||||
.instance
|
||||
.store
|
||||
.compiler()
|
||||
.signatures()
|
||||
@@ -549,7 +553,7 @@ impl Func {
|
||||
if arg.ty() != *ty {
|
||||
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");
|
||||
}
|
||||
unsafe {
|
||||
@@ -561,7 +565,7 @@ impl Func {
|
||||
if let Err(error) = unsafe {
|
||||
wasmtime_runtime::catch_traps(
|
||||
self.export.vmctx,
|
||||
self.store.engine().config().max_wasm_stack,
|
||||
self.instance.store.engine().config().max_wasm_stack,
|
||||
|| {
|
||||
(self.trampoline)(
|
||||
self.export.vmctx,
|
||||
@@ -593,8 +597,7 @@ impl Func {
|
||||
|
||||
pub(crate) fn from_wasmtime_function(
|
||||
export: wasmtime_runtime::ExportFunction,
|
||||
store: &Store,
|
||||
instance: InstanceHandle,
|
||||
instance: StoreInstanceHandle,
|
||||
) -> Self {
|
||||
// Each function signature in a module should have a trampoline stored
|
||||
// on that module as well, so unwrap the result here since otherwise
|
||||
@@ -607,7 +610,6 @@ impl Func {
|
||||
instance,
|
||||
export,
|
||||
trampoline,
|
||||
store: store.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,7 +736,7 @@ impl Func {
|
||||
}
|
||||
|
||||
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
|
||||
/// implemented.
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -1004,7 +1022,14 @@ impl Caller<'_> {
|
||||
Some(Export::Memory(m)) => m,
|
||||
_ => 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))
|
||||
}
|
||||
}
|
||||
@@ -1057,8 +1082,8 @@ macro_rules! impl_into_func {
|
||||
// Double-check ourselves in debug mode, but we control
|
||||
// the `Any` here so an unsafe downcast should also
|
||||
// work.
|
||||
debug_assert!(state.is::<(F, Store)>());
|
||||
let (func, store) = &*(state as *const _ as *const (F, Store));
|
||||
debug_assert!(state.is::<(F, Weak<StoreInner>)>());
|
||||
let (func, store) = &*(state as *const _ as *const (F, Weak<StoreInner>));
|
||||
panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
func(
|
||||
Caller { store, caller_vmctx },
|
||||
@@ -1102,7 +1127,7 @@ macro_rules! impl_into_func {
|
||||
let mut ret = Vec::new();
|
||||
R::push(&mut ret);
|
||||
let ty = FuncType::new(_args.into(), ret.into());
|
||||
let store_clone = store.clone();
|
||||
let store_weak = store.weak();
|
||||
unsafe {
|
||||
let trampoline = trampoline::<$($args,)* R>;
|
||||
let (instance, export) = crate::trampoline::generate_raw_func_export(
|
||||
@@ -1113,11 +1138,10 @@ macro_rules! impl_into_func {
|
||||
),
|
||||
trampoline,
|
||||
store,
|
||||
Box::new((self, store_clone)),
|
||||
Box::new((self, store_weak)),
|
||||
)
|
||||
.expect("failed to generate export");
|
||||
Func {
|
||||
store: store.clone(),
|
||||
instance,
|
||||
export,
|
||||
trampoline,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use crate::externals::{Export, Extern, Global, Memory, Table};
|
||||
use crate::func::Func;
|
||||
use crate::module::Module;
|
||||
use crate::runtime::{Config, Store};
|
||||
use crate::trap::Trap;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::{Export, Extern, Func, Global, Memory, Module, Store, Table, Trap};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use std::any::Any;
|
||||
use wasmtime_jit::{CompiledModule, Resolver};
|
||||
use wasmtime_runtime::{InstanceHandle, InstantiationError, SignatureRegistry};
|
||||
use wasmtime_runtime::{InstantiationError, SignatureRegistry};
|
||||
|
||||
struct SimpleResolver<'a> {
|
||||
imports: &'a [Extern],
|
||||
@@ -21,22 +18,35 @@ impl Resolver for SimpleResolver<'_> {
|
||||
}
|
||||
|
||||
fn instantiate(
|
||||
config: &Config,
|
||||
store: &Store,
|
||||
compiled_module: &CompiledModule,
|
||||
imports: &[Extern],
|
||||
sig_registry: &SignatureRegistry,
|
||||
host: Box<dyn Any>,
|
||||
) -> Result<InstanceHandle, Error> {
|
||||
) -> Result<StoreInstanceHandle, Error> {
|
||||
let mut resolver = SimpleResolver { imports };
|
||||
unsafe {
|
||||
let instance = compiled_module
|
||||
.instantiate(
|
||||
config.validating_config.operator_config.enable_bulk_memory,
|
||||
let config = store.engine().config();
|
||||
let instance = compiled_module.instantiate(
|
||||
&mut resolver,
|
||||
sig_registry,
|
||||
config.memory_creator.as_ref().map(|a| a as _),
|
||||
config.max_wasm_stack,
|
||||
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 {
|
||||
match e {
|
||||
@@ -68,7 +78,7 @@ fn instantiate(
|
||||
/// call any code or execute anything!
|
||||
#[derive(Clone)]
|
||||
pub struct Instance {
|
||||
pub(crate) instance_handle: InstanceHandle,
|
||||
pub(crate) handle: StoreInstanceHandle,
|
||||
module: Module,
|
||||
}
|
||||
|
||||
@@ -137,9 +147,8 @@ impl Instance {
|
||||
}
|
||||
|
||||
let info = module.register_frame_info();
|
||||
let config = store.engine().config();
|
||||
let instance_handle = instantiate(
|
||||
config,
|
||||
let handle = instantiate(
|
||||
store,
|
||||
module.compiled_module(),
|
||||
imports,
|
||||
store.compiler().signatures(),
|
||||
@@ -147,7 +156,7 @@ impl Instance {
|
||||
)?;
|
||||
|
||||
Ok(Instance {
|
||||
instance_handle,
|
||||
handle,
|
||||
module: module.clone(),
|
||||
})
|
||||
}
|
||||
@@ -164,13 +173,9 @@ impl Instance {
|
||||
pub fn exports<'instance>(
|
||||
&'instance self,
|
||||
) -> impl ExactSizeIterator<Item = Export<'instance>> + 'instance {
|
||||
let instance_handle = &self.instance_handle;
|
||||
let store = self.module.store();
|
||||
self.instance_handle
|
||||
.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());
|
||||
self.handle.exports().map(move |(name, entity_index)| {
|
||||
let export = self.handle.lookup_by_declaration(entity_index);
|
||||
let extern_ = Extern::from_wasmtime_export(export, self.handle.clone());
|
||||
Export::new(name, extern_)
|
||||
})
|
||||
}
|
||||
@@ -182,12 +187,8 @@ impl Instance {
|
||||
///
|
||||
/// Returns `None` if there was no export named `name`.
|
||||
pub fn get_export(&self, name: &str) -> Option<Extern> {
|
||||
let export = self.instance_handle.lookup(&name)?;
|
||||
Some(Extern::from_wasmtime_export(
|
||||
export,
|
||||
self.module.store(),
|
||||
self.instance_handle.clone(),
|
||||
))
|
||||
let export = self.handle.lookup(&name)?;
|
||||
Some(Extern::from_wasmtime_export(export, self.handle.clone()))
|
||||
}
|
||||
|
||||
/// Looks up an exported [`Func`] value by name.
|
||||
@@ -221,9 +222,4 @@ impl Instance {
|
||||
pub fn get_global(&self, name: &str) -> Option<Global> {
|
||||
self.get_export(name)?.into_global()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn handle(&self) -> &InstanceHandle {
|
||||
&self.instance_handle
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use crate::externals::MemoryCreator;
|
||||
use crate::trampoline::MemoryCreatorProxy;
|
||||
use crate::trampoline::{MemoryCreatorProxy, StoreInstanceHandle};
|
||||
use anyhow::{bail, Result};
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::min;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::Arc;
|
||||
use wasmparser::{OperatorValidatorConfig, ValidatingParserConfig};
|
||||
use wasmtime_environ::settings::{self, Configurable};
|
||||
use wasmtime_environ::{CacheConfig, Tunables};
|
||||
use wasmtime_jit::{native, CompilationStrategy, Compiler};
|
||||
use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent, VTuneAgent};
|
||||
use wasmtime_runtime::{debug_builtins, RuntimeMemoryCreator, VMInterrupts};
|
||||
use wasmtime_runtime::{debug_builtins, InstanceHandle, RuntimeMemoryCreator, VMInterrupts};
|
||||
|
||||
// Runtime Environment
|
||||
|
||||
@@ -550,13 +550,13 @@ impl Engine {
|
||||
/// ocnfiguration (see [`Config`] for more information).
|
||||
#[derive(Clone)]
|
||||
pub struct Store {
|
||||
// FIXME(#777) should be `Arc` and this type should be thread-safe
|
||||
inner: Rc<StoreInner>,
|
||||
}
|
||||
|
||||
struct StoreInner {
|
||||
pub(crate) struct StoreInner {
|
||||
engine: Engine,
|
||||
compiler: RefCell<Compiler>,
|
||||
instances: RefCell<Vec<InstanceHandle>>,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
@@ -573,10 +573,15 @@ impl Store {
|
||||
inner: Rc::new(StoreInner {
|
||||
engine: engine.clone(),
|
||||
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.
|
||||
pub fn engine(&self) -> &Engine {
|
||||
&self.inner.engine
|
||||
@@ -595,6 +600,31 @@ impl Store {
|
||||
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
|
||||
/// `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
|
||||
/// particular `Store`.
|
||||
///
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! Support for a calling of an imported function.
|
||||
|
||||
use crate::runtime::Store;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::Store;
|
||||
use anyhow::Result;
|
||||
use std::any::Any;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
@@ -18,15 +19,13 @@ pub(crate) fn create_handle(
|
||||
finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
|
||||
state: Box<dyn Any>,
|
||||
) -> Result<InstanceHandle> {
|
||||
) -> Result<StoreInstanceHandle> {
|
||||
let imports = Imports::new(
|
||||
HashSet::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
PrimaryMap::new(),
|
||||
);
|
||||
let data_initializers = Vec::new();
|
||||
|
||||
// Compute indices into the shared signature table.
|
||||
let signatures = module
|
||||
@@ -37,24 +36,17 @@ pub(crate) fn create_handle(
|
||||
.collect::<PrimaryMap<_, _>>();
|
||||
|
||||
unsafe {
|
||||
Ok(InstanceHandle::new(
|
||||
let handle = InstanceHandle::new(
|
||||
Arc::new(module),
|
||||
finished_functions.into_boxed_slice(),
|
||||
trampolines,
|
||||
imports,
|
||||
store.memory_creator(),
|
||||
&data_initializers,
|
||||
signatures.into_boxed_slice(),
|
||||
None,
|
||||
store
|
||||
.engine()
|
||||
.config()
|
||||
.validating_config
|
||||
.operator_config
|
||||
.enable_bulk_memory,
|
||||
state,
|
||||
store.compiler().interrupts().clone(),
|
||||
store.engine().config().max_wasm_stack,
|
||||
)?)
|
||||
)?;
|
||||
Ok(store.add_instance(handle))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Support for a calling of an imported function.
|
||||
|
||||
use super::create_handle::create_handle;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::{FuncType, Store, Trap};
|
||||
use anyhow::{bail, Result};
|
||||
use std::any::Any;
|
||||
@@ -203,7 +204,7 @@ pub fn create_handle_with_function(
|
||||
ft: &FuncType,
|
||||
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>,
|
||||
store: &Store,
|
||||
) -> Result<(InstanceHandle, VMTrampoline)> {
|
||||
) -> Result<(StoreInstanceHandle, VMTrampoline)> {
|
||||
let isa = {
|
||||
let isa_builder = native::builder();
|
||||
let flag_builder = settings::builder();
|
||||
@@ -267,7 +268,7 @@ pub unsafe fn create_handle_with_raw_function(
|
||||
trampoline: VMTrampoline,
|
||||
store: &Store,
|
||||
state: Box<dyn Any>,
|
||||
) -> Result<InstanceHandle> {
|
||||
) -> Result<StoreInstanceHandle> {
|
||||
let isa = {
|
||||
let isa_builder = native::builder();
|
||||
let flag_builder = settings::builder();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::create_handle::create_handle;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::Store;
|
||||
use crate::{GlobalType, Mutability, Val};
|
||||
use anyhow::{bail, Result};
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
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 {
|
||||
ty: match gt.content().get_wasmtime_type() {
|
||||
Some(t) => t,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
use super::create_handle::create_handle;
|
||||
use crate::externals::{LinearMemory, MemoryCreator};
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::Store;
|
||||
use crate::{Limits, MemoryType};
|
||||
use anyhow::Result;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::{wasm, EntityIndex, MemoryPlan, Module, WASM_PAGE_SIZE};
|
||||
use wasmtime_runtime::{
|
||||
InstanceHandle, RuntimeLinearMemory, RuntimeMemoryCreator, VMMemoryDefinition,
|
||||
};
|
||||
use wasmtime_runtime::{RuntimeLinearMemory, RuntimeMemoryCreator, VMMemoryDefinition};
|
||||
|
||||
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 memory = wasm::Memory {
|
||||
|
||||
@@ -15,14 +15,42 @@ use self::table::create_handle_with_table;
|
||||
use crate::{FuncType, GlobalType, MemoryType, Store, TableType, Trap, Val};
|
||||
use anyhow::Result;
|
||||
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(
|
||||
ft: &FuncType,
|
||||
func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>,
|
||||
store: &Store,
|
||||
) -> Result<(
|
||||
wasmtime_runtime::InstanceHandle,
|
||||
StoreInstanceHandle,
|
||||
wasmtime_runtime::ExportFunction,
|
||||
VMTrampoline,
|
||||
)> {
|
||||
@@ -42,10 +70,7 @@ pub unsafe fn generate_raw_func_export(
|
||||
trampoline: VMTrampoline,
|
||||
store: &Store,
|
||||
state: Box<dyn Any>,
|
||||
) -> Result<(
|
||||
wasmtime_runtime::InstanceHandle,
|
||||
wasmtime_runtime::ExportFunction,
|
||||
)> {
|
||||
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportFunction)> {
|
||||
let instance = func::create_handle_with_raw_function(ft, func, trampoline, store, state)?;
|
||||
match instance.lookup("trampoline").expect("trampoline export") {
|
||||
wasmtime_runtime::Export::Function(f) => Ok((instance, f)),
|
||||
@@ -57,10 +82,7 @@ pub fn generate_global_export(
|
||||
store: &Store,
|
||||
gt: &GlobalType,
|
||||
val: Val,
|
||||
) -> Result<(
|
||||
wasmtime_runtime::InstanceHandle,
|
||||
wasmtime_runtime::ExportGlobal,
|
||||
)> {
|
||||
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportGlobal)> {
|
||||
let instance = create_global(store, gt, val)?;
|
||||
match instance.lookup("global").expect("global export") {
|
||||
wasmtime_runtime::Export::Global(g) => Ok((instance, g)),
|
||||
@@ -71,10 +93,7 @@ pub fn generate_global_export(
|
||||
pub fn generate_memory_export(
|
||||
store: &Store,
|
||||
m: &MemoryType,
|
||||
) -> Result<(
|
||||
wasmtime_runtime::InstanceHandle,
|
||||
wasmtime_runtime::ExportMemory,
|
||||
)> {
|
||||
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportMemory)> {
|
||||
let instance = create_handle_with_memory(store, m)?;
|
||||
match instance.lookup("memory").expect("memory export") {
|
||||
wasmtime_runtime::Export::Memory(m) => Ok((instance, m)),
|
||||
@@ -85,10 +104,7 @@ pub fn generate_memory_export(
|
||||
pub fn generate_table_export(
|
||||
store: &Store,
|
||||
t: &TableType,
|
||||
) -> Result<(
|
||||
wasmtime_runtime::InstanceHandle,
|
||||
wasmtime_runtime::ExportTable,
|
||||
)> {
|
||||
) -> Result<(StoreInstanceHandle, wasmtime_runtime::ExportTable)> {
|
||||
let instance = create_handle_with_table(store, t)?;
|
||||
match instance.lookup("table").expect("table export") {
|
||||
wasmtime_runtime::Export::Table(t) => Ok((instance, t)),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::create_handle::create_handle;
|
||||
use crate::trampoline::StoreInstanceHandle;
|
||||
use crate::Store;
|
||||
use crate::{TableType, ValType};
|
||||
use anyhow::{bail, Result};
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
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 table = wasm::Table {
|
||||
|
||||
@@ -26,6 +26,6 @@ impl InstanceExt for Instance {
|
||||
where
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +224,7 @@ pub(crate) fn from_checked_anyfunc(
|
||||
signature: item.type_index,
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ impl InstanceExt for Instance {
|
||||
where
|
||||
H: 'static + Fn(winapi::um::winnt::PEXCEPTION_POINTERS) -> bool,
|
||||
{
|
||||
self.instance_handle.clone().set_signal_handler(handler);
|
||||
self.handle.set_signal_handler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
use crate::resolver::Resolver;
|
||||
use more_asserts::assert_ge;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::{Global, GlobalInit, Memory, Table, TableElementType};
|
||||
use wasmtime_environ::{EntityIndex, MemoryPlan, MemoryStyle, Module, TablePlan};
|
||||
use wasmtime_runtime::{
|
||||
Export, Imports, InstanceHandle, LinkError, SignatureRegistry, VMFunctionImport,
|
||||
VMGlobalImport, VMMemoryImport, VMTableImport,
|
||||
Export, Imports, LinkError, SignatureRegistry, VMFunctionImport, VMGlobalImport,
|
||||
VMMemoryImport, VMTableImport,
|
||||
};
|
||||
|
||||
/// 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,
|
||||
resolver: &mut dyn Resolver,
|
||||
) -> Result<Imports, LinkError> {
|
||||
let mut dependencies = HashSet::new();
|
||||
|
||||
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 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
|
||||
)));
|
||||
}
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(f.vmctx) });
|
||||
function_imports.push(VMFunctionImport {
|
||||
body: f.address,
|
||||
vmctx: f.vmctx,
|
||||
@@ -73,7 +69,6 @@ pub fn resolve_imports(
|
||||
module_name, field_name,
|
||||
)));
|
||||
}
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(t.vmctx) });
|
||||
table_imports.push(VMTableImport {
|
||||
from: t.definition,
|
||||
vmctx: t.vmctx,
|
||||
@@ -115,7 +110,6 @@ pub fn resolve_imports(
|
||||
}
|
||||
assert_ge!(m.memory.offset_guard_size, import_memory.offset_guard_size);
|
||||
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(m.vmctx) });
|
||||
memory_imports.push(VMMemoryImport {
|
||||
from: m.definition,
|
||||
vmctx: m.vmctx,
|
||||
@@ -143,7 +137,6 @@ pub fn resolve_imports(
|
||||
module_name, field_name
|
||||
)));
|
||||
}
|
||||
dependencies.insert(unsafe { InstanceHandle::from_vmctx(g.vmctx) });
|
||||
global_imports.push(VMGlobalImport { from: g.definition });
|
||||
}
|
||||
(EntityIndex::Global(_), Some(_)) => {
|
||||
@@ -162,7 +155,6 @@ pub fn resolve_imports(
|
||||
}
|
||||
|
||||
Ok(Imports::new(
|
||||
dependencies,
|
||||
function_imports,
|
||||
table_imports,
|
||||
memory_imports,
|
||||
|
||||
@@ -204,21 +204,11 @@ impl CompiledModule {
|
||||
/// See `InstanceHandle::new`
|
||||
pub unsafe fn instantiate(
|
||||
&self,
|
||||
is_bulk_memory: bool,
|
||||
resolver: &mut dyn Resolver,
|
||||
sig_registry: &SignatureRegistry,
|
||||
mem_creator: Option<&dyn RuntimeMemoryCreator>,
|
||||
max_wasm_stack: usize,
|
||||
host_state: Box<dyn Any>,
|
||||
) -> 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)?;
|
||||
InstanceHandle::new(
|
||||
Arc::clone(&self.module),
|
||||
@@ -226,16 +216,24 @@ impl CompiledModule {
|
||||
self.trampolines.clone(),
|
||||
imports,
|
||||
mem_creator,
|
||||
&data_initializers,
|
||||
self.signatures.clone(),
|
||||
self.dbg_jit_registration.as_ref().map(|r| Rc::clone(&r)),
|
||||
is_bulk_memory,
|
||||
host_state,
|
||||
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.
|
||||
pub fn module(&self) -> &Arc<Module> {
|
||||
&self.module
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
use crate::instance::InstanceHandle;
|
||||
use crate::vmcontext::{VMFunctionImport, VMGlobalImport, VMMemoryImport, VMTableImport};
|
||||
use std::collections::HashSet;
|
||||
use wasmtime_environ::entity::{BoxedSlice, PrimaryMap};
|
||||
use wasmtime_environ::wasm::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex};
|
||||
|
||||
/// Resolved import pointers.
|
||||
#[derive(Clone)]
|
||||
pub struct Imports {
|
||||
/// The set of instances that the imports depend on.
|
||||
pub dependencies: HashSet<InstanceHandle>,
|
||||
|
||||
/// Resolved addresses for imported functions.
|
||||
pub functions: BoxedSlice<FuncIndex, VMFunctionImport>,
|
||||
|
||||
@@ -26,14 +21,12 @@ pub struct Imports {
|
||||
impl Imports {
|
||||
/// Construct a new `Imports` instance.
|
||||
pub fn new(
|
||||
dependencies: HashSet<InstanceHandle>,
|
||||
function_imports: PrimaryMap<FuncIndex, VMFunctionImport>,
|
||||
table_imports: PrimaryMap<TableIndex, VMTableImport>,
|
||||
memory_imports: PrimaryMap<MemoryIndex, VMMemoryImport>,
|
||||
global_imports: PrimaryMap<GlobalIndex, VMGlobalImport>,
|
||||
) -> Self {
|
||||
Self {
|
||||
dependencies,
|
||||
functions: function_imports.into_boxed_slice(),
|
||||
tables: table_imports.into_boxed_slice(),
|
||||
memories: memory_imports.into_boxed_slice(),
|
||||
@@ -44,7 +37,6 @@ impl Imports {
|
||||
/// Construct a new `Imports` instance with no imports.
|
||||
pub fn none() -> Self {
|
||||
Self {
|
||||
dependencies: HashSet::new(),
|
||||
functions: PrimaryMap::new().into_boxed_slice(),
|
||||
tables: PrimaryMap::new().into_boxed_slice(),
|
||||
memories: PrimaryMap::new().into_boxed_slice(),
|
||||
|
||||
@@ -20,7 +20,7 @@ use more_asserts::assert_lt;
|
||||
use std::alloc::{self, Layout};
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -39,7 +39,7 @@ cfg_if::cfg_if! {
|
||||
|
||||
impl InstanceHandle {
|
||||
/// 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
|
||||
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 {
|
||||
/// 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
|
||||
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.
|
||||
#[repr(C)]
|
||||
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.
|
||||
module: Arc<Module>,
|
||||
|
||||
@@ -878,13 +870,10 @@ impl InstanceHandle {
|
||||
trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
|
||||
imports: Imports,
|
||||
mem_creator: Option<&dyn RuntimeMemoryCreator>,
|
||||
data_initializers: &[DataInitializer<'_>],
|
||||
vmshared_signatures: BoxedSlice<SignatureIndex, VMSharedSignatureIndex>,
|
||||
dbg_jit_registration: Option<Rc<GdbJitImageRegistration>>,
|
||||
is_bulk_memory: bool,
|
||||
host_state: Box<dyn Any>,
|
||||
interrupts: Arc<VMInterrupts>,
|
||||
max_wasm_stack: usize,
|
||||
) -> Result<Self, InstantiationError> {
|
||||
let tables = create_tables(&module);
|
||||
let memories = create_memories(&module, mem_creator.unwrap_or(&DefaultMemoryCreator {}))?;
|
||||
@@ -909,8 +898,6 @@ impl InstanceHandle {
|
||||
|
||||
let handle = {
|
||||
let instance = Instance {
|
||||
refcount: Cell::new(1),
|
||||
dependencies: imports.dependencies,
|
||||
module,
|
||||
offsets,
|
||||
memories,
|
||||
@@ -983,30 +970,44 @@ impl InstanceHandle {
|
||||
);
|
||||
*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
|
||||
// when bulk memory is disabled, since the bulk memory proposal changes
|
||||
// instantiation such that the intermediate results of failed
|
||||
// initializations are visible.
|
||||
if !is_bulk_memory {
|
||||
check_table_init_bounds(instance)?;
|
||||
check_memory_init_bounds(instance, data_initializers)?;
|
||||
check_table_init_bounds(self.instance())?;
|
||||
check_memory_init_bounds(self.instance(), data_initializers)?;
|
||||
}
|
||||
|
||||
// Apply the initializers.
|
||||
initialize_tables(instance)?;
|
||||
initialize_passive_elements(instance);
|
||||
initialize_memories(instance, data_initializers)?;
|
||||
initialize_globals(instance);
|
||||
// Apply fallible initializers. Note that this can "leak" state even if
|
||||
// it fails.
|
||||
initialize_tables(self.instance())?;
|
||||
initialize_memories(self.instance(), data_initializers)?;
|
||||
|
||||
// Ensure that our signal handlers are ready for action.
|
||||
// TODO: Move these calls out of `InstanceHandle`.
|
||||
traphandlers::init();
|
||||
|
||||
// The WebAssembly spec specifies that the start function is
|
||||
// invoked automatically at instantiation time.
|
||||
instance.invoke_start_function(max_wasm_stack)?;
|
||||
|
||||
Ok(handle)
|
||||
// And finally, invoke the start function.
|
||||
self.instance().invoke_start_function(max_wasm_stack)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new `InstanceHandle` pointing at the instance
|
||||
@@ -1017,7 +1018,6 @@ impl InstanceHandle {
|
||||
/// be a `VMContext` allocated as part of an `Instance`.
|
||||
pub unsafe fn from_vmctx(vmctx: *mut VMContext) -> Self {
|
||||
let instance = (&mut *vmctx).instance();
|
||||
instance.refcount.set(instance.refcount.get() + 1);
|
||||
Self {
|
||||
instance: instance as *const Instance as *mut Instance,
|
||||
}
|
||||
@@ -1130,31 +1130,30 @@ impl InstanceHandle {
|
||||
pub(crate) fn instance(&self) -> &Instance {
|
||||
unsafe { &*(self.instance as *const Instance) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for InstanceHandle {
|
||||
fn clone(&self) -> Self {
|
||||
let instance = self.instance();
|
||||
instance.refcount.set(instance.refcount.get() + 1);
|
||||
Self {
|
||||
/// Returns a clone of this instance.
|
||||
///
|
||||
/// This is unsafe because the returned handle here is just a cheap clone
|
||||
/// of the internals, there's no lifetime tracking around its validity.
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InstanceHandle {
|
||||
fn drop(&mut self) {
|
||||
/// Deallocates memory associated with this instance.
|
||||
///
|
||||
/// 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 count = instance.refcount.get();
|
||||
instance.refcount.set(count - 1);
|
||||
if count == 1 {
|
||||
let layout = instance.alloc_layout();
|
||||
unsafe {
|
||||
ptr::drop_in_place(self.instance);
|
||||
alloc::dealloc(self.instance.cast(), layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_table_init_bounds(instance: &Instance) -> Result<(), InstantiationError> {
|
||||
|
||||
@@ -39,6 +39,7 @@ fn dtor_runs() {
|
||||
Func::wrap(&store, move || {
|
||||
drop(&a);
|
||||
});
|
||||
drop(store);
|
||||
assert_eq!(HITS.load(SeqCst), 1);
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ fn dtor_delayed() -> Result<()> {
|
||||
let module = Module::new(&store, &wasm)?;
|
||||
let instance = Instance::new(&module, &[func.into()])?;
|
||||
assert_eq!(HITS.load(SeqCst), 0);
|
||||
drop(instance);
|
||||
drop((instance, module, store));
|
||||
assert_eq!(HITS.load(SeqCst), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -15,4 +15,5 @@ mod memory_creator;
|
||||
mod name;
|
||||
mod stack_overflow;
|
||||
mod traps;
|
||||
mod use_after_drop;
|
||||
mod wast;
|
||||
|
||||
@@ -175,9 +175,9 @@ mod not_for_windows {
|
||||
let tot_pages = *mem_creator.num_total_pages.lock().unwrap();
|
||||
assert_eq!(tot_pages, 4);
|
||||
|
||||
drop(instance1);
|
||||
drop((instance1, instance2, store, module));
|
||||
let tot_pages = *mem_creator.num_total_pages.lock().unwrap();
|
||||
assert_eq!(tot_pages, 2);
|
||||
assert_eq!(tot_pages, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
21
tests/all/use_after_drop.rs
Normal file
21
tests/all/use_after_drop.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user