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, VMCallerCheckedFuncRef, VMContext,
VMExternRef, VMExternRefActivationsTable, VMRuntimeLimits, VMSharedSignatureIndex,
VMTrampoline, WasmFault,
};
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,
},
}
#[derive(Default)]
enum EpochDeadline<T> {
#[default]
Trap,
Callback(Box<dyn FnMut(StoreContextMut<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(StoreContextMut<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) -> &'a 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: &VMCallerCheckedFuncRef) -> 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);
}
pub(crate) fn wasm_fault(&self, pc: usize, addr: usize) -> Option<WasmFault> {
if addr == 0 {
return None;
}
let mut fault = None;
for instance in self.instances.iter() {
if let Some(f) = instance.handle.wasm_fault(addr) {
assert!(fault.is_none());
fault = Some(f);
}
}
if fault.is_some() {
return fault;
}
eprintln!(
"\
Wasmtime caught a segfault for a wasm program because the faulting instruction
is allowed to segfault due to how linear memories are implemented. The address
that was accessed, however, is not known to any linear memory in use within this
Store. This may be indicative of a critical bug in Wasmtime's code generation
because all addresses which are known to be reachable from wasm won't reach this
message.
pc: 0x{pc:x}
address: 0x{addr:x}
This is a possible security issue because WebAssembly has accessed something it
shouldn't have been able to. Other accesses may have succeeded and this one just
happened to be caught. The process will now be aborted to prevent this damage
from going any further and to alert what's going on. If this is a security
issue please reach out to the Wasmtime team via its security policy
at https://bytecodealliance.org/security.
"
);
std::process::abort();
}
}
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> {
let mut behavior = std::mem::take(&mut self.epoch_deadline_behavior);
let delta_result = match &mut behavior {
EpochDeadline::Trap => Err(Trap::Interrupt.into()),
EpochDeadline::Callback(callback) => {
let delta = callback((&mut *self).as_context_mut())?;
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())
}
};
self.epoch_deadline_behavior = behavior;
delta_result
}
}
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(StoreContextMut<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;
}
}
}