use crate::prelude::*;
use crate::runtime::vm::{self as runtime, GcStore};
use crate::store::{AutoAssertNoGc, StoreInstanceId, StoreOpaque};
use crate::trampoline::generate_table_export;
use crate::{AnyRef, AsContext, AsContextMut, ExternRef, Func, HeapType, Ref, TableType, Trap};
use core::iter;
use wasmtime_environ::DefinedTableIndex;
#[derive(Copy, Clone, Debug)]
#[repr(C)] pub struct Table {
instance: StoreInstanceId,
index: DefinedTableIndex,
}
const _: () = {
#[repr(C)]
struct Tmp(u64, u32);
#[repr(C)]
struct C(Tmp, u32);
assert!(core::mem::size_of::<C>() == core::mem::size_of::<Table>());
assert!(core::mem::align_of::<C>() == core::mem::align_of::<Table>());
assert!(core::mem::offset_of!(Table, instance) == 0);
};
impl Table {
pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> {
Table::_new(store.as_context_mut().0, ty, init)
}
#[cfg(feature = "async")]
pub async fn new_async(
mut store: impl AsContextMut<Data: Send>,
ty: TableType,
init: Ref,
) -> Result<Table> {
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: Ref) -> Result<Table> {
let table = generate_table_export(store, &ty)?;
let init = init.into_table_element(store, ty.element())?;
let (wasmtime_table, gc_store) = table.wasmtime_table(store, iter::empty());
wasmtime_table.fill(gc_store, 0, init, ty.minimum())?;
Ok(table)
}
pub fn ty(&self, store: impl AsContext) -> TableType {
self._ty(store.as_context().0)
}
fn _ty(&self, store: &StoreOpaque) -> TableType {
TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store))
}
fn wasmtime_table<'a>(
&self,
store: &'a mut StoreOpaque,
lazy_init_range: impl Iterator<Item = u64>,
) -> (&'a mut runtime::Table, Option<&'a mut GcStore>) {
self.instance.assert_belongs_to(store.id());
let (store, instance) = store.optional_gc_store_and_instance_mut(self.instance.instance());
(
instance.get_defined_table_with_lazy_init(self.index, lazy_init_range),
store,
)
}
pub fn get(&self, mut store: impl AsContextMut, index: u64) -> Option<Ref> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
let (table, gc_store) = self.wasmtime_table(&mut store, iter::once(index));
match table.get(gc_store, index)? {
runtime::TableElement::FuncRef(f) => {
let func = unsafe { f.map(|f| Func::from_vm_func_ref(store.id(), f)) };
Some(func.into())
}
runtime::TableElement::UninitFunc => {
unreachable!("lazy init above should have converted UninitFunc")
}
runtime::TableElement::GcRef(None) => {
Some(Ref::null(self._ty(&store).element().heap_type()))
}
#[cfg_attr(
not(feature = "gc"),
expect(unreachable_code, unused_variables, reason = "definitions cfg'd off")
)]
runtime::TableElement::GcRef(Some(x)) => {
match self._ty(&store).element().heap_type().top() {
HeapType::Any => {
let x = AnyRef::from_cloned_gc_ref(&mut store, x);
Some(x.into())
}
HeapType::Extern => {
let x = ExternRef::from_cloned_gc_ref(&mut store, x);
Some(x.into())
}
HeapType::Func => {
unreachable!("never have TableElement::GcRef for func tables")
}
ty => unreachable!("not a top type: {ty:?}"),
}
}
runtime::TableElement::ContRef(_c) => {
unimplemented!()
}
}
}
pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> {
let store = store.as_context_mut().0;
let ty = self.ty(&store);
let val = val.into_table_element(store, ty.element())?;
let (table, _) = self.wasmtime_table(store, iter::empty());
table
.set(index, val)
.map_err(|()| anyhow!("table element index out of bounds"))
}
pub fn size(&self, store: impl AsContext) -> u64 {
self.internal_size(store.as_context().0)
}
pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u64 {
u64::try_from(store[self.instance].table(self.index).current_elements).unwrap()
}
pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> {
let store = store.as_context_mut().0;
let ty = self.ty(&store);
let init = init.into_table_element(store, ty.element())?;
let (table, _gc_store) = self.wasmtime_table(store, iter::empty());
let table: *mut _ = table;
unsafe {
match (*table).grow(delta, init, store)? {
Some(size) => {
let vm = (*table).vmtable();
store[self.instance].table_ptr(self.index).write(vm);
Ok(u64::try_from(size).unwrap())
}
None => bail!("failed to grow table by `{}`", delta),
}
}
}
#[cfg(feature = "async")]
pub async fn grow_async(
&self,
mut store: impl AsContextMut<Data: Send>,
delta: u64,
init: Ref,
) -> Result<u64> {
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: u64,
src_table: &Table,
src_index: u64,
len: u64,
) -> Result<()> {
let store = store.as_context_mut().0;
let dst_ty = dst_table.ty(&store);
let src_ty = src_table.ty(&store);
src_ty
.element()
.ensure_matches(store.engine(), dst_ty.element())
.context(
"type mismatch: source table's element type does not match \
destination table's element type",
)?;
unsafe {
Self::copy_raw(store, dst_table, dst_index, src_table, src_index, len)?;
}
Ok(())
}
pub(crate) unsafe fn copy_raw(
store: &mut StoreOpaque,
dst_table: &Table,
dst_index: u64,
src_table: &Table,
src_index: u64,
len: u64,
) -> Result<(), Trap> {
let src_range = src_index..(src_index.checked_add(len).unwrap_or(u64::MAX));
src_table.wasmtime_table(store, src_range);
dst_table.wasmtime_table(store, iter::empty());
let src_instance = src_table.instance.instance();
let dst_instance = dst_table.instance.instance();
match (
src_instance == dst_instance,
src_table.index == dst_table.index,
) {
(false, _) => {
let (gc_store, [src_instance, dst_instance]) = unsafe {
store.optional_gc_store_and_instances_mut([src_instance, dst_instance])
};
src_instance.get_defined_table(src_table.index).copy_to(
dst_instance.get_defined_table(dst_table.index),
gc_store,
dst_index,
src_index,
len,
)
}
(true, false) => {
let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
let [(_, src_table), (_, dst_table)] = instance
.tables_mut()
.get_disjoint_mut([src_table.index, dst_table.index])
.unwrap();
src_table.copy_to(dst_table, gc_store, dst_index, src_index, len)
}
(true, true) => {
let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance);
instance
.get_defined_table(src_table.index)
.copy_within(gc_store, dst_index, src_index, len)
}
}
}
pub fn fill(&self, mut store: impl AsContextMut, dst: u64, val: Ref, len: u64) -> Result<()> {
let store = store.as_context_mut().0;
let ty = self.ty(&store);
let val = val.into_table_element(store, ty.element())?;
let (table, gc_store) = self.wasmtime_table(store, iter::empty());
table.fill(gc_store, dst, val, len)?;
Ok(())
}
#[cfg(feature = "gc")]
pub(crate) fn trace_roots(
&self,
store: &mut StoreOpaque,
gc_roots_list: &mut crate::runtime::vm::GcRootsList,
) {
if !self
._ty(store)
.element()
.is_vmgcref_type_and_points_to_object()
{
return;
}
let (table, _) = self.wasmtime_table(store, iter::empty());
for gc_ref in table.gc_refs_mut() {
if let Some(gc_ref) = gc_ref {
unsafe {
gc_roots_list.add_root(gc_ref.into(), "Wasm table element");
}
}
}
}
pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTableIndex) -> Table {
Table { instance, index }
}
pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Table {
let module = store[self.instance].env_module();
let index = module.table_index(self.index);
&module.tables[index]
}
pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMTableImport {
let instance = &store[self.instance];
crate::runtime::vm::VMTableImport {
from: instance.table_ptr(self.index).into(),
vmctx: instance.vmctx().into(),
index: self.index,
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
store.id() == self.instance.store_id()
}
#[cfg_attr(
not(test),
expect(dead_code, reason = "Not used yet, but added for consistency")
)]
pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<'_> {
store[self.instance].table_ptr(self.index).as_ptr().addr()
}
}
#[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, &[])?;
let t1 = instance.get_table(&mut store, "t").unwrap();
let t2 = instance.get_table(&mut store, "t").unwrap();
assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none());
assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none());
let e = ExternRef::new(&mut store, 42)?;
t1.set(&mut store, 0, e.into())?;
assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some());
assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some());
assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0));
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(())
}
}