use crate::{module::ModuleRegistry, Engine, Module, Trap};
use anyhow::{bail, Result};
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::marker;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::pin::Pin;
use std::ptr;
use std::sync::Arc;
use std::task::{Context, Poll};
use wasmtime_runtime::{
InstanceAllocationRequest, InstanceAllocator, InstanceHandle, ModuleInfo,
OnDemandInstanceAllocator, SignalHandler, VMCallerCheckedAnyfunc, VMContext, VMExternRef,
VMExternRefActivationsTable, VMInterrupts, VMSharedSignatureIndex, VMTrampoline,
};
mod context;
pub use self::context::*;
mod data;
pub use self::data::*;
pub struct Store<T> {
inner: ManuallyDrop<Box<StoreInner<T>>>,
}
pub struct StoreInner<T> {
_marker: marker::PhantomPinned,
inner: StoreInnermost,
limiter: Option<Box<dyn FnMut(&mut T) -> &mut (dyn crate::ResourceLimiter) + Send + Sync>>,
entering_native_hook: Option<Box<dyn FnMut(&mut T) -> Result<(), crate::Trap> + Send + Sync>>,
exiting_native_hook: Option<Box<dyn FnMut(&mut T) -> Result<(), crate::Trap> + Send + Sync>>,
data: ManuallyDrop<T>,
}
impl<T> Deref for StoreInner<T> {
type Target = StoreInnermost;
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 StoreInnermost {
engine: Engine,
interrupts: Arc<VMInterrupts>,
instances: Vec<StoreInstance>,
signal_handler: Option<Box<SignalHandler<'static>>>,
externref_activations_table: VMExternRefActivationsTable,
modules: ModuleRegistry,
host_trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
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: StoreData,
default_callee: InstanceHandle,
}
#[cfg(feature = "async")]
struct AsyncState {
current_suspend:
UnsafeCell<*const wasmtime_fiber::Suspend<Result<(), Trap>, (), Result<(), Trap>>>,
current_poll_cx: UnsafeCell<*mut Context<'static>>,
}
#[cfg(feature = "async")]
unsafe impl Send for AsyncState {}
#[cfg(feature = "async")]
unsafe impl Sync for AsyncState {}
struct StoreInstance {
handle: InstanceHandle,
ondemand: bool,
}
#[derive(Copy, Clone)]
enum OutOfGas {
Trap,
InjectFuel {
injection_count: u32,
fuel_to_inject: u64,
},
}
impl<T> Store<T> {
pub fn new(engine: &Engine, data: T) -> Self {
let finished_functions = &Default::default();
let default_callee = unsafe {
OnDemandInstanceAllocator::default()
.allocate(InstanceAllocationRequest {
host_state: Box::new(()),
finished_functions,
shared_signatures: None.into(),
imports: Default::default(),
module: Arc::new(wasmtime_environ::Module::default()),
store: None,
})
.expect("failed to allocate default callee")
};
let mut inner = Box::new(StoreInner {
_marker: marker::PhantomPinned,
inner: StoreInnermost {
engine: engine.clone(),
interrupts: Default::default(),
instances: Vec::new(),
signal_handler: None,
externref_activations_table: VMExternRefActivationsTable::new(),
modules: ModuleRegistry::default(),
host_trampolines: HashMap::default(),
instance_count: 0,
instance_limit: wasmtime_runtime::DEFAULT_INSTANCE_LIMIT,
memory_count: 0,
memory_limit: wasmtime_runtime::DEFAULT_MEMORY_LIMIT,
table_count: 0,
table_limit: wasmtime_runtime::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: StoreData::new(),
default_callee,
},
limiter: None,
entering_native_hook: None,
exiting_native_hook: None,
data: ManuallyDrop::new(data),
});
let store = StoreContextMut(&mut *inner).opaque().traitobj;
unsafe {
inner.default_callee.set_store(store);
}
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(Box::new(limiter));
}
pub fn entering_native_code_hook(
&mut self,
hook: impl FnMut(&mut T) -> Result<(), Trap> + Send + Sync + 'static,
) {
self.inner.entering_native_hook = Some(Box::new(hook));
}
pub fn exiting_native_code_hook(
&mut self,
hook: impl FnMut(&mut T) -> Result<(), Trap> + Send + Sync + 'static,
) {
self.inner.exiting_native_hook = Some(Box::new(hook));
}
pub fn engine(&self) -> &Engine {
self.inner.engine()
}
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
self.inner.interrupt_handle()
}
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 out_of_fuel_trap(&mut self) {
self.inner.out_of_fuel_trap()
}
pub fn out_of_fuel_async_yield(&mut self, injection_count: u32, fuel_to_inject: u64) {
self.inner
.out_of_fuel_async_yield(injection_count, fuel_to_inject)
}
}
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 interrupt_handle(&self) -> Result<InterruptHandle> {
self.0.interrupt_handle()
}
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 interrupt_handle(&self) -> Result<InterruptHandle> {
self.0.interrupt_handle()
}
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 out_of_fuel_trap(&mut self) {
self.0.out_of_fuel_trap()
}
pub fn out_of_fuel_async_yield(&mut self, injection_count: u32, fuel_to_inject: u64) {
self.0
.out_of_fuel_async_yield(injection_count, fuel_to_inject)
}
pub(crate) fn store_data(self) -> &'a StoreData {
self.0.store_data()
}
}
impl<T> StoreInner<T> {
#[inline]
fn data(&self) -> &T {
&self.data
}
#[inline]
fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub fn limiter(&mut self) -> Option<&mut dyn crate::limits::ResourceLimiter> {
let accessor = self.limiter.as_mut()?;
Some(accessor(&mut self.data))
}
pub fn entering_native_hook(&mut self) -> Result<(), Trap> {
if let Some(hook) = &mut self.entering_native_hook {
hook(&mut self.data)
} else {
Ok(())
}
}
pub fn exiting_native_hook(&mut self) -> Result<(), Trap> {
if let Some(hook) = &mut self.exiting_native_hook {
hook(&mut self.data)
} else {
Ok(())
}
}
}
impl StoreInnermost {
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
}
pub fn store_data(&self) -> &StoreData {
&self.store_data
}
pub fn store_data_mut(&mut self) -> &mut StoreData {
&mut self.store_data
}
pub fn register_host_trampoline(
&mut self,
idx: VMSharedSignatureIndex,
trampoline: VMTrampoline,
) {
self.host_trampolines.insert(idx, trampoline);
}
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
if self.engine.config().tunables.interruptable {
Ok(InterruptHandle {
interrupts: self.interrupts.clone(),
})
} else {
bail!("interrupts aren't enabled for this `Store`")
}
}
#[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 interrupts(&self) -> &VMInterrupts {
&self.interrupts
}
#[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(&self, anyfunc: &VMCallerCheckedAnyfunc) -> VMTrampoline {
if let Some(trampoline) = self.host_trampolines.get(&anyfunc.type_index) {
return *trampoline;
}
if let Some(trampoline) = self.modules.lookup_trampoline(anyfunc) {
return trampoline;
}
panic!("trampoline missing")
}
#[cfg(feature = "async")]
#[inline]
pub fn async_cx(&self) -> AsyncCx {
debug_assert!(self.async_support());
AsyncCx {
current_suspend: self.async_state.current_suspend.get(),
current_poll_cx: self.async_state.current_poll_cx.get(),
}
}
pub fn fuel_consumed(&self) -> Option<u64> {
if !self.engine.config().tunables.consume_fuel {
return None;
}
let consumed = unsafe { *self.interrupts.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: u32, 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 out_of_gas_yield(&mut self, fuel_to_inject: u64) -> Result<(), Trap> {
#[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();
let result = unsafe { self.async_cx().block_on(Pin::new_unchecked(&mut future)) };
match result {
Ok(()) => {
self.add_fuel(fuel_to_inject).unwrap();
Ok(())
}
Err(trap) => Err(trap),
}
}
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.interrupts.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(())
}
#[inline]
pub fn signal_handler(&self) -> Option<*const SignalHandler<'static>> {
let handler = self.signal_handler.as_ref()?;
Some(&**handler as *const _)
}
#[inline]
pub fn vminterrupts(&self) -> *mut VMInterrupts {
&*self.interrupts as *const VMInterrupts as *mut VMInterrupts
}
pub unsafe fn insert_vmexternref(&mut self, r: VMExternRef) {
self.externref_activations_table
.insert_with_gc(r, &self.modules)
}
#[inline]
pub fn default_callee(&self) -> *mut VMContext {
self.default_callee.vmctx_ptr()
}
}
impl StoreOpaqueSend<'_> {
#[cfg(feature = "async")]
pub async fn on_fiber<R>(
&mut self,
func: impl FnOnce(&mut StoreOpaque<'_>) -> R + Send,
) -> Result<R, Trap> {
let config = self.engine.config();
debug_assert!(self.async_support());
debug_assert!(config.async_stack_size > 0);
let mut slot = None;
let future = {
let current_poll_cx = self.async_state.current_poll_cx.get();
let current_suspend = self.async_state.current_suspend.get();
let stack = self
.engine
.allocator()
.allocate_fiber_stack()
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
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(&mut self.opaque()));
Ok(())
}
})
.map_err(|e| Trap::from(anyhow::Error::from(e)))?;
FiberFuture {
fiber,
current_poll_cx,
engine,
}
};
future.await?;
return Ok(slot.unwrap());
struct FiberFuture<'a> {
fiber: wasmtime_fiber::Fiber<'a, Result<(), Trap>, (), Result<(), Trap>>,
current_poll_cx: *mut *mut Context<'static>,
engine: Engine,
}
unsafe impl Send for FiberFuture<'_> {}
impl Future for FiberFuture<'_> {
type Output = Result<(), Trap>;
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(Trap::new("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<(), Trap>, (), Result<(), Trap>>,
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, Trap> {
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().map_err(Trap::from_runtime)?;
let res = (*suspend).suspend(());
before.replace().map_err(Trap::from_runtime)?;
res?;
}
}
}
unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
fn vminterrupts(&self) -> *mut VMInterrupts {
<StoreInnermost>::vminterrupts(self)
}
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 limiter(&mut self) -> Option<&mut dyn wasmtime_runtime::ResourceLimiter> {
<Self>::limiter(self)
}
fn out_of_gas(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
return match &mut self.out_of_gas_behavior {
OutOfGas::Trap => Err(Box::new(OutOfGasError)),
#[cfg(feature = "async")]
OutOfGas::InjectFuel {
injection_count,
fuel_to_inject,
} => {
if *injection_count == 0 {
return Err(Box::new(OutOfGasError));
}
*injection_count -= 1;
let fuel = *fuel_to_inject;
StoreContextMut(self).opaque().out_of_gas_yield(fuel)?;
Ok(())
}
#[cfg(not(feature = "async"))]
OutOfGas::InjectFuel { .. } => unreachable!(),
};
#[derive(Debug)]
struct OutOfGasError;
impl fmt::Display for OutOfGasError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("all fuel consumed by WebAssembly")
}
}
impl std::error::Error for OutOfGasError {}
}
}
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 StoreInnermost {
fn drop(&mut self) {
let allocator = self.engine.allocator();
unsafe {
let ondemand = OnDemandInstanceAllocator::default();
for instance in self.instances.iter() {
if instance.ondemand {
ondemand.deallocate(&instance.handle);
} else {
allocator.deallocate(&instance.handle);
}
}
ondemand.deallocate(&self.default_callee);
}
}
}
impl wasmtime_runtime::ModuleInfoLookup for ModuleRegistry {
fn lookup(&self, pc: usize) -> Option<Arc<dyn ModuleInfo>> {
self.lookup_module(pc)
}
}
#[derive(Debug)]
pub struct InterruptHandle {
interrupts: Arc<VMInterrupts>,
}
impl InterruptHandle {
pub fn interrupt(&self) {
self.interrupts.interrupt()
}
}
struct Reset<T: Copy>(*mut T, T);
impl<T: Copy> Drop for Reset<T> {
fn drop(&mut self) {
unsafe {
*self.0 = self.1;
}
}
}