use crate::linker::Definition;
use crate::module::BareModuleInfo;
use crate::{module::ModuleRegistry, Engine, Module, Trap, Val, ValRaw};
use anyhow::{anyhow, bail, Result};
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt;
use std::future::Future;
use std::marker;
use std::mem::{self, ManuallyDrop};
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use std::ptr;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::task::{Context, Poll};
use wasmtime_runtime::{
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
OnDemandInstanceAllocator, SignalHandler, StorePtr, VMCallerCheckedAnyfunc, VMContext,
VMExternRef, VMExternRefActivationsTable, VMRuntimeLimits, VMSharedSignatureIndex,
VMTrampoline,
};
mod context;
pub use self::context::*;
mod data;
pub use self::data::*;
pub struct Store<T> {
inner: ManuallyDrop<Box<StoreInner<T>>>,
}
#[derive(Copy, Clone, Debug)]
pub enum CallHook {
CallingWasm,
ReturningFromWasm,
CallingHost,
ReturningFromHost,
}
impl CallHook {
pub fn entering_host(&self) -> bool {
match self {
CallHook::ReturningFromWasm | CallHook::CallingHost => true,
_ => false,
}
}
pub fn exiting_host(&self) -> bool {
match self {
CallHook::ReturningFromHost | CallHook::CallingWasm => true,
_ => false,
}
}
}
pub struct StoreInner<T> {
inner: StoreOpaque,
limiter: Option<ResourceLimiterInner<T>>,
call_hook: Option<CallHookInner<T>>,
epoch_deadline_behavior: EpochDeadline<T>,
data: ManuallyDrop<T>,
}
enum ResourceLimiterInner<T> {
Sync(Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync>),
#[cfg(feature = "async")]
Async(Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiterAsync) + Send + Sync>),
}
#[cfg(feature = "async")]
#[async_trait::async_trait]
pub trait CallHookHandler<T>: Send {
async fn handle_call_event(&self, t: &mut T, ch: CallHook) -> Result<()>;
}
enum CallHookInner<T> {
Sync(Box<dyn FnMut(&mut T, CallHook) -> Result<()> + Send + Sync>),
#[cfg(feature = "async")]
Async(Box<dyn CallHookHandler<T> + Send + Sync>),
}
impl<T> Deref for StoreInner<T> {
type Target = StoreOpaque;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for StoreInner<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub struct StoreOpaque {
_marker: marker::PhantomPinned,
engine: Engine,
runtime_limits: VMRuntimeLimits,
instances: Vec<StoreInstance>,
signal_handler: Option<Box<SignalHandler<'static>>>,
externref_activations_table: VMExternRefActivationsTable,
modules: ModuleRegistry,
host_trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
host_func_trampolines_registered: usize,
instance_count: usize,
instance_limit: usize,
memory_count: usize,
memory_limit: usize,
table_count: usize,
table_limit: usize,
fuel_adj: i64,
#[cfg(feature = "async")]
async_state: AsyncState,
out_of_gas_behavior: OutOfGas,
store_data: ManuallyDrop<StoreData>,
default_caller: InstanceHandle,
hostcall_val_storage: Vec<Val>,
wasm_val_raw_storage: Vec<ValRaw>,
rooted_host_funcs: ManuallyDrop<Vec<Arc<[Definition]>>>,
}
#[cfg(feature = "async")]
struct AsyncState {
current_suspend: UnsafeCell<*const wasmtime_fiber::Suspend<Result<()>, (), Result<()>>>,
current_poll_cx: UnsafeCell<*mut Context<'static>>,
}
#[cfg(feature = "async")]
unsafe impl Send for AsyncState {}
#[cfg(feature = "async")]
unsafe impl Sync for AsyncState {}
pub(crate) struct AutoAssertNoGc<T>
where
T: std::ops::DerefMut<Target = StoreOpaque>,
{
#[cfg(debug_assertions)]
prev_okay: bool,
store: T,
}
impl<T> AutoAssertNoGc<T>
where
T: std::ops::DerefMut<Target = StoreOpaque>,
{
pub fn new(mut store: T) -> Self {
drop(&mut store);
#[cfg(debug_assertions)]
{
let prev_okay = store.externref_activations_table.set_gc_okay(false);
return AutoAssertNoGc { store, prev_okay };
}
#[cfg(not(debug_assertions))]
{
return AutoAssertNoGc { store };
}
}
}
impl<T> std::ops::Deref for AutoAssertNoGc<T>
where
T: std::ops::DerefMut<Target = StoreOpaque>,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.store
}
}
impl<T> std::ops::DerefMut for AutoAssertNoGc<T>
where
T: std::ops::DerefMut<Target = StoreOpaque>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.store
}
}
impl<T> Drop for AutoAssertNoGc<T>
where
T: std::ops::DerefMut<Target = StoreOpaque>,
{
fn drop(&mut self) {
#[cfg(debug_assertions)]
{
self.store
.externref_activations_table
.set_gc_okay(self.prev_okay);
}
}
}
struct StoreInstance {
handle: InstanceHandle,
ondemand: bool,
}
#[derive(Copy, Clone)]
enum OutOfGas {
Trap,
InjectFuel {
injection_count: u64,
fuel_to_inject: u64,
},
}
enum EpochDeadline<T> {
Trap,
Callback(Box<dyn FnMut(&mut T) -> Result<u64> + Send + Sync>),
#[cfg(feature = "async")]
YieldAndExtendDeadline { delta: u64 },
}
impl<T> Store<T> {
pub fn new(engine: &Engine, data: T) -> Self {
let default_callee = {
let module = Arc::new(wasmtime_environ::Module::default());
let shim = BareModuleInfo::empty(module).into_traitobj();
OnDemandInstanceAllocator::default()
.allocate(InstanceAllocationRequest {
host_state: Box::new(()),
imports: Default::default(),
store: StorePtr::empty(),
runtime_info: &shim,
})
.expect("failed to allocate default callee")
};
let mut inner = Box::new(StoreInner {
inner: StoreOpaque {
_marker: marker::PhantomPinned,
engine: engine.clone(),
runtime_limits: Default::default(),
instances: Vec::new(),
signal_handler: None,
externref_activations_table: VMExternRefActivationsTable::new(),
modules: ModuleRegistry::default(),
host_trampolines: HashMap::default(),
host_func_trampolines_registered: 0,
instance_count: 0,
instance_limit: crate::DEFAULT_INSTANCE_LIMIT,
memory_count: 0,
memory_limit: crate::DEFAULT_MEMORY_LIMIT,
table_count: 0,
table_limit: crate::DEFAULT_TABLE_LIMIT,
fuel_adj: 0,
#[cfg(feature = "async")]
async_state: AsyncState {
current_suspend: UnsafeCell::new(ptr::null()),
current_poll_cx: UnsafeCell::new(ptr::null_mut()),
},
out_of_gas_behavior: OutOfGas::Trap,
store_data: ManuallyDrop::new(StoreData::new()),
default_caller: default_callee,
hostcall_val_storage: Vec::new(),
wasm_val_raw_storage: Vec::new(),
rooted_host_funcs: ManuallyDrop::new(Vec::new()),
},
limiter: None,
call_hook: None,
epoch_deadline_behavior: EpochDeadline::Trap,
data: ManuallyDrop::new(data),
});
unsafe {
let traitobj = std::mem::transmute::<
*mut (dyn wasmtime_runtime::Store + '_),
*mut (dyn wasmtime_runtime::Store + 'static),
>(&mut *inner);
inner.default_caller.set_store(traitobj);
}
Self {
inner: ManuallyDrop::new(inner),
}
}
#[inline]
pub fn data(&self) -> &T {
self.inner.data()
}
#[inline]
pub fn data_mut(&mut self) -> &mut T {
self.inner.data_mut()
}
pub fn into_data(mut self) -> T {
unsafe {
let mut inner = ManuallyDrop::take(&mut self.inner);
std::mem::forget(self);
ManuallyDrop::take(&mut inner.data)
}
}
pub fn limiter(
&mut self,
mut limiter: impl FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync + 'static,
) {
let inner = &mut self.inner;
let (instance_limit, table_limit, memory_limit) = {
let l = limiter(&mut inner.data);
(l.instances(), l.tables(), l.memories())
};
let innermost = &mut inner.inner;
innermost.instance_limit = instance_limit;
innermost.table_limit = table_limit;
innermost.memory_limit = memory_limit;
inner.limiter = Some(ResourceLimiterInner::Sync(Box::new(limiter)));
}
#[cfg(feature = "async")]
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
pub fn limiter_async(
&mut self,
mut limiter: impl FnMut(&mut T) -> &mut (dyn crate::ResourceLimiterAsync)
+ Send
+ Sync
+ 'static,
) {
debug_assert!(self.inner.async_support());
let inner = &mut self.inner;
let (instance_limit, table_limit, memory_limit) = {
let l = limiter(&mut inner.data);
(l.instances(), l.tables(), l.memories())
};
let innermost = &mut inner.inner;
innermost.instance_limit = instance_limit;
innermost.table_limit = table_limit;
innermost.memory_limit = memory_limit;
inner.limiter = Some(ResourceLimiterInner::Async(Box::new(limiter)));
}
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
#[cfg(feature = "async")]
pub fn call_hook_async(&mut self, hook: impl CallHookHandler<T> + Send + Sync + 'static) {
self.inner.call_hook = Some(CallHookInner::Async(Box::new(hook)));
}
pub fn call_hook(
&mut self,
hook: impl FnMut(&mut T, CallHook) -> Result<()> + Send + Sync + 'static,
) {
self.inner.call_hook = Some(CallHookInner::Sync(Box::new(hook)));
}
pub fn engine(&self) -> &Engine {
self.inner.engine()
}
pub fn gc(&mut self) {
self.inner.gc()
}
pub fn fuel_consumed(&self) -> Option<u64> {
self.inner.fuel_consumed()
}
pub fn add_fuel(&mut self, fuel: u64) -> Result<()> {
self.inner.add_fuel(fuel)
}
pub fn consume_fuel(&mut self, fuel: u64) -> Result<u64> {
self.inner.consume_fuel(fuel)
}
pub fn out_of_fuel_trap(&mut self) {
self.inner.out_of_fuel_trap()
}
pub fn out_of_fuel_async_yield(&mut self, injection_count: u64, fuel_to_inject: u64) {
self.inner
.out_of_fuel_async_yield(injection_count, fuel_to_inject)
}
pub fn set_epoch_deadline(&mut self, ticks_beyond_current: u64) {
self.inner.set_epoch_deadline(ticks_beyond_current);
}
pub fn epoch_deadline_trap(&mut self) {
self.inner.epoch_deadline_trap();
}
pub fn epoch_deadline_callback(
&mut self,
callback: impl FnMut(&mut T) -> Result<u64> + Send + Sync + 'static,
) {
self.inner.epoch_deadline_callback(Box::new(callback));
}
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
#[cfg(feature = "async")]
pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
self.inner.epoch_deadline_async_yield_and_update(delta);
}
}
impl<'a, T> StoreContext<'a, T> {
pub(crate) fn async_support(&self) -> bool {
self.0.async_support()
}
pub fn engine(&self) -> &Engine {
self.0.engine()
}
pub fn data(&self) -> &T {
self.0.data()
}
pub fn fuel_consumed(&self) -> Option<u64> {
self.0.fuel_consumed()
}
}
impl<'a, T> StoreContextMut<'a, T> {
pub fn data(&self) -> &T {
self.0.data()
}
pub fn data_mut(&mut self) -> &mut T {
self.0.data_mut()
}
pub fn engine(&self) -> &Engine {
self.0.engine()
}
pub fn gc(&mut self) {
self.0.gc()
}
pub fn fuel_consumed(&self) -> Option<u64> {
self.0.fuel_consumed()
}
pub fn add_fuel(&mut self, fuel: u64) -> Result<()> {
self.0.add_fuel(fuel)
}
pub fn consume_fuel(&mut self, fuel: u64) -> Result<u64> {
self.0.consume_fuel(fuel)
}
pub fn out_of_fuel_trap(&mut self) {
self.0.out_of_fuel_trap()
}
pub fn out_of_fuel_async_yield(&mut self, injection_count: u64, fuel_to_inject: u64) {
self.0
.out_of_fuel_async_yield(injection_count, fuel_to_inject)
}
pub fn set_epoch_deadline(&mut self, ticks_beyond_current: u64) {
self.0.set_epoch_deadline(ticks_beyond_current);
}
pub fn epoch_deadline_trap(&mut self) {
self.0.epoch_deadline_trap();
}
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
#[cfg(feature = "async")]
pub fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
self.0.epoch_deadline_async_yield_and_update(delta);
}
}
impl<T> StoreInner<T> {
#[inline]
fn data(&self) -> &T {
&self.data
}
#[inline]
fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub fn call_hook(&mut self, s: CallHook) -> Result<()> {
match &mut self.call_hook {
Some(CallHookInner::Sync(hook)) => hook(&mut self.data, s),
#[cfg(feature = "async")]
Some(CallHookInner::Async(handler)) => unsafe {
Ok(self
.inner
.async_cx()
.ok_or_else(|| anyhow!("couldn't grab async_cx for call hook"))?
.block_on(handler.handle_call_event(&mut self.data, s).as_mut())??)
},
None => Ok(()),
}
}
}
#[doc(hidden)]
impl StoreOpaque {
pub fn id(&self) -> StoreId {
self.store_data.id()
}
pub fn bump_resource_counts(&mut self, module: &Module) -> Result<()> {
fn bump(slot: &mut usize, max: usize, amt: usize, desc: &str) -> Result<()> {
let new = slot.saturating_add(amt);
if new > max {
bail!(
"resource limit exceeded: {} count too high at {}",
desc,
new
);
}
*slot = new;
Ok(())
}
let module = module.env_module();
let memories = module.memory_plans.len() - module.num_imported_memories;
let tables = module.table_plans.len() - module.num_imported_tables;
bump(&mut self.instance_count, self.instance_limit, 1, "instance")?;
bump(
&mut self.memory_count,
self.memory_limit,
memories,
"memory",
)?;
bump(&mut self.table_count, self.table_limit, tables, "table")?;
Ok(())
}
#[inline]
pub fn async_support(&self) -> bool {
cfg!(feature = "async") && self.engine().config().async_support
}
#[inline]
pub fn engine(&self) -> &Engine {
&self.engine
}
#[inline]
pub fn store_data(&self) -> &StoreData {
&self.store_data
}
#[inline]
pub fn store_data_mut(&mut self) -> &mut StoreData {
&mut self.store_data
}
#[inline]
pub(crate) fn modules(&self) -> &ModuleRegistry {
&self.modules
}
#[inline]
pub(crate) fn modules_mut(&mut self) -> &mut ModuleRegistry {
&mut self.modules
}
pub unsafe fn add_instance(&mut self, handle: InstanceHandle, ondemand: bool) -> InstanceId {
self.instances.push(StoreInstance {
handle: handle.clone(),
ondemand,
});
InstanceId(self.instances.len() - 1)
}
pub fn instance(&self, id: InstanceId) -> &InstanceHandle {
&self.instances[id.0].handle
}
pub fn instance_mut(&mut self, id: InstanceId) -> &mut InstanceHandle {
&mut self.instances[id.0].handle
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))] pub fn set_signal_handler(&mut self, handler: Option<Box<SignalHandler<'static>>>) {
self.signal_handler = handler;
}
#[inline]
pub fn runtime_limits(&self) -> &VMRuntimeLimits {
&self.runtime_limits
}
#[inline]
pub fn externref_activations_table(&mut self) -> &mut VMExternRefActivationsTable {
&mut self.externref_activations_table
}
pub fn gc(&mut self) {
unsafe { wasmtime_runtime::gc(&self.modules, &mut self.externref_activations_table) }
}
pub fn lookup_trampoline(&mut self, anyfunc: &VMCallerCheckedAnyfunc) -> VMTrampoline {
if let Some(trampoline) = self.modules.lookup_trampoline(anyfunc) {
return trampoline;
}
if let Some(trampoline) = self.host_trampolines.get(&anyfunc.type_index) {
return *trampoline;
}
for f in self
.store_data
.funcs()
.skip(self.host_func_trampolines_registered)
{
self.host_func_trampolines_registered += 1;
self.host_trampolines.insert(f.sig_index(), f.trampoline());
if f.sig_index() == anyfunc.type_index {
return f.trampoline();
}
}
panic!("trampoline missing")
}
#[cfg(feature = "async")]
#[inline]
pub fn async_cx(&self) -> Option<AsyncCx> {
debug_assert!(self.async_support());
let poll_cx_box_ptr = self.async_state.current_poll_cx.get();
if poll_cx_box_ptr.is_null() {
return None;
}
let poll_cx_inner_ptr = unsafe { *poll_cx_box_ptr };
if poll_cx_inner_ptr.is_null() {
return None;
}
Some(AsyncCx {
current_suspend: self.async_state.current_suspend.get(),
current_poll_cx: poll_cx_box_ptr,
})
}
pub fn fuel_consumed(&self) -> Option<u64> {
if !self.engine.config().tunables.consume_fuel {
return None;
}
let consumed = unsafe { *self.runtime_limits.fuel_consumed.get() };
Some(u64::try_from(self.fuel_adj + consumed).unwrap())
}
fn out_of_fuel_trap(&mut self) {
self.out_of_gas_behavior = OutOfGas::Trap;
}
fn out_of_fuel_async_yield(&mut self, injection_count: u64, fuel_to_inject: u64) {
assert!(
self.async_support(),
"cannot use `out_of_fuel_async_yield` without enabling async support in the config"
);
self.out_of_gas_behavior = OutOfGas::InjectFuel {
injection_count,
fuel_to_inject,
};
}
#[cfg(feature = "async")]
fn async_yield_impl(&mut self) -> Result<()> {
#[derive(Default)]
struct Yield {
yielded: bool,
}
impl Future for Yield {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.yielded {
Poll::Ready(())
} else {
self.yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
let mut future = Yield::default();
unsafe {
self.async_cx()
.expect("attempted to pull async context during shutdown")
.block_on(Pin::new_unchecked(&mut future))
}
}
fn add_fuel(&mut self, fuel: u64) -> Result<()> {
anyhow::ensure!(
self.engine().config().tunables.consume_fuel,
"fuel is not configured in this store"
);
let fuel = i64::try_from(fuel).unwrap_or(i64::max_value());
let adj = self.fuel_adj;
let consumed_ptr = unsafe { &mut *self.runtime_limits.fuel_consumed.get() };
match (consumed_ptr.checked_sub(fuel), adj.checked_add(fuel)) {
(Some(consumed), Some(adj)) => {
self.fuel_adj = adj;
*consumed_ptr = consumed;
}
_ => {
self.fuel_adj = i64::max_value();
*consumed_ptr = (*consumed_ptr + adj) - i64::max_value();
}
}
Ok(())
}
fn consume_fuel(&mut self, fuel: u64) -> Result<u64> {
let consumed_ptr = unsafe { &mut *self.runtime_limits.fuel_consumed.get() };
match i64::try_from(fuel)
.ok()
.and_then(|fuel| consumed_ptr.checked_add(fuel))
{
Some(consumed) if consumed <= 0 => {
*consumed_ptr = consumed;
Ok(u64::try_from(-consumed).unwrap())
}
_ => bail!("not enough fuel remaining in store"),
}
}
#[inline]
pub fn signal_handler(&self) -> Option<*const SignalHandler<'static>> {
let handler = self.signal_handler.as_ref()?;
Some(&**handler as *const _)
}
#[inline]
pub fn vmruntime_limits(&self) -> *mut VMRuntimeLimits {
&self.runtime_limits as *const VMRuntimeLimits as *mut VMRuntimeLimits
}
pub unsafe fn insert_vmexternref_without_gc(&mut self, r: VMExternRef) {
self.externref_activations_table.insert_without_gc(r);
}
#[inline]
pub fn default_caller(&self) -> *mut VMContext {
self.default_caller.vmctx_ptr()
}
pub fn traitobj(&self) -> *mut dyn wasmtime_runtime::Store {
self.default_caller.store()
}
#[inline]
pub fn take_hostcall_val_storage(&mut self) -> Vec<Val> {
mem::take(&mut self.hostcall_val_storage)
}
#[inline]
pub fn save_hostcall_val_storage(&mut self, storage: Vec<Val>) {
if storage.capacity() > self.hostcall_val_storage.capacity() {
self.hostcall_val_storage = storage;
}
}
#[inline]
pub fn take_wasm_val_raw_storage(&mut self) -> Vec<ValRaw> {
mem::take(&mut self.wasm_val_raw_storage)
}
#[inline]
pub fn save_wasm_val_raw_storage(&mut self, storage: Vec<ValRaw>) {
if storage.capacity() > self.wasm_val_raw_storage.capacity() {
self.wasm_val_raw_storage = storage;
}
}
pub(crate) fn push_rooted_funcs(&mut self, funcs: Arc<[Definition]>) {
self.rooted_host_funcs.push(funcs);
}
}
impl<T> StoreContextMut<'_, T> {
#[cfg(feature = "async")]
pub(crate) async fn on_fiber<R>(
&mut self,
func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send,
) -> Result<R>
where
T: Send,
{
let config = self.engine().config();
debug_assert!(self.0.async_support());
debug_assert!(config.async_stack_size > 0);
let mut slot = None;
let future = {
let current_poll_cx = self.0.async_state.current_poll_cx.get();
let current_suspend = self.0.async_state.current_suspend.get();
let stack = self.engine().allocator().allocate_fiber_stack()?;
let engine = self.engine().clone();
let slot = &mut slot;
let fiber = wasmtime_fiber::Fiber::new(stack, move |keep_going, suspend| {
keep_going?;
unsafe {
let _reset = Reset(current_suspend, *current_suspend);
*current_suspend = suspend;
*slot = Some(func(self));
Ok(())
}
})?;
FiberFuture {
fiber,
current_poll_cx,
engine,
}
};
future.await?;
return Ok(slot.unwrap());
struct FiberFuture<'a> {
fiber: wasmtime_fiber::Fiber<'a, Result<()>, (), Result<()>>,
current_poll_cx: *mut *mut Context<'static>,
engine: Engine,
}
unsafe impl Send for FiberFuture<'_> {}
impl Future for FiberFuture<'_> {
type Output = Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
unsafe {
let _reset = Reset(self.current_poll_cx, *self.current_poll_cx);
*self.current_poll_cx =
std::mem::transmute::<&mut Context<'_>, *mut Context<'static>>(cx);
match self.fiber.resume(Ok(())) {
Ok(result) => Poll::Ready(result),
Err(()) => Poll::Pending,
}
}
}
}
impl Drop for FiberFuture<'_> {
fn drop(&mut self) {
if !self.fiber.done() {
let result = self.fiber.resume(Err(anyhow!("future dropped")));
debug_assert!(result.is_ok());
}
unsafe {
self.engine
.allocator()
.deallocate_fiber_stack(self.fiber.stack());
}
}
}
}
}
#[cfg(feature = "async")]
pub struct AsyncCx {
current_suspend: *mut *const wasmtime_fiber::Suspend<Result<()>, (), Result<()>>,
current_poll_cx: *mut *mut Context<'static>,
}
#[cfg(feature = "async")]
impl AsyncCx {
pub unsafe fn block_on<U>(
&self,
mut future: Pin<&mut (dyn Future<Output = U> + Send)>,
) -> Result<U> {
let suspend = *self.current_suspend;
let _reset = Reset(self.current_suspend, suspend);
*self.current_suspend = ptr::null();
assert!(!suspend.is_null());
loop {
let future_result = {
let poll_cx = *self.current_poll_cx;
let _reset = Reset(self.current_poll_cx, poll_cx);
*self.current_poll_cx = ptr::null_mut();
assert!(!poll_cx.is_null());
future.as_mut().poll(&mut *poll_cx)
};
match future_result {
Poll::Ready(t) => break Ok(t),
Poll::Pending => {}
}
let before = wasmtime_runtime::TlsRestore::take();
let res = (*suspend).suspend(());
before.replace();
res?;
}
}
}
unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
fn vmruntime_limits(&self) -> *mut VMRuntimeLimits {
<StoreOpaque>::vmruntime_limits(self)
}
fn epoch_ptr(&self) -> *const AtomicU64 {
self.engine.epoch_counter() as *const _
}
fn externref_activations_table(
&mut self,
) -> (
&mut VMExternRefActivationsTable,
&dyn wasmtime_runtime::ModuleInfoLookup,
) {
let inner = &mut self.inner;
(&mut inner.externref_activations_table, &inner.modules)
}
fn memory_growing(
&mut self,
current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool, anyhow::Error> {
match self.limiter {
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
Ok(limiter(&mut self.data).memory_growing(current, desired, maximum))
}
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
Ok(self
.inner
.async_cx()
.expect("ResourceLimiterAsync requires async Store")
.block_on(
limiter(&mut self.data)
.memory_growing(current, desired, maximum)
.as_mut(),
)?)
},
None => Ok(true),
}
}
fn memory_grow_failed(&mut self, error: &anyhow::Error) {
match self.limiter {
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
limiter(&mut self.data).memory_grow_failed(error)
}
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(ref mut limiter)) => {
limiter(&mut self.data).memory_grow_failed(error)
}
None => {}
}
}
fn table_growing(
&mut self,
current: u32,
desired: u32,
maximum: Option<u32>,
) -> Result<bool, anyhow::Error> {
#[cfg(feature = "async")]
let async_cx = if self.async_support() {
Some(self.async_cx().unwrap())
} else {
None
};
match self.limiter {
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
Ok(limiter(&mut self.data).table_growing(current, desired, maximum))
}
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
Ok(async_cx
.expect("ResourceLimiterAsync requires async Store")
.block_on(
limiter(&mut self.data)
.table_growing(current, desired, maximum)
.as_mut(),
)?)
},
None => Ok(true),
}
}
fn table_grow_failed(&mut self, error: &anyhow::Error) {
match self.limiter {
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
limiter(&mut self.data).table_grow_failed(error)
}
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(ref mut limiter)) => {
limiter(&mut self.data).table_grow_failed(error)
}
None => {}
}
}
fn out_of_gas(&mut self) -> Result<(), anyhow::Error> {
return match &mut self.out_of_gas_behavior {
OutOfGas::Trap => Err(Trap::OutOfFuel.into()),
#[cfg(feature = "async")]
OutOfGas::InjectFuel {
injection_count,
fuel_to_inject,
} => {
if *injection_count == 0 {
return Err(Trap::OutOfFuel.into());
}
*injection_count -= 1;
let fuel = *fuel_to_inject;
self.async_yield_impl()?;
if fuel > 0 {
self.add_fuel(fuel).unwrap();
}
Ok(())
}
#[cfg(not(feature = "async"))]
OutOfGas::InjectFuel { .. } => unreachable!(),
};
}
fn new_epoch(&mut self) -> Result<u64, anyhow::Error> {
return match &mut self.epoch_deadline_behavior {
EpochDeadline::Trap => Err(Trap::Interrupt.into()),
EpochDeadline::Callback(callback) => {
let delta = callback(&mut self.data)?;
self.set_epoch_deadline(delta);
Ok(self.get_epoch_deadline())
}
#[cfg(feature = "async")]
EpochDeadline::YieldAndExtendDeadline { delta } => {
let delta = *delta;
self.async_yield_impl()?;
self.set_epoch_deadline(delta);
Ok(self.get_epoch_deadline())
}
};
}
}
impl<T> StoreInner<T> {
pub(crate) fn set_epoch_deadline(&mut self, delta: u64) {
let epoch_deadline = unsafe { (*self.vmruntime_limits()).epoch_deadline.get_mut() };
*epoch_deadline = self.engine().current_epoch() + delta;
}
fn epoch_deadline_trap(&mut self) {
self.epoch_deadline_behavior = EpochDeadline::Trap;
}
fn epoch_deadline_callback(
&mut self,
callback: Box<dyn FnMut(&mut T) -> Result<u64> + Send + Sync>,
) {
self.epoch_deadline_behavior = EpochDeadline::Callback(callback);
}
fn epoch_deadline_async_yield_and_update(&mut self, delta: u64) {
assert!(
self.async_support(),
"cannot use `epoch_deadline_async_yield_and_update` without enabling async support in the config"
);
#[cfg(feature = "async")]
{
self.epoch_deadline_behavior = EpochDeadline::YieldAndExtendDeadline { delta };
}
drop(delta); }
fn get_epoch_deadline(&self) -> u64 {
let epoch_deadline = unsafe { (*self.vmruntime_limits()).epoch_deadline.get_mut() };
*epoch_deadline
}
}
impl<T: Default> Default for Store<T> {
fn default() -> Store<T> {
Store::new(&Engine::default(), T::default())
}
}
impl<T: fmt::Debug> fmt::Debug for Store<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let inner = &**self.inner as *const StoreInner<T>;
f.debug_struct("Store")
.field("inner", &inner)
.field("data", &self.inner.data)
.finish()
}
}
impl<T> Drop for Store<T> {
fn drop(&mut self) {
unsafe {
ManuallyDrop::drop(&mut self.inner.data);
ManuallyDrop::drop(&mut self.inner);
}
}
}
impl Drop for StoreOpaque {
fn drop(&mut self) {
unsafe {
let allocator = self.engine.allocator();
let ondemand = OnDemandInstanceAllocator::default();
for instance in self.instances.iter_mut() {
if instance.ondemand {
ondemand.deallocate(&mut instance.handle);
} else {
allocator.deallocate(&mut instance.handle);
}
}
ondemand.deallocate(&mut self.default_caller);
ManuallyDrop::drop(&mut self.store_data);
ManuallyDrop::drop(&mut self.rooted_host_funcs);
}
}
}
impl wasmtime_runtime::ModuleInfoLookup for ModuleRegistry {
fn lookup(&self, pc: usize) -> Option<&dyn ModuleInfo> {
self.lookup_module(pc)
}
}
struct Reset<T: Copy>(*mut T, T);
impl<T: Copy> Drop for Reset<T> {
fn drop(&mut self) {
unsafe {
*self.0 = self.1;
}
}
}