diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 2f90384ed77b..c627f38e2040 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -1,13 +1,11 @@ -use crate::store::{StoreData, StoreOpaque, Stored}; -use crate::trampoline::{generate_global_export, generate_table_export}; -use crate::{ - AsContext, AsContextMut, Engine, ExternRef, ExternType, Func, GlobalType, Memory, Mutability, - SharedMemory, TableType, Val, ValType, -}; -use anyhow::{anyhow, bail, Result}; -use std::mem; -use std::ptr; -use wasmtime_runtime::{self as runtime}; +use crate::store::StoreOpaque; +use crate::{AsContext, Engine, ExternType, Func, Memory, SharedMemory}; + +mod global; +mod table; + +pub use global::Global; +pub use table::Table; // Externals @@ -170,583 +168,6 @@ impl From for Extern { } } -/// A WebAssembly `global` value which can be read and written to. -/// -/// A `global` in WebAssembly is sort of like a global variable within an -/// [`Instance`](crate::Instance). The `global.get` and `global.set` -/// instructions will modify and read global values in a wasm module. Globals -/// can either be imported or exported from wasm modules. -/// -/// A [`Global`] "belongs" to the store that it was originally created within -/// (either via [`Global::new`] or via instantiating a -/// [`Module`](crate::Module)). Operations on a [`Global`] only work with the -/// store it belongs to, and if another store is passed in by accident then -/// methods will panic. -#[derive(Copy, Clone, Debug)] -#[repr(transparent)] // here for the C API -pub struct Global(Stored); - -impl Global { - /// Creates a new WebAssembly `global` value with the provide type `ty` and - /// initial value `val`. - /// - /// The `store` argument will be the owner of the [`Global`] returned. Using - /// the returned [`Global`] other items in the store may access this global. - /// For example this could be provided as an argument to - /// [`Instance::new`](crate::Instance::new) or - /// [`Linker::define`](crate::Linker::define). - /// - /// # Errors - /// - /// Returns an error if the `ty` provided does not match the type of the - /// value `val`, or if `val` comes from a different store than `store`. - /// - /// # Examples - /// - /// ``` - /// # use wasmtime::*; - /// # fn main() -> anyhow::Result<()> { - /// let engine = Engine::default(); - /// let mut store = Store::new(&engine, ()); - /// - /// let ty = GlobalType::new(ValType::I32, Mutability::Const); - /// let i32_const = Global::new(&mut store, ty, 1i32.into())?; - /// let ty = GlobalType::new(ValType::F64, Mutability::Var); - /// let f64_mut = Global::new(&mut store, ty, 2.0f64.into())?; - /// - /// let module = Module::new( - /// &engine, - /// "(module - /// (global (import \"\" \"i32-const\") i32) - /// (global (import \"\" \"f64-mut\") (mut f64)) - /// )" - /// )?; - /// - /// let mut linker = Linker::new(&engine); - /// linker.define(&store, "", "i32-const", i32_const)?; - /// linker.define(&store, "", "f64-mut", f64_mut)?; - /// - /// let instance = linker.instantiate(&mut store, &module)?; - /// // ... - /// # Ok(()) - /// # } - /// ``` - pub fn new(mut store: impl AsContextMut, ty: GlobalType, val: Val) -> Result { - Global::_new(store.as_context_mut().0, ty, val) - } - - fn _new(store: &mut StoreOpaque, ty: GlobalType, val: Val) -> Result { - if !val.comes_from_same_store(store) { - bail!("cross-`Store` globals are not supported"); - } - if val.ty() != *ty.content() { - bail!("value provided does not match the type of this global"); - } - unsafe { - let wasmtime_export = generate_global_export(store, ty, val); - Ok(Global::from_wasmtime_global(wasmtime_export, store)) - } - } - - /// Returns the underlying type of this `global`. - /// - /// # Panics - /// - /// Panics if `store` does not own this global. - pub fn ty(&self, store: impl AsContext) -> GlobalType { - let store = store.as_context(); - let ty = &store[self.0].global; - GlobalType::from_wasmtime_global(&ty) - } - - /// Returns the current [`Val`] of this global. - /// - /// # Panics - /// - /// Panics if `store` does not own this global. - pub fn get(&self, mut store: impl AsContextMut) -> Val { - unsafe { - let store = store.as_context_mut(); - let definition = &*store[self.0].definition; - match self.ty(&store).content() { - ValType::I32 => Val::from(*definition.as_i32()), - ValType::I64 => Val::from(*definition.as_i64()), - ValType::F32 => Val::F32(*definition.as_u32()), - ValType::F64 => Val::F64(*definition.as_u64()), - ValType::ExternRef => Val::ExternRef( - definition - .as_externref() - .clone() - .map(|inner| ExternRef { inner }), - ), - ValType::FuncRef => { - Val::FuncRef(Func::from_raw(store, definition.as_func_ref().cast())) - } - ValType::V128 => Val::V128((*definition.as_u128()).into()), - } - } - } - - /// Attempts to set the current value of this global to [`Val`]. - /// - /// # Errors - /// - /// Returns an error if this global has a different type than `Val`, if - /// it's not a mutable global, or if `val` comes from a different store than - /// the one provided. - /// - /// # Panics - /// - /// Panics if `store` does not own this global. - pub fn set(&self, mut store: impl AsContextMut, val: Val) -> Result<()> { - let store = store.as_context_mut().0; - let ty = self.ty(&store); - if ty.mutability() != Mutability::Var { - bail!("immutable global cannot be set"); - } - let ty = ty.content(); - if val.ty() != *ty { - bail!("global of type {:?} cannot be set to {:?}", ty, val.ty()); - } - if !val.comes_from_same_store(store) { - bail!("cross-`Store` values are not supported"); - } - unsafe { - let definition = &mut *store[self.0].definition; - match val { - Val::I32(i) => *definition.as_i32_mut() = i, - Val::I64(i) => *definition.as_i64_mut() = i, - Val::F32(f) => *definition.as_u32_mut() = f, - Val::F64(f) => *definition.as_u64_mut() = f, - Val::FuncRef(f) => { - *definition.as_func_ref_mut() = f.map_or(ptr::null_mut(), |f| { - f.caller_checked_func_ref(store).as_ptr().cast() - }); - } - Val::ExternRef(x) => { - let old = mem::replace(definition.as_externref_mut(), x.map(|x| x.inner)); - drop(old); - } - Val::V128(i) => *definition.as_u128_mut() = i.into(), - } - } - Ok(()) - } - - pub(crate) unsafe fn from_wasmtime_global( - wasmtime_export: wasmtime_runtime::ExportGlobal, - store: &mut StoreOpaque, - ) -> Global { - Global(store.store_data_mut().insert(wasmtime_export)) - } - - pub(crate) fn wasmtime_ty<'a>(&self, data: &'a StoreData) -> &'a wasmtime_environ::Global { - &data[self.0].global - } - - pub(crate) fn vmimport(&self, store: &StoreOpaque) -> wasmtime_runtime::VMGlobalImport { - wasmtime_runtime::VMGlobalImport { - from: store[self.0].definition, - } - } - - /// Get a stable hash key for this global. - /// - /// Even if the same underlying global definition is added to the - /// `StoreData` multiple times and becomes multiple `wasmtime::Global`s, - /// this hash key will be consistent across all of these globals. - pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl std::hash::Hash + Eq { - store[self.0].definition as usize - } -} - -#[cfg(test)] -mod global_tests { - use super::*; - use crate::{Instance, Module, Store}; - - #[test] - fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> { - let mut store = Store::<()>::default(); - let module = Module::new( - store.engine(), - r#" - (module - (global (export "g") (mut i32) (i32.const 0)) - ) - "#, - )?; - let instance = Instance::new(&mut store, &module, &[])?; - - // Each time we `get_global`, we call `Global::from_wasmtime` which adds - // a new entry to `StoreData`, so `g1` and `g2` will have different - // indices into `StoreData`. - let g1 = instance.get_global(&mut store, "g").unwrap(); - let g2 = instance.get_global(&mut store, "g").unwrap(); - - // That said, they really point to the same global. - assert_eq!(g1.get(&mut store).unwrap_i32(), 0); - assert_eq!(g2.get(&mut store).unwrap_i32(), 0); - g1.set(&mut store, Val::I32(42))?; - assert_eq!(g1.get(&mut store).unwrap_i32(), 42); - assert_eq!(g2.get(&mut store).unwrap_i32(), 42); - - // And therefore their hash keys are the same. - assert!(g1.hash_key(&store.as_context().0) == g2.hash_key(&store.as_context().0)); - - // But the hash keys are different from different globals. - let instance2 = Instance::new(&mut store, &module, &[])?; - let g3 = instance2.get_global(&mut store, "g").unwrap(); - assert!(g1.hash_key(&store.as_context().0) != g3.hash_key(&store.as_context().0)); - - Ok(()) - } -} - -/// A WebAssembly `table`, or an array of values. -/// -/// Like [`Memory`] a table is an indexed array of values, but unlike [`Memory`] -/// it's an array of WebAssembly reference type values rather than bytes. One of -/// the most common usages of a table is a function table for wasm modules (a -/// `funcref` table), where each element has the `ValType::FuncRef` type. -/// -/// A [`Table`] "belongs" to the store that it was originally created within -/// (either via [`Table::new`] or via instantiating a -/// [`Module`](crate::Module)). Operations on a [`Table`] only work with the -/// store it belongs to, and if another store is passed in by accident then -/// methods will panic. -#[derive(Copy, Clone, Debug)] -#[repr(transparent)] // here for the C API -pub struct Table(Stored); - -impl Table { - /// Creates a new [`Table`] with the given parameters. - /// - /// * `store` - the owner of the resulting [`Table`] - /// * `ty` - the type of this table, containing both the element type as - /// well as the initial size and maximum size, if any. - /// * `init` - the initial value to fill all table entries with, if the - /// table starts with an initial size. - /// - /// # Errors - /// - /// Returns an error if `init` does not match the element type of the table, - /// or if `init` does not belong to the `store` provided. - /// - /// # Panics - /// - /// This function will panic when used with a [`Store`](`crate::Store`) - /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) - /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`). - /// When using an async resource limiter, use [`Table::new_async`] - /// instead. - /// - /// # Examples - /// - /// ``` - /// # use wasmtime::*; - /// # fn main() -> anyhow::Result<()> { - /// let engine = Engine::default(); - /// let mut store = Store::new(&engine, ()); - /// - /// let ty = TableType::new(ValType::FuncRef, 2, None); - /// let table = Table::new(&mut store, ty, Val::FuncRef(None))?; - /// - /// let module = Module::new( - /// &engine, - /// "(module - /// (table (import \"\" \"\") 2 funcref) - /// (func $f (result i32) - /// i32.const 10) - /// (elem (i32.const 0) $f) - /// )" - /// )?; - /// - /// let instance = Instance::new(&mut store, &module, &[table.into()])?; - /// // ... - /// # Ok(()) - /// # } - /// ``` - pub fn new(mut store: impl AsContextMut, ty: TableType, init: Val) -> Result
{ - Table::_new(store.as_context_mut().0, ty, init) - } - - #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] - /// Async variant of [`Table::new`]. You must use this variant with - /// [`Store`](`crate::Store`)s which have a - /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). - /// - /// # Panics - /// - /// This function will panic when used with a non-async - /// [`Store`](`crate::Store`) - #[cfg(feature = "async")] - pub async fn new_async( - mut store: impl AsContextMut, - ty: TableType, - init: Val, - ) -> Result
- where - T: Send, - { - let mut store = store.as_context_mut(); - assert!( - store.0.async_support(), - "cannot use `new_async` without enabling async support on the config" - ); - store - .on_fiber(|store| Table::_new(store.0, ty, init)) - .await? - } - - fn _new(store: &mut StoreOpaque, ty: TableType, init: Val) -> Result
{ - let wasmtime_export = generate_table_export(store, &ty)?; - let init = init.into_table_element(store, ty.element())?; - unsafe { - let table = Table::from_wasmtime_table(wasmtime_export, store); - (*table.wasmtime_table(store, std::iter::empty())).fill(0, init, ty.minimum())?; - - Ok(table) - } - } - - /// Returns the underlying type of this table, including its element type as - /// well as the maximum/minimum lower bounds. - /// - /// # Panics - /// - /// Panics if `store` does not own this table. - pub fn ty(&self, store: impl AsContext) -> TableType { - let store = store.as_context(); - let ty = &store[self.0].table.table; - TableType::from_wasmtime_table(ty) - } - - fn wasmtime_table( - &self, - store: &mut StoreOpaque, - lazy_init_range: impl Iterator, - ) -> *mut runtime::Table { - unsafe { - let export = &store[self.0]; - wasmtime_runtime::Instance::from_vmctx(export.vmctx, |handle| { - let idx = handle.table_index(&*export.definition); - handle.get_defined_table_with_lazy_init(idx, lazy_init_range) - }) - } - } - - /// Returns the table element value at `index`. - /// - /// Returns `None` if `index` is out of bounds. - /// - /// # Panics - /// - /// Panics if `store` does not own this table. - pub fn get(&self, mut store: impl AsContextMut, index: u32) -> Option { - let store = store.as_context_mut().0; - let table = self.wasmtime_table(store, std::iter::once(index)); - unsafe { - match (*table).get(index)? { - runtime::TableElement::FuncRef(f) => { - let func = Func::from_caller_checked_func_ref(store, f); - Some(Val::FuncRef(func)) - } - runtime::TableElement::ExternRef(None) => Some(Val::ExternRef(None)), - runtime::TableElement::ExternRef(Some(x)) => { - Some(Val::ExternRef(Some(ExternRef { inner: x }))) - } - runtime::TableElement::UninitFunc => { - unreachable!("lazy init above should have converted UninitFunc") - } - } - } - } - - /// Writes the `val` provided into `index` within this table. - /// - /// # Errors - /// - /// Returns an error if `index` is out of bounds, if `val` does not have - /// the right type to be stored in this table, or if `val` belongs to a - /// different store. - /// - /// # Panics - /// - /// Panics if `store` does not own this table. - pub fn set(&self, mut store: impl AsContextMut, index: u32, val: Val) -> Result<()> { - let store = store.as_context_mut().0; - let ty = self.ty(&store).element().clone(); - let val = val.into_table_element(store, ty)?; - let table = self.wasmtime_table(store, std::iter::empty()); - unsafe { - (*table) - .set(index, val) - .map_err(|()| anyhow!("table element index out of bounds")) - } - } - - /// Returns the current size of this table. - /// - /// # Panics - /// - /// Panics if `store` does not own this table. - pub fn size(&self, store: impl AsContext) -> u32 { - self.internal_size(store.as_context().0) - } - - pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u32 { - unsafe { (*store[self.0].definition).current_elements } - } - - /// Grows the size of this table by `delta` more elements, initialization - /// all new elements to `init`. - /// - /// Returns the previous size of this table if successful. - /// - /// # Errors - /// - /// Returns an error if the table cannot be grown by `delta`, for example - /// if it would cause the table to exceed its maximum size. Also returns an - /// error if `init` is not of the right type or if `init` does not belong to - /// `store`. - /// - /// # Panics - /// - /// Panics if `store` does not own this table. - /// - /// This function will panic when used with a [`Store`](`crate::Store`) - /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) - /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)). - /// When using an async resource limiter, use [`Table::grow_async`] - /// instead. - pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Val) -> Result { - let store = store.as_context_mut().0; - let ty = self.ty(&store).element().clone(); - let init = init.into_table_element(store, ty)?; - let table = self.wasmtime_table(store, std::iter::empty()); - unsafe { - match (*table).grow(delta, init, store)? { - Some(size) => { - let vm = (*table).vmtable(); - *store[self.0].definition = vm; - Ok(size) - } - None => bail!("failed to grow table by `{}`", delta), - } - } - } - - #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] - /// Async variant of [`Table::grow`]. Required when using a - /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). - /// - /// # Panics - /// - /// This function will panic when used with a non-async - /// [`Store`](`crate::Store`). - #[cfg(feature = "async")] - pub async fn grow_async( - &self, - mut store: impl AsContextMut, - delta: u32, - init: Val, - ) -> Result - where - T: Send, - { - let mut store = store.as_context_mut(); - assert!( - store.0.async_support(), - "cannot use `grow_async` without enabling async support on the config" - ); - store - .on_fiber(|store| self.grow(store, delta, init)) - .await? - } - - /// Copy `len` elements from `src_table[src_index..]` into - /// `dst_table[dst_index..]`. - /// - /// # Errors - /// - /// Returns an error if the range is out of bounds of either the source or - /// destination tables. - /// - /// # Panics - /// - /// Panics if `store` does not own either `dst_table` or `src_table`. - pub fn copy( - mut store: impl AsContextMut, - dst_table: &Table, - dst_index: u32, - src_table: &Table, - src_index: u32, - len: u32, - ) -> Result<()> { - let store = store.as_context_mut().0; - if dst_table.ty(&store).element() != src_table.ty(&store).element() { - bail!("tables do not have the same element type"); - } - - let dst_table = dst_table.wasmtime_table(store, std::iter::empty()); - let src_range = src_index..(src_index.checked_add(len).unwrap_or(u32::MAX)); - let src_table = src_table.wasmtime_table(store, src_range); - unsafe { - runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)?; - } - Ok(()) - } - - /// Fill `table[dst..(dst + len)]` with the given value. - /// - /// # Errors - /// - /// Returns an error if - /// - /// * `val` is not of the same type as this table's - /// element type, - /// - /// * the region to be filled is out of bounds, or - /// - /// * `val` comes from a different `Store` from this table. - /// - /// # Panics - /// - /// Panics if `store` does not own either `dst_table` or `src_table`. - pub fn fill(&self, mut store: impl AsContextMut, dst: u32, val: Val, len: u32) -> Result<()> { - let store = store.as_context_mut().0; - let ty = self.ty(&store).element().clone(); - let val = val.into_table_element(store, ty)?; - - let table = self.wasmtime_table(store, std::iter::empty()); - unsafe { - (*table).fill(dst, val, len)?; - } - - Ok(()) - } - - pub(crate) unsafe fn from_wasmtime_table( - wasmtime_export: wasmtime_runtime::ExportTable, - store: &mut StoreOpaque, - ) -> Table { - Table(store.store_data_mut().insert(wasmtime_export)) - } - - pub(crate) fn wasmtime_ty<'a>(&self, data: &'a StoreData) -> &'a wasmtime_environ::Table { - &data[self.0].table.table - } - - pub(crate) fn vmimport(&self, store: &StoreOpaque) -> wasmtime_runtime::VMTableImport { - let export = &store[self.0]; - wasmtime_runtime::VMTableImport { - from: export.definition, - vmctx: export.vmctx, - } - } -} - // Exports /// An exported WebAssembly value. diff --git a/crates/wasmtime/src/externals/global.rs b/crates/wasmtime/src/externals/global.rs new file mode 100644 index 000000000000..fb089ba9f52b --- /dev/null +++ b/crates/wasmtime/src/externals/global.rs @@ -0,0 +1,238 @@ +use crate::store::{StoreData, StoreOpaque, Stored}; +use crate::trampoline::generate_global_export; +use crate::{AsContext, AsContextMut, ExternRef, Func, GlobalType, Mutability, Val, ValType}; +use anyhow::{bail, Result}; +use std::mem; +use std::ptr; + +/// A WebAssembly `global` value which can be read and written to. +/// +/// A `global` in WebAssembly is sort of like a global variable within an +/// [`Instance`](crate::Instance). The `global.get` and `global.set` +/// instructions will modify and read global values in a wasm module. Globals +/// can either be imported or exported from wasm modules. +/// +/// A [`Global`] "belongs" to the store that it was originally created within +/// (either via [`Global::new`] or via instantiating a +/// [`Module`](crate::Module)). Operations on a [`Global`] only work with the +/// store it belongs to, and if another store is passed in by accident then +/// methods will panic. +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] // here for the C API +pub struct Global(pub(super) Stored); + +impl Global { + /// Creates a new WebAssembly `global` value with the provide type `ty` and + /// initial value `val`. + /// + /// The `store` argument will be the owner of the [`Global`] returned. Using + /// the returned [`Global`] other items in the store may access this global. + /// For example this could be provided as an argument to + /// [`Instance::new`](crate::Instance::new) or + /// [`Linker::define`](crate::Linker::define). + /// + /// # Errors + /// + /// Returns an error if the `ty` provided does not match the type of the + /// value `val`, or if `val` comes from a different store than `store`. + /// + /// # Examples + /// + /// ``` + /// # use wasmtime::*; + /// # fn main() -> anyhow::Result<()> { + /// let engine = Engine::default(); + /// let mut store = Store::new(&engine, ()); + /// + /// let ty = GlobalType::new(ValType::I32, Mutability::Const); + /// let i32_const = Global::new(&mut store, ty, 1i32.into())?; + /// let ty = GlobalType::new(ValType::F64, Mutability::Var); + /// let f64_mut = Global::new(&mut store, ty, 2.0f64.into())?; + /// + /// let module = Module::new( + /// &engine, + /// "(module + /// (global (import \"\" \"i32-const\") i32) + /// (global (import \"\" \"f64-mut\") (mut f64)) + /// )" + /// )?; + /// + /// let mut linker = Linker::new(&engine); + /// linker.define(&store, "", "i32-const", i32_const)?; + /// linker.define(&store, "", "f64-mut", f64_mut)?; + /// + /// let instance = linker.instantiate(&mut store, &module)?; + /// // ... + /// # Ok(()) + /// # } + /// ``` + pub fn new(mut store: impl AsContextMut, ty: GlobalType, val: Val) -> Result { + Global::_new(store.as_context_mut().0, ty, val) + } + + fn _new(store: &mut StoreOpaque, ty: GlobalType, val: Val) -> Result { + if !val.comes_from_same_store(store) { + bail!("cross-`Store` globals are not supported"); + } + if val.ty() != *ty.content() { + bail!("value provided does not match the type of this global"); + } + unsafe { + let wasmtime_export = generate_global_export(store, ty, val); + Ok(Global::from_wasmtime_global(wasmtime_export, store)) + } + } + + /// Returns the underlying type of this `global`. + /// + /// # Panics + /// + /// Panics if `store` does not own this global. + pub fn ty(&self, store: impl AsContext) -> GlobalType { + let store = store.as_context(); + let ty = &store[self.0].global; + GlobalType::from_wasmtime_global(&ty) + } + + /// Returns the current [`Val`] of this global. + /// + /// # Panics + /// + /// Panics if `store` does not own this global. + pub fn get(&self, mut store: impl AsContextMut) -> Val { + unsafe { + let store = store.as_context_mut(); + let definition = &*store[self.0].definition; + match self.ty(&store).content() { + ValType::I32 => Val::from(*definition.as_i32()), + ValType::I64 => Val::from(*definition.as_i64()), + ValType::F32 => Val::F32(*definition.as_u32()), + ValType::F64 => Val::F64(*definition.as_u64()), + ValType::ExternRef => Val::ExternRef( + definition + .as_externref() + .clone() + .map(|inner| ExternRef { inner }), + ), + ValType::FuncRef => { + Val::FuncRef(Func::from_raw(store, definition.as_func_ref().cast())) + } + ValType::V128 => Val::V128((*definition.as_u128()).into()), + } + } + } + + /// Attempts to set the current value of this global to [`Val`]. + /// + /// # Errors + /// + /// Returns an error if this global has a different type than `Val`, if + /// it's not a mutable global, or if `val` comes from a different store than + /// the one provided. + /// + /// # Panics + /// + /// Panics if `store` does not own this global. + pub fn set(&self, mut store: impl AsContextMut, val: Val) -> Result<()> { + let store = store.as_context_mut().0; + let ty = self.ty(&store); + if ty.mutability() != Mutability::Var { + bail!("immutable global cannot be set"); + } + let ty = ty.content(); + if val.ty() != *ty { + bail!("global of type {:?} cannot be set to {:?}", ty, val.ty()); + } + if !val.comes_from_same_store(store) { + bail!("cross-`Store` values are not supported"); + } + unsafe { + let definition = &mut *store[self.0].definition; + match val { + Val::I32(i) => *definition.as_i32_mut() = i, + Val::I64(i) => *definition.as_i64_mut() = i, + Val::F32(f) => *definition.as_u32_mut() = f, + Val::F64(f) => *definition.as_u64_mut() = f, + Val::FuncRef(f) => { + *definition.as_func_ref_mut() = + f.map_or(ptr::null_mut(), |f| f.vm_func_ref(store).as_ptr().cast()); + } + Val::ExternRef(x) => { + let old = mem::replace(definition.as_externref_mut(), x.map(|x| x.inner)); + drop(old); + } + Val::V128(i) => *definition.as_u128_mut() = i.into(), + } + } + Ok(()) + } + + pub(crate) unsafe fn from_wasmtime_global( + wasmtime_export: wasmtime_runtime::ExportGlobal, + store: &mut StoreOpaque, + ) -> Global { + Global(store.store_data_mut().insert(wasmtime_export)) + } + + pub(crate) fn wasmtime_ty<'a>(&self, data: &'a StoreData) -> &'a wasmtime_environ::Global { + &data[self.0].global + } + + pub(crate) fn vmimport(&self, store: &StoreOpaque) -> wasmtime_runtime::VMGlobalImport { + wasmtime_runtime::VMGlobalImport { + from: store[self.0].definition, + } + } + + /// Get a stable hash key for this global. + /// + /// Even if the same underlying global definition is added to the + /// `StoreData` multiple times and becomes multiple `wasmtime::Global`s, + /// this hash key will be consistent across all of these globals. + pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl std::hash::Hash + Eq { + store[self.0].definition as usize + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Instance, Module, Store}; + + #[test] + fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> { + let mut store = Store::<()>::default(); + let module = Module::new( + store.engine(), + r#" + (module + (global (export "g") (mut i32) (i32.const 0)) + ) + "#, + )?; + let instance = Instance::new(&mut store, &module, &[])?; + + // Each time we `get_global`, we call `Global::from_wasmtime` which adds + // a new entry to `StoreData`, so `g1` and `g2` will have different + // indices into `StoreData`. + let g1 = instance.get_global(&mut store, "g").unwrap(); + let g2 = instance.get_global(&mut store, "g").unwrap(); + + // That said, they really point to the same global. + assert_eq!(g1.get(&mut store).unwrap_i32(), 0); + assert_eq!(g2.get(&mut store).unwrap_i32(), 0); + g1.set(&mut store, Val::I32(42))?; + assert_eq!(g1.get(&mut store).unwrap_i32(), 42); + assert_eq!(g2.get(&mut store).unwrap_i32(), 42); + + // And therefore their hash keys are the same. + assert!(g1.hash_key(&store.as_context().0) == g2.hash_key(&store.as_context().0)); + + // But the hash keys are different from different globals. + let instance2 = Instance::new(&mut store, &module, &[])?; + let g3 = instance2.get_global(&mut store, "g").unwrap(); + assert!(g1.hash_key(&store.as_context().0) != g3.hash_key(&store.as_context().0)); + + Ok(()) + } +} diff --git a/crates/wasmtime/src/externals/table.rs b/crates/wasmtime/src/externals/table.rs new file mode 100644 index 000000000000..cbd688207d5d --- /dev/null +++ b/crates/wasmtime/src/externals/table.rs @@ -0,0 +1,403 @@ +use crate::store::{StoreData, StoreOpaque, Stored}; +use crate::trampoline::generate_table_export; +use crate::{AsContext, AsContextMut, ExternRef, Func, TableType, Val}; +use anyhow::{anyhow, bail, Result}; +use wasmtime_runtime::{self as runtime}; + +/// A WebAssembly `table`, or an array of values. +/// +/// Like [`Memory`][crate::Memory] a table is an indexed array of values, but +/// unlike [`Memory`][crate::Memory] it's an array of WebAssembly reference type +/// values rather than bytes. One of the most common usages of a table is a +/// function table for wasm modules (a `funcref` table), where each element has +/// the `ValType::FuncRef` type. +/// +/// A [`Table`] "belongs" to the store that it was originally created within +/// (either via [`Table::new`] or via instantiating a +/// [`Module`](crate::Module)). Operations on a [`Table`] only work with the +/// store it belongs to, and if another store is passed in by accident then +/// methods will panic. +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] // here for the C API +pub struct Table(pub(super) Stored); + +impl Table { + /// Creates a new [`Table`] with the given parameters. + /// + /// * `store` - the owner of the resulting [`Table`] + /// * `ty` - the type of this table, containing both the element type as + /// well as the initial size and maximum size, if any. + /// * `init` - the initial value to fill all table entries with, if the + /// table starts with an initial size. + /// + /// # Errors + /// + /// Returns an error if `init` does not match the element type of the table, + /// or if `init` does not belong to the `store` provided. + /// + /// # Panics + /// + /// This function will panic when used with a [`Store`](`crate::Store`) + /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) + /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`). + /// When using an async resource limiter, use [`Table::new_async`] + /// instead. + /// + /// # Examples + /// + /// ``` + /// # use wasmtime::*; + /// # fn main() -> anyhow::Result<()> { + /// let engine = Engine::default(); + /// let mut store = Store::new(&engine, ()); + /// + /// let ty = TableType::new(ValType::FuncRef, 2, None); + /// let table = Table::new(&mut store, ty, Val::FuncRef(None))?; + /// + /// let module = Module::new( + /// &engine, + /// "(module + /// (table (import \"\" \"\") 2 funcref) + /// (func $f (result i32) + /// i32.const 10) + /// (elem (i32.const 0) $f) + /// )" + /// )?; + /// + /// let instance = Instance::new(&mut store, &module, &[table.into()])?; + /// // ... + /// # Ok(()) + /// # } + /// ``` + pub fn new(mut store: impl AsContextMut, ty: TableType, init: Val) -> Result
{ + Table::_new(store.as_context_mut().0, ty, init) + } + + #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] + /// Async variant of [`Table::new`]. You must use this variant with + /// [`Store`](`crate::Store`)s which have a + /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). + /// + /// # Panics + /// + /// This function will panic when used with a non-async + /// [`Store`](`crate::Store`) + #[cfg(feature = "async")] + pub async fn new_async( + mut store: impl AsContextMut, + ty: TableType, + init: Val, + ) -> Result
+ where + T: Send, + { + let mut store = store.as_context_mut(); + assert!( + store.0.async_support(), + "cannot use `new_async` without enabling async support on the config" + ); + store + .on_fiber(|store| Table::_new(store.0, ty, init)) + .await? + } + + fn _new(store: &mut StoreOpaque, ty: TableType, init: Val) -> Result
{ + let wasmtime_export = generate_table_export(store, &ty)?; + let init = init.into_table_element(store, ty.element())?; + unsafe { + let table = Table::from_wasmtime_table(wasmtime_export, store); + (*table.wasmtime_table(store, std::iter::empty())).fill(0, init, ty.minimum())?; + + Ok(table) + } + } + + /// Returns the underlying type of this table, including its element type as + /// well as the maximum/minimum lower bounds. + /// + /// # Panics + /// + /// Panics if `store` does not own this table. + pub fn ty(&self, store: impl AsContext) -> TableType { + let store = store.as_context(); + let ty = &store[self.0].table.table; + TableType::from_wasmtime_table(ty) + } + + fn wasmtime_table( + &self, + store: &mut StoreOpaque, + lazy_init_range: impl Iterator, + ) -> *mut runtime::Table { + unsafe { + let export = &store[self.0]; + wasmtime_runtime::Instance::from_vmctx(export.vmctx, |handle| { + let idx = handle.table_index(&*export.definition); + handle.get_defined_table_with_lazy_init(idx, lazy_init_range) + }) + } + } + + /// Returns the table element value at `index`. + /// + /// Returns `None` if `index` is out of bounds. + /// + /// # Panics + /// + /// Panics if `store` does not own this table. + pub fn get(&self, mut store: impl AsContextMut, index: u32) -> Option { + let store = store.as_context_mut().0; + let table = self.wasmtime_table(store, std::iter::once(index)); + unsafe { + match (*table).get(index)? { + runtime::TableElement::FuncRef(f) => { + let func = Func::from_caller_checked_func_ref(store, f); + Some(Val::FuncRef(func)) + } + runtime::TableElement::ExternRef(None) => Some(Val::ExternRef(None)), + runtime::TableElement::ExternRef(Some(x)) => { + Some(Val::ExternRef(Some(ExternRef { inner: x }))) + } + runtime::TableElement::UninitFunc => { + unreachable!("lazy init above should have converted UninitFunc") + } + } + } + } + + /// Writes the `val` provided into `index` within this table. + /// + /// # Errors + /// + /// Returns an error if `index` is out of bounds, if `val` does not have + /// the right type to be stored in this table, or if `val` belongs to a + /// different store. + /// + /// # Panics + /// + /// Panics if `store` does not own this table. + pub fn set(&self, mut store: impl AsContextMut, index: u32, val: Val) -> Result<()> { + let store = store.as_context_mut().0; + let ty = self.ty(&store).element().clone(); + let val = val.into_table_element(store, ty)?; + let table = self.wasmtime_table(store, std::iter::empty()); + unsafe { + (*table) + .set(index, val) + .map_err(|()| anyhow!("table element index out of bounds")) + } + } + + /// Returns the current size of this table. + /// + /// # Panics + /// + /// Panics if `store` does not own this table. + pub fn size(&self, store: impl AsContext) -> u32 { + self.internal_size(store.as_context().0) + } + + pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u32 { + unsafe { (*store[self.0].definition).current_elements } + } + + /// Grows the size of this table by `delta` more elements, initialization + /// all new elements to `init`. + /// + /// Returns the previous size of this table if successful. + /// + /// # Errors + /// + /// Returns an error if the table cannot be grown by `delta`, for example + /// if it would cause the table to exceed its maximum size. Also returns an + /// error if `init` is not of the right type or if `init` does not belong to + /// `store`. + /// + /// # Panics + /// + /// Panics if `store` does not own this table. + /// + /// This function will panic when used with a [`Store`](`crate::Store`) + /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) + /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)). + /// When using an async resource limiter, use [`Table::grow_async`] + /// instead. + pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Val) -> Result { + let store = store.as_context_mut().0; + let ty = self.ty(&store).element().clone(); + let init = init.into_table_element(store, ty)?; + let table = self.wasmtime_table(store, std::iter::empty()); + unsafe { + match (*table).grow(delta, init, store)? { + Some(size) => { + let vm = (*table).vmtable(); + *store[self.0].definition = vm; + Ok(size) + } + None => bail!("failed to grow table by `{}`", delta), + } + } + } + + #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] + /// Async variant of [`Table::grow`]. Required when using a + /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). + /// + /// # Panics + /// + /// This function will panic when used with a non-async + /// [`Store`](`crate::Store`). + #[cfg(feature = "async")] + pub async fn grow_async( + &self, + mut store: impl AsContextMut, + delta: u32, + init: Val, + ) -> Result + where + T: Send, + { + let mut store = store.as_context_mut(); + assert!( + store.0.async_support(), + "cannot use `grow_async` without enabling async support on the config" + ); + store + .on_fiber(|store| self.grow(store, delta, init)) + .await? + } + + /// Copy `len` elements from `src_table[src_index..]` into + /// `dst_table[dst_index..]`. + /// + /// # Errors + /// + /// Returns an error if the range is out of bounds of either the source or + /// destination tables. + /// + /// # Panics + /// + /// Panics if `store` does not own either `dst_table` or `src_table`. + pub fn copy( + mut store: impl AsContextMut, + dst_table: &Table, + dst_index: u32, + src_table: &Table, + src_index: u32, + len: u32, + ) -> Result<()> { + let store = store.as_context_mut().0; + if dst_table.ty(&store).element() != src_table.ty(&store).element() { + bail!("tables do not have the same element type"); + } + + let dst_table = dst_table.wasmtime_table(store, std::iter::empty()); + let src_range = src_index..(src_index.checked_add(len).unwrap_or(u32::MAX)); + let src_table = src_table.wasmtime_table(store, src_range); + unsafe { + runtime::Table::copy(dst_table, src_table, dst_index, src_index, len)?; + } + Ok(()) + } + + /// Fill `table[dst..(dst + len)]` with the given value. + /// + /// # Errors + /// + /// Returns an error if + /// + /// * `val` is not of the same type as this table's + /// element type, + /// + /// * the region to be filled is out of bounds, or + /// + /// * `val` comes from a different `Store` from this table. + /// + /// # Panics + /// + /// Panics if `store` does not own either `dst_table` or `src_table`. + pub fn fill(&self, mut store: impl AsContextMut, dst: u32, val: Val, len: u32) -> Result<()> { + let store = store.as_context_mut().0; + let ty = self.ty(&store).element().clone(); + let val = val.into_table_element(store, ty)?; + + let table = self.wasmtime_table(store, std::iter::empty()); + unsafe { + (*table).fill(dst, val, len)?; + } + + Ok(()) + } + + pub(crate) unsafe fn from_wasmtime_table( + wasmtime_export: wasmtime_runtime::ExportTable, + store: &mut StoreOpaque, + ) -> Table { + Table(store.store_data_mut().insert(wasmtime_export)) + } + + pub(crate) fn wasmtime_ty<'a>(&self, data: &'a StoreData) -> &'a wasmtime_environ::Table { + &data[self.0].table.table + } + + pub(crate) fn vmimport(&self, store: &StoreOpaque) -> wasmtime_runtime::VMTableImport { + let export = &store[self.0]; + wasmtime_runtime::VMTableImport { + from: export.definition, + vmctx: export.vmctx, + } + } + + /// Get a stable hash key for this table. + /// + /// Even if the same underlying table definition is added to the + /// `StoreData` multiple times and becomes multiple `wasmtime::Table`s, + /// this hash key will be consistent across all of these tables. + #[allow(dead_code)] // Not used yet, but added for consistency. + pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl std::hash::Hash + Eq { + store[self.0].definition as usize + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Instance, Module, Store}; + + #[test] + fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> { + let mut store = Store::<()>::default(); + let module = Module::new( + store.engine(), + r#" + (module + (table (export "t") 1 1 externref) + ) + "#, + )?; + let instance = Instance::new(&mut store, &module, &[])?; + + // Each time we `get_table`, we call `Table::from_wasmtime` which adds + // a new entry to `StoreData`, so `t1` and `t2` will have different + // indices into `StoreData`. + let t1 = instance.get_table(&mut store, "t").unwrap(); + let t2 = instance.get_table(&mut store, "t").unwrap(); + + // That said, they really point to the same table. + assert!(t1.get(&mut store, 0).unwrap().unwrap_externref().is_none()); + assert!(t2.get(&mut store, 0).unwrap().unwrap_externref().is_none()); + t1.set(&mut store, 0, Val::ExternRef(Some(ExternRef::new(42))))?; + assert!(t1.get(&mut store, 0).unwrap().unwrap_externref().is_some()); + assert!(t2.get(&mut store, 0).unwrap().unwrap_externref().is_some()); + + // And therefore their hash keys are the same. + assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0)); + + // But the hash keys are different from different tables. + let instance2 = Instance::new(&mut store, &module, &[])?; + let t3 = instance2.get_table(&mut store, "t").unwrap(); + assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0)); + + Ok(()) + } +} diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 3ec071c72980..8e3a39d004bf 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -946,9 +946,7 @@ impl Func { /// this function is properly rooted within it. Additionally this function /// should not be liberally used since it's a very low-level knob. pub unsafe fn to_raw(&self, mut store: impl AsContextMut) -> *mut c_void { - self.caller_checked_func_ref(store.as_context_mut().0) - .as_ptr() - .cast() + self.vm_func_ref(store.as_context_mut().0).as_ptr().cast() } /// Invokes this function with the `params` given, returning the results @@ -1080,7 +1078,7 @@ impl Func { } #[inline] - pub(crate) fn caller_checked_func_ref(&self, store: &mut StoreOpaque) -> NonNull { + pub(crate) fn vm_func_ref(&self, store: &mut StoreOpaque) -> NonNull { let func_data = &mut store.store_data_mut()[self.0]; if let Some(in_store) = func_data.in_store_func_ref { in_store.as_non_null() @@ -1339,6 +1337,16 @@ impl Func { // (unsafely), which should be safe since we just did the type check above. unsafe { Ok(TypedFunc::new_unchecked(*self)) } } + + /// Get a stable hash key for this function. + /// + /// Even if the same underlying function is added to the `StoreData` + /// multiple times and becomes multiple `wasmtime::Func`s, this hash key + /// will be consistent across all of these functions. + #[allow(dead_code)] // Not used yet, but added for consistency. + pub(crate) fn hash_key(&self, store: &mut StoreOpaque) -> impl std::hash::Hash + Eq { + self.vm_func_ref(store).as_ptr() as usize + } } /// Prepares for entrance into WebAssembly. @@ -2391,3 +2399,47 @@ mod rooted { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Instance, Module, Store}; + + #[test] + fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> { + let mut store = Store::<()>::default(); + let module = Module::new( + store.engine(), + r#" + (module + (func (export "f") + nop + ) + ) + "#, + )?; + let instance = Instance::new(&mut store, &module, &[])?; + + // Each time we `get_func`, we call `Func::from_wasmtime` which adds a + // new entry to `StoreData`, so `f1` and `f2` will have different + // indices into `StoreData`. + let f1 = instance.get_func(&mut store, "f").unwrap(); + let f2 = instance.get_func(&mut store, "f").unwrap(); + + // But their hash keys are the same. + assert!( + f1.hash_key(&mut store.as_context_mut().0) + == f2.hash_key(&mut store.as_context_mut().0) + ); + + // But the hash keys are different from different funcs. + let instance2 = Instance::new(&mut store, &module, &[])?; + let f3 = instance2.get_func(&mut store, "f").unwrap(); + assert!( + f1.hash_key(&mut store.as_context_mut().0) + != f3.hash_key(&mut store.as_context_mut().0) + ); + + Ok(()) + } +} diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs index 8229e14585b3..f1188faad4aa 100644 --- a/crates/wasmtime/src/func/typed.rs +++ b/crates/wasmtime/src/func/typed.rs @@ -84,7 +84,7 @@ where !store.0.async_support(), "must use `call_async` with async stores" ); - let func = self.func.caller_checked_func_ref(store.0); + let func = self.func.vm_func_ref(store.0); unsafe { Self::call_raw(&mut store, func, params) } } @@ -122,7 +122,7 @@ where ); store .on_fiber(|store| { - let func = self.func.caller_checked_func_ref(store.0); + let func = self.func.vm_func_ref(store.0); unsafe { Self::call_raw(store, func, params) } }) .await? @@ -439,7 +439,7 @@ unsafe impl WasmTy for Option { #[inline] fn into_abi(self, store: &mut StoreOpaque) -> Self::Abi { if let Some(f) = self { - f.caller_checked_func_ref(store).as_ptr() + f.vm_func_ref(store).as_ptr() } else { ptr::null_mut() } diff --git a/crates/wasmtime/src/trampoline/global.rs b/crates/wasmtime/src/trampoline/global.rs index 263e91b2e34f..50a77811d1e4 100644 --- a/crates/wasmtime/src/trampoline/global.rs +++ b/crates/wasmtime/src/trampoline/global.rs @@ -53,9 +53,8 @@ pub fn generate_global_export( Val::F64(x) => *global.as_f64_bits_mut() = x, Val::V128(x) => *global.as_u128_mut() = x.into(), Val::FuncRef(f) => { - *global.as_func_ref_mut() = f.map_or(ptr::null_mut(), |f| { - f.caller_checked_func_ref(store).as_ptr() - }) + *global.as_func_ref_mut() = + f.map_or(ptr::null_mut(), |f| f.vm_func_ref(store).as_ptr()) } Val::ExternRef(x) => *global.as_externref_mut() = x.map(|x| x.inner), } diff --git a/crates/wasmtime/src/values.rs b/crates/wasmtime/src/values.rs index 87040ec6850f..d5605e962dda 100644 --- a/crates/wasmtime/src/values.rs +++ b/crates/wasmtime/src/values.rs @@ -194,9 +194,7 @@ impl Val { if !f.comes_from_same_store(store) { bail!("cross-`Store` values are not supported in tables"); } - Ok(TableElement::FuncRef( - f.caller_checked_func_ref(store).as_ptr(), - )) + Ok(TableElement::FuncRef(f.vm_func_ref(store).as_ptr())) } (Val::FuncRef(None), ValType::FuncRef) => Ok(TableElement::FuncRef(ptr::null_mut())), (Val::ExternRef(Some(x)), ValType::ExternRef) => {