Fix running enter/exit hooks on start functions (#3001)
This commit fixes running the store's enter/exit hooks into wasm which accidentally weren't run for an instance's `start` function. The fix here was mostly to just sink the enter/exit hook much lower in the code to `invoke_wasm_and_catch_traps`, which is the common entry point for all wasm calls. This did involve propagating the `StoreContext<T>` generic rather than using `StoreOpaque` unfortunately, but it is overally not too too much code and we generally wanted most of it inlined anyway.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use crate::store::{StoreData, StoreOpaque, StoreOpaqueSend, Stored};
|
use crate::store::{StoreData, StoreInnermost, StoreOpaque, Stored};
|
||||||
use crate::{
|
use crate::{
|
||||||
AsContext, AsContextMut, Engine, Extern, FuncType, Instance, InterruptHandle, StoreContext,
|
AsContext, AsContextMut, Engine, Extern, FuncType, Instance, InterruptHandle, StoreContext,
|
||||||
StoreContextMut, Trap, Val, ValType,
|
StoreContextMut, Trap, Val, ValType,
|
||||||
@@ -681,10 +681,7 @@ impl Func {
|
|||||||
"must use `call_async` when async support is enabled on the config",
|
"must use `call_async` when async support is enabled on the config",
|
||||||
);
|
);
|
||||||
let my_ty = self.ty(&store);
|
let my_ty = self.ty(&store);
|
||||||
store.as_context_mut().0.exiting_native_hook()?;
|
self.call_impl(&mut store.as_context_mut(), my_ty, params)
|
||||||
let r = self.call_impl(&mut store.as_context_mut().opaque(), my_ty, params);
|
|
||||||
store.as_context_mut().0.entering_native_hook()?;
|
|
||||||
r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invokes this function with the `params` given, returning the results
|
/// Invokes this function with the `params` given, returning the results
|
||||||
@@ -719,105 +716,110 @@ impl Func {
|
|||||||
where
|
where
|
||||||
T: Send,
|
T: Send,
|
||||||
{
|
{
|
||||||
let my_ty = self.ty(&store);
|
let mut store = store.as_context_mut();
|
||||||
store.as_context_mut().0.exiting_native_hook()?;
|
|
||||||
let r = self
|
|
||||||
._call_async(store.as_context_mut().opaque_send(), my_ty, params)
|
|
||||||
.await;
|
|
||||||
store.as_context_mut().0.entering_native_hook()?;
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
|
||||||
async fn _call_async(
|
|
||||||
&self,
|
|
||||||
mut store: StoreOpaqueSend<'_>,
|
|
||||||
my_ty: FuncType,
|
|
||||||
params: &[Val],
|
|
||||||
) -> Result<Box<[Val]>> {
|
|
||||||
assert!(
|
assert!(
|
||||||
store.async_support(),
|
store.0.async_support(),
|
||||||
"cannot use `call_async` without enabling async support in the config",
|
"cannot use `call_async` without enabling async support in the config",
|
||||||
);
|
);
|
||||||
|
let my_ty = self.ty(&store);
|
||||||
let result = store
|
let result = store
|
||||||
.on_fiber(|store| self.call_impl(store, my_ty, params))
|
.on_fiber(|store| self.call_impl(store, my_ty, params))
|
||||||
.await??;
|
.await??;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_impl(
|
fn call_impl<T>(
|
||||||
&self,
|
&self,
|
||||||
store: &mut StoreOpaque<'_>,
|
store: &mut StoreContextMut<'_, T>,
|
||||||
my_ty: FuncType,
|
my_ty: FuncType,
|
||||||
params: &[Val],
|
params: &[Val],
|
||||||
) -> Result<Box<[Val]>> {
|
) -> Result<Box<[Val]>> {
|
||||||
let data = &store[self.0];
|
let mut values_vec = write_params(&mut store.as_context_mut().opaque(), &my_ty, params)?;
|
||||||
let trampoline = data.trampoline();
|
|
||||||
let anyfunc = data.export().anyfunc;
|
|
||||||
// We need to perform a dynamic check that the arguments given to us
|
|
||||||
// match the signature of this function and are appropriate to pass to
|
|
||||||
// this function. This involves checking to make sure we have the right
|
|
||||||
// number and types of arguments as well as making sure everything is
|
|
||||||
// from the same `Store`.
|
|
||||||
if my_ty.params().len() != params.len() {
|
|
||||||
bail!(
|
|
||||||
"expected {} arguments, got {}",
|
|
||||||
my_ty.params().len(),
|
|
||||||
params.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut values_vec = vec![0; max(params.len(), my_ty.results().len())];
|
|
||||||
|
|
||||||
// Store the argument values into `values_vec`.
|
|
||||||
let param_tys = my_ty.params();
|
|
||||||
for ((arg, slot), ty) in params.iter().cloned().zip(&mut values_vec).zip(param_tys) {
|
|
||||||
if arg.ty() != ty {
|
|
||||||
bail!(
|
|
||||||
"argument type mismatch: found {} but expected {}",
|
|
||||||
arg.ty(),
|
|
||||||
ty
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if !arg.comes_from_same_store(store) {
|
|
||||||
bail!("cross-`Store` values are not currently supported");
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
arg.write_value_to(store, slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the trampoline.
|
// Call the trampoline.
|
||||||
unsafe {
|
unsafe {
|
||||||
let anyfunc = anyfunc.as_ref();
|
let data = &store.0.store_data()[self.0];
|
||||||
|
let trampoline = data.trampoline();
|
||||||
|
let anyfunc = data.export().anyfunc;
|
||||||
invoke_wasm_and_catch_traps(store, |callee| {
|
invoke_wasm_and_catch_traps(store, |callee| {
|
||||||
trampoline(
|
trampoline(
|
||||||
anyfunc.vmctx,
|
(*anyfunc.as_ptr()).vmctx,
|
||||||
callee,
|
callee,
|
||||||
anyfunc.func_ptr.as_ptr(),
|
(*anyfunc.as_ptr()).func_ptr.as_ptr(),
|
||||||
values_vec.as_mut_ptr(),
|
values_vec.as_mut_ptr(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the return values out of `values_vec`.
|
return Ok(read_results(
|
||||||
let mut results = Vec::with_capacity(my_ty.results().len());
|
&mut store.as_context_mut().opaque(),
|
||||||
for (index, ty) in my_ty.results().enumerate() {
|
&my_ty,
|
||||||
unsafe {
|
&values_vec,
|
||||||
let ptr = values_vec.as_ptr().add(index);
|
));
|
||||||
results.push(Val::read_value_from(store, ptr, ty));
|
|
||||||
|
fn write_params(
|
||||||
|
store: &mut StoreOpaque<'_>,
|
||||||
|
ty: &FuncType,
|
||||||
|
params: &[Val],
|
||||||
|
) -> Result<Vec<u128>> {
|
||||||
|
// We need to perform a dynamic check that the arguments given to us
|
||||||
|
// match the signature of this function and are appropriate to pass to
|
||||||
|
// this function. This involves checking to make sure we have the right
|
||||||
|
// number and types of arguments as well as making sure everything is
|
||||||
|
// from the same `Store`.
|
||||||
|
if ty.params().len() != params.len() {
|
||||||
|
bail!(
|
||||||
|
"expected {} arguments, got {}",
|
||||||
|
ty.params().len(),
|
||||||
|
params.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut values_vec = vec![0; max(params.len(), ty.results().len())];
|
||||||
|
|
||||||
|
// Store the argument values into `values_vec`.
|
||||||
|
let param_tys = ty.params();
|
||||||
|
for ((arg, slot), ty) in params.iter().cloned().zip(&mut values_vec).zip(param_tys) {
|
||||||
|
if arg.ty() != ty {
|
||||||
|
bail!(
|
||||||
|
"argument type mismatch: found {} but expected {}",
|
||||||
|
arg.ty(),
|
||||||
|
ty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !arg.comes_from_same_store(store) {
|
||||||
|
bail!("cross-`Store` values are not currently supported");
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
arg.write_value_to(store, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(values_vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(results.into())
|
fn read_results(
|
||||||
|
store: &mut StoreOpaque<'_>,
|
||||||
|
ty: &FuncType,
|
||||||
|
values_vec: &[u128],
|
||||||
|
) -> Box<[Val]> {
|
||||||
|
let mut results = Vec::with_capacity(ty.results().len());
|
||||||
|
for (index, ty) in ty.results().enumerate() {
|
||||||
|
unsafe {
|
||||||
|
let ptr = &values_vec[index];
|
||||||
|
results.push(Val::read_value_from(store, ptr, ty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn caller_checked_anyfunc(
|
pub(crate) fn caller_checked_anyfunc(
|
||||||
&self,
|
&self,
|
||||||
store: &StoreOpaque,
|
store: &StoreInnermost,
|
||||||
) -> NonNull<VMCallerCheckedAnyfunc> {
|
) -> NonNull<VMCallerCheckedAnyfunc> {
|
||||||
store[self.0].export().anyfunc
|
store.store_data()[self.0].export().anyfunc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn from_wasmtime_function(
|
pub(crate) unsafe fn from_wasmtime_function(
|
||||||
@@ -1033,28 +1035,39 @@ impl Func {
|
|||||||
///
|
///
|
||||||
/// The `closure` provided receives a default "callee" `VMContext` parameter it
|
/// The `closure` provided receives a default "callee" `VMContext` parameter it
|
||||||
/// can pass to the called wasm function, if desired.
|
/// can pass to the called wasm function, if desired.
|
||||||
#[inline]
|
pub(crate) fn invoke_wasm_and_catch_traps<T>(
|
||||||
pub(crate) fn invoke_wasm_and_catch_traps(
|
store: &mut StoreContextMut<'_, T>,
|
||||||
store: &mut StoreOpaque<'_>,
|
|
||||||
closure: impl FnMut(*mut VMContext),
|
closure: impl FnMut(*mut VMContext),
|
||||||
) -> Result<(), Trap> {
|
) -> Result<(), Trap> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let exit = if store.externref_activations_table().stack_canary().is_some() {
|
let exit = if store
|
||||||
|
.0
|
||||||
|
.externref_activations_table()
|
||||||
|
.stack_canary()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
enter_wasm(store)?;
|
enter_wasm(store)?;
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Err(trap) = store.0.exiting_native_hook() {
|
||||||
|
if exit {
|
||||||
|
exit_wasm(store);
|
||||||
|
}
|
||||||
|
return Err(trap);
|
||||||
|
}
|
||||||
let result = wasmtime_runtime::catch_traps(
|
let result = wasmtime_runtime::catch_traps(
|
||||||
store.vminterrupts(),
|
store.0.vminterrupts(),
|
||||||
store.signal_handler(),
|
store.0.signal_handler(),
|
||||||
store.default_callee(),
|
store.0.default_callee(),
|
||||||
closure,
|
closure,
|
||||||
);
|
);
|
||||||
if exit {
|
if exit {
|
||||||
exit_wasm(store);
|
exit_wasm(store);
|
||||||
}
|
}
|
||||||
|
store.0.entering_native_hook()?;
|
||||||
result.map_err(Trap::from_runtime)
|
result.map_err(Trap::from_runtime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1076,8 +1089,7 @@ pub(crate) fn invoke_wasm_and_catch_traps(
|
|||||||
///
|
///
|
||||||
/// This function may fail if the the stack limit can't be set because an
|
/// This function may fail if the the stack limit can't be set because an
|
||||||
/// interrupt already happened.
|
/// interrupt already happened.
|
||||||
#[inline]
|
fn enter_wasm<T>(store: &mut StoreContextMut<'_, T>) -> Result<(), Trap> {
|
||||||
fn enter_wasm(store: &mut StoreOpaque<'_>) -> Result<(), Trap> {
|
|
||||||
let stack_pointer = psm::stack_pointer() as usize;
|
let stack_pointer = psm::stack_pointer() as usize;
|
||||||
|
|
||||||
// Determine the stack pointer where, after which, any wasm code will
|
// Determine the stack pointer where, after which, any wasm code will
|
||||||
@@ -1107,7 +1119,7 @@ fn enter_wasm(store: &mut StoreOpaque<'_>) -> Result<(), Trap> {
|
|||||||
// synchronize with any other memory it's hoped that the choice of `Relaxed`
|
// synchronize with any other memory it's hoped that the choice of `Relaxed`
|
||||||
// here should be correct for our use case.
|
// here should be correct for our use case.
|
||||||
let wasm_stack_limit = stack_pointer - store.engine().config().max_wasm_stack;
|
let wasm_stack_limit = stack_pointer - store.engine().config().max_wasm_stack;
|
||||||
let interrupts = store.interrupts();
|
let interrupts = store.0.interrupts();
|
||||||
match interrupts.stack_limit.swap(wasm_stack_limit, Relaxed) {
|
match interrupts.stack_limit.swap(wasm_stack_limit, Relaxed) {
|
||||||
wasmtime_environ::INTERRUPTED => {
|
wasmtime_environ::INTERRUPTED => {
|
||||||
// This means that an interrupt happened before we actually
|
// This means that an interrupt happened before we actually
|
||||||
@@ -1123,18 +1135,19 @@ fn enter_wasm(store: &mut StoreOpaque<'_>) -> Result<(), Trap> {
|
|||||||
n => debug_assert_eq!(usize::max_value(), n),
|
n => debug_assert_eq!(usize::max_value(), n),
|
||||||
}
|
}
|
||||||
store
|
store
|
||||||
|
.0
|
||||||
.externref_activations_table()
|
.externref_activations_table()
|
||||||
.set_stack_canary(Some(stack_pointer));
|
.set_stack_canary(Some(stack_pointer));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
fn exit_wasm<T>(store: &mut StoreContextMut<'_, T>) {
|
||||||
fn exit_wasm(store: &mut StoreOpaque<'_>) {
|
store.0.externref_activations_table().set_stack_canary(None);
|
||||||
store.externref_activations_table().set_stack_canary(None);
|
|
||||||
|
|
||||||
// see docs above for why this uses `Relaxed`
|
// see docs above for why this uses `Relaxed`
|
||||||
store
|
store
|
||||||
|
.0
|
||||||
.interrupts()
|
.interrupts()
|
||||||
.stack_limit
|
.stack_limit
|
||||||
.store(usize::max_value(), Relaxed);
|
.store(usize::max_value(), Relaxed);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::{invoke_wasm_and_catch_traps, HostAbi};
|
use super::{invoke_wasm_and_catch_traps, HostAbi};
|
||||||
use crate::store::StoreOpaque;
|
use crate::store::StoreOpaque;
|
||||||
use crate::{AsContextMut, ExternRef, Func, Trap, ValType};
|
use crate::{AsContextMut, ExternRef, Func, StoreContextMut, Trap, ValType};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use std::marker;
|
use std::marker;
|
||||||
use std::mem::{self, MaybeUninit};
|
use std::mem::{self, MaybeUninit};
|
||||||
@@ -71,15 +71,12 @@ where
|
|||||||
/// This function will panic if it is called when the underlying [`Func`] is
|
/// This function will panic if it is called when the underlying [`Func`] is
|
||||||
/// connected to an asynchronous store.
|
/// connected to an asynchronous store.
|
||||||
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Results, Trap> {
|
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Results, Trap> {
|
||||||
store.as_context_mut().0.exiting_native_hook()?;
|
let mut store = store.as_context_mut();
|
||||||
let mut store_opaque = store.as_context_mut().opaque();
|
|
||||||
assert!(
|
assert!(
|
||||||
!store_opaque.async_support(),
|
!store.0.async_support(),
|
||||||
"must use `call_async` with async stores"
|
"must use `call_async` with async stores"
|
||||||
);
|
);
|
||||||
let r = unsafe { self._call(&mut store_opaque, params) };
|
unsafe { self._call(&mut store, params) }
|
||||||
store.as_context_mut().0.entering_native_hook()?;
|
|
||||||
r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invokes this WebAssembly function with the specified parameters.
|
/// Invokes this WebAssembly function with the specified parameters.
|
||||||
@@ -103,24 +100,25 @@ where
|
|||||||
where
|
where
|
||||||
T: Send,
|
T: Send,
|
||||||
{
|
{
|
||||||
store.as_context_mut().0.exiting_native_hook()?;
|
let mut store = store.as_context_mut();
|
||||||
let mut store_opaque = store.as_context_mut().opaque_send();
|
|
||||||
assert!(
|
assert!(
|
||||||
store_opaque.async_support(),
|
store.0.async_support(),
|
||||||
"must use `call` with non-async stores"
|
"must use `call` with non-async stores"
|
||||||
);
|
);
|
||||||
let r = store_opaque
|
store
|
||||||
.on_fiber(|store| unsafe { self._call(store, params) })
|
.on_fiber(|store| unsafe { self._call(store, params) })
|
||||||
.await?;
|
.await?
|
||||||
store.as_context_mut().0.entering_native_hook()?;
|
|
||||||
r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn _call(&self, store: &mut StoreOpaque<'_>, params: Params) -> Result<Results, Trap> {
|
unsafe fn _call<T>(
|
||||||
|
&self,
|
||||||
|
store: &mut StoreContextMut<'_, T>,
|
||||||
|
params: Params,
|
||||||
|
) -> Result<Results, Trap> {
|
||||||
// Validate that all runtime values flowing into this store indeed
|
// Validate that all runtime values flowing into this store indeed
|
||||||
// belong within this store, otherwise it would be unsafe for store
|
// belong within this store, otherwise it would be unsafe for store
|
||||||
// values to cross each other.
|
// values to cross each other.
|
||||||
let params = match params.into_abi(store) {
|
let params = match params.into_abi(&mut store.as_context_mut().opaque()) {
|
||||||
Some(abi) => abi,
|
Some(abi) => abi,
|
||||||
None => {
|
None => {
|
||||||
return Err(Trap::new(
|
return Err(Trap::new(
|
||||||
@@ -135,7 +133,7 @@ where
|
|||||||
// other side of a C++ shim, so it can never be inlined enough to make
|
// other side of a C++ shim, so it can never be inlined enough to make
|
||||||
// the memory go away, so the size matters here for performance.
|
// the memory go away, so the size matters here for performance.
|
||||||
let mut captures = (
|
let mut captures = (
|
||||||
self.func.caller_checked_anyfunc(store),
|
self.func.caller_checked_anyfunc(store.0),
|
||||||
MaybeUninit::uninit(),
|
MaybeUninit::uninit(),
|
||||||
params,
|
params,
|
||||||
false,
|
false,
|
||||||
@@ -156,7 +154,10 @@ where
|
|||||||
let (_, ret, _, returned) = captures;
|
let (_, ret, _, returned) = captures;
|
||||||
debug_assert_eq!(result.is_ok(), returned);
|
debug_assert_eq!(result.is_ok(), returned);
|
||||||
result?;
|
result?;
|
||||||
Ok(Results::from_abi(store, ret.assume_init()))
|
Ok(Results::from_abi(
|
||||||
|
&mut store.as_context_mut().opaque(),
|
||||||
|
ret.assume_init(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::linker::Definition;
|
use crate::linker::Definition;
|
||||||
use crate::signatures::SignatureCollection;
|
use crate::signatures::SignatureCollection;
|
||||||
use crate::store::{InstanceId, StoreData, StoreOpaque, StoreOpaqueSend, Stored};
|
use crate::store::{InstanceId, StoreData, StoreOpaque, Stored};
|
||||||
use crate::types::matching;
|
use crate::types::matching;
|
||||||
use crate::{
|
use crate::{
|
||||||
AsContext, AsContextMut, Engine, Export, Extern, ExternType, Func, Global, InstanceType,
|
AsContext, AsContextMut, Engine, Export, Extern, ExternType, Func, Global, InstanceType,
|
||||||
@@ -128,7 +128,7 @@ impl Instance {
|
|||||||
typecheck_externs(&mut cx, module, imports)?;
|
typecheck_externs(&mut cx, module, imports)?;
|
||||||
Instantiator::new(&mut cx, module, ImportSource::Externs(imports))?
|
Instantiator::new(&mut cx, module, ImportSource::Externs(imports))?
|
||||||
};
|
};
|
||||||
i.run(store.as_context_mut().opaque())
|
i.run(&mut store.as_context_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as [`Instance::new`], except for usage in [asynchronous stores].
|
/// Same as [`Instance::new`], except for usage in [asynchronous stores].
|
||||||
@@ -164,7 +164,7 @@ impl Instance {
|
|||||||
typecheck_externs(&mut cx, module, imports)?;
|
typecheck_externs(&mut cx, module, imports)?;
|
||||||
Instantiator::new(&mut cx, module, ImportSource::Externs(imports))?
|
Instantiator::new(&mut cx, module, ImportSource::Externs(imports))?
|
||||||
};
|
};
|
||||||
i.run_async(store.as_context_mut().opaque_send()).await
|
i.run_async(&mut store.as_context_mut()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_wasmtime(handle: InstanceData, store: &mut StoreOpaque) -> Instance {
|
pub(crate) fn from_wasmtime(handle: InstanceData, store: &mut StoreOpaque) -> Instance {
|
||||||
@@ -466,18 +466,20 @@ impl<'a> Instantiator<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self, mut store: StoreOpaque<'_>) -> Result<Instance, Error> {
|
fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<Instance, Error> {
|
||||||
assert!(
|
assert!(
|
||||||
!store.async_support(),
|
!store.0.async_support(),
|
||||||
"cannot use `new` when async support is enabled on the config"
|
"cannot use `new` when async support is enabled on the config"
|
||||||
);
|
);
|
||||||
|
|
||||||
// NB: this is the same code as `run_async`. It's intentionally
|
// NB: this is the same code as `run_async`. It's intentionally
|
||||||
// small but should be kept in sync (modulo the async bits).
|
// small but should be kept in sync (modulo the async bits).
|
||||||
loop {
|
loop {
|
||||||
if let Some((instance, start, toplevel)) = self.step(&mut store)? {
|
if let Some((instance, start, toplevel)) =
|
||||||
|
self.step(&mut store.as_context_mut().opaque())?
|
||||||
|
{
|
||||||
if let Some(start) = start {
|
if let Some(start) = start {
|
||||||
Instantiator::start_raw(&mut store, instance, start)?;
|
Instantiator::start_raw(store, instance, start)?;
|
||||||
}
|
}
|
||||||
if toplevel {
|
if toplevel {
|
||||||
break Ok(instance);
|
break Ok(instance);
|
||||||
@@ -487,16 +489,19 @@ impl<'a> Instantiator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
async fn run_async(&mut self, mut store: StoreOpaqueSend<'_>) -> Result<Instance, Error> {
|
async fn run_async<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<Instance, Error>
|
||||||
|
where
|
||||||
|
T: Send,
|
||||||
|
{
|
||||||
assert!(
|
assert!(
|
||||||
store.async_support(),
|
store.0.async_support(),
|
||||||
"cannot use `new_async` without enabling async support on the config"
|
"cannot use `new_async` without enabling async support on the config"
|
||||||
);
|
);
|
||||||
|
|
||||||
// NB: this is the same code as `run`. It's intentionally
|
// NB: this is the same code as `run`. It's intentionally
|
||||||
// small but should be kept in sync (modulo the async bits).
|
// small but should be kept in sync (modulo the async bits).
|
||||||
loop {
|
loop {
|
||||||
let step = self.step(&mut store.opaque())?;
|
let step = self.step(&mut store.as_context_mut().opaque())?;
|
||||||
if let Some((instance, start, toplevel)) = step {
|
if let Some((instance, start, toplevel)) = step {
|
||||||
if let Some(start) = start {
|
if let Some(start) = start {
|
||||||
store
|
store
|
||||||
@@ -817,14 +822,18 @@ impl<'a> Instantiator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_raw(store: &mut StoreOpaque<'_>, instance: Instance, start: FuncIndex) -> Result<()> {
|
fn start_raw<T>(
|
||||||
let id = match &store[instance.0] {
|
store: &mut StoreContextMut<'_, T>,
|
||||||
|
instance: Instance,
|
||||||
|
start: FuncIndex,
|
||||||
|
) -> Result<()> {
|
||||||
|
let id = match &store.0.store_data()[instance.0] {
|
||||||
InstanceData::Instantiated { id, .. } => *id,
|
InstanceData::Instantiated { id, .. } => *id,
|
||||||
InstanceData::Synthetic(_) => return Ok(()),
|
InstanceData::Synthetic(_) => return Ok(()),
|
||||||
};
|
};
|
||||||
// If a start function is present, invoke it. Make sure we use all the
|
// If a start function is present, invoke it. Make sure we use all the
|
||||||
// trap-handling configuration in `store` as well.
|
// trap-handling configuration in `store` as well.
|
||||||
let instance = store.instance(id);
|
let instance = store.0.instance(id);
|
||||||
let f = match instance.lookup_by_declaration(&EntityIndex::Function(start)) {
|
let f = match instance.lookup_by_declaration(&EntityIndex::Function(start)) {
|
||||||
wasmtime_runtime::Export::Function(f) => f,
|
wasmtime_runtime::Export::Function(f) => f,
|
||||||
_ => unreachable!(), // valid modules shouldn't hit this
|
_ => unreachable!(), // valid modules shouldn't hit this
|
||||||
@@ -941,20 +950,20 @@ impl<T> InstancePre<T> {
|
|||||||
/// Panics if any import closed over by this [`InstancePre`] isn't owned by
|
/// Panics if any import closed over by this [`InstancePre`] isn't owned by
|
||||||
/// `store`, or if `store` has async support enabled.
|
/// `store`, or if `store` has async support enabled.
|
||||||
pub fn instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
|
pub fn instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
|
||||||
let mut store = store.as_context_mut().opaque();
|
|
||||||
// For the unsafety here the typecheck happened at creation time of this
|
// For the unsafety here the typecheck happened at creation time of this
|
||||||
// structure and then othrewise the `T` of `InstancePre<T>` connects any
|
// structure and then othrewise the `T` of `InstancePre<T>` connects any
|
||||||
// host functions we have in our definition list to the `store` that was
|
// host functions we have in our definition list to the `store` that was
|
||||||
// passed in.
|
// passed in.
|
||||||
unsafe {
|
let mut instantiator = unsafe {
|
||||||
|
let mut store = store.as_context_mut().opaque();
|
||||||
self.ensure_comes_from_same_store(&store)?;
|
self.ensure_comes_from_same_store(&store)?;
|
||||||
Instantiator::new(
|
Instantiator::new(
|
||||||
&mut store,
|
&mut store,
|
||||||
&self.module,
|
&self.module,
|
||||||
ImportSource::Definitions(&self.items),
|
ImportSource::Definitions(&self.items),
|
||||||
)?
|
)?
|
||||||
.run(store)
|
};
|
||||||
}
|
instantiator.run(&mut store.as_context_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new instance, running the start function asynchronously
|
/// Creates a new instance, running the start function asynchronously
|
||||||
@@ -986,7 +995,7 @@ impl<T> InstancePre<T> {
|
|||||||
ImportSource::Definitions(&self.items),
|
ImportSource::Definitions(&self.items),
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
i.run_async(store.as_context_mut().opaque_send()).await
|
i.run_async(&mut store.as_context_mut()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_comes_from_same_store(&self, store: &StoreOpaque<'_>) -> Result<()> {
|
fn ensure_comes_from_same_store(&self, store: &StoreOpaque<'_>) -> Result<()> {
|
||||||
|
|||||||
@@ -921,7 +921,7 @@ impl StoreInnermost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoreOpaqueSend<'_> {
|
impl<T> StoreContextMut<'_, T> {
|
||||||
/// Executes a synchronous computation `func` asynchronously on a new fiber.
|
/// Executes a synchronous computation `func` asynchronously on a new fiber.
|
||||||
///
|
///
|
||||||
/// This function will convert the synchronous `func` into an asynchronous
|
/// This function will convert the synchronous `func` into an asynchronous
|
||||||
@@ -932,21 +932,23 @@ impl StoreOpaqueSend<'_> {
|
|||||||
/// necessary to suspend the fiber later on and poll sub-futures. It's hoped
|
/// necessary to suspend the fiber later on and poll sub-futures. It's hoped
|
||||||
/// that the various comments are illuminating as to what's going on here.
|
/// that the various comments are illuminating as to what's going on here.
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
pub async fn on_fiber<R>(
|
pub(crate) async fn on_fiber<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
func: impl FnOnce(&mut StoreOpaque<'_>) -> R + Send,
|
func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send,
|
||||||
) -> Result<R, Trap> {
|
) -> Result<R, Trap>
|
||||||
let config = self.engine.config();
|
where
|
||||||
|
T: Send,
|
||||||
debug_assert!(self.async_support());
|
{
|
||||||
|
let config = self.engine().config();
|
||||||
|
debug_assert!(self.0.async_support());
|
||||||
debug_assert!(config.async_stack_size > 0);
|
debug_assert!(config.async_stack_size > 0);
|
||||||
|
|
||||||
let mut slot = None;
|
let mut slot = None;
|
||||||
let future = {
|
let future = {
|
||||||
let current_poll_cx = self.async_state.current_poll_cx.get();
|
let current_poll_cx = self.0.async_state.current_poll_cx.get();
|
||||||
let current_suspend = self.async_state.current_suspend.get();
|
let current_suspend = self.0.async_state.current_suspend.get();
|
||||||
let stack = self
|
let stack = self
|
||||||
.engine
|
.engine()
|
||||||
.allocator()
|
.allocator()
|
||||||
.allocate_fiber_stack()
|
.allocate_fiber_stack()
|
||||||
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
|
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
|
||||||
@@ -970,7 +972,7 @@ impl StoreOpaqueSend<'_> {
|
|||||||
let _reset = Reset(current_suspend, *current_suspend);
|
let _reset = Reset(current_suspend, *current_suspend);
|
||||||
*current_suspend = suspend;
|
*current_suspend = suspend;
|
||||||
|
|
||||||
*slot = Some(func(&mut self.opaque()));
|
*slot = Some(func(self));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -50,18 +50,6 @@ impl<'a, T> StoreContextMut<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `opaque`, but produces a value which implements the `Send`
|
|
||||||
/// trait. Useful for futures.
|
|
||||||
pub(crate) fn opaque_send(mut self) -> StoreOpaqueSend<'a>
|
|
||||||
where
|
|
||||||
T: Send,
|
|
||||||
{
|
|
||||||
StoreOpaqueSend {
|
|
||||||
traitobj: self.traitobj(),
|
|
||||||
inner: self.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn traitobj(&mut self) -> *mut dyn wasmtime_runtime::Store {
|
fn traitobj(&mut self) -> *mut dyn wasmtime_runtime::Store {
|
||||||
unsafe {
|
unsafe {
|
||||||
std::mem::transmute::<
|
std::mem::transmute::<
|
||||||
@@ -241,38 +229,3 @@ impl<'a> DerefMut for StoreOpaque<'a> {
|
|||||||
&mut *self.inner
|
&mut *self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StoreOpaqueSend<'a> {
|
|
||||||
/// The actual pointer to the `StoreInner` internals.
|
|
||||||
inner: &'a mut StoreInnermost,
|
|
||||||
pub traitobj: *mut dyn wasmtime_runtime::Store,
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is needed due to the raw pointer `traitobj`, which is simply a pointer
|
|
||||||
// to `inner` which is already `Send`.
|
|
||||||
unsafe impl Send for StoreOpaqueSend<'_> {}
|
|
||||||
|
|
||||||
impl StoreOpaqueSend<'_> {
|
|
||||||
pub fn opaque(&mut self) -> StoreOpaque<'_> {
|
|
||||||
StoreOpaque {
|
|
||||||
inner: &mut *self.inner,
|
|
||||||
traitobj: self.traitobj,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Deref for StoreOpaqueSend<'a> {
|
|
||||||
type Target = StoreInnermost;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&*self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DerefMut for StoreOpaqueSend<'a> {
|
|
||||||
#[inline]
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut *self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -200,6 +200,43 @@ async fn call_linked_func_async() -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instantiate() -> Result<(), Error> {
|
||||||
|
let mut store = Store::<State>::default();
|
||||||
|
store.entering_native_code_hook(State::entering_native);
|
||||||
|
store.exiting_native_code_hook(State::exiting_native);
|
||||||
|
|
||||||
|
let m = Module::new(store.engine(), "(module)")?;
|
||||||
|
Instance::new(&mut store, &m, &[])?;
|
||||||
|
assert_eq!(store.data().switches_into_native, 0);
|
||||||
|
|
||||||
|
let m = Module::new(store.engine(), "(module (func) (start 0))")?;
|
||||||
|
Instance::new(&mut store, &m, &[])?;
|
||||||
|
assert_eq!(store.data().switches_into_native, 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn instantiate_async() -> Result<(), Error> {
|
||||||
|
let mut config = Config::new();
|
||||||
|
config.async_support(true);
|
||||||
|
let engine = Engine::new(&config)?;
|
||||||
|
let mut store = Store::new(&engine, State::default());
|
||||||
|
store.entering_native_code_hook(State::entering_native);
|
||||||
|
store.exiting_native_code_hook(State::exiting_native);
|
||||||
|
|
||||||
|
let m = Module::new(store.engine(), "(module)")?;
|
||||||
|
Instance::new_async(&mut store, &m, &[]).await?;
|
||||||
|
assert_eq!(store.data().switches_into_native, 0);
|
||||||
|
|
||||||
|
let m = Module::new(store.engine(), "(module (func) (start 0))")?;
|
||||||
|
Instance::new_async(&mut store, &m, &[]).await?;
|
||||||
|
assert_eq!(store.data().switches_into_native, 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
enum Context {
|
enum Context {
|
||||||
Native,
|
Native,
|
||||||
Vm,
|
Vm,
|
||||||
|
|||||||
Reference in New Issue
Block a user