Browse Source

Add `Func::hash_key` and `Table::hash_key` methods (#7086)

* Wasmtime: Move `Global` and `Table` to `externals` submodules

Just mechanical code motion, not functional changes.

* Wasmtime: Add `hash_key` methods for `Func` and `Table`

* Rename `wasmtime::Func::caller_checked_func_ref` to `wasmtime::Func::vm_func_ref`

We removed the "caller checked" part of the `VMFuncRef` type's name a while ago,
so update this method to be in line with that.

No functional changes, just mechanical renaming.

* cargo fmt

* Fix doc link
pull/7092/head
Nick Fitzgerald 1 year ago
committed by GitHub
parent
commit
d127a14a93
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 595
      crates/wasmtime/src/externals.rs
  2. 238
      crates/wasmtime/src/externals/global.rs
  3. 403
      crates/wasmtime/src/externals/table.rs
  4. 60
      crates/wasmtime/src/func.rs
  5. 6
      crates/wasmtime/src/func/typed.rs
  6. 5
      crates/wasmtime/src/trampoline/global.rs
  7. 4
      crates/wasmtime/src/values.rs

595
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<Table> 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<wasmtime_runtime::ExportGlobal>);
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> {
Global::_new(store.as_context_mut().0, ty, val)
}
fn _new(store: &mut StoreOpaque, ty: GlobalType, val: Val) -> Result<Global> {
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<wasmtime_runtime::ExportTable>);
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> {
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<T>(
mut store: impl AsContextMut<Data = T>,
ty: TableType,
init: Val,
) -> Result<Table>
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<Table> {
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<Item = u32>,
) -> *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<Val> {
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<u32> {
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<T>(
&self,
mut store: impl AsContextMut<Data = T>,
delta: u32,
init: Val,
) -> Result<u32>
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.

238
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<wasmtime_runtime::ExportGlobal>);
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> {
Global::_new(store.as_context_mut().0, ty, val)
}
fn _new(store: &mut StoreOpaque, ty: GlobalType, val: Val) -> Result<Global> {
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(())
}
}

403
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<wasmtime_runtime::ExportTable>);
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> {
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<T>(
mut store: impl AsContextMut<Data = T>,
ty: TableType,
init: Val,
) -> Result<Table>
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<Table> {
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<Item = u32>,
) -> *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<Val> {
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<u32> {
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<T>(
&self,
mut store: impl AsContextMut<Data = T>,
delta: u32,
init: Val,
) -> Result<u32>
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(())
}
}

60
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<VMFuncRef> {
pub(crate) fn vm_func_ref(&self, store: &mut StoreOpaque) -> NonNull<VMFuncRef> {
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(())
}
}

6
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<Func> {
#[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()
}

5
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),
}

4
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) => {

Loading…
Cancel
Save