1 use crate::prelude::*; 2 use crate::runtime::vm::{self as runtime, GcStore}; 3 use crate::store::{AutoAssertNoGc, StoreInstanceId, StoreOpaque}; 4 use crate::trampoline::generate_table_export; 5 use crate::{AnyRef, AsContext, AsContextMut, ExternRef, Func, HeapType, Ref, TableType}; 6 use core::iter; 7 use wasmtime_environ::DefinedTableIndex; 8 9 /// A WebAssembly `table`, or an array of values. 10 /// 11 /// Like [`Memory`][crate::Memory] a table is an indexed array of values, but 12 /// unlike [`Memory`][crate::Memory] it's an array of WebAssembly reference type 13 /// values rather than bytes. One of the most common usages of a table is a 14 /// function table for wasm modules (a `funcref` table), where each element has 15 /// the `ValType::FuncRef` type. 16 /// 17 /// A [`Table`] "belongs" to the store that it was originally created within 18 /// (either via [`Table::new`] or via instantiating a 19 /// [`Module`](crate::Module)). Operations on a [`Table`] only work with the 20 /// store it belongs to, and if another store is passed in by accident then 21 /// methods will panic. 22 #[derive(Copy, Clone, Debug)] 23 #[repr(C)] // here for the C API 24 pub struct Table { 25 instance: StoreInstanceId, 26 index: DefinedTableIndex, 27 } 28 29 // Double-check that the C representation in `extern.h` matches our in-Rust 30 // representation here in terms of size/alignment/etc. 31 const _: () = { 32 #[repr(C)] 33 struct Tmp(u64, u32); 34 #[repr(C)] 35 struct C(Tmp, u32); 36 assert!(core::mem::size_of::<C>() == core::mem::size_of::<Table>()); 37 assert!(core::mem::align_of::<C>() == core::mem::align_of::<Table>()); 38 assert!(core::mem::offset_of!(Table, instance) == 0); 39 }; 40 41 impl Table { 42 /// Creates a new [`Table`] with the given parameters. 43 /// 44 /// * `store` - the owner of the resulting [`Table`] 45 /// * `ty` - the type of this table, containing both the element type as 46 /// well as the initial size and maximum size, if any. 47 /// * `init` - the initial value to fill all table entries with, if the 48 /// table starts with an initial size. 49 /// 50 /// # Errors 51 /// 52 /// Returns an error if `init` does not match the element type of the table, 53 /// or if `init` does not belong to the `store` provided. 54 /// 55 /// # Panics 56 /// 57 /// This function will panic when used with a [`Store`](`crate::Store`) 58 /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) 59 /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`). 60 /// When using an async resource limiter, use [`Table::new_async`] 61 /// instead. 62 /// 63 /// # Examples 64 /// 65 /// ``` 66 /// # use wasmtime::*; 67 /// # fn main() -> anyhow::Result<()> { 68 /// let engine = Engine::default(); 69 /// let mut store = Store::new(&engine, ()); 70 /// 71 /// let ty = TableType::new(RefType::FUNCREF, 2, None); 72 /// let table = Table::new(&mut store, ty, Ref::Func(None))?; 73 /// 74 /// let module = Module::new( 75 /// &engine, 76 /// "(module 77 /// (table (import \"\" \"\") 2 funcref) 78 /// (func $f (result i32) 79 /// i32.const 10) 80 /// (elem (i32.const 0) $f) 81 /// )" 82 /// )?; 83 /// 84 /// let instance = Instance::new(&mut store, &module, &[table.into()])?; 85 /// // ... 86 /// # Ok(()) 87 /// # } 88 /// ``` 89 pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> { 90 Table::_new(store.as_context_mut().0, ty, init) 91 } 92 93 /// Async variant of [`Table::new`]. You must use this variant with 94 /// [`Store`](`crate::Store`)s which have a 95 /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). 96 /// 97 /// # Panics 98 /// 99 /// This function will panic when used with a non-async 100 /// [`Store`](`crate::Store`) 101 #[cfg(feature = "async")] 102 pub async fn new_async( 103 mut store: impl AsContextMut<Data: Send>, 104 ty: TableType, 105 init: Ref, 106 ) -> Result<Table> { 107 let mut store = store.as_context_mut(); 108 assert!( 109 store.0.async_support(), 110 "cannot use `new_async` without enabling async support on the config" 111 ); 112 store 113 .on_fiber(|store| Table::_new(store.0, ty, init)) 114 .await? 115 } 116 117 fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> { 118 let table = generate_table_export(store, &ty)?; 119 let init = init.into_table_element(store, ty.element())?; 120 let (wasmtime_table, gc_store) = table.wasmtime_table(store, iter::empty()); 121 wasmtime_table.fill(gc_store, 0, init, ty.minimum())?; 122 Ok(table) 123 } 124 125 /// Returns the underlying type of this table, including its element type as 126 /// well as the maximum/minimum lower bounds. 127 /// 128 /// # Panics 129 /// 130 /// Panics if `store` does not own this table. 131 pub fn ty(&self, store: impl AsContext) -> TableType { 132 self._ty(store.as_context().0) 133 } 134 135 fn _ty(&self, store: &StoreOpaque) -> TableType { 136 TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store)) 137 } 138 139 /// Returns the `runtime::Table` within `store` as well as the optional 140 /// `GcStore` in use within `store`. 141 /// 142 /// # Panics 143 /// 144 /// Panics if this table does not belong to `store`. 145 fn wasmtime_table<'a>( 146 &self, 147 store: &'a mut StoreOpaque, 148 lazy_init_range: impl Iterator<Item = u64>, 149 ) -> (&'a mut runtime::Table, Option<&'a mut GcStore>) { 150 self.instance.assert_belongs_to(store.id()); 151 let (store, instance) = store.optional_gc_store_and_instance_mut(self.instance.instance()); 152 153 ( 154 instance.get_defined_table_with_lazy_init(self.index, lazy_init_range), 155 store, 156 ) 157 } 158 159 /// Returns the table element value at `index`. 160 /// 161 /// Returns `None` if `index` is out of bounds. 162 /// 163 /// # Panics 164 /// 165 /// Panics if `store` does not own this table. 166 pub fn get(&self, mut store: impl AsContextMut, index: u64) -> Option<Ref> { 167 let mut store = AutoAssertNoGc::new(store.as_context_mut().0); 168 let (table, gc_store) = self.wasmtime_table(&mut store, iter::once(index)); 169 match table.get(gc_store, index)? { 170 runtime::TableElement::FuncRef(f) => { 171 // SAFETY: the `table` belongs to `store`, so elements within 172 // the table must also belong to the store. 173 let func = unsafe { f.map(|f| Func::from_vm_func_ref(store.id(), f)) }; 174 Some(func.into()) 175 } 176 177 runtime::TableElement::UninitFunc => { 178 unreachable!("lazy init above should have converted UninitFunc") 179 } 180 181 runtime::TableElement::GcRef(None) => { 182 Some(Ref::null(self._ty(&store).element().heap_type())) 183 } 184 185 #[cfg_attr( 186 not(feature = "gc"), 187 expect(unreachable_code, unused_variables, reason = "definitions cfg'd off") 188 )] 189 runtime::TableElement::GcRef(Some(x)) => { 190 match self._ty(&store).element().heap_type().top() { 191 HeapType::Any => { 192 let x = AnyRef::from_cloned_gc_ref(&mut store, x); 193 Some(x.into()) 194 } 195 HeapType::Extern => { 196 let x = ExternRef::from_cloned_gc_ref(&mut store, x); 197 Some(x.into()) 198 } 199 HeapType::Func => { 200 unreachable!("never have TableElement::GcRef for func tables") 201 } 202 ty => unreachable!("not a top type: {ty:?}"), 203 } 204 } 205 206 runtime::TableElement::ContRef(_c) => { 207 // TODO(#10248) Required to support stack switching in the embedder API. 208 unimplemented!() 209 } 210 } 211 } 212 213 /// Writes the `val` provided into `index` within this table. 214 /// 215 /// # Errors 216 /// 217 /// Returns an error if `index` is out of bounds, if `val` does not have 218 /// the right type to be stored in this table, or if `val` belongs to a 219 /// different store. 220 /// 221 /// # Panics 222 /// 223 /// Panics if `store` does not own this table. 224 pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> { 225 let store = store.as_context_mut().0; 226 let ty = self.ty(&store); 227 let val = val.into_table_element(store, ty.element())?; 228 let (table, _) = self.wasmtime_table(store, iter::empty()); 229 table 230 .set(index, val) 231 .map_err(|()| anyhow!("table element index out of bounds")) 232 } 233 234 /// Returns the current size of this table. 235 /// 236 /// # Panics 237 /// 238 /// Panics if `store` does not own this table. 239 pub fn size(&self, store: impl AsContext) -> u64 { 240 self.internal_size(store.as_context().0) 241 } 242 243 pub(crate) fn internal_size(&self, store: &StoreOpaque) -> u64 { 244 // unwrap here should be ok because the runtime should always guarantee 245 // that we can fit the number of elements in a 64-bit integer. 246 u64::try_from(store[self.instance].table(self.index).current_elements).unwrap() 247 } 248 249 /// Grows the size of this table by `delta` more elements, initialization 250 /// all new elements to `init`. 251 /// 252 /// Returns the previous size of this table if successful. 253 /// 254 /// # Errors 255 /// 256 /// Returns an error if the table cannot be grown by `delta`, for example 257 /// if it would cause the table to exceed its maximum size. Also returns an 258 /// error if `init` is not of the right type or if `init` does not belong to 259 /// `store`. 260 /// 261 /// # Panics 262 /// 263 /// Panics if `store` does not own this table. 264 /// 265 /// This function will panic when used with a [`Store`](`crate::Store`) 266 /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) 267 /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)). 268 /// When using an async resource limiter, use [`Table::grow_async`] 269 /// instead. 270 pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> { 271 let store = store.as_context_mut().0; 272 let ty = self.ty(&store); 273 let init = init.into_table_element(store, ty.element())?; 274 let (table, _gc_store) = self.wasmtime_table(store, iter::empty()); 275 // FIXME(#11179) shouldn't need to subvert the borrow checker 276 let table: *mut _ = table; 277 unsafe { 278 match (*table).grow(delta, init, store)? { 279 Some(size) => { 280 let vm = (*table).vmtable(); 281 store[self.instance].table_ptr(self.index).write(vm); 282 // unwrap here should be ok because the runtime should always guarantee 283 // that we can fit the table size in a 64-bit integer. 284 Ok(u64::try_from(size).unwrap()) 285 } 286 None => bail!("failed to grow table by `{}`", delta), 287 } 288 } 289 } 290 291 /// Async variant of [`Table::grow`]. Required when using a 292 /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). 293 /// 294 /// # Panics 295 /// 296 /// This function will panic when used with a non-async 297 /// [`Store`](`crate::Store`). 298 #[cfg(feature = "async")] 299 pub async fn grow_async( 300 &self, 301 mut store: impl AsContextMut<Data: Send>, 302 delta: u64, 303 init: Ref, 304 ) -> Result<u64> { 305 let mut store = store.as_context_mut(); 306 assert!( 307 store.0.async_support(), 308 "cannot use `grow_async` without enabling async support on the config" 309 ); 310 store 311 .on_fiber(|store| self.grow(store, delta, init)) 312 .await? 313 } 314 315 /// Copy `len` elements from `src_table[src_index..]` into 316 /// `dst_table[dst_index..]`. 317 /// 318 /// # Errors 319 /// 320 /// Returns an error if the range is out of bounds of either the source or 321 /// destination tables, or if the source table's element type does not match 322 /// the destination table's element type. 323 /// 324 /// # Panics 325 /// 326 /// Panics if `store` does not own either `dst_table` or `src_table`. 327 pub fn copy( 328 mut store: impl AsContextMut, 329 dst_table: &Table, 330 dst_index: u64, 331 src_table: &Table, 332 src_index: u64, 333 len: u64, 334 ) -> Result<()> { 335 let store = store.as_context_mut().0; 336 337 let dst_ty = dst_table.ty(&store); 338 let src_ty = src_table.ty(&store); 339 src_ty 340 .element() 341 .ensure_matches(store.engine(), dst_ty.element()) 342 .context( 343 "type mismatch: source table's element type does not match \ 344 destination table's element type", 345 )?; 346 347 let (dst_table, _) = dst_table.wasmtime_table(store, iter::empty()); 348 // FIXME(#11179) shouldn't need to subvert the borrow checker 349 let dst_table: *mut _ = dst_table; 350 let src_range = src_index..(src_index.checked_add(len).unwrap_or(u64::MAX)); 351 let (src_table, _) = src_table.wasmtime_table(store, src_range); 352 // FIXME(#11179) shouldn't need to subvert the borrow checker 353 let src_table: *mut _ = src_table; 354 unsafe { 355 runtime::Table::copy( 356 store.optional_gc_store_mut(), 357 dst_table, 358 src_table, 359 dst_index, 360 src_index, 361 len, 362 )?; 363 } 364 Ok(()) 365 } 366 367 /// Fill `table[dst..(dst + len)]` with the given value. 368 /// 369 /// # Errors 370 /// 371 /// Returns an error if 372 /// 373 /// * `val` is not of the same type as this table's 374 /// element type, 375 /// 376 /// * the region to be filled is out of bounds, or 377 /// 378 /// * `val` comes from a different `Store` from this table. 379 /// 380 /// # Panics 381 /// 382 /// Panics if `store` does not own either `dst_table` or `src_table`. 383 pub fn fill(&self, mut store: impl AsContextMut, dst: u64, val: Ref, len: u64) -> Result<()> { 384 let store = store.as_context_mut().0; 385 let ty = self.ty(&store); 386 let val = val.into_table_element(store, ty.element())?; 387 388 let (table, gc_store) = self.wasmtime_table(store, iter::empty()); 389 table.fill(gc_store, dst, val, len)?; 390 391 Ok(()) 392 } 393 394 #[cfg(feature = "gc")] 395 pub(crate) fn trace_roots( 396 &self, 397 store: &mut StoreOpaque, 398 gc_roots_list: &mut crate::runtime::vm::GcRootsList, 399 ) { 400 if !self 401 ._ty(store) 402 .element() 403 .is_vmgcref_type_and_points_to_object() 404 { 405 return; 406 } 407 408 let (table, _) = self.wasmtime_table(store, iter::empty()); 409 for gc_ref in table.gc_refs_mut() { 410 if let Some(gc_ref) = gc_ref { 411 unsafe { 412 gc_roots_list.add_root(gc_ref.into(), "Wasm table element"); 413 } 414 } 415 } 416 } 417 418 pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTableIndex) -> Table { 419 Table { instance, index } 420 } 421 422 pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Table { 423 let module = store[self.instance].env_module(); 424 let index = module.table_index(self.index); 425 &module.tables[index] 426 } 427 428 pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMTableImport { 429 let instance = &store[self.instance]; 430 crate::runtime::vm::VMTableImport { 431 from: instance.table_ptr(self.index).into(), 432 vmctx: instance.vmctx().into(), 433 index: self.index, 434 } 435 } 436 437 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool { 438 store.id() == self.instance.store_id() 439 } 440 441 /// Get a stable hash key for this table. 442 /// 443 /// Even if the same underlying table definition is added to the 444 /// `StoreData` multiple times and becomes multiple `wasmtime::Table`s, 445 /// this hash key will be consistent across all of these tables. 446 #[cfg_attr( 447 not(test), 448 expect(dead_code, reason = "Not used yet, but added for consistency") 449 )] 450 pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<'_> { 451 store[self.instance].table_ptr(self.index).as_ptr().addr() 452 } 453 } 454 455 #[cfg(test)] 456 mod tests { 457 use super::*; 458 use crate::{Instance, Module, Store}; 459 460 #[test] 461 fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> { 462 let mut store = Store::<()>::default(); 463 let module = Module::new( 464 store.engine(), 465 r#" 466 (module 467 (table (export "t") 1 1 externref) 468 ) 469 "#, 470 )?; 471 let instance = Instance::new(&mut store, &module, &[])?; 472 473 // Each time we `get_table`, we call `Table::from_wasmtime` which adds 474 // a new entry to `StoreData`, so `t1` and `t2` will have different 475 // indices into `StoreData`. 476 let t1 = instance.get_table(&mut store, "t").unwrap(); 477 let t2 = instance.get_table(&mut store, "t").unwrap(); 478 479 // That said, they really point to the same table. 480 assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none()); 481 assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none()); 482 let e = ExternRef::new(&mut store, 42)?; 483 t1.set(&mut store, 0, e.into())?; 484 assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some()); 485 assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some()); 486 487 // And therefore their hash keys are the same. 488 assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0)); 489 490 // But the hash keys are different from different tables. 491 let instance2 = Instance::new(&mut store, &module, &[])?; 492 let t3 = instance2.get_table(&mut store, "t").unwrap(); 493 assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0)); 494 495 Ok(()) 496 } 497 } 498