use crate::{
collections::arena::{Arena, ArenaIndex, GuardedEntity},
core::TrapCode,
engine::{DedupFuncType, FuelCosts},
externref::{ExternObject, ExternObjectEntity, ExternObjectIdx},
func::{Trampoline, TrampolineEntity, TrampolineIdx},
memory::{DataSegment, MemoryError},
module::InstantiationError,
table::TableError,
Config,
DataSegmentEntity,
DataSegmentIdx,
ElementSegment,
ElementSegmentEntity,
ElementSegmentIdx,
Engine,
Error,
Func,
FuncEntity,
FuncIdx,
FuncType,
Global,
GlobalEntity,
GlobalIdx,
Instance,
InstanceEntity,
InstanceIdx,
Memory,
MemoryEntity,
MemoryIdx,
ResourceLimiter,
Table,
TableEntity,
TableIdx,
};
use core::{
fmt::{self, Debug},
sync::atomic::{AtomicU32, Ordering},
};
use std::boxed::Box;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct StoreIdx(u32);
impl ArenaIndex for StoreIdx {
fn into_usize(self) -> usize {
self.0 as usize
}
fn from_usize(value: usize) -> Self {
let value = value.try_into().unwrap_or_else(|error| {
panic!("index {value} is out of bounds as store index: {error}")
});
Self(value)
}
}
impl StoreIdx {
fn new() -> Self {
static CURRENT_STORE_IDX: AtomicU32 = AtomicU32::new(0);
let next_idx = CURRENT_STORE_IDX.fetch_add(1, Ordering::AcqRel);
Self(next_idx)
}
}
pub type Stored<Idx> = GuardedEntity<StoreIdx, Idx>;
pub struct ResourceLimiterRef<'a>(Option<&'a mut (dyn ResourceLimiter)>);
impl Debug for ResourceLimiterRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ResourceLimiterRef(...)")
}
}
impl<'a> ResourceLimiterRef<'a> {
pub fn as_resource_limiter(&mut self) -> &mut Option<&'a mut dyn ResourceLimiter> {
&mut self.0
}
}
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(...)")
}
}
#[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(...)")
}
}
#[derive(Debug)]
pub struct Store<T> {
pub(crate) inner: StoreInner,
trampolines: Arena<TrampolineIdx, TrampolineEntity<T>>,
data: T,
limiter: Option<ResourceLimiterQuery<T>>,
call_hook: Option<CallHookWrapper<T>>,
}
#[derive(Debug)]
pub struct StoreInner {
store_idx: StoreIdx,
funcs: Arena<FuncIdx, FuncEntity>,
memories: Arena<MemoryIdx, MemoryEntity>,
tables: Arena<TableIdx, TableEntity>,
globals: Arena<GlobalIdx, GlobalEntity>,
instances: Arena<InstanceIdx, InstanceEntity>,
datas: Arena<DataSegmentIdx, DataSegmentEntity>,
elems: Arena<ElementSegmentIdx, ElementSegmentEntity>,
extern_objects: Arena<ExternObjectIdx, ExternObjectEntity>,
engine: Engine,
fuel: Fuel,
}
#[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<()>>;
};
}
#[derive(Debug)]
pub enum CallHook {
CallingWasm,
ReturningFromWasm,
CallingHost,
ReturningFromHost,
}
#[derive(Debug, Clone)]
pub enum FuelError {
FuelMeteringDisabled,
OutOfFuel,
}
#[cfg(feature = "std")]
impl std::error::Error for FuelError {}
impl fmt::Display for FuelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FuelMeteringDisabled => write!(f, "fuel metering is disabled"),
Self::OutOfFuel => write!(f, "all fuel consumed"),
}
}
}
impl FuelError {
#[cold]
pub fn fuel_metering_disabled() -> Self {
Self::FuelMeteringDisabled
}
#[cold]
pub fn out_of_fuel() -> Self {
Self::OutOfFuel
}
}
#[derive(Debug, Copy, Clone)]
pub struct Fuel {
remaining: u64,
enabled: bool,
costs: FuelCosts,
}
impl Fuel {
pub fn new(config: &Config) -> Self {
let enabled = config.get_consume_fuel();
let costs = *config.fuel_costs();
Self {
remaining: 0,
enabled,
costs,
}
}
fn is_fuel_metering_enabled(&self) -> bool {
self.enabled
}
fn check_fuel_metering_enabled(&self) -> Result<(), FuelError> {
if !self.is_fuel_metering_enabled() {
return Err(FuelError::fuel_metering_disabled());
}
Ok(())
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<(), FuelError> {
self.check_fuel_metering_enabled()?;
self.remaining = fuel;
Ok(())
}
pub fn get_fuel(&self) -> Result<u64, FuelError> {
self.check_fuel_metering_enabled()?;
Ok(self.remaining)
}
pub(crate) fn consume_fuel_unchecked(&mut self, delta: u64) -> Result<u64, TrapCode> {
self.remaining = self
.remaining
.checked_sub(delta)
.ok_or(TrapCode::OutOfFuel)?;
Ok(self.remaining)
}
pub(crate) fn consume_fuel(
&mut self,
f: impl FnOnce(&FuelCosts) -> u64,
) -> Result<u64, FuelError> {
self.check_fuel_metering_enabled()?;
self.consume_fuel_unchecked(f(&self.costs))
.map_err(|_| FuelError::OutOfFuel)
}
pub(crate) fn consume_fuel_if(
&mut self,
f: impl FnOnce(&FuelCosts) -> u64,
) -> Result<(), TrapCode> {
match self.consume_fuel(f) {
Err(FuelError::OutOfFuel) => Err(TrapCode::OutOfFuel),
Err(FuelError::FuelMeteringDisabled) | Ok(_) => Ok(()),
}
}
}
impl StoreInner {
pub fn new(engine: &Engine) -> Self {
let fuel = Fuel::new(engine.config());
StoreInner {
engine: engine.clone(),
store_idx: StoreIdx::new(),
funcs: Arena::new(),
memories: Arena::new(),
tables: Arena::new(),
globals: Arena::new(),
instances: Arena::new(),
datas: Arena::new(),
elems: Arena::new(),
extern_objects: Arena::new(),
fuel,
}
}
pub fn engine(&self) -> &Engine {
&self.engine
}
pub fn fuel_mut(&mut self) -> &mut Fuel {
&mut self.fuel
}
fn wrap_stored<Idx>(&self, entity_idx: Idx) -> Stored<Idx> {
Stored::new(self.store_idx, entity_idx)
}
fn unwrap_stored<Idx>(&self, stored: &Stored<Idx>) -> Idx
where
Idx: ArenaIndex + Debug,
{
stored.entity_index(self.store_idx).unwrap_or_else(|| {
panic!(
"entity reference ({:?}) does not belong to store {:?}",
stored, self.store_idx,
)
})
}
pub fn alloc_global(&mut self, global: GlobalEntity) -> Global {
let global = self.globals.alloc(global);
Global::from_inner(self.wrap_stored(global))
}
pub fn alloc_table(&mut self, table: TableEntity) -> Table {
let table = self.tables.alloc(table);
Table::from_inner(self.wrap_stored(table))
}
pub fn alloc_memory(&mut self, memory: MemoryEntity) -> Memory {
let memory = self.memories.alloc(memory);
Memory::from_inner(self.wrap_stored(memory))
}
pub fn alloc_data_segment(&mut self, segment: DataSegmentEntity) -> DataSegment {
let segment = self.datas.alloc(segment);
DataSegment::from_inner(self.wrap_stored(segment))
}
pub(super) fn alloc_element_segment(
&mut self,
segment: ElementSegmentEntity,
) -> ElementSegment {
let segment = self.elems.alloc(segment);
ElementSegment::from_inner(self.wrap_stored(segment))
}
pub(super) fn alloc_extern_object(&mut self, object: ExternObjectEntity) -> ExternObject {
let object = self.extern_objects.alloc(object);
ExternObject::from_inner(self.wrap_stored(object))
}
pub fn alloc_instance(&mut self) -> Instance {
let instance = self.instances.alloc(InstanceEntity::uninitialized());
Instance::from_inner(self.wrap_stored(instance))
}
pub fn initialize_instance(&mut self, instance: Instance, init: InstanceEntity) {
assert!(
init.is_initialized(),
"encountered an uninitialized new instance entity: {init:?}",
);
let idx = self.unwrap_stored(instance.as_inner());
let uninit = self
.instances
.get_mut(idx)
.unwrap_or_else(|| panic!("missing entity for the given instance: {instance:?}"));
assert!(
!uninit.is_initialized(),
"encountered an already initialized instance: {uninit:?}",
);
*uninit = init;
}
fn resolve<'a, Idx, Entity>(
&self,
idx: &Stored<Idx>,
entities: &'a Arena<Idx, Entity>,
) -> &'a Entity
where
Idx: ArenaIndex + Debug,
{
let idx = self.unwrap_stored(idx);
entities
.get(idx)
.unwrap_or_else(|| panic!("failed to resolve stored entity: {idx:?}"))
}
fn resolve_mut<Idx, Entity>(idx: Idx, entities: &mut Arena<Idx, Entity>) -> &mut Entity
where
Idx: ArenaIndex + Debug,
{
entities
.get_mut(idx)
.unwrap_or_else(|| panic!("failed to resolve stored entity: {idx:?}"))
}
pub fn resolve_func_type(&self, func_type: &DedupFuncType) -> FuncType {
self.resolve_func_type_with(func_type, FuncType::clone)
}
pub fn resolve_func_type_with<R>(
&self,
func_type: &DedupFuncType,
f: impl FnOnce(&FuncType) -> R,
) -> R {
self.engine.resolve_func_type(func_type, f)
}
pub fn resolve_global(&self, global: &Global) -> &GlobalEntity {
self.resolve(global.as_inner(), &self.globals)
}
pub fn resolve_global_mut(&mut self, global: &Global) -> &mut GlobalEntity {
let idx = self.unwrap_stored(global.as_inner());
Self::resolve_mut(idx, &mut self.globals)
}
pub fn resolve_table(&self, table: &Table) -> &TableEntity {
self.resolve(table.as_inner(), &self.tables)
}
pub fn resolve_table_mut(&mut self, table: &Table) -> &mut TableEntity {
let idx = self.unwrap_stored(table.as_inner());
Self::resolve_mut(idx, &mut self.tables)
}
pub fn resolve_table_and_element_mut(
&mut self,
table: &Table,
elem: &ElementSegment,
) -> (&mut TableEntity, &mut ElementSegmentEntity) {
let table_idx = self.unwrap_stored(table.as_inner());
let elem_idx = self.unwrap_stored(elem.as_inner());
let table = Self::resolve_mut(table_idx, &mut self.tables);
let elem = Self::resolve_mut(elem_idx, &mut self.elems);
(table, elem)
}
pub fn resolve_table_and_fuel_mut(&mut self, table: &Table) -> (&mut TableEntity, &mut Fuel) {
let idx = self.unwrap_stored(table.as_inner());
let table = Self::resolve_mut(idx, &mut self.tables);
let fuel = &mut self.fuel;
(table, fuel)
}
pub fn resolve_table_pair_and_fuel(
&mut self,
fst: &Table,
snd: &Table,
) -> (&mut TableEntity, &mut TableEntity, &mut Fuel) {
let fst = self.unwrap_stored(fst.as_inner());
let snd = self.unwrap_stored(snd.as_inner());
let (fst, snd) = self.tables.get_pair_mut(fst, snd).unwrap_or_else(|| {
panic!("failed to resolve stored pair of entities: {fst:?} and {snd:?}")
});
let fuel = &mut self.fuel;
(fst, snd, fuel)
}
pub(super) fn resolve_table_init_params(
&mut self,
table: &Table,
segment: &ElementSegment,
) -> (&mut TableEntity, &ElementSegmentEntity, &mut Fuel) {
let mem_idx = self.unwrap_stored(table.as_inner());
let elem_idx = segment.as_inner();
let elem = self.resolve(elem_idx, &self.elems);
let mem = Self::resolve_mut(mem_idx, &mut self.tables);
let fuel = &mut self.fuel;
(mem, elem, fuel)
}
pub fn resolve_element_segment(&self, segment: &ElementSegment) -> &ElementSegmentEntity {
self.resolve(segment.as_inner(), &self.elems)
}
pub fn resolve_element_segment_mut(
&mut self,
segment: &ElementSegment,
) -> &mut ElementSegmentEntity {
let idx = self.unwrap_stored(segment.as_inner());
Self::resolve_mut(idx, &mut self.elems)
}
pub fn resolve_memory<'a>(&'a self, memory: &Memory) -> &'a MemoryEntity {
self.resolve(memory.as_inner(), &self.memories)
}
pub fn resolve_memory_mut<'a>(&'a mut self, memory: &Memory) -> &'a mut MemoryEntity {
let idx = self.unwrap_stored(memory.as_inner());
Self::resolve_mut(idx, &mut self.memories)
}
pub fn resolve_memory_and_fuel_mut(
&mut self,
memory: &Memory,
) -> (&mut MemoryEntity, &mut Fuel) {
let idx = self.unwrap_stored(memory.as_inner());
let memory = Self::resolve_mut(idx, &mut self.memories);
let fuel = &mut self.fuel;
(memory, fuel)
}
pub(super) fn resolve_memory_init_params(
&mut self,
memory: &Memory,
segment: &DataSegment,
) -> (&mut MemoryEntity, &DataSegmentEntity, &mut Fuel) {
let mem_idx = self.unwrap_stored(memory.as_inner());
let data_idx = segment.as_inner();
let data = self.resolve(data_idx, &self.datas);
let mem = Self::resolve_mut(mem_idx, &mut self.memories);
let fuel = &mut self.fuel;
(mem, data, fuel)
}
pub(super) fn resolve_memory_pair_and_fuel(
&mut self,
fst: &Memory,
snd: &Memory,
) -> (&mut MemoryEntity, &mut MemoryEntity, &mut Fuel) {
let fst = self.unwrap_stored(fst.as_inner());
let snd = self.unwrap_stored(snd.as_inner());
let (fst, snd) = self.memories.get_pair_mut(fst, snd).unwrap_or_else(|| {
panic!("failed to resolve stored pair of entities: {fst:?} and {snd:?}")
});
let fuel = &mut self.fuel;
(fst, snd, fuel)
}
pub fn resolve_data_segment_mut(&mut self, segment: &DataSegment) -> &mut DataSegmentEntity {
let idx = self.unwrap_stored(segment.as_inner());
Self::resolve_mut(idx, &mut self.datas)
}
pub fn resolve_instance(&self, instance: &Instance) -> &InstanceEntity {
self.resolve(instance.as_inner(), &self.instances)
}
pub fn resolve_external_object(&self, object: &ExternObject) -> &ExternObjectEntity {
self.resolve(object.as_inner(), &self.extern_objects)
}
pub fn alloc_func(&mut self, func: FuncEntity) -> Func {
let idx = self.funcs.alloc(func);
Func::from_inner(self.wrap_stored(idx))
}
pub fn resolve_func(&self, func: &Func) -> &FuncEntity {
let entity_index = self.unwrap_stored(func.as_inner());
self.funcs.get(entity_index).unwrap_or_else(|| {
panic!("failed to resolve stored Wasm or host function: {entity_index:?}")
})
}
}
impl<T> Default for Store<T>
where
T: Default,
{
fn default() -> Self {
let engine = Engine::default();
Self {
inner: StoreInner::new(&engine),
trampolines: Arena::new(),
data: T::default(),
limiter: None,
call_hook: None,
}
}
}
impl<T> Store<T> {
pub fn new(engine: &Engine, data: T) -> Self {
Self {
inner: StoreInner::new(engine),
trampolines: Arena::new(),
data,
limiter: None,
call_hook: None,
}
}
pub fn engine(&self) -> &Engine {
self.inner.engine()
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub fn into_data(self) -> T {
self.data
}
pub fn limiter(
&mut self,
limiter: impl FnMut(&mut T) -> &mut (dyn ResourceLimiter) + Send + Sync + 'static,
) {
self.limiter = Some(ResourceLimiterQuery(Box::new(limiter)))
}
pub(crate) fn check_new_instances_limit(
&mut self,
num_new_instances: usize,
) -> Result<(), InstantiationError> {
let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
if let Some(limiter) = limiter.as_resource_limiter() {
if inner.instances.len().saturating_add(num_new_instances) > limiter.instances() {
return Err(InstantiationError::TooManyInstances);
}
}
Ok(())
}
pub(crate) fn check_new_memories_limit(
&mut self,
num_new_memories: usize,
) -> Result<(), MemoryError> {
let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
if let Some(limiter) = limiter.as_resource_limiter() {
if inner.memories.len().saturating_add(num_new_memories) > limiter.memories() {
return Err(MemoryError::TooManyMemories);
}
}
Ok(())
}
pub(crate) fn check_new_tables_limit(
&mut self,
num_new_tables: usize,
) -> Result<(), TableError> {
let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref();
if let Some(limiter) = limiter.as_resource_limiter() {
if inner.tables.len().saturating_add(num_new_tables) > limiter.tables() {
return Err(TableError::TooManyTables);
}
}
Ok(())
}
pub(crate) fn store_inner_and_resource_limiter_ref(
&mut self,
) -> (&mut StoreInner, ResourceLimiterRef) {
let resource_limiter = ResourceLimiterRef(match &mut self.limiter {
Some(q) => Some(q.0(&mut self.data)),
None => None,
});
(&mut self.inner, resource_limiter)
}
pub fn get_fuel(&self) -> Result<u64, Error> {
self.inner.fuel.get_fuel().map_err(Into::into)
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<(), Error> {
self.inner.fuel.set_fuel(fuel).map_err(Into::into)
}
pub(super) fn alloc_trampoline(&mut self, func: TrampolineEntity<T>) -> Trampoline {
let idx = self.trampolines.alloc(func);
Trampoline::from_inner(self.inner.wrap_stored(idx))
}
pub(super) fn resolve_memory_and_state_mut(
&mut self,
memory: &Memory,
) -> (&mut MemoryEntity, &mut T) {
(self.inner.resolve_memory_mut(memory), &mut self.data)
}
pub(super) fn resolve_trampoline(&self, func: &Trampoline) -> &TrampolineEntity<T> {
let entity_index = self.inner.unwrap_stored(func.as_inner());
self.trampolines
.get(entity_index)
.unwrap_or_else(|| panic!("failed to resolve stored host function: {entity_index:?}"))
}
pub fn call_hook(
&mut self,
hook: impl FnMut(&mut T, CallHook) -> Result<(), Error> + Send + Sync + 'static,
) {
self.call_hook = Some(CallHookWrapper(Box::new(hook)));
}
#[inline]
pub(crate) fn invoke_call_hook(&mut self, call_type: CallHook) -> Result<(), Error> {
match self.call_hook.as_mut() {
None => Ok(()),
Some(call_hook) => Self::invoke_call_hook_impl(&mut self.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)
}
}
pub trait AsContext {
type Data;
fn as_context(&self) -> StoreContext<Self::Data>;
}
pub trait AsContextMut: AsContext {
fn as_context_mut(&mut self) -> StoreContextMut<Self::Data>;
}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct StoreContext<'a, T> {
pub(crate) store: &'a Store<T>,
}
impl<T> StoreContext<'_, T> {
pub fn engine(&self) -> &Engine {
self.store.engine()
}
pub fn data(&self) -> &T {
self.store.data()
}
pub fn get_fuel(&self) -> Result<u64, Error> {
self.store.get_fuel()
}
}
impl<'a, T: AsContext> From<&'a T> for StoreContext<'a, T::Data> {
#[inline]
fn from(ctx: &'a T) -> Self {
ctx.as_context()
}
}
impl<'a, T: AsContext> From<&'a mut T> for StoreContext<'a, T::Data> {
#[inline]
fn from(ctx: &'a mut T) -> Self {
T::as_context(ctx)
}
}
impl<'a, T: AsContextMut> From<&'a mut T> for StoreContextMut<'a, T::Data> {
#[inline]
fn from(ctx: &'a mut T) -> Self {
ctx.as_context_mut()
}
}
#[derive(Debug)]
#[repr(transparent)]
pub struct StoreContextMut<'a, T> {
pub(crate) store: &'a mut Store<T>,
}
impl<T> StoreContextMut<'_, T> {
pub fn engine(&self) -> &Engine {
self.store.engine()
}
pub fn data(&self) -> &T {
self.store.data()
}
pub fn data_mut(&mut self) -> &mut T {
self.store.data_mut()
}
pub fn get_fuel(&self) -> Result<u64, Error> {
self.store.get_fuel()
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<(), Error> {
self.store.set_fuel(fuel)
}
}
impl<T> AsContext for &'_ T
where
T: AsContext,
{
type Data = T::Data;
#[inline]
fn as_context(&self) -> StoreContext<'_, T::Data> {
T::as_context(*self)
}
}
impl<T> AsContext for &'_ mut T
where
T: AsContext,
{
type Data = T::Data;
#[inline]
fn as_context(&self) -> StoreContext<'_, T::Data> {
T::as_context(*self)
}
}
impl<T> AsContextMut for &'_ mut T
where
T: AsContextMut,
{
#[inline]
fn as_context_mut(&mut self) -> StoreContextMut<'_, T::Data> {
T::as_context_mut(*self)
}
}
impl<T> AsContext for StoreContext<'_, T> {
type Data = T;
#[inline]
fn as_context(&self) -> StoreContext<'_, Self::Data> {
StoreContext { store: self.store }
}
}
impl<T> AsContext for StoreContextMut<'_, T> {
type Data = T;
#[inline]
fn as_context(&self) -> StoreContext<'_, Self::Data> {
StoreContext { store: self.store }
}
}
impl<T> AsContextMut for StoreContextMut<'_, T> {
#[inline]
fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> {
StoreContextMut {
store: &mut *self.store,
}
}
}
impl<T> AsContext for Store<T> {
type Data = T;
#[inline]
fn as_context(&self) -> StoreContext<'_, Self::Data> {
StoreContext { store: self }
}
}
impl<T> AsContextMut for Store<T> {
#[inline]
fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> {
StoreContextMut { store: self }
}
}