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