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