use crate::instance::InstanceData;
use crate::linker::Definition;
use crate::module::{BareModuleInfo, RegisteredModuleId};
use crate::trampoline::VMHostGlobalContext;
use crate::{module::ModuleRegistry, Engine, Module, Trap, Val, ValRaw};
use crate::{Global, Instance, Memory};
use anyhow::{anyhow, bail, Result};
use std::cell::UnsafeCell;
use std::fmt;
use std::future::Future;
use std::marker;
use std::mem::{self, ManuallyDrop};
use std::num::NonZeroU64;
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::{
mpk::ProtectionKey, ExportGlobal, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
ModuleInfo, OnDemandInstanceAllocator, SignalHandler, StoreBox, StorePtr, VMContext,
VMExternRef, VMExternRefActivationsTable, VMFuncRef, VMRuntimeLimits, WasmFault,
};
mod context;
pub use self::context::*;
mod data;
pub use self::data::*;
mod func_refs;
use func_refs::FuncRefs;
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:
Option<Box<dyn FnMut(StoreContextMut<T>) -> Result<UpdateDeadline> + Send + Sync>>,
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>),
}
pub enum UpdateDeadline {
Continue(u64),
#[cfg(feature = "async")]
Yield(u64),
}
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>,
#[cfg(feature = "component-model")]
num_component_instances: usize,
signal_handler: Option<Box<SignalHandler<'static>>>,
externref_activations_table: VMExternRefActivationsTable,
modules: ModuleRegistry,
func_refs: FuncRefs,
host_globals: Vec<StoreBox<VMHostGlobalContext>>,
instance_count: usize,
instance_limit: usize,
memory_count: usize,
memory_limit: usize,
table_count: usize,
table_limit: usize,
#[cfg(feature = "async")]
async_state: AsyncState,
fuel_reserve: u64,
fuel_yield_interval: Option<NonZeroU64>,
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]>>>,
pkey: Option<ProtectionKey>,
#[cfg(feature = "component-model")]
component_host_table: wasmtime_runtime::component::ResourceTable,
#[cfg(feature = "component-model")]
component_calls: wasmtime_runtime::component::CallContexts,
}
#[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 {
let _ = &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,
kind: StoreInstanceKind,
}
enum StoreInstanceKind {
Real {
module_id: RegisteredModuleId,
},
Dummy,
}
impl<T> Store<T> {
pub fn new(engine: &Engine, data: T) -> Self {
let pkey = engine.allocator().next_available_pkey();
let mut inner = Box::new(StoreInner {
inner: StoreOpaque {
_marker: marker::PhantomPinned,
engine: engine.clone(),
runtime_limits: Default::default(),
instances: Vec::new(),
#[cfg(feature = "component-model")]
num_component_instances: 0,
signal_handler: None,
externref_activations_table: VMExternRefActivationsTable::new(),
modules: ModuleRegistry::default(),
func_refs: FuncRefs::default(),
host_globals: Vec::new(),
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,
#[cfg(feature = "async")]
async_state: AsyncState {
current_suspend: UnsafeCell::new(ptr::null()),
current_poll_cx: UnsafeCell::new(ptr::null_mut()),
},
fuel_reserve: 0,
fuel_yield_interval: None,
store_data: ManuallyDrop::new(StoreData::new()),
default_caller: InstanceHandle::null(),
hostcall_val_storage: Vec::new(),
wasm_val_raw_storage: Vec::new(),
rooted_host_funcs: ManuallyDrop::new(Vec::new()),
pkey,
#[cfg(feature = "component-model")]
component_host_table: Default::default(),
#[cfg(feature = "component-model")]
component_calls: Default::default(),
},
limiter: None,
call_hook: None,
epoch_deadline_behavior: None,
data: ManuallyDrop::new(data),
});
inner.default_caller = {
let module = Arc::new(wasmtime_environ::Module::default());
let shim = BareModuleInfo::empty(module).into_traitobj();
let allocator = OnDemandInstanceAllocator::default();
allocator
.validate_module(shim.module(), shim.offsets())
.unwrap();
let mut instance = unsafe {
allocator
.allocate_module(InstanceAllocationRequest {
host_state: Box::new(()),
imports: Default::default(),
store: StorePtr::empty(),
runtime_info: &shim,
wmemcheck: engine.config().wmemcheck,
pkey: None,
})
.expect("failed to allocate default callee")
};
unsafe {
let traitobj = std::mem::transmute::<
*mut (dyn wasmtime_runtime::Store + '_),
*mut (dyn wasmtime_runtime::Store + 'static),
>(&mut *inner);
instance.set_store(traitobj);
}
instance
};
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 get_fuel(&self) -> Result<u64> {
self.inner.get_fuel()
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<()> {
self.inner.set_fuel(fuel)
}
pub fn fuel_async_yield_interval(&mut self, interval: Option<u64>) -> Result<()> {
self.inner.fuel_async_yield_interval(interval)
}
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<UpdateDeadline> + 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 get_fuel(&self) -> Result<u64> {
self.0.get_fuel()
}
}
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 get_fuel(&self) -> Result<u64> {
self.0.get_fuel()
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<()> {
self.0.set_fuel(fuel)
}
pub fn fuel_async_yield_interval(&mut self, interval: Option<u64>) -> Result<()> {
self.0.fuel_async_yield_interval(interval)
}
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<()> {
if let Some(pkey) = &self.inner.pkey {
let allocator = self.engine().allocator();
match s {
CallHook::CallingWasm | CallHook::ReturningFromHost => {
allocator.restrict_to_pkey(*pkey)
}
CallHook::ReturningFromWasm | CallHook::CallingHost => allocator.allow_all_pkeys(),
}
}
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(()),
}
}
}
fn get_fuel(injected_fuel: i64, fuel_reserve: u64) -> u64 {
fuel_reserve.saturating_add_signed(-injected_fuel)
}
fn refuel(
injected_fuel: &mut i64,
fuel_reserve: &mut u64,
yield_interval: Option<NonZeroU64>,
) -> bool {
let fuel = get_fuel(*injected_fuel, *fuel_reserve);
if fuel > 0 {
set_fuel(injected_fuel, fuel_reserve, yield_interval, fuel);
true
} else {
false
}
}
fn set_fuel(
injected_fuel: &mut i64,
fuel_reserve: &mut u64,
yield_interval: Option<NonZeroU64>,
new_fuel_amount: u64,
) {
let interval = yield_interval.unwrap_or(NonZeroU64::MAX).get();
let injected = std::cmp::min(interval, new_fuel_amount);
let injected = std::cmp::min(injected, i64::MAX as u64);
*fuel_reserve = new_fuel_amount - injected;
*injected_fuel = -(injected as i64);
}
#[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(crate) fn func_refs(&mut self) -> &mut FuncRefs {
&mut self.func_refs
}
pub(crate) fn fill_func_refs(&mut self) {
self.func_refs.fill(&mut self.modules);
}
pub(crate) fn push_instance_pre_func_refs(&mut self, func_refs: Arc<[VMFuncRef]>) {
self.func_refs.push_instance_pre_func_refs(func_refs);
}
pub(crate) fn host_globals(&mut self) -> &mut Vec<StoreBox<VMHostGlobalContext>> {
&mut self.host_globals
}
pub fn module_for_instance(&self, instance: InstanceId) -> Option<&'_ Module> {
match self.instances[instance.0].kind {
StoreInstanceKind::Dummy => None,
StoreInstanceKind::Real { module_id } => {
let module = self
.modules()
.lookup_module_by_id(module_id)
.expect("should always have a registered module for real instances");
Some(module)
}
}
}
pub unsafe fn add_instance(
&mut self,
handle: InstanceHandle,
module_id: RegisteredModuleId,
) -> InstanceId {
self.instances.push(StoreInstance {
handle: handle.clone(),
kind: StoreInstanceKind::Real { module_id },
});
InstanceId(self.instances.len() - 1)
}
pub unsafe fn add_dummy_instance(&mut self, handle: InstanceHandle) -> InstanceId {
self.instances.push(StoreInstance {
handle: handle.clone(),
kind: StoreInstanceKind::Dummy,
});
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
}
pub fn all_instances<'a>(&'a mut self) -> impl ExactSizeIterator<Item = Instance> + 'a {
let instances = self
.instances
.iter()
.enumerate()
.filter_map(|(idx, inst)| {
let id = InstanceId::from_index(idx);
if let StoreInstanceKind::Dummy = inst.kind {
None
} else {
Some(InstanceData::from_id(id))
}
})
.collect::<Vec<_>>();
instances
.into_iter()
.map(|i| Instance::from_wasmtime(i, self))
}
pub fn all_memories<'a>(&'a mut self) -> impl Iterator<Item = Memory> + 'a {
let mems = self
.instances
.iter_mut()
.flat_map(|instance| instance.handle.defined_memories())
.collect::<Vec<_>>();
mems.into_iter()
.map(|memory| unsafe { Memory::from_wasmtime_memory(memory, self) })
}
pub fn all_globals<'a>(&'a mut self) -> impl Iterator<Item = Global> + 'a {
unsafe {
let mut globals = self
.host_globals()
.iter()
.map(|global| ExportGlobal {
definition: &mut (*global.get()).global as *mut _,
global: (*global.get()).ty.to_wasm_type(),
})
.collect::<Vec<_>>();
globals.extend(
self.instances.iter_mut().flat_map(|instance| {
instance.handle.defined_globals().map(|(_i, global)| global)
}),
);
globals
.into_iter()
.map(|g| Global::from_wasmtime_global(g, self))
}
}
#[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.runtime_limits(),
&self.modules,
&mut self.externref_activations_table,
)
}
}
#[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 get_fuel(&self) -> Result<u64> {
anyhow::ensure!(
self.engine().config().tunables.consume_fuel,
"fuel is not configured in this store"
);
let injected_fuel = unsafe { *self.runtime_limits.fuel_consumed.get() };
Ok(get_fuel(injected_fuel, self.fuel_reserve))
}
fn refuel(&mut self) -> bool {
let injected_fuel = unsafe { &mut *self.runtime_limits.fuel_consumed.get() };
refuel(
injected_fuel,
&mut self.fuel_reserve,
self.fuel_yield_interval,
)
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<()> {
anyhow::ensure!(
self.engine().config().tunables.consume_fuel,
"fuel is not configured in this store"
);
let injected_fuel = unsafe { &mut *self.runtime_limits.fuel_consumed.get() };
set_fuel(
injected_fuel,
&mut self.fuel_reserve,
self.fuel_yield_interval,
fuel,
);
Ok(())
}
pub fn fuel_async_yield_interval(&mut self, interval: Option<u64>) -> Result<()> {
anyhow::ensure!(
self.engine().config().tunables.consume_fuel,
"fuel is not configured in this store"
);
anyhow::ensure!(
self.engine().config().async_support,
"async support is not configured in this store"
);
anyhow::ensure!(
interval != Some(0),
"fuel_async_yield_interval must not be 0"
);
self.fuel_yield_interval = interval.and_then(|i| NonZeroU64::new(i));
self.set_fuel(self.get_fuel()?)
}
#[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))
}
}
#[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()
}
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();
}
#[inline]
pub(crate) fn get_pkey(&self) -> Option<ProtectionKey> {
self.pkey.clone()
}
#[inline]
#[cfg(feature = "component-model")]
pub(crate) fn component_calls_and_host_table(
&mut self,
) -> (
&mut wasmtime_runtime::component::CallContexts,
&mut wasmtime_runtime::component::ResourceTable,
) {
(&mut self.component_calls, &mut self.component_host_table)
}
#[cfg(feature = "component-model")]
pub(crate) fn push_component_instance(&mut self, instance: crate::component::Instance) {
let _ = instance;
self.num_component_instances += 1;
}
}
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,
state: Some(wasmtime_runtime::AsyncWasmCallState::new()),
}
};
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,
state: Option<wasmtime_runtime::AsyncWasmCallState>,
}
unsafe impl Send for FiberFuture<'_> {}
impl FiberFuture<'_> {
fn resume(&mut self, val: Result<()>) -> Result<Result<()>, ()> {
unsafe {
let prev = self.state.take().unwrap().push();
let restore = Restore {
fiber: self,
state: Some(prev),
};
return restore.fiber.fiber.resume(val);
}
struct Restore<'a, 'b> {
fiber: &'a mut FiberFuture<'b>,
state: Option<wasmtime_runtime::PreviousAsyncWasmCallState>,
}
impl Drop for Restore<'_, '_> {
fn drop(&mut self) {
unsafe {
self.fiber.state = Some(self.state.take().unwrap().restore());
}
}
}
}
}
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.resume(Ok(())) {
Ok(result) => Poll::Ready(result),
Err(()) => {
if let Some(range) = self.fiber.stack().range() {
wasmtime_runtime::AsyncWasmCallState::assert_current_state_not_in_range(range);
}
Poll::Pending
}
}
}
}
}
impl Drop for FiberFuture<'_> {
fn drop(&mut self) {
if !self.fiber.done() {
let result = self.resume(Err(anyhow!("future dropped")));
debug_assert!(result.is_ok());
}
self.state.take().unwrap().assert_null();
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 => {}
}
(*suspend).suspend(())?;
}
}
}
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)) => {
limiter(&mut self.data).memory_growing(current, desired, maximum)
}
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
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) -> Result<()> {
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 => {
log::debug!("ignoring memory growth failure error: {error:?}");
Ok(())
}
}
}
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)) => {
limiter(&mut self.data).table_growing(current, desired, maximum)
}
#[cfg(feature = "async")]
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
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) -> Result<()> {
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 => {
log::debug!("ignoring table growth failure: {error:?}");
Ok(())
}
}
}
fn out_of_gas(&mut self) -> Result<()> {
if !self.refuel() {
return Err(Trap::OutOfFuel.into());
}
#[cfg(feature = "async")]
if self.fuel_yield_interval.is_some() {
self.async_yield_impl()?;
}
Ok(())
}
fn new_epoch(&mut self) -> Result<u64, anyhow::Error> {
let mut behavior = self.epoch_deadline_behavior.take();
let delta_result = match &mut behavior {
None => Err(Trap::Interrupt.into()),
Some(callback) => callback((&mut *self).as_context_mut()).and_then(|update| {
let delta = match update {
UpdateDeadline::Continue(delta) => delta,
#[cfg(feature = "async")]
UpdateDeadline::Yield(delta) => {
assert!(
self.async_support(),
"cannot use `UpdateDeadline::Yield` without enabling async support in the config"
);
self.async_yield_impl()?;
delta
}
};
self.set_epoch_deadline(delta);
Ok(self.get_epoch_deadline())
})
};
self.epoch_deadline_behavior = behavior;
delta_result
}
#[cfg(feature = "component-model")]
fn component_calls(&mut self) -> &mut wasmtime_runtime::component::CallContexts {
&mut self.component_calls
}
}
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 = None;
}
fn epoch_deadline_callback(
&mut self,
callback: Box<dyn FnMut(StoreContextMut<T>) -> Result<UpdateDeadline> + Send + Sync>,
) {
self.epoch_deadline_behavior = Some(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 =
Some(Box::new(move |_store| Ok(UpdateDeadline::Yield(delta))));
}
let _ = 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 let StoreInstanceKind::Dummy = instance.kind {
ondemand.deallocate_module(&mut instance.handle);
} else {
allocator.deallocate_module(&mut instance.handle);
}
}
ondemand.deallocate_module(&mut self.default_caller);
#[cfg(feature = "component-model")]
{
for _ in 0..self.num_component_instances {
allocator.decrement_component_instance_count();
}
}
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_info(pc)
}
}
struct Reset<T: Copy>(*mut T, T);
impl<T: Copy> Drop for Reset<T> {
fn drop(&mut self) {
unsafe {
*self.0 = self.1;
}
}
}
#[cfg(test)]
mod tests {
use super::{get_fuel, refuel, set_fuel};
use std::num::NonZeroU64;
struct FuelTank {
pub consumed_fuel: i64,
pub reserve_fuel: u64,
pub yield_interval: Option<NonZeroU64>,
}
impl FuelTank {
fn new() -> Self {
FuelTank {
consumed_fuel: 0,
reserve_fuel: 0,
yield_interval: None,
}
}
fn get_fuel(&self) -> u64 {
get_fuel(self.consumed_fuel, self.reserve_fuel)
}
fn refuel(&mut self) -> bool {
refuel(
&mut self.consumed_fuel,
&mut self.reserve_fuel,
self.yield_interval,
)
}
fn set_fuel(&mut self, fuel: u64) {
set_fuel(
&mut self.consumed_fuel,
&mut self.reserve_fuel,
self.yield_interval,
fuel,
);
}
}
#[test]
fn smoke() {
let mut tank = FuelTank::new();
tank.set_fuel(10);
assert_eq!(tank.consumed_fuel, -10);
assert_eq!(tank.reserve_fuel, 0);
tank.yield_interval = NonZeroU64::new(10);
tank.set_fuel(25);
assert_eq!(tank.consumed_fuel, -10);
assert_eq!(tank.reserve_fuel, 15);
}
#[test]
fn does_not_lose_precision() {
let mut tank = FuelTank::new();
tank.set_fuel(u64::MAX);
assert_eq!(tank.get_fuel(), u64::MAX);
tank.set_fuel(i64::MAX as u64);
assert_eq!(tank.get_fuel(), i64::MAX as u64);
tank.set_fuel(i64::MAX as u64 + 1);
assert_eq!(tank.get_fuel(), i64::MAX as u64 + 1);
}
#[test]
fn yielding_does_not_lose_precision() {
let mut tank = FuelTank::new();
tank.yield_interval = NonZeroU64::new(10);
tank.set_fuel(u64::MAX);
assert_eq!(tank.get_fuel(), u64::MAX);
assert_eq!(tank.consumed_fuel, -10);
assert_eq!(tank.reserve_fuel, u64::MAX - 10);
tank.yield_interval = NonZeroU64::new(u64::MAX);
tank.set_fuel(u64::MAX);
assert_eq!(tank.get_fuel(), u64::MAX);
assert_eq!(tank.consumed_fuel, -i64::MAX);
assert_eq!(tank.reserve_fuel, u64::MAX - (i64::MAX as u64));
tank.yield_interval = NonZeroU64::new((i64::MAX as u64) + 1);
tank.set_fuel(u64::MAX);
assert_eq!(tank.get_fuel(), u64::MAX);
assert_eq!(tank.consumed_fuel, -i64::MAX);
assert_eq!(tank.reserve_fuel, u64::MAX - (i64::MAX as u64));
}
#[test]
fn refueling() {
let mut tank = FuelTank::new();
tank.yield_interval = NonZeroU64::new(10);
tank.reserve_fuel = 42;
tank.consumed_fuel = 4;
assert!(tank.refuel());
assert_eq!(tank.reserve_fuel, 28);
assert_eq!(tank.consumed_fuel, -10);
tank.yield_interval = NonZeroU64::new(1);
tank.reserve_fuel = 8;
tank.consumed_fuel = 4;
assert_eq!(tank.get_fuel(), 4);
assert!(tank.refuel());
assert_eq!(tank.reserve_fuel, 3);
assert_eq!(tank.consumed_fuel, -1);
assert_eq!(tank.get_fuel(), 4);
tank.yield_interval = NonZeroU64::new(10);
tank.reserve_fuel = 3;
tank.consumed_fuel = 4;
assert_eq!(tank.get_fuel(), 0);
assert!(!tank.refuel());
assert_eq!(tank.reserve_fuel, 3);
assert_eq!(tank.consumed_fuel, 4);
assert_eq!(tank.get_fuel(), 0);
}
}