mod context;
mod error;
mod inner;
mod pruned;
mod typeid;
use self::pruned::PrunedStoreVTable;
pub use self::{
context::{AsContext, AsContextMut, StoreContext, StoreContextMut},
error::{InternalStoreError, StoreError},
inner::{StoreInner, Stored},
pruned::PrunedStore,
};
use crate::{
collections::arena::Arena,
core::{CoreMemory, ResourceLimiterRef},
func::{FuncInOut, HostFuncEntity, Trampoline, TrampolineEntity, TrampolineIdx},
Engine,
Error,
Instance,
Memory,
ResourceLimiter,
};
use alloc::boxed::Box;
use core::{
any::{type_name, TypeId},
fmt::{self, Debug},
};
#[derive(Debug)]
pub struct Store<T> {
pub(crate) inner: StoreInner,
typed: TypedStoreInner<T>,
id: TypeId,
restore_pruned: PrunedStoreVTable,
}
impl<T> Default for Store<T>
where
T: Default,
{
fn default() -> Self {
let engine = Engine::default();
Self::new(&engine, T::default())
}
}
impl<T> Store<T> {
pub fn new(engine: &Engine, data: T) -> Self {
Self {
inner: StoreInner::new(engine),
typed: TypedStoreInner::new(data),
id: typeid::of::<T>(),
restore_pruned: PrunedStoreVTable::new::<T>(),
}
}
}
impl<T> Store<T> {
pub fn engine(&self) -> &Engine {
self.inner.engine()
}
pub fn data(&self) -> &T {
&self.typed.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.typed.data
}
pub fn into_data(self) -> T {
*self.typed.data
}
pub fn limiter(
&mut self,
limiter: impl (FnMut(&mut T) -> &mut dyn ResourceLimiter) + Send + Sync + 'static,
) {
self.typed.limiter = Some(ResourceLimiterQuery(Box::new(limiter)))
}
fn call_host_func(
&mut self,
func: &HostFuncEntity,
instance: Option<&Instance>,
params_results: FuncInOut,
) -> Result<(), StoreError<Error>> {
let trampoline = self.resolve_trampoline(func.trampoline())?.clone();
trampoline
.call(self, instance, params_results)
.map_err(StoreError::external)?;
Ok(())
}
pub(crate) fn can_create_more_instances(&mut self, additional: usize) -> bool {
let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
if let Some(limiter) = limiter.as_resource_limiter() {
if inner.len_instances().saturating_add(additional) > limiter.instances() {
return false;
}
}
true
}
pub(crate) fn can_create_more_memories(&mut self, additional: usize) -> bool {
let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
if let Some(limiter) = limiter.as_resource_limiter() {
if inner.len_memories().saturating_add(additional) > limiter.memories() {
return false;
}
}
true
}
pub(crate) fn can_create_more_tables(&mut self, additional: usize) -> bool {
let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
if let Some(limiter) = limiter.as_resource_limiter() {
if inner.len_tables().saturating_add(additional) > limiter.tables() {
return false;
}
}
true
}
pub(crate) fn store_inner_and_resource_limiter_ref(
&mut self,
) -> (&mut StoreInner, ResourceLimiterRef<'_>) {
let resource_limiter = match &mut self.typed.limiter {
Some(query) => {
let limiter = query.0(&mut self.typed.data);
ResourceLimiterRef::from(limiter)
}
None => ResourceLimiterRef::default(),
};
(&mut self.inner, resource_limiter)
}
pub fn get_fuel(&self) -> Result<u64, Error> {
self.inner.get_fuel()
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<(), Error> {
self.inner.set_fuel(fuel)
}
pub(super) fn alloc_trampoline(&mut self, func: TrampolineEntity<T>) -> Trampoline {
let idx = self.typed.trampolines.alloc(func);
Trampoline::from_inner(self.inner.wrap_stored(idx))
}
pub(super) fn resolve_memory_and_state_mut(
&mut self,
memory: &Memory,
) -> (&mut CoreMemory, &mut T) {
(self.inner.resolve_memory_mut(memory), &mut self.typed.data)
}
fn resolve_trampoline(
&self,
func: &Trampoline,
) -> Result<&TrampolineEntity<T>, InternalStoreError> {
let entity_index = self.inner.unwrap_stored(func.as_inner())?;
let Some(trampoline) = self.typed.trampolines.get(entity_index) else {
return Err(InternalStoreError::not_found());
};
Ok(trampoline)
}
pub fn call_hook(
&mut self,
hook: impl FnMut(&mut T, CallHook) -> Result<(), Error> + Send + Sync + 'static,
) {
self.typed.call_hook = Some(CallHookWrapper(Box::new(hook)));
}
#[inline]
pub(crate) fn invoke_call_hook(&mut self, call_type: CallHook) -> Result<(), Error> {
match self.typed.call_hook.as_mut() {
None => Ok(()),
Some(call_hook) => {
Self::invoke_call_hook_impl(&mut self.typed.data, call_type, call_hook)
}
}
}
#[cold]
fn invoke_call_hook_impl(
data: &mut T,
call_type: CallHook,
call_hook: &mut CallHookWrapper<T>,
) -> Result<(), Error> {
call_hook.0(data, call_type)
}
}
#[derive(Debug)]
pub struct TypedStoreInner<T> {
trampolines: Arena<TrampolineIdx, TrampolineEntity<T>>,
limiter: Option<ResourceLimiterQuery<T>>,
call_hook: Option<CallHookWrapper<T>>,
data: Box<T>,
}
impl<T> TypedStoreInner<T> {
fn new(data: T) -> Self {
Self {
trampolines: Arena::new(),
data: Box::new(data),
limiter: None,
call_hook: None,
}
}
}
struct ResourceLimiterQuery<T>(Box<dyn (FnMut(&mut T) -> &mut dyn ResourceLimiter) + Send + Sync>);
impl<T> Debug for ResourceLimiterQuery<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ResourceLimiterQuery<{}>(...)", type_name::<T>())
}
}
#[allow(clippy::type_complexity)]
struct CallHookWrapper<T>(Box<dyn FnMut(&mut T, CallHook) -> Result<(), Error> + Send + Sync>);
impl<T> Debug for CallHookWrapper<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CallHook<{}>", type_name::<T>())
}
}
#[derive(Debug)]
pub enum CallHook {
CallingWasm,
ReturningFromWasm,
CallingHost,
ReturningFromHost,
}
#[derive(Debug, Copy, Clone)]
pub enum CallHooks {
Call,
Ignore,
}
#[test]
fn test_store_is_send_sync() {
const _: () = {
#[allow(clippy::extra_unused_type_parameters)]
fn assert_send<T: Send>() {}
#[allow(clippy::extra_unused_type_parameters)]
fn assert_sync<T: Sync>() {}
let _ = assert_send::<Store<()>>;
let _ = assert_sync::<Store<()>>;
};
}