1 //! Memory management for tables. 2 //! 3 //! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories. 4 5 #![cfg_attr(feature = "gc", allow(irrefutable_let_patterns))] 6 7 use crate::prelude::*; 8 use crate::runtime::vm::stack_switching::VMContObj; 9 use crate::runtime::vm::vmcontext::{VMFuncRef, VMTableDefinition}; 10 use crate::runtime::vm::{GcStore, SendSyncPtr, VMGcRef, VMStore}; 11 use core::alloc::Layout; 12 use core::mem; 13 use core::ops::Range; 14 use core::ptr::{self, NonNull}; 15 use core::slice; 16 use core::{cmp, usize}; 17 use wasmtime_environ::{ 18 FUNCREF_INIT_BIT, FUNCREF_MASK, IndexType, Trap, Tunables, WasmHeapTopType, WasmRefType, 19 }; 20 21 /// An element going into or coming out of a table. 22 /// 23 /// Table elements are stored as pointers and are default-initialized with 24 /// `ptr::null_mut`. 25 pub enum TableElement { 26 /// A `funcref`. 27 FuncRef(Option<NonNull<VMFuncRef>>), 28 29 /// A GC reference. 30 GcRef(Option<VMGcRef>), 31 32 /// An uninitialized funcref value. This should never be exposed 33 /// beyond the `wasmtime` crate boundary; the upper-level code 34 /// (which has access to the info needed for lazy initialization) 35 /// will replace it when fetched. 36 UninitFunc, 37 38 /// A `contref` 39 ContRef(Option<VMContObj>), 40 } 41 42 #[derive(Copy, Clone, PartialEq, Eq, Debug)] 43 pub enum TableElementType { 44 Func, 45 GcRef, 46 Cont, 47 } 48 49 impl TableElementType { 50 fn matches(&self, val: &TableElement) -> bool { 51 match (val, self) { 52 (TableElement::FuncRef(_), TableElementType::Func) => true, 53 (TableElement::GcRef(_), TableElementType::GcRef) => true, 54 (TableElement::ContRef(_), TableElementType::Cont) => true, 55 _ => false, 56 } 57 } 58 59 /// Returns the size required to actually store an element of this particular type 60 pub fn element_size(&self) -> usize { 61 match self { 62 TableElementType::Func => core::mem::size_of::<FuncTableElem>(), 63 TableElementType::GcRef => core::mem::size_of::<Option<VMGcRef>>(), 64 TableElementType::Cont => core::mem::size_of::<ContTableElem>(), 65 } 66 } 67 } 68 69 // The usage of `*mut VMFuncRef` is safe w.r.t. thread safety, this just relies 70 // on thread-safety of `VMGcRef` itself. 71 unsafe impl Send for TableElement where VMGcRef: Send {} 72 unsafe impl Sync for TableElement where VMGcRef: Sync {} 73 74 impl TableElement { 75 /// Consumes a table element into a pointer/reference, as it 76 /// exists outside the table itself. This strips off any tag bits 77 /// or other information that only lives inside the table. 78 /// 79 /// Can only be done to an initialized table element; lazy init 80 /// must occur first. (In other words, lazy values do not survive 81 /// beyond the table, as every table read path initializes them.) 82 /// 83 /// # Safety 84 /// 85 /// The same warnings as for `into_table_values()` apply. 86 pub(crate) unsafe fn into_func_ref_asserting_initialized(self) -> Option<NonNull<VMFuncRef>> { 87 match self { 88 Self::FuncRef(e) => e, 89 Self::UninitFunc => panic!("Uninitialized table element value outside of table slot"), 90 Self::GcRef(_) => panic!("GC reference is not a function reference"), 91 Self::ContRef(_) => panic!("Continuation reference is not a function reference"), 92 } 93 } 94 95 /// Indicates whether this value is the "uninitialized element" 96 /// value. 97 pub(crate) fn is_uninit(&self) -> bool { 98 match self { 99 Self::UninitFunc => true, 100 _ => false, 101 } 102 } 103 } 104 105 impl From<Option<NonNull<VMFuncRef>>> for TableElement { 106 fn from(f: Option<NonNull<VMFuncRef>>) -> TableElement { 107 TableElement::FuncRef(f) 108 } 109 } 110 111 impl From<Option<VMGcRef>> for TableElement { 112 fn from(x: Option<VMGcRef>) -> TableElement { 113 TableElement::GcRef(x) 114 } 115 } 116 117 impl From<VMGcRef> for TableElement { 118 fn from(x: VMGcRef) -> TableElement { 119 TableElement::GcRef(Some(x)) 120 } 121 } 122 123 impl From<Option<VMContObj>> for TableElement { 124 fn from(c: Option<VMContObj>) -> TableElement { 125 TableElement::ContRef(c) 126 } 127 } 128 129 impl From<VMContObj> for TableElement { 130 fn from(c: VMContObj) -> TableElement { 131 TableElement::ContRef(Some(c)) 132 } 133 } 134 135 #[derive(Copy, Clone)] 136 #[repr(transparent)] 137 struct TaggedFuncRef(*mut VMFuncRef); 138 139 impl TaggedFuncRef { 140 const UNINIT: TaggedFuncRef = TaggedFuncRef(ptr::null_mut()); 141 142 /// Converts the given `ptr`, a valid funcref pointer, into a tagged pointer 143 /// by adding in the `FUNCREF_INIT_BIT`. 144 fn from(ptr: Option<NonNull<VMFuncRef>>, lazy_init: bool) -> Self { 145 let ptr = ptr.map(|p| p.as_ptr()).unwrap_or(ptr::null_mut()); 146 if lazy_init { 147 let masked = ptr.map_addr(|a| a | FUNCREF_INIT_BIT); 148 TaggedFuncRef(masked) 149 } else { 150 TaggedFuncRef(ptr) 151 } 152 } 153 154 /// Converts a tagged pointer into a `TableElement`, returning `UninitFunc` 155 /// for null (not a tagged value) or `FuncRef` for otherwise tagged values. 156 fn into_table_element(self, lazy_init: bool) -> TableElement { 157 let ptr = self.0; 158 if lazy_init && ptr.is_null() { 159 TableElement::UninitFunc 160 } else { 161 // Masking off the tag bit is harmless whether the table uses lazy 162 // init or not. 163 let unmasked = ptr.map_addr(|a| a & FUNCREF_MASK); 164 TableElement::FuncRef(NonNull::new(unmasked)) 165 } 166 } 167 } 168 169 pub type FuncTableElem = Option<SendSyncPtr<VMFuncRef>>; 170 pub type ContTableElem = Option<VMContObj>; 171 172 /// The maximum of the sizes of any of the table element types 173 #[cfg(feature = "pooling-allocator")] 174 pub const NOMINAL_MAX_TABLE_ELEM_SIZE: usize = { 175 // ContTableElem intentionally excluded for "nominal" calculation. 176 let sizes = [ 177 core::mem::size_of::<FuncTableElem>(), 178 core::mem::size_of::<Option<VMGcRef>>(), 179 ]; 180 181 // This is equivalent to `|data| {data.iter().reduce(std::cmp::max).unwrap()}`, 182 // but as a `const` function, so we can use it to define a constant. 183 const fn slice_max(data: &[usize]) -> usize { 184 match data { 185 [] => 0, 186 [head, tail @ ..] => { 187 let tail_max = slice_max(tail); 188 if *head >= tail_max { *head } else { tail_max } 189 } 190 } 191 } 192 193 slice_max(&sizes) 194 }; 195 196 pub enum StaticTable { 197 Func(StaticFuncTable), 198 GcRef(StaticGcRefTable), 199 Cont(StaticContTable), 200 } 201 202 impl From<StaticFuncTable> for StaticTable { 203 fn from(value: StaticFuncTable) -> Self { 204 Self::Func(value) 205 } 206 } 207 208 impl From<StaticGcRefTable> for StaticTable { 209 fn from(value: StaticGcRefTable) -> Self { 210 Self::GcRef(value) 211 } 212 } 213 214 impl From<StaticContTable> for StaticTable { 215 fn from(value: StaticContTable) -> Self { 216 Self::Cont(value) 217 } 218 } 219 220 pub struct StaticFuncTable { 221 /// Where data for this table is stored. The length of this list is the 222 /// maximum size of the table. 223 data: SendSyncPtr<[FuncTableElem]>, 224 /// The current size of the table. 225 size: usize, 226 /// Whether elements of this table are initialized lazily. 227 lazy_init: bool, 228 } 229 230 pub struct StaticGcRefTable { 231 /// Where data for this table is stored. The length of this list is the 232 /// maximum size of the table. 233 data: SendSyncPtr<[Option<VMGcRef>]>, 234 /// The current size of the table. 235 size: usize, 236 } 237 238 pub struct StaticContTable { 239 /// Where data for this table is stored. The length of this list is the 240 /// maximum size of the table. 241 data: SendSyncPtr<[ContTableElem]>, 242 /// The current size of the table. 243 size: usize, 244 } 245 246 pub enum DynamicTable { 247 Func(DynamicFuncTable), 248 GcRef(DynamicGcRefTable), 249 Cont(DynamicContTable), 250 } 251 252 impl From<DynamicFuncTable> for DynamicTable { 253 fn from(value: DynamicFuncTable) -> Self { 254 Self::Func(value) 255 } 256 } 257 258 impl From<DynamicGcRefTable> for DynamicTable { 259 fn from(value: DynamicGcRefTable) -> Self { 260 Self::GcRef(value) 261 } 262 } 263 264 impl From<DynamicContTable> for DynamicTable { 265 fn from(value: DynamicContTable) -> Self { 266 Self::Cont(value) 267 } 268 } 269 270 pub struct DynamicFuncTable { 271 /// Dynamically managed storage space for this table. The length of this 272 /// vector is the current size of the table. 273 elements: Vec<FuncTableElem>, 274 /// Maximum size that `elements` can grow to. 275 maximum: Option<usize>, 276 /// Whether elements of this table are initialized lazily. 277 lazy_init: bool, 278 } 279 280 pub struct DynamicGcRefTable { 281 /// Dynamically managed storage space for this table. The length of this 282 /// vector is the current size of the table. 283 elements: Vec<Option<VMGcRef>>, 284 /// Maximum size that `elements` can grow to. 285 maximum: Option<usize>, 286 } 287 288 pub struct DynamicContTable { 289 /// Dynamically managed storage space for this table. The length of this 290 /// vector is the current size of the table. 291 elements: Vec<ContTableElem>, 292 /// Maximum size that `elements` can grow to. 293 maximum: Option<usize>, 294 } 295 296 /// Represents an instance's table. 297 pub enum Table { 298 /// A "static" table where storage space is managed externally, currently 299 /// used with the pooling allocator. 300 Static(StaticTable), 301 /// A "dynamic" table where table storage space is dynamically allocated via 302 /// `malloc` (aka Rust's `Vec`). 303 Dynamic(DynamicTable), 304 } 305 306 impl From<StaticTable> for Table { 307 fn from(value: StaticTable) -> Self { 308 Self::Static(value) 309 } 310 } 311 312 impl From<StaticFuncTable> for Table { 313 fn from(value: StaticFuncTable) -> Self { 314 let t: StaticTable = value.into(); 315 t.into() 316 } 317 } 318 319 impl From<StaticGcRefTable> for Table { 320 fn from(value: StaticGcRefTable) -> Self { 321 let t: StaticTable = value.into(); 322 t.into() 323 } 324 } 325 326 impl From<StaticContTable> for Table { 327 fn from(value: StaticContTable) -> Self { 328 let t: StaticTable = value.into(); 329 t.into() 330 } 331 } 332 333 impl From<DynamicTable> for Table { 334 fn from(value: DynamicTable) -> Self { 335 Self::Dynamic(value) 336 } 337 } 338 339 impl From<DynamicFuncTable> for Table { 340 fn from(value: DynamicFuncTable) -> Self { 341 let t: DynamicTable = value.into(); 342 t.into() 343 } 344 } 345 346 impl From<DynamicGcRefTable> for Table { 347 fn from(value: DynamicGcRefTable) -> Self { 348 let t: DynamicTable = value.into(); 349 t.into() 350 } 351 } 352 353 impl From<DynamicContTable> for Table { 354 fn from(value: DynamicContTable) -> Self { 355 let t: DynamicTable = value.into(); 356 t.into() 357 } 358 } 359 360 pub(crate) fn wasm_to_table_type(ty: WasmRefType) -> TableElementType { 361 match ty.heap_type.top() { 362 WasmHeapTopType::Func => TableElementType::Func, 363 WasmHeapTopType::Any | WasmHeapTopType::Extern => TableElementType::GcRef, 364 WasmHeapTopType::Cont => TableElementType::Cont, 365 } 366 } 367 368 /// Allocate dynamic table elements of the given length. 369 /// 370 /// Relies on the fact that our tables' elements are initialized to `None`, 371 /// which is represented by zero, to allocate pre-zeroed memory from the global 372 /// allocator and avoid manual zero-initialization. 373 /// 374 /// # Safety 375 /// 376 /// Should only ever be called with a `T` that is a table element type and where 377 /// `Option<T>`'s `None` variant is represented with zero. 378 unsafe fn alloc_dynamic_table_elements<T>(len: usize) -> Result<Vec<Option<T>>> { 379 debug_assert!( 380 core::mem::MaybeUninit::<Option<T>>::zeroed() 381 .assume_init() 382 .is_none(), 383 "null table elements are represented with zeroed memory" 384 ); 385 386 if len == 0 { 387 return Ok(vec![]); 388 } 389 390 let align = mem::align_of::<Option<T>>(); 391 392 let size = mem::size_of::<Option<T>>(); 393 let size = size.next_multiple_of(align); 394 let size = size.checked_mul(len).unwrap(); 395 396 let layout = Layout::from_size_align(size, align)?; 397 398 let ptr = alloc::alloc::alloc_zeroed(layout); 399 ensure!(!ptr.is_null(), "failed to allocate memory for table"); 400 401 let elems = Vec::<Option<T>>::from_raw_parts(ptr.cast(), len, len); 402 debug_assert!(elems.iter().all(|e| e.is_none())); 403 404 Ok(elems) 405 } 406 407 impl Table { 408 /// Create a new dynamic (movable) table instance for the specified table plan. 409 pub fn new_dynamic( 410 ty: &wasmtime_environ::Table, 411 tunables: &Tunables, 412 store: &mut dyn VMStore, 413 ) -> Result<Self> { 414 let (minimum, maximum) = Self::limit_new(ty, store)?; 415 match wasm_to_table_type(ty.ref_type) { 416 TableElementType::Func => Ok(Self::from(DynamicFuncTable { 417 elements: unsafe { alloc_dynamic_table_elements(minimum)? }, 418 maximum, 419 lazy_init: tunables.table_lazy_init, 420 })), 421 TableElementType::GcRef => Ok(Self::from(DynamicGcRefTable { 422 elements: unsafe { alloc_dynamic_table_elements(minimum)? }, 423 maximum, 424 })), 425 TableElementType::Cont => Ok(Self::from(DynamicContTable { 426 elements: vec![None; minimum], 427 maximum, 428 })), 429 } 430 } 431 432 /// Create a new static (immovable) table instance for the specified table plan. 433 pub unsafe fn new_static( 434 ty: &wasmtime_environ::Table, 435 tunables: &Tunables, 436 data: SendSyncPtr<[u8]>, 437 store: &mut dyn VMStore, 438 ) -> Result<Self> { 439 let (minimum, maximum) = Self::limit_new(ty, store)?; 440 let size = minimum; 441 let max = maximum.unwrap_or(usize::MAX); 442 443 match wasm_to_table_type(ty.ref_type) { 444 TableElementType::Func => { 445 let len = { 446 let data = data.as_non_null().as_ref(); 447 let (before, data, after) = data.align_to::<FuncTableElem>(); 448 assert!(before.is_empty()); 449 assert!(after.is_empty()); 450 data.len() 451 }; 452 ensure!( 453 usize::try_from(ty.limits.min).unwrap() <= len, 454 "initial table size of {} exceeds the pooling allocator's \ 455 configured maximum table size of {len} elements", 456 ty.limits.min, 457 ); 458 let data = SendSyncPtr::new(NonNull::slice_from_raw_parts( 459 data.as_non_null().cast::<FuncTableElem>(), 460 cmp::min(len, max), 461 )); 462 Ok(Self::from(StaticFuncTable { 463 data, 464 size, 465 lazy_init: tunables.table_lazy_init, 466 })) 467 } 468 TableElementType::GcRef => { 469 let len = { 470 let data = data.as_non_null().as_ref(); 471 let (before, data, after) = data.align_to::<Option<VMGcRef>>(); 472 assert!(before.is_empty()); 473 assert!(after.is_empty()); 474 data.len() 475 }; 476 ensure!( 477 usize::try_from(ty.limits.min).unwrap() <= len, 478 "initial table size of {} exceeds the pooling allocator's \ 479 configured maximum table size of {len} elements", 480 ty.limits.min, 481 ); 482 let data = SendSyncPtr::new(NonNull::slice_from_raw_parts( 483 data.as_non_null().cast::<Option<VMGcRef>>(), 484 cmp::min(len, max), 485 )); 486 Ok(Self::from(StaticGcRefTable { data, size })) 487 } 488 TableElementType::Cont => { 489 let len = { 490 let data = data.as_non_null().as_ref(); 491 let (before, data, after) = data.align_to::<ContTableElem>(); 492 assert!(before.is_empty()); 493 assert!(after.is_empty()); 494 data.len() 495 }; 496 ensure!( 497 usize::try_from(ty.limits.min).unwrap() <= len, 498 "initial table size of {} exceeds the pooling allocator's \ 499 configured maximum table size of {len} elements", 500 ty.limits.min, 501 ); 502 let data = SendSyncPtr::new(NonNull::slice_from_raw_parts( 503 data.as_non_null().cast::<ContTableElem>(), 504 cmp::min(len, max), 505 )); 506 Ok(Self::from(StaticContTable { data, size })) 507 } 508 } 509 } 510 511 // Calls the `store`'s limiter to optionally prevent the table from being created. 512 // 513 // Returns the minimum and maximum size of the table if the table can be created. 514 fn limit_new( 515 ty: &wasmtime_environ::Table, 516 store: &mut dyn VMStore, 517 ) -> Result<(usize, Option<usize>)> { 518 // No matter how the table limits are specified 519 // The table size is limited by the host's pointer size 520 let absolute_max = usize::MAX; 521 522 // If the minimum overflows the host's pointer size, then we can't satisfy this request. 523 // We defer the error to later so the `store` can be informed. 524 let minimum = usize::try_from(ty.limits.min).ok(); 525 526 // The maximum size of the table is limited by: 527 // * the host's pointer size. 528 // * the table's maximum size if defined. 529 // * if the table is 64-bit. 530 let maximum = match (ty.limits.max, ty.idx_type) { 531 (Some(max), _) => usize::try_from(max).ok(), 532 (None, IndexType::I64) => usize::try_from(u64::MAX).ok(), 533 (None, IndexType::I32) => usize::try_from(u32::MAX).ok(), 534 }; 535 536 // Inform the store's limiter what's about to happen. 537 if !store.table_growing(0, minimum.unwrap_or(absolute_max), maximum)? { 538 bail!( 539 "table minimum size of {} elements exceeds table limits", 540 ty.limits.min 541 ); 542 } 543 544 // At this point we need to actually handle overflows, so bail out with 545 // an error if we made it this far. 546 let minimum = minimum.ok_or_else(|| { 547 format_err!( 548 "table minimum size of {} elements exceeds table limits", 549 ty.limits.min 550 ) 551 })?; 552 Ok((minimum, maximum)) 553 } 554 555 /// Returns the type of the elements in this table. 556 pub fn element_type(&self) -> TableElementType { 557 match self { 558 Table::Static(StaticTable::Func(_)) | Table::Dynamic(DynamicTable::Func(_)) => { 559 TableElementType::Func 560 } 561 Table::Static(StaticTable::GcRef(_)) | Table::Dynamic(DynamicTable::GcRef(_)) => { 562 TableElementType::GcRef 563 } 564 Table::Static(StaticTable::Cont(_)) | Table::Dynamic(DynamicTable::Cont(_)) => { 565 TableElementType::Cont 566 } 567 } 568 } 569 570 /// Returns whether or not the underlying storage of the table is "static". 571 #[cfg(feature = "pooling-allocator")] 572 pub(crate) fn is_static(&self) -> bool { 573 matches!(self, Table::Static(_)) 574 } 575 576 /// Returns the number of allocated elements. 577 pub fn size(&self) -> usize { 578 match self { 579 Table::Static(StaticTable::Func(StaticFuncTable { size, .. })) => *size, 580 Table::Static(StaticTable::GcRef(StaticGcRefTable { size, .. })) => *size, 581 Table::Static(StaticTable::Cont(StaticContTable { size, .. })) => *size, 582 Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => elements.len(), 583 Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => { 584 elements.len() 585 } 586 Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => elements.len(), 587 } 588 } 589 590 /// Returns the maximum number of elements at runtime. 591 /// 592 /// Returns `None` if the table is unbounded. 593 /// 594 /// The runtime maximum may not be equal to the maximum from the table's Wasm type 595 /// when it is being constrained by an instance allocator. 596 pub fn maximum(&self) -> Option<usize> { 597 match self { 598 Table::Static(StaticTable::Cont(StaticContTable { data, .. })) => Some(data.len()), 599 Table::Static(StaticTable::Func(StaticFuncTable { data, .. })) => Some(data.len()), 600 Table::Static(StaticTable::GcRef(StaticGcRefTable { data, .. })) => Some(data.len()), 601 Table::Dynamic(DynamicTable::Func(DynamicFuncTable { maximum, .. })) => *maximum, 602 Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { maximum, .. })) => *maximum, 603 Table::Dynamic(DynamicTable::Cont(DynamicContTable { maximum, .. })) => *maximum, 604 } 605 } 606 607 /// Initializes the contents of this table to the specified function 608 /// 609 /// # Panics 610 /// 611 /// Panics if the table is not a function table. 612 pub fn init_func( 613 &mut self, 614 dst: u64, 615 items: impl ExactSizeIterator<Item = Option<NonNull<VMFuncRef>>>, 616 ) -> Result<(), Trap> { 617 let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?; 618 619 let (funcrefs, lazy_init) = self.funcrefs_mut(); 620 let elements = funcrefs 621 .get_mut(dst..) 622 .and_then(|s| s.get_mut(..items.len())) 623 .ok_or(Trap::TableOutOfBounds)?; 624 625 for (item, slot) in items.zip(elements) { 626 *slot = TaggedFuncRef::from(item, lazy_init); 627 } 628 Ok(()) 629 } 630 631 /// Fill `table[dst..]` with values from `items` 632 /// 633 /// Returns a trap error on out-of-bounds accesses. 634 pub fn init_gc_refs( 635 &mut self, 636 dst: u64, 637 items: impl ExactSizeIterator<Item = Option<VMGcRef>>, 638 ) -> Result<(), Trap> { 639 let dst = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?; 640 641 let elements = self 642 .gc_refs_mut() 643 .get_mut(dst..) 644 .and_then(|s| s.get_mut(..items.len())) 645 .ok_or(Trap::TableOutOfBounds)?; 646 647 for (item, slot) in items.zip(elements) { 648 *slot = item; 649 } 650 Ok(()) 651 } 652 653 /// Fill `table[dst..dst + len]` with `val`. 654 /// 655 /// Returns a trap error on out-of-bounds accesses. 656 /// 657 /// # Panics 658 /// 659 /// Panics if `val` does not have a type that matches this table, or if 660 /// `gc_store.is_none()` and this is a table of GC references. 661 pub fn fill( 662 &mut self, 663 mut gc_store: Option<&mut GcStore>, 664 dst: u64, 665 val: TableElement, 666 len: u64, 667 ) -> Result<(), Trap> { 668 let start = usize::try_from(dst).map_err(|_| Trap::TableOutOfBounds)?; 669 let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; 670 let end = start 671 .checked_add(len) 672 .ok_or_else(|| Trap::TableOutOfBounds)?; 673 674 if end > self.size() { 675 return Err(Trap::TableOutOfBounds); 676 } 677 678 match val { 679 TableElement::FuncRef(f) => { 680 let (funcrefs, lazy_init) = self.funcrefs_mut(); 681 funcrefs[start..end].fill(TaggedFuncRef::from(f, lazy_init)); 682 } 683 TableElement::GcRef(r) => { 684 // Clone the init GC reference into each table slot. 685 for slot in &mut self.gc_refs_mut()[start..end] { 686 match gc_store.as_deref_mut() { 687 Some(s) => s.write_gc_ref(slot, r.as_ref()), 688 None => { 689 debug_assert!(slot.as_ref().is_none_or(|x| x.is_i31())); 690 debug_assert!(r.as_ref().is_none_or(|r| r.is_i31())); 691 *slot = r.as_ref().map(|r| r.copy_i31()); 692 } 693 } 694 } 695 696 // Drop the init GC reference, since we aren't holding onto this 697 // reference anymore, only the clones in the table. 698 if let Some(r) = r { 699 match gc_store { 700 Some(s) => s.drop_gc_ref(r), 701 None => debug_assert!(r.is_i31()), 702 } 703 } 704 } 705 TableElement::UninitFunc => { 706 let (funcrefs, _lazy_init) = self.funcrefs_mut(); 707 funcrefs[start..end].fill(TaggedFuncRef::UNINIT); 708 } 709 TableElement::ContRef(c) => { 710 let contrefs = self.contrefs_mut(); 711 contrefs[start..end].fill(c); 712 } 713 } 714 715 Ok(()) 716 } 717 718 /// Grow table by the specified amount of elements. 719 /// 720 /// Returns the previous size of the table if growth is successful. 721 /// 722 /// Returns `None` if table can't be grown by the specified amount of 723 /// elements, or if the `init_value` is the wrong kind of table element. 724 /// 725 /// # Panics 726 /// 727 /// Panics if `init_value` does not have a type that matches this table. 728 /// 729 /// # Unsafety 730 /// 731 /// Resizing the table can reallocate its internal elements buffer. This 732 /// table's instance's `VMContext` has raw pointers to the elements buffer 733 /// that are used by Wasm, and they need to be fixed up before we call into 734 /// Wasm again. Failure to do so will result in use-after-free inside Wasm. 735 /// 736 /// Generally, prefer using `InstanceHandle::table_grow`, which encapsulates 737 /// this unsafety. 738 pub unsafe fn grow( 739 &mut self, 740 delta: u64, 741 init_value: TableElement, 742 store: &mut dyn VMStore, 743 ) -> Result<Option<usize>, Error> { 744 let old_size = self.size(); 745 746 // Don't try to resize the table if its size isn't changing, just return 747 // success. 748 if delta == 0 { 749 return Ok(Some(old_size)); 750 } 751 // Cannot return `Trap::TableOutOfBounds` here because `impl std::error::Error for Trap` is not available in no-std. 752 let delta = 753 usize::try_from(delta).map_err(|_| format_err!("delta exceeds host pointer size"))?; 754 755 let new_size = match old_size.checked_add(delta) { 756 Some(s) => s, 757 None => { 758 store.table_grow_failed(format_err!("overflow calculating new table size"))?; 759 return Ok(None); 760 } 761 }; 762 763 if !store.table_growing(old_size, new_size, self.maximum())? { 764 return Ok(None); 765 } 766 767 // The WebAssembly spec requires failing a `table.grow` request if 768 // it exceeds the declared limits of the table. We may have set lower 769 // limits in the instance allocator as well. 770 if let Some(max) = self.maximum() { 771 if new_size > max { 772 store.table_grow_failed(format_err!("Table maximum size exceeded"))?; 773 return Ok(None); 774 } 775 } 776 777 debug_assert!(self.type_matches(&init_value)); 778 779 // First resize the storage and then fill with the init value 780 match self { 781 Table::Static(StaticTable::Func(StaticFuncTable { data, size, .. })) => { 782 unsafe { 783 debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none())); 784 } 785 *size = new_size; 786 } 787 Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => { 788 unsafe { 789 debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none())); 790 } 791 *size = new_size; 792 } 793 Table::Static(StaticTable::Cont(StaticContTable { data, size })) => { 794 unsafe { 795 debug_assert!(data.as_ref()[*size..new_size].iter().all(|x| x.is_none())); 796 } 797 *size = new_size; 798 } 799 800 // These calls to `resize` could move the base address of 801 // `elements`. If this table's limits declare it to be fixed-size, 802 // then during AOT compilation we may have promised Cranelift that 803 // the table base address won't change, so it is allowed to optimize 804 // loading the base address. However, in that case the above checks 805 // that delta is non-zero and the new size doesn't exceed the 806 // maximum mean we can't get here. 807 Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => { 808 elements.resize(new_size, None); 809 } 810 Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => { 811 elements.resize_with(new_size, || None); 812 } 813 Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => { 814 elements.resize(new_size, None); 815 } 816 } 817 818 self.fill( 819 store.store_opaque_mut().optional_gc_store_mut(), 820 u64::try_from(old_size).unwrap(), 821 init_value, 822 u64::try_from(delta).unwrap(), 823 ) 824 .expect("table should not be out of bounds"); 825 826 Ok(Some(old_size)) 827 } 828 829 /// Get reference to the specified element. 830 /// 831 /// Returns `None` if the index is out of bounds. 832 /// 833 /// Panics if this is a table of GC references and `gc_store` is `None`. 834 pub fn get(&self, gc_store: Option<&mut GcStore>, index: u64) -> Option<TableElement> { 835 let index = usize::try_from(index).ok()?; 836 match self.element_type() { 837 TableElementType::Func => { 838 let (funcrefs, lazy_init) = self.funcrefs(); 839 funcrefs 840 .get(index) 841 .copied() 842 .map(|e| e.into_table_element(lazy_init)) 843 } 844 TableElementType::GcRef => self.gc_refs().get(index).map(|r| { 845 let r = r.as_ref().map(|r| match gc_store { 846 Some(s) => s.clone_gc_ref(r), 847 None => r.copy_i31(), 848 }); 849 850 TableElement::GcRef(r) 851 }), 852 TableElementType::Cont => self 853 .contrefs() 854 .get(index) 855 .copied() 856 .map(|e| TableElement::ContRef(e)), 857 } 858 } 859 860 /// Set reference to the specified element. 861 /// 862 /// # Errors 863 /// 864 /// Returns an error if `index` is out of bounds or if this table type does 865 /// not match the element type. 866 /// 867 /// # Panics 868 /// 869 /// Panics if `elem` is not of the right type for this table. 870 pub fn set(&mut self, index: u64, elem: TableElement) -> Result<(), ()> { 871 let index: usize = index.try_into().map_err(|_| ())?; 872 match elem { 873 TableElement::FuncRef(f) => { 874 let (funcrefs, lazy_init) = self.funcrefs_mut(); 875 *funcrefs.get_mut(index).ok_or(())? = TaggedFuncRef::from(f, lazy_init); 876 } 877 TableElement::UninitFunc => { 878 let (funcrefs, _lazy_init) = self.funcrefs_mut(); 879 *funcrefs.get_mut(index).ok_or(())? = TaggedFuncRef::UNINIT; 880 } 881 TableElement::GcRef(e) => { 882 *self.gc_refs_mut().get_mut(index).ok_or(())? = e; 883 } 884 TableElement::ContRef(c) => { 885 *self.contrefs_mut().get_mut(index).ok_or(())? = c; 886 } 887 } 888 Ok(()) 889 } 890 891 /// Copy `len` elements from `src_table[src_index..]` into `dst_table[dst_index..]`. 892 /// 893 /// # Errors 894 /// 895 /// Returns an error if the range is out of bounds of either the source or 896 /// destination tables. 897 pub unsafe fn copy( 898 gc_store: Option<&mut GcStore>, 899 dst_table: *mut Self, 900 src_table: *mut Self, 901 dst_index: u64, 902 src_index: u64, 903 len: u64, 904 ) -> Result<(), Trap> { 905 // https://webassembly.github.io/bulk-memory-operations/core/exec/instructions.html#exec-table-copy 906 907 let src_index = usize::try_from(src_index).map_err(|_| Trap::TableOutOfBounds)?; 908 let dst_index = usize::try_from(dst_index).map_err(|_| Trap::TableOutOfBounds)?; 909 let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; 910 911 if src_index 912 .checked_add(len) 913 .map_or(true, |n| n > (*src_table).size()) 914 || dst_index 915 .checked_add(len) 916 .map_or(true, |m| m > (*dst_table).size()) 917 { 918 return Err(Trap::TableOutOfBounds); 919 } 920 921 debug_assert!( 922 (*dst_table).element_type() == (*src_table).element_type(), 923 "table element type mismatch" 924 ); 925 926 let src_range = src_index..src_index + len; 927 let dst_range = dst_index..dst_index + len; 928 929 // Check if the tables are the same as we cannot mutably borrow and also borrow the same `RefCell` 930 if ptr::eq(dst_table, src_table) { 931 (*dst_table).copy_elements_within(gc_store, dst_range, src_range); 932 } else { 933 Self::copy_elements(gc_store, &mut *dst_table, &*src_table, dst_range, src_range); 934 } 935 936 Ok(()) 937 } 938 939 /// Return a `VMTableDefinition` for exposing the table to compiled wasm code. 940 pub fn vmtable(&mut self) -> VMTableDefinition { 941 match self { 942 Table::Static(StaticTable::Func(StaticFuncTable { data, size, .. })) => { 943 VMTableDefinition { 944 base: data.cast().into(), 945 current_elements: *size, 946 } 947 } 948 Table::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => { 949 VMTableDefinition { 950 base: data.cast().into(), 951 current_elements: *size, 952 } 953 } 954 Table::Static(StaticTable::Cont(StaticContTable { data, size })) => VMTableDefinition { 955 base: data.cast().into(), 956 current_elements: *size, 957 }, 958 Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => { 959 VMTableDefinition { 960 base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(), 961 current_elements: elements.len(), 962 } 963 } 964 Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => { 965 VMTableDefinition { 966 base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(), 967 current_elements: elements.len(), 968 } 969 } 970 Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => { 971 VMTableDefinition { 972 base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(), 973 current_elements: elements.len(), 974 } 975 } 976 } 977 } 978 979 fn type_matches(&self, val: &TableElement) -> bool { 980 self.element_type().matches(val) 981 } 982 983 fn funcrefs(&self) -> (&[TaggedFuncRef], bool) { 984 assert_eq!(self.element_type(), TableElementType::Func); 985 match self { 986 Self::Dynamic(DynamicTable::Func(DynamicFuncTable { 987 elements, 988 lazy_init, 989 .. 990 })) => ( 991 unsafe { slice::from_raw_parts(elements.as_ptr().cast(), elements.len()) }, 992 *lazy_init, 993 ), 994 Self::Static(StaticTable::Func(StaticFuncTable { 995 data, 996 size, 997 lazy_init, 998 })) => ( 999 unsafe { slice::from_raw_parts(data.as_ptr().cast(), *size) }, 1000 *lazy_init, 1001 ), 1002 _ => unreachable!(), 1003 } 1004 } 1005 1006 fn funcrefs_mut(&mut self) -> (&mut [TaggedFuncRef], bool) { 1007 assert_eq!(self.element_type(), TableElementType::Func); 1008 match self { 1009 Self::Dynamic(DynamicTable::Func(DynamicFuncTable { 1010 elements, 1011 lazy_init, 1012 .. 1013 })) => ( 1014 unsafe { slice::from_raw_parts_mut(elements.as_mut_ptr().cast(), elements.len()) }, 1015 *lazy_init, 1016 ), 1017 Self::Static(StaticTable::Func(StaticFuncTable { 1018 data, 1019 size, 1020 lazy_init, 1021 })) => ( 1022 unsafe { slice::from_raw_parts_mut(data.as_ptr().cast(), *size) }, 1023 *lazy_init, 1024 ), 1025 _ => unreachable!(), 1026 } 1027 } 1028 1029 fn gc_refs(&self) -> &[Option<VMGcRef>] { 1030 assert_eq!(self.element_type(), TableElementType::GcRef); 1031 match self { 1032 Self::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => elements, 1033 Self::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => unsafe { 1034 &data.as_non_null().as_ref()[..*size] 1035 }, 1036 _ => unreachable!(), 1037 } 1038 } 1039 1040 fn contrefs(&self) -> &[Option<VMContObj>] { 1041 assert_eq!(self.element_type(), TableElementType::Cont); 1042 match self { 1043 Self::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => unsafe { 1044 slice::from_raw_parts(elements.as_ptr().cast(), elements.len()) 1045 }, 1046 Self::Static(StaticTable::Cont(StaticContTable { data, size })) => unsafe { 1047 slice::from_raw_parts(data.as_ptr().cast(), *size) 1048 }, 1049 _ => unreachable!(), 1050 } 1051 } 1052 1053 fn contrefs_mut(&mut self) -> &mut [Option<VMContObj>] { 1054 assert_eq!(self.element_type(), TableElementType::Cont); 1055 match self { 1056 Self::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => unsafe { 1057 slice::from_raw_parts_mut(elements.as_mut_ptr().cast(), elements.len()) 1058 }, 1059 Self::Static(StaticTable::Cont(StaticContTable { data, size })) => unsafe { 1060 slice::from_raw_parts_mut(data.as_ptr().cast(), *size) 1061 }, 1062 _ => unreachable!(), 1063 } 1064 } 1065 1066 /// Get this table's GC references as a slice. 1067 /// 1068 /// Panics if this is not a table of GC references. 1069 pub fn gc_refs_mut(&mut self) -> &mut [Option<VMGcRef>] { 1070 assert_eq!(self.element_type(), TableElementType::GcRef); 1071 match self { 1072 Self::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => elements, 1073 Self::Static(StaticTable::GcRef(StaticGcRefTable { data, size })) => unsafe { 1074 &mut data.as_non_null().as_mut()[..*size] 1075 }, 1076 _ => unreachable!(), 1077 } 1078 } 1079 1080 fn copy_elements( 1081 gc_store: Option<&mut GcStore>, 1082 dst_table: &mut Self, 1083 src_table: &Self, 1084 dst_range: Range<usize>, 1085 src_range: Range<usize>, 1086 ) { 1087 // This can only be used when copying between different tables 1088 debug_assert!(!ptr::eq(dst_table, src_table)); 1089 1090 let ty = dst_table.element_type(); 1091 1092 match ty { 1093 TableElementType::Func => { 1094 // `funcref` are `Copy`, so just do a mempcy 1095 let (dst_funcrefs, _lazy_init) = dst_table.funcrefs_mut(); 1096 let (src_funcrefs, _lazy_init) = src_table.funcrefs(); 1097 dst_funcrefs[dst_range].copy_from_slice(&src_funcrefs[src_range]); 1098 } 1099 TableElementType::GcRef => { 1100 assert_eq!( 1101 dst_range.end - dst_range.start, 1102 src_range.end - src_range.start 1103 ); 1104 assert!(dst_range.end <= dst_table.gc_refs().len()); 1105 assert!(src_range.end <= src_table.gc_refs().len()); 1106 let gc_store = gc_store.unwrap(); 1107 for (dst, src) in dst_range.zip(src_range) { 1108 gc_store.write_gc_ref( 1109 &mut dst_table.gc_refs_mut()[dst], 1110 src_table.gc_refs()[src].as_ref(), 1111 ); 1112 } 1113 } 1114 TableElementType::Cont => { 1115 // `contref` are `Copy`, so just do a mempcy 1116 dst_table.contrefs_mut()[dst_range] 1117 .copy_from_slice(&src_table.contrefs()[src_range]); 1118 } 1119 } 1120 } 1121 1122 fn copy_elements_within( 1123 &mut self, 1124 gc_store: Option<&mut GcStore>, 1125 dst_range: Range<usize>, 1126 src_range: Range<usize>, 1127 ) { 1128 assert_eq!( 1129 dst_range.end - dst_range.start, 1130 src_range.end - src_range.start 1131 ); 1132 1133 // This is a no-op. 1134 if src_range.start == dst_range.start { 1135 return; 1136 } 1137 1138 let ty = self.element_type(); 1139 match ty { 1140 TableElementType::Func => { 1141 // `funcref` are `Copy`, so just do a memmove 1142 let (funcrefs, _lazy_init) = self.funcrefs_mut(); 1143 funcrefs.copy_within(src_range, dst_range.start); 1144 } 1145 TableElementType::GcRef => { 1146 let gc_store = gc_store.unwrap(); 1147 1148 // We need to clone each `externref` while handling overlapping 1149 // ranges 1150 let elements = self.gc_refs_mut(); 1151 if dst_range.start < src_range.start { 1152 for (d, s) in dst_range.zip(src_range) { 1153 let (ds, ss) = elements.split_at_mut(s); 1154 let dst = &mut ds[d]; 1155 let src = ss[0].as_ref(); 1156 gc_store.write_gc_ref(dst, src); 1157 } 1158 } else { 1159 for (s, d) in src_range.rev().zip(dst_range.rev()) { 1160 let (ss, ds) = elements.split_at_mut(d); 1161 let dst = &mut ds[0]; 1162 let src = ss[s].as_ref(); 1163 gc_store.write_gc_ref(dst, src); 1164 } 1165 } 1166 } 1167 TableElementType::Cont => { 1168 // `contref` are `Copy`, so just do a memmove 1169 self.contrefs_mut().copy_within(src_range, dst_range.start); 1170 } 1171 } 1172 } 1173 } 1174 1175 // The default table representation is an empty funcref table that cannot grow. 1176 impl Default for Table { 1177 fn default() -> Self { 1178 Self::from(StaticFuncTable { 1179 data: SendSyncPtr::new(NonNull::from(&mut [])), 1180 size: 0, 1181 lazy_init: false, 1182 }) 1183 } 1184 } 1185