1 use crate::prelude::*; 2 use crate::runtime::RootedGcRefImpl; 3 use crate::runtime::vm::{self as runtime, GcStore, TableElementType, VMFuncRef, VMGcRef}; 4 use crate::store::{AutoAssertNoGc, StoreInstanceId, StoreOpaque}; 5 use crate::trampoline::generate_table_export; 6 use crate::{ 7 AnyRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, HeapType, Ref, RefType, TableType, 8 Trap, 9 }; 10 use core::iter; 11 use core::ptr::NonNull; 12 use wasmtime_environ::DefinedTableIndex; 13 14 /// A WebAssembly `table`, or an array of values. 15 /// 16 /// Like [`Memory`][crate::Memory] a table is an indexed array of values, but 17 /// unlike [`Memory`][crate::Memory] it's an array of WebAssembly reference type 18 /// values rather than bytes. One of the most common usages of a table is a 19 /// function table for wasm modules (a `funcref` table), where each element has 20 /// the `ValType::FuncRef` type. 21 /// 22 /// A [`Table`] "belongs" to the store that it was originally created within 23 /// (either via [`Table::new`] or via instantiating a 24 /// [`Module`](crate::Module)). Operations on a [`Table`] only work with the 25 /// store it belongs to, and if another store is passed in by accident then 26 /// methods will panic. 27 #[derive(Copy, Clone, Debug)] 28 #[repr(C)] // here for the C API 29 pub struct Table { 30 instance: StoreInstanceId, 31 index: DefinedTableIndex, 32 } 33 34 // Double-check that the C representation in `extern.h` matches our in-Rust 35 // representation here in terms of size/alignment/etc. 36 const _: () = { 37 #[repr(C)] 38 struct Tmp(u64, u32); 39 #[repr(C)] 40 struct C(Tmp, u32); 41 assert!(core::mem::size_of::<C>() == core::mem::size_of::<Table>()); 42 assert!(core::mem::align_of::<C>() == core::mem::align_of::<Table>()); 43 assert!(core::mem::offset_of!(Table, instance) == 0); 44 }; 45 46 impl Table { 47 /// Creates a new [`Table`] with the given parameters. 48 /// 49 /// * `store` - the owner of the resulting [`Table`] 50 /// * `ty` - the type of this table, containing both the element type as 51 /// well as the initial size and maximum size, if any. 52 /// * `init` - the initial value to fill all table entries with, if the 53 /// table starts with an initial size. 54 /// 55 /// # Errors 56 /// 57 /// Returns an error if `init` does not match the element type of the table, 58 /// or if `init` does not belong to the `store` provided. 59 /// 60 /// # Panics 61 /// 62 /// This function will panic when used with a [`Store`](`crate::Store`) 63 /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) 64 /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`). 65 /// When using an async resource limiter, use [`Table::new_async`] 66 /// instead. 67 /// 68 /// # Examples 69 /// 70 /// ``` 71 /// # use wasmtime::*; 72 /// # fn main() -> anyhow::Result<()> { 73 /// let engine = Engine::default(); 74 /// let mut store = Store::new(&engine, ()); 75 /// 76 /// let ty = TableType::new(RefType::FUNCREF, 2, None); 77 /// let table = Table::new(&mut store, ty, Ref::Func(None))?; 78 /// 79 /// let module = Module::new( 80 /// &engine, 81 /// "(module 82 /// (table (import \"\" \"\") 2 funcref) 83 /// (func $f (result i32) 84 /// i32.const 10) 85 /// (elem (i32.const 0) $f) 86 /// )" 87 /// )?; 88 /// 89 /// let instance = Instance::new(&mut store, &module, &[table.into()])?; 90 /// // ... 91 /// # Ok(()) 92 /// # } 93 /// ``` 94 pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> { 95 Table::_new(store.as_context_mut().0, ty, init) 96 } 97 98 /// Async variant of [`Table::new`]. You must use this variant with 99 /// [`Store`](`crate::Store`)s which have a 100 /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). 101 /// 102 /// # Panics 103 /// 104 /// This function will panic when used with a non-async 105 /// [`Store`](`crate::Store`) 106 #[cfg(feature = "async")] 107 pub async fn new_async( 108 mut store: impl AsContextMut<Data: Send>, 109 ty: TableType, 110 init: Ref, 111 ) -> Result<Table> { 112 let mut store = store.as_context_mut(); 113 assert!( 114 store.0.async_support(), 115 "cannot use `new_async` without enabling async support on the config" 116 ); 117 store 118 .on_fiber(|store| Table::_new(store.0, ty, init)) 119 .await? 120 } 121 122 fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> { 123 let table = generate_table_export(store, &ty)?; 124 table._fill(store, 0, init, ty.minimum())?; 125 Ok(table) 126 } 127 128 /// Returns the underlying type of this table, including its element type as 129 /// well as the maximum/minimum lower bounds. 130 /// 131 /// # Panics 132 /// 133 /// Panics if `store` does not own this table. 134 pub fn ty(&self, store: impl AsContext) -> TableType { 135 self._ty(store.as_context().0) 136 } 137 138 fn _ty(&self, store: &StoreOpaque) -> TableType { 139 TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store)) 140 } 141 142 /// Returns the `runtime::Table` within `store` as well as the optional 143 /// `GcStore` in use within `store`. 144 /// 145 /// # Panics 146 /// 147 /// Panics if this table does not belong to `store`. 148 fn wasmtime_table<'a>( 149 &self, 150 store: &'a mut StoreOpaque, 151 lazy_init_range: impl IntoIterator<Item = u64>, 152 ) -> (&'a mut runtime::Table, Option<&'a mut GcStore>) { 153 self.instance.assert_belongs_to(store.id()); 154 let (store, instance) = store.optional_gc_store_and_instance_mut(self.instance.instance()); 155 156 ( 157 instance.get_defined_table_with_lazy_init(self.index, lazy_init_range), 158 store, 159 ) 160 } 161 162 /// Returns the table element value at `index`. 163 /// 164 /// Returns `None` if `index` is out of bounds. 165 /// 166 /// # Panics 167 /// 168 /// Panics if `store` does not own this table. 169 pub fn get(&self, mut store: impl AsContextMut, index: u64) -> Option<Ref> { 170 let mut store = AutoAssertNoGc::new(store.as_context_mut().0); 171 let (table, _gc_store) = self.wasmtime_table(&mut store, [index]); 172 match table.element_type() { 173 TableElementType::Func => { 174 let ptr = table.get_func(index).ok()?; 175 Some( 176 // SAFETY: `store` owns this table, so therefore it owns all 177 // functions within the table too. 178 ptr.map(|p| unsafe { Func::from_vm_func_ref(store.id(), p) }) 179 .into(), 180 ) 181 } 182 TableElementType::GcRef => { 183 let gc_ref = table 184 .get_gc_ref(index) 185 .ok()? 186 .map(|r| r.unchecked_copy()) 187 .map(|r| store.clone_gc_ref(&r)); 188 Some(match self._ty(&store).element().heap_type().top() { 189 HeapType::Extern => { 190 Ref::Extern(gc_ref.map(|r| ExternRef::from_cloned_gc_ref(&mut store, r))) 191 } 192 HeapType::Any => { 193 Ref::Any(gc_ref.map(|r| AnyRef::from_cloned_gc_ref(&mut store, r))) 194 } 195 HeapType::Exn => { 196 Ref::Exn(gc_ref.map(|r| ExnRef::from_cloned_gc_ref(&mut store, r))) 197 } 198 _ => unreachable!(), 199 }) 200 } 201 // TODO(#10248) Required to support stack switching in the embedder 202 // API. 203 TableElementType::Cont => panic!("unimplemented table for cont"), 204 } 205 } 206 207 /// Writes the `val` provided into `index` within this table. 208 /// 209 /// # Errors 210 /// 211 /// Returns an error if `index` is out of bounds, if `val` does not have 212 /// the right type to be stored in this table, or if `val` belongs to a 213 /// different store. 214 /// 215 /// # Panics 216 /// 217 /// Panics if `store` does not own this table. 218 pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> { 219 self.set_(store.as_context_mut().0, index, val) 220 } 221 222 pub(crate) fn set_(&self, store: &mut StoreOpaque, index: u64, val: Ref) -> Result<()> { 223 let ty = self._ty(store); 224 match element_type(&ty) { 225 TableElementType::Func => { 226 let element = val.into_table_func(store, ty.element())?; 227 let (table, _gc_store) = self.wasmtime_table(store, iter::empty()); 228 table.set_func(index, element)?; 229 } 230 TableElementType::GcRef => { 231 let mut store = AutoAssertNoGc::new(store); 232 let element = val.into_table_gc_ref(&mut store, ty.element())?; 233 // Note that `unchecked_copy` should be ok as we're under an 234 // `AutoAssertNoGc` which means that despite this not being 235 // rooted we don't have to worry about it going away. 236 let element = element.map(|r| r.unchecked_copy()); 237 let (table, gc_store) = self.wasmtime_table(&mut store, iter::empty()); 238 table.set_gc_ref(gc_store, index, element.as_ref())?; 239 } 240 // TODO(#10248) Required to support stack switching in the embedder 241 // API. 242 TableElementType::Cont => bail!("unimplemented table for cont"), 243 } 244 Ok(()) 245 } 246 247 /// Returns the current size of this table. 248 /// 249 /// # Panics 250 /// 251 /// Panics if `store` does not own this table. 252 pub fn size(&self, store: impl AsContext) -> u64 { 253 self._size(store.as_context().0) 254 } 255 256 pub(crate) fn _size(&self, store: &StoreOpaque) -> u64 { 257 // unwrap here should be ok because the runtime should always guarantee 258 // that we can fit the number of elements in a 64-bit integer. 259 u64::try_from(store[self.instance].table(self.index).current_elements).unwrap() 260 } 261 262 /// Grows the size of this table by `delta` more elements, initialization 263 /// all new elements to `init`. 264 /// 265 /// Returns the previous size of this table if successful. 266 /// 267 /// # Errors 268 /// 269 /// Returns an error if the table cannot be grown by `delta`, for example 270 /// if it would cause the table to exceed its maximum size. Also returns an 271 /// error if `init` is not of the right type or if `init` does not belong to 272 /// `store`. 273 /// 274 /// # Panics 275 /// 276 /// Panics if `store` does not own this table. 277 /// 278 /// This function will panic when used with a [`Store`](`crate::Store`) 279 /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) 280 /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`)). 281 /// When using an async resource limiter, use [`Table::grow_async`] 282 /// instead. 283 pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> { 284 let store = store.as_context_mut().0; 285 let ty = self.ty(&store); 286 let (table, _gc_store) = self.wasmtime_table(store, iter::empty()); 287 // FIXME(#11179) shouldn't need to subvert the borrow checker 288 let table: *mut _ = table; 289 unsafe { 290 let result = match element_type(&ty) { 291 TableElementType::Func => { 292 let element = init.into_table_func(store, ty.element())?; 293 (*table).grow_func(store, delta, element)? 294 } 295 TableElementType::GcRef => { 296 // FIXME: `grow_gc_ref` shouldn't require the whole store 297 // and should require `AutoAssertNoGc`. For now though we 298 // know that table growth doesn't trigger GC so it should be 299 // ok to create a copy of the GC reference even though it's 300 // not tracked anywhere. 301 let element = init 302 .into_table_gc_ref(&mut AutoAssertNoGc::new(store), ty.element())? 303 .map(|r| r.unchecked_copy()); 304 (*table).grow_gc_ref(store, delta, element.as_ref())? 305 } 306 // TODO(#10248) Required to support stack switching in the 307 // embedder API. 308 TableElementType::Cont => bail!("unimplemented table for cont"), 309 }; 310 match result { 311 Some(size) => { 312 let vm = (*table).vmtable(); 313 store[self.instance].table_ptr(self.index).write(vm); 314 // unwrap here should be ok because the runtime should always guarantee 315 // that we can fit the table size in a 64-bit integer. 316 Ok(u64::try_from(size).unwrap()) 317 } 318 None => bail!("failed to grow table by `{}`", delta), 319 } 320 } 321 } 322 323 /// Async variant of [`Table::grow`]. Required when using a 324 /// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`). 325 /// 326 /// # Panics 327 /// 328 /// This function will panic when used with a non-async 329 /// [`Store`](`crate::Store`). 330 #[cfg(feature = "async")] 331 pub async fn grow_async( 332 &self, 333 mut store: impl AsContextMut<Data: Send>, 334 delta: u64, 335 init: Ref, 336 ) -> Result<u64> { 337 let mut store = store.as_context_mut(); 338 assert!( 339 store.0.async_support(), 340 "cannot use `grow_async` without enabling async support on the config" 341 ); 342 store 343 .on_fiber(|store| self.grow(store, delta, init)) 344 .await? 345 } 346 347 /// Copy `len` elements from `src_table[src_index..]` into 348 /// `dst_table[dst_index..]`. 349 /// 350 /// # Errors 351 /// 352 /// Returns an error if the range is out of bounds of either the source or 353 /// destination tables, or if the source table's element type does not match 354 /// the destination table's element type. 355 /// 356 /// # Panics 357 /// 358 /// Panics if `store` does not own either `dst_table` or `src_table`. 359 pub fn copy( 360 mut store: impl AsContextMut, 361 dst_table: &Table, 362 dst_index: u64, 363 src_table: &Table, 364 src_index: u64, 365 len: u64, 366 ) -> Result<()> { 367 let store = store.as_context_mut().0; 368 369 let dst_ty = dst_table.ty(&store); 370 let src_ty = src_table.ty(&store); 371 src_ty 372 .element() 373 .ensure_matches(store.engine(), dst_ty.element()) 374 .context( 375 "type mismatch: source table's element type does not match \ 376 destination table's element type", 377 )?; 378 379 // SAFETY: the the two tables have the same type, as type-checked above. 380 unsafe { 381 Self::copy_raw(store, dst_table, dst_index, src_table, src_index, len)?; 382 } 383 Ok(()) 384 } 385 386 /// Copies the elements of `src_table` to `dst_table`. 387 /// 388 /// # Panics 389 /// 390 /// Panics if the either table doesn't belong to `store`. 391 /// 392 /// # Safety 393 /// 394 /// Requires that the two tables have previously been type-checked to have 395 /// the same type. 396 pub(crate) unsafe fn copy_raw( 397 store: &mut StoreOpaque, 398 dst_table: &Table, 399 dst_index: u64, 400 src_table: &Table, 401 src_index: u64, 402 len: u64, 403 ) -> Result<(), Trap> { 404 // Handle lazy initialization of the source table first before doing 405 // anything else. 406 let src_range = src_index..(src_index.checked_add(len).unwrap_or(u64::MAX)); 407 src_table.wasmtime_table(store, src_range); 408 409 // validate `dst_table` belongs to `store`. 410 dst_table.wasmtime_table(store, iter::empty()); 411 412 // Figure out which of the three cases we're in: 413 // 414 // 1. Cross-instance table copy. 415 // 2. Intra-instance table copy. 416 // 3. Intra-table copy. 417 // 418 // We handle each of them slightly differently. 419 let src_instance = src_table.instance.instance(); 420 let dst_instance = dst_table.instance.instance(); 421 match ( 422 src_instance == dst_instance, 423 src_table.index == dst_table.index, 424 ) { 425 // 1. Cross-instance table copy: split the mutable store borrow into 426 // two mutable instance borrows, get each instance's defined table, 427 // and do the copy. 428 (false, _) => { 429 // SAFETY: accessing two instances mutably at the same time 430 // requires only accessing defined entities on each instance 431 // which is done below with `get_defined_*` methods. 432 let (gc_store, [src_instance, dst_instance]) = unsafe { 433 store.optional_gc_store_and_instances_mut([src_instance, dst_instance]) 434 }; 435 src_instance.get_defined_table(src_table.index).copy_to( 436 dst_instance.get_defined_table(dst_table.index), 437 gc_store, 438 dst_index, 439 src_index, 440 len, 441 ) 442 } 443 444 // 2. Intra-instance, distinct-tables copy: split the mutable 445 // instance borrow into two distinct mutable table borrows and do 446 // the copy. 447 (true, false) => { 448 let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance); 449 let [(_, src_table), (_, dst_table)] = instance 450 .tables_mut() 451 .get_disjoint_mut([src_table.index, dst_table.index]) 452 .unwrap(); 453 src_table.copy_to(dst_table, gc_store, dst_index, src_index, len) 454 } 455 456 // 3. Intra-table copy: get the table and copy within it! 457 (true, true) => { 458 let (gc_store, instance) = store.optional_gc_store_and_instance_mut(src_instance); 459 instance 460 .get_defined_table(src_table.index) 461 .copy_within(gc_store, dst_index, src_index, len) 462 } 463 } 464 } 465 466 /// Fill `table[dst..(dst + len)]` with the given value. 467 /// 468 /// # Errors 469 /// 470 /// Returns an error if 471 /// 472 /// * `val` is not of the same type as this table's 473 /// element type, 474 /// 475 /// * the region to be filled is out of bounds, or 476 /// 477 /// * `val` comes from a different `Store` from this table. 478 /// 479 /// # Panics 480 /// 481 /// Panics if `store` does not own either `dst_table` or `src_table`. 482 pub fn fill(&self, mut store: impl AsContextMut, dst: u64, val: Ref, len: u64) -> Result<()> { 483 self._fill(store.as_context_mut().0, dst, val, len) 484 } 485 486 pub(crate) fn _fill( 487 &self, 488 store: &mut StoreOpaque, 489 dst: u64, 490 val: Ref, 491 len: u64, 492 ) -> Result<()> { 493 let ty = self._ty(&store); 494 match element_type(&ty) { 495 TableElementType::Func => { 496 let val = val.into_table_func(store, ty.element())?; 497 let (table, _) = self.wasmtime_table(store, iter::empty()); 498 table.fill_func(dst, val, len)?; 499 } 500 TableElementType::GcRef => { 501 // Note that `val` is a `VMGcRef` temporarily read from the 502 // store here, and blocking GC with `AutoAssertNoGc` should 503 // ensure that it's not collected while being worked on here. 504 let mut store = AutoAssertNoGc::new(store); 505 let val = val.into_table_gc_ref(&mut store, ty.element())?; 506 let val = val.map(|g| g.unchecked_copy()); 507 let (table, gc_store) = self.wasmtime_table(&mut store, iter::empty()); 508 table.fill_gc_ref(gc_store, dst, val.as_ref(), len)?; 509 } 510 // TODO(#10248) Required to support stack switching in the embedder 511 // API. 512 TableElementType::Cont => bail!("unimplemented table for cont"), 513 } 514 515 Ok(()) 516 } 517 518 #[cfg(feature = "gc")] 519 pub(crate) fn trace_roots( 520 &self, 521 store: &mut StoreOpaque, 522 gc_roots_list: &mut crate::runtime::vm::GcRootsList, 523 ) { 524 if !self 525 ._ty(store) 526 .element() 527 .is_vmgcref_type_and_points_to_object() 528 { 529 return; 530 } 531 532 let (table, _) = self.wasmtime_table(store, iter::empty()); 533 for gc_ref in table.gc_refs_mut() { 534 if let Some(gc_ref) = gc_ref { 535 unsafe { 536 gc_roots_list.add_root(gc_ref.into(), "Wasm table element"); 537 } 538 } 539 } 540 } 541 542 pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTableIndex) -> Table { 543 Table { instance, index } 544 } 545 546 pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Table { 547 let module = store[self.instance].env_module(); 548 let index = module.table_index(self.index); 549 &module.tables[index] 550 } 551 552 pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMTableImport { 553 let instance = &store[self.instance]; 554 crate::runtime::vm::VMTableImport { 555 from: instance.table_ptr(self.index).into(), 556 vmctx: instance.vmctx().into(), 557 index: self.index, 558 } 559 } 560 561 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool { 562 store.id() == self.instance.store_id() 563 } 564 565 /// Get a stable hash key for this table. 566 /// 567 /// Even if the same underlying table definition is added to the 568 /// `StoreData` multiple times and becomes multiple `wasmtime::Table`s, 569 /// this hash key will be consistent across all of these tables. 570 #[cfg_attr( 571 not(test), 572 expect(dead_code, reason = "Not used yet, but added for consistency") 573 )] 574 pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<'_> { 575 store[self.instance].table_ptr(self.index).as_ptr().addr() 576 } 577 } 578 579 fn element_type(ty: &TableType) -> TableElementType { 580 match ty.element().heap_type().top() { 581 HeapType::Func => TableElementType::Func, 582 HeapType::Exn | HeapType::Extern | HeapType::Any => TableElementType::GcRef, 583 HeapType::Cont => TableElementType::Cont, 584 _ => unreachable!(), 585 } 586 } 587 588 impl Ref { 589 fn into_table_func( 590 self, 591 store: &mut StoreOpaque, 592 ty: &RefType, 593 ) -> Result<Option<NonNull<VMFuncRef>>> { 594 self.ensure_matches_ty(store, &ty) 595 .context("type mismatch: value does not match table element type")?; 596 597 match (self, ty.heap_type().top()) { 598 (Ref::Func(None), HeapType::Func) => { 599 assert!(ty.is_nullable()); 600 Ok(None) 601 } 602 (Ref::Func(Some(f)), HeapType::Func) => { 603 debug_assert!( 604 f.comes_from_same_store(store), 605 "checked in `ensure_matches_ty`" 606 ); 607 Ok(Some(f.vm_func_ref(store))) 608 } 609 610 _ => unreachable!("checked that the value matches the type above"), 611 } 612 } 613 614 fn into_table_gc_ref<'a>( 615 self, 616 store: &'a mut AutoAssertNoGc<'_>, 617 ty: &RefType, 618 ) -> Result<Option<&'a VMGcRef>> { 619 self.ensure_matches_ty(store, &ty) 620 .context("type mismatch: value does not match table element type")?; 621 622 match (self, ty.heap_type().top()) { 623 (Ref::Extern(e), HeapType::Extern) => match e { 624 None => { 625 assert!(ty.is_nullable()); 626 Ok(None) 627 } 628 Some(e) => Ok(Some(e.try_gc_ref(store)?)), 629 }, 630 631 (Ref::Any(a), HeapType::Any) => match a { 632 None => { 633 assert!(ty.is_nullable()); 634 Ok(None) 635 } 636 Some(a) => Ok(Some(a.try_gc_ref(store)?)), 637 }, 638 639 (Ref::Exn(e), HeapType::Exn) => match e { 640 None => { 641 assert!(ty.is_nullable()); 642 Ok(None) 643 } 644 Some(e) => Ok(Some(e.try_gc_ref(store)?)), 645 }, 646 647 _ => unreachable!("checked that the value matches the type above"), 648 } 649 } 650 } 651 652 #[cfg(test)] 653 mod tests { 654 use super::*; 655 use crate::{Instance, Module, Store}; 656 657 #[test] 658 fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> { 659 let mut store = Store::<()>::default(); 660 let module = Module::new( 661 store.engine(), 662 r#" 663 (module 664 (table (export "t") 1 1 externref) 665 ) 666 "#, 667 )?; 668 let instance = Instance::new(&mut store, &module, &[])?; 669 670 // Each time we `get_table`, we call `Table::from_wasmtime` which adds 671 // a new entry to `StoreData`, so `t1` and `t2` will have different 672 // indices into `StoreData`. 673 let t1 = instance.get_table(&mut store, "t").unwrap(); 674 let t2 = instance.get_table(&mut store, "t").unwrap(); 675 676 // That said, they really point to the same table. 677 assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none()); 678 assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none()); 679 let e = ExternRef::new(&mut store, 42)?; 680 t1.set(&mut store, 0, e.into())?; 681 assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some()); 682 assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some()); 683 684 // And therefore their hash keys are the same. 685 assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0)); 686 687 // But the hash keys are different from different tables. 688 let instance2 = Instance::new(&mut store, &module, &[])?; 689 let t3 = instance2.get_table(&mut store, "t").unwrap(); 690 assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0)); 691 692 Ok(()) 693 } 694 } 695