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, InstanceHandle};
#[derive(Clone, Debug)]
pub enum Extern {
Func(Func),
Global(Global),
Table(Table),
Memory(Memory),
SharedMemory(SharedMemory),
}
impl Extern {
pub fn into_func(self) -> Option<Func> {
match self {
Extern::Func(func) => Some(func),
_ => None,
}
}
pub fn into_global(self) -> Option<Global> {
match self {
Extern::Global(global) => Some(global),
_ => None,
}
}
pub fn into_table(self) -> Option<Table> {
match self {
Extern::Table(table) => Some(table),
_ => None,
}
}
pub fn into_memory(self) -> Option<Memory> {
match self {
Extern::Memory(memory) => Some(memory),
_ => None,
}
}
pub fn into_shared_memory(self) -> Option<SharedMemory> {
match self {
Extern::SharedMemory(memory) => Some(memory),
_ => None,
}
}
pub fn ty(&self, store: impl AsContext) -> ExternType {
let store = store.as_context();
match self {
Extern::Func(ft) => ExternType::Func(ft.ty(store)),
Extern::Memory(ft) => ExternType::Memory(ft.ty(store)),
Extern::SharedMemory(ft) => ExternType::Memory(ft.ty()),
Extern::Table(tt) => ExternType::Table(tt.ty(store)),
Extern::Global(gt) => ExternType::Global(gt.ty(store)),
}
}
pub(crate) unsafe fn from_wasmtime_export(
wasmtime_export: wasmtime_runtime::Export,
store: &mut StoreOpaque,
) -> Extern {
match wasmtime_export {
wasmtime_runtime::Export::Function(f) => {
Extern::Func(Func::from_wasmtime_function(f, store))
}
wasmtime_runtime::Export::Memory(m) => {
if m.memory.memory.shared {
Extern::SharedMemory(SharedMemory::from_wasmtime_memory(m, store))
} else {
Extern::Memory(Memory::from_wasmtime_memory(m, store))
}
}
wasmtime_runtime::Export::Global(g) => {
Extern::Global(Global::from_wasmtime_global(g, store))
}
wasmtime_runtime::Export::Table(t) => {
Extern::Table(Table::from_wasmtime_table(t, store))
}
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
match self {
Extern::Func(f) => f.comes_from_same_store(store),
Extern::Global(g) => store.store_data().contains(g.0),
Extern::Memory(m) => m.comes_from_same_store(store),
Extern::SharedMemory(m) => Engine::same(m.engine(), store.engine()),
Extern::Table(t) => store.store_data().contains(t.0),
}
}
}
impl From<Func> for Extern {
fn from(r: Func) -> Self {
Extern::Func(r)
}
}
impl From<Global> for Extern {
fn from(r: Global) -> Self {
Extern::Global(r)
}
}
impl From<Memory> for Extern {
fn from(r: Memory) -> Self {
Extern::Memory(r)
}
}
impl From<SharedMemory> for Extern {
fn from(r: SharedMemory) -> Self {
Extern::SharedMemory(r)
}
}
impl From<Table> for Extern {
fn from(r: Table) -> Self {
Extern::Table(r)
}
}
#[derive(Copy, Clone, Debug)]
#[repr(transparent)] pub struct Global(Stored<wasmtime_runtime::ExportGlobal>);
impl Global {
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))
}
}
pub fn ty(&self, store: impl AsContext) -> GlobalType {
let store = store.as_context();
let ty = &store[self.0].global;
GlobalType::from_wasmtime_global(&ty)
}
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_anyfunc() as usize))
}
ValType::V128 => Val::V128(*definition.as_u128()),
}
}
}
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_anyfunc_mut() = f.map_or(ptr::null(), |f| {
f.caller_checked_anyfunc(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,
}
}
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,
}
}
}
#[derive(Copy, Clone, Debug)]
#[repr(transparent)] pub struct Table(Stored<wasmtime_runtime::ExportTable>);
impl Table {
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")))]
#[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)
}
}
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];
let mut handle = InstanceHandle::from_vmctx(export.vmctx);
let idx = handle.table_index(&*export.definition);
handle.get_defined_table_with_lazy_init(idx, lazy_init_range)
}
}
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_anyfunc(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")
}
}
}
}
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"))
}
}
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 }
}
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")))]
#[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?
}
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(())
}
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,
}
}
}
#[derive(Clone)]
pub struct Export<'instance> {
name: &'instance str,
definition: Extern,
}
impl<'instance> Export<'instance> {
pub(crate) fn new(name: &'instance str, definition: Extern) -> Export<'instance> {
Export { name, definition }
}
pub fn name(&self) -> &'instance str {
self.name
}
pub fn ty(&self, store: impl AsContext) -> ExternType {
self.definition.ty(store)
}
pub fn into_extern(self) -> Extern {
self.definition
}
pub fn into_func(self) -> Option<Func> {
self.definition.into_func()
}
pub fn into_table(self) -> Option<Table> {
self.definition.into_table()
}
pub fn into_memory(self) -> Option<Memory> {
self.definition.into_memory()
}
pub fn into_global(self) -> Option<Global> {
self.definition.into_global()
}
}