1 //! Working with GC `struct` objects.
2 
3 use crate::runtime::vm::VMGcRef;
4 use crate::store::StoreId;
5 use crate::vm::{self, VMGcHeader, VMStore, VMStructRef};
6 use crate::{AnyRef, FieldType};
7 use crate::{
8     AsContext, AsContextMut, EqRef, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType,
9     OwnedRooted, RefType, Rooted, StructType, Val, ValRaw, ValType, WasmTy,
10     prelude::*,
11     store::{AutoAssertNoGc, StoreContextMut, StoreOpaque, StoreResourceLimiter},
12 };
13 use core::mem::{self, MaybeUninit};
14 use wasmtime_environ::{GcLayout, GcStructLayout, VMGcKind, VMSharedTypeIndex};
15 
16 /// An allocator for a particular Wasm GC struct type.
17 ///
18 /// Every `StructRefPre` is associated with a particular
19 /// [`Store`][crate::Store] and a particular [StructType][crate::StructType].
20 ///
21 /// Reusing an allocator across many allocations amortizes some per-type runtime
22 /// overheads inside Wasmtime. A `StructRefPre` is to `StructRef`s as an
23 /// `InstancePre` is to `Instance`s.
24 ///
25 /// # Example
26 ///
27 /// ```
28 /// use wasmtime::*;
29 ///
30 /// # fn foo() -> Result<()> {
31 /// let mut config = Config::new();
32 /// config.wasm_function_references(true);
33 /// config.wasm_gc(true);
34 ///
35 /// let engine = Engine::new(&config)?;
36 /// let mut store = Store::new(&engine, ());
37 ///
38 /// // Define a struct type.
39 /// let struct_ty = StructType::new(
40 ///    store.engine(),
41 ///    [FieldType::new(Mutability::Var, StorageType::I8)],
42 /// )?;
43 ///
44 /// // Create an allocator for the struct type.
45 /// let allocator = StructRefPre::new(&mut store, struct_ty);
46 ///
47 /// {
48 ///     let mut scope = RootScope::new(&mut store);
49 ///
50 ///     // Allocate a bunch of instances of our struct type using the same
51 ///     // allocator! This is faster than creating a new allocator for each
52 ///     // instance we want to allocate.
53 ///     for i in 0..10 {
54 ///         StructRef::new(&mut scope, &allocator, &[Val::I32(i)])?;
55 ///     }
56 /// }
57 /// # Ok(())
58 /// # }
59 /// # foo().unwrap();
60 /// ```
61 pub struct StructRefPre {
62     store_id: StoreId,
63     ty: StructType,
64 }
65 
66 impl StructRefPre {
67     /// Create a new `StructRefPre` that is associated with the given store
68     /// and type.
69     pub fn new(mut store: impl AsContextMut, ty: StructType) -> Self {
70         Self::_new(store.as_context_mut().0, ty)
71     }
72 
73     pub(crate) fn _new(store: &mut StoreOpaque, ty: StructType) -> Self {
74         store.insert_gc_host_alloc_type(ty.registered_type().clone());
75         let store_id = store.id();
76 
77         StructRefPre { store_id, ty }
78     }
79 
80     pub(crate) fn layout(&self) -> &GcStructLayout {
81         self.ty
82             .registered_type()
83             .layout()
84             .expect("struct types have a layout")
85             .unwrap_struct()
86     }
87 
88     pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
89         self.ty.registered_type().index()
90     }
91 }
92 
93 /// A reference to a GC-managed `struct` instance.
94 ///
95 /// WebAssembly `struct`s are static, fixed-length, ordered sequences of
96 /// fields. Fields are named by index, not by identifier; in this way, they are
97 /// similar to Rust's tuples. Each field is mutable or constant and stores
98 /// unpacked [`Val`][crate::Val]s or packed 8-/16-bit integers.
99 ///
100 /// Like all WebAssembly references, these are opaque and unforgeable to Wasm:
101 /// they cannot be faked and Wasm cannot, for example, cast the integer
102 /// `0x12345678` into a reference, pretend it is a valid `structref`, and trick
103 /// the host into dereferencing it and segfaulting or worse.
104 ///
105 /// Note that you can also use `Rooted<StructRef>` and
106 /// `OwnedRooted<StructRef>` as a type parameter with
107 /// [`Func::typed`][crate::Func::typed]- and
108 /// [`Func::wrap`][crate::Func::wrap]-style APIs.
109 ///
110 /// # Example
111 ///
112 /// ```
113 /// use wasmtime::*;
114 ///
115 /// # fn foo() -> Result<()> {
116 /// let mut config = Config::new();
117 /// config.wasm_function_references(true);
118 /// config.wasm_gc(true);
119 ///
120 /// let engine = Engine::new(&config)?;
121 /// let mut store = Store::new(&engine, ());
122 ///
123 /// // Define a struct type.
124 /// let struct_ty = StructType::new(
125 ///    store.engine(),
126 ///    [FieldType::new(Mutability::Var, StorageType::I8)],
127 /// )?;
128 ///
129 /// // Create an allocator for the struct type.
130 /// let allocator = StructRefPre::new(&mut store, struct_ty);
131 ///
132 /// {
133 ///     let mut scope = RootScope::new(&mut store);
134 ///
135 ///     // Allocate an instance of the struct type.
136 ///     let my_struct = StructRef::new(&mut scope, &allocator, &[Val::I32(42)])?;
137 ///
138 ///     // That instance's field should have the expected value.
139 ///     let val = my_struct.field(&mut scope, 0)?.unwrap_i32();
140 ///     assert_eq!(val, 42);
141 ///
142 ///     // And we can update the field's value because it is a mutable field.
143 ///     my_struct.set_field(&mut scope, 0, Val::I32(36))?;
144 ///     let new_val = my_struct.field(&mut scope, 0)?.unwrap_i32();
145 ///     assert_eq!(new_val, 36);
146 /// }
147 /// # Ok(())
148 /// # }
149 /// # foo().unwrap();
150 /// ```
151 #[derive(Debug)]
152 #[repr(transparent)]
153 pub struct StructRef {
154     pub(super) inner: GcRootIndex,
155 }
156 
157 unsafe impl GcRefImpl for StructRef {
158     fn transmute_ref(index: &GcRootIndex) -> &Self {
159         // Safety: `StructRef` is a newtype of a `GcRootIndex`.
160         let me: &Self = unsafe { mem::transmute(index) };
161 
162         // Assert we really are just a newtype of a `GcRootIndex`.
163         assert!(matches!(
164             me,
165             Self {
166                 inner: GcRootIndex { .. },
167             }
168         ));
169 
170         me
171     }
172 }
173 
174 impl Rooted<StructRef> {
175     /// Upcast this `structref` into an `anyref`.
176     #[inline]
177     pub fn to_anyref(self) -> Rooted<AnyRef> {
178         self.unchecked_cast()
179     }
180 
181     /// Upcast this `structref` into an `eqref`.
182     #[inline]
183     pub fn to_eqref(self) -> Rooted<EqRef> {
184         self.unchecked_cast()
185     }
186 }
187 
188 impl OwnedRooted<StructRef> {
189     /// Upcast this `structref` into an `anyref`.
190     #[inline]
191     pub fn to_anyref(self) -> OwnedRooted<AnyRef> {
192         self.unchecked_cast()
193     }
194 
195     /// Upcast this `structref` into an `eqref`.
196     #[inline]
197     pub fn to_eqref(self) -> OwnedRooted<EqRef> {
198         self.unchecked_cast()
199     }
200 }
201 
202 impl StructRef {
203     /// Synchronously allocate a new `struct` and get a reference to it.
204     ///
205     /// # Automatic Garbage Collection
206     ///
207     /// If the GC heap is at capacity, and there isn't room for allocating this
208     /// new struct, then this method will automatically trigger a synchronous
209     /// collection in an attempt to free up space in the GC heap.
210     ///
211     /// # Errors
212     ///
213     /// If the given `fields` values' types do not match the field types of the
214     /// `allocator`'s struct type, an error is returned.
215     ///
216     /// If the allocation cannot be satisfied because the GC heap is currently
217     /// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory]
218     /// error is returned. The allocation might succeed on a second attempt if
219     /// you drop some rooted GC references and try again.
220     ///
221     /// # Panics
222     ///
223     /// Panics if your engine is configured for async; use
224     /// [`StructRef::new_async`][crate::StructRef::new_async] to perform
225     /// synchronous allocation instead.
226     ///
227     /// Panics if the allocator, or any of the field values, is not associated
228     /// with the given store.
229     pub fn new(
230         mut store: impl AsContextMut,
231         allocator: &StructRefPre,
232         fields: &[Val],
233     ) -> Result<Rooted<StructRef>> {
234         let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
235         assert!(!store.async_support());
236         vm::assert_ready(Self::_new_async(store, limiter.as_mut(), allocator, fields))
237     }
238 
239     /// Asynchronously allocate a new `struct` and get a reference to it.
240     ///
241     /// # Automatic Garbage Collection
242     ///
243     /// If the GC heap is at capacity, and there isn't room for allocating this
244     /// new struct, then this method will automatically trigger a synchronous
245     /// collection in an attempt to free up space in the GC heap.
246     ///
247     /// # Errors
248     ///
249     /// If the given `fields` values' types do not match the field types of the
250     /// `allocator`'s struct type, an error is returned.
251     ///
252     /// If the allocation cannot be satisfied because the GC heap is currently
253     /// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory]
254     /// error is returned. The allocation might succeed on a second attempt if
255     /// you drop some rooted GC references and try again.
256     ///
257     /// # Panics
258     ///
259     /// Panics if the allocator, or any of the field values, is not associated
260     /// with the given store.
261     #[cfg(feature = "async")]
262     pub async fn new_async(
263         mut store: impl AsContextMut,
264         allocator: &StructRefPre,
265         fields: &[Val],
266     ) -> Result<Rooted<StructRef>> {
267         let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
268         Self::_new_async(store, limiter.as_mut(), allocator, fields).await
269     }
270 
271     pub(crate) async fn _new_async(
272         store: &mut StoreOpaque,
273         limiter: Option<&mut StoreResourceLimiter<'_>>,
274         allocator: &StructRefPre,
275         fields: &[Val],
276     ) -> Result<Rooted<StructRef>> {
277         Self::type_check_fields(store, allocator, fields)?;
278         store
279             .retry_after_gc_async(limiter, (), |store, ()| {
280                 Self::new_unchecked(store, allocator, fields)
281             })
282             .await
283     }
284 
285     /// Type check the field values before allocating a new struct.
286     fn type_check_fields(
287         store: &mut StoreOpaque,
288         allocator: &StructRefPre,
289         fields: &[Val],
290     ) -> Result<(), Error> {
291         let expected_len = allocator.ty.fields().len();
292         let actual_len = fields.len();
293         ensure!(
294             actual_len == expected_len,
295             "expected {expected_len} fields, got {actual_len}"
296         );
297         for (ty, val) in allocator.ty.fields().zip(fields) {
298             assert!(
299                 val.comes_from_same_store(store),
300                 "field value comes from the wrong store",
301             );
302             let ty = ty.element_type().unpack();
303             val.ensure_matches_ty(store, ty)
304                 .context("field type mismatch")?;
305         }
306         Ok(())
307     }
308 
309     /// Given that the field values have already been type checked, allocate a
310     /// new struct.
311     ///
312     /// Does not attempt GC+retry on OOM, that is the caller's responsibility.
313     fn new_unchecked(
314         store: &mut StoreOpaque,
315         allocator: &StructRefPre,
316         fields: &[Val],
317     ) -> Result<Rooted<StructRef>> {
318         assert_eq!(
319             store.id(),
320             allocator.store_id,
321             "attempted to use a `StructRefPre` with the wrong store"
322         );
323 
324         // Allocate the struct and write each field value into the appropriate
325         // offset.
326         let structref = store
327             .require_gc_store_mut()?
328             .alloc_uninit_struct(allocator.type_index(), &allocator.layout())
329             .context("unrecoverable error when allocating new `structref`")?
330             .map_err(|n| GcHeapOutOfMemory::new((), n))?;
331 
332         // From this point on, if we get any errors, then the struct is not
333         // fully initialized, so we need to eagerly deallocate it before the
334         // next GC where the collector might try to interpret one of the
335         // uninitialized fields as a GC reference.
336         let mut store = AutoAssertNoGc::new(store);
337         match (|| {
338             for (index, (ty, val)) in allocator.ty.fields().zip(fields).enumerate() {
339                 structref.initialize_field(
340                     &mut store,
341                     allocator.layout(),
342                     ty.element_type(),
343                     index,
344                     *val,
345                 )?;
346             }
347             Ok(())
348         })() {
349             Ok(()) => Ok(Rooted::new(&mut store, structref.into())),
350             Err(e) => {
351                 store
352                     .require_gc_store_mut()?
353                     .dealloc_uninit_struct(structref);
354                 Err(e)
355             }
356         }
357     }
358 
359     #[inline]
360     pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
361         self.inner.comes_from_same_store(store)
362     }
363 
364     /// Get this `structref`'s type.
365     ///
366     /// # Errors
367     ///
368     /// Return an error if this reference has been unrooted.
369     ///
370     /// # Panics
371     ///
372     /// Panics if this reference is associated with a different store.
373     pub fn ty(&self, store: impl AsContext) -> Result<StructType> {
374         self._ty(store.as_context().0)
375     }
376 
377     pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<StructType> {
378         assert!(self.comes_from_same_store(store));
379         let index = self.type_index(store)?;
380         Ok(StructType::from_shared_type_index(store.engine(), index))
381     }
382 
383     /// Does this `structref` match the given type?
384     ///
385     /// That is, is this struct's type a subtype of the given type?
386     ///
387     /// # Errors
388     ///
389     /// Return an error if this reference has been unrooted.
390     ///
391     /// # Panics
392     ///
393     /// Panics if this reference is associated with a different store or if the
394     /// type is not associated with the store's engine.
395     pub fn matches_ty(&self, store: impl AsContext, ty: &StructType) -> Result<bool> {
396         self._matches_ty(store.as_context().0, ty)
397     }
398 
399     pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &StructType) -> Result<bool> {
400         assert!(self.comes_from_same_store(store));
401         Ok(self._ty(store)?.matches(ty))
402     }
403 
404     pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &StructType) -> Result<()> {
405         if !self.comes_from_same_store(store) {
406             bail!("function used with wrong store");
407         }
408         if self._matches_ty(store, ty)? {
409             Ok(())
410         } else {
411             let actual_ty = self._ty(store)?;
412             bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
413         }
414     }
415 
416     /// Get the values of this struct's fields.
417     ///
418     /// Note that `i8` and `i16` field values are zero-extended into
419     /// `Val::I32(_)`s.
420     ///
421     /// # Errors
422     ///
423     /// Return an error if this reference has been unrooted.
424     ///
425     /// # Panics
426     ///
427     /// Panics if this reference is associated with a different store.
428     pub fn fields<'a, T: 'static>(
429         &'a self,
430         store: impl Into<StoreContextMut<'a, T>>,
431     ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
432         self._fields(store.into().0)
433     }
434 
435     pub(crate) fn _fields<'a>(
436         &'a self,
437         store: &'a mut StoreOpaque,
438     ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
439         assert!(self.comes_from_same_store(store));
440         let store = AutoAssertNoGc::new(store);
441 
442         let gc_ref = self.inner.try_gc_ref(&store)?;
443         let header = store.require_gc_store()?.header(gc_ref);
444         debug_assert!(header.kind().matches(VMGcKind::StructRef));
445 
446         let index = header.ty().expect("structrefs should have concrete types");
447         let ty = StructType::from_shared_type_index(store.engine(), index);
448         let len = ty.fields().len();
449 
450         return Ok(Fields {
451             structref: self,
452             store,
453             index: 0,
454             len,
455         });
456 
457         struct Fields<'a, 'b> {
458             structref: &'a StructRef,
459             store: AutoAssertNoGc<'b>,
460             index: usize,
461             len: usize,
462         }
463 
464         impl Iterator for Fields<'_, '_> {
465             type Item = Val;
466 
467             #[inline]
468             fn next(&mut self) -> Option<Self::Item> {
469                 let i = self.index;
470                 debug_assert!(i <= self.len);
471                 if i >= self.len {
472                     return None;
473                 }
474                 self.index += 1;
475                 Some(self.structref._field(&mut self.store, i).unwrap())
476             }
477 
478             #[inline]
479             fn size_hint(&self) -> (usize, Option<usize>) {
480                 let len = self.len - self.index;
481                 (len, Some(len))
482             }
483         }
484 
485         impl ExactSizeIterator for Fields<'_, '_> {
486             #[inline]
487             fn len(&self) -> usize {
488                 self.len - self.index
489             }
490         }
491     }
492 
493     fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
494         assert!(self.comes_from_same_store(&store));
495         let gc_ref = self.inner.try_gc_ref(store)?;
496         Ok(store.require_gc_store()?.header(gc_ref))
497     }
498 
499     fn structref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMStructRef> {
500         assert!(self.comes_from_same_store(&store));
501         let gc_ref = self.inner.try_gc_ref(store)?;
502         debug_assert!(self.header(store)?.kind().matches(VMGcKind::StructRef));
503         Ok(gc_ref.as_structref_unchecked())
504     }
505 
506     fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<GcStructLayout> {
507         assert!(self.comes_from_same_store(&store));
508         let type_index = self.type_index(store)?;
509         let layout = store
510             .engine()
511             .signatures()
512             .layout(type_index)
513             .expect("struct types should have GC layouts");
514         match layout {
515             GcLayout::Struct(s) => Ok(s),
516             GcLayout::Array(_) => unreachable!(),
517         }
518     }
519 
520     fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
521         let ty = self._ty(store)?;
522         match ty.field(field) {
523             Some(f) => Ok(f),
524             None => {
525                 let len = ty.fields().len();
526                 bail!("cannot access field {field}: struct only has {len} fields")
527             }
528         }
529     }
530 
531     /// Get this struct's `index`th field.
532     ///
533     /// Note that `i8` and `i16` field values are zero-extended into
534     /// `Val::I32(_)`s.
535     ///
536     /// # Errors
537     ///
538     /// Returns an `Err(_)` if the index is out of bounds or this reference has
539     /// been unrooted.
540     ///
541     /// # Panics
542     ///
543     /// Panics if this reference is associated with a different store.
544     pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
545         let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
546         self._field(&mut store, index)
547     }
548 
549     pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
550         assert!(self.comes_from_same_store(store));
551         let structref = self.structref(store)?.unchecked_copy();
552         let field_ty = self.field_ty(store, index)?;
553         let layout = self.layout(store)?;
554         Ok(structref.read_field(store, &layout, field_ty.element_type(), index))
555     }
556 
557     /// Set this struct's `index`th field.
558     ///
559     /// # Errors
560     ///
561     /// Returns an error in the following scenarios:
562     ///
563     /// * When given a value of the wrong type, such as trying to set an `f32`
564     ///   field to an `i64` value.
565     ///
566     /// * When the field is not mutable.
567     ///
568     /// * When this struct does not have an `index`th field, i.e. `index` is out
569     ///   of bounds.
570     ///
571     /// * When `value` is a GC reference that has since been unrooted.
572     ///
573     /// # Panics
574     ///
575     /// Panics if this reference is associated with a different store.
576     pub fn set_field(&self, mut store: impl AsContextMut, index: usize, value: Val) -> Result<()> {
577         self._set_field(store.as_context_mut().0, index, value)
578     }
579 
580     pub(crate) fn _set_field(
581         &self,
582         store: &mut StoreOpaque,
583         index: usize,
584         value: Val,
585     ) -> Result<()> {
586         assert!(self.comes_from_same_store(store));
587         let mut store = AutoAssertNoGc::new(store);
588 
589         let field_ty = self.field_ty(&store, index)?;
590         ensure!(
591             field_ty.mutability().is_var(),
592             "cannot set field {index}: field is not mutable"
593         );
594 
595         value
596             .ensure_matches_ty(&store, &field_ty.element_type().unpack())
597             .with_context(|| format!("cannot set field {index}: type mismatch"))?;
598 
599         let layout = self.layout(&store)?;
600         let structref = self.structref(&store)?.unchecked_copy();
601 
602         structref.write_field(&mut store, &layout, field_ty.element_type(), index, value)
603     }
604 
605     pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result<VMSharedTypeIndex> {
606         let gc_ref = self.inner.try_gc_ref(store)?;
607         let header = store.require_gc_store()?.header(gc_ref);
608         debug_assert!(header.kind().matches(VMGcKind::StructRef));
609         Ok(header.ty().expect("structrefs should have concrete types"))
610     }
611 
612     /// Create a new `Rooted<StructRef>` from the given GC reference.
613     ///
614     /// `gc_ref` should point to a valid `structref` and should belong to the
615     /// store's GC heap. Failure to uphold these invariants is memory safe but
616     /// will lead to general incorrectness such as panics or wrong results.
617     pub(crate) fn from_cloned_gc_ref(
618         store: &mut AutoAssertNoGc<'_>,
619         gc_ref: VMGcRef,
620     ) -> Rooted<Self> {
621         debug_assert!(gc_ref.is_structref(&*store.unwrap_gc_store().gc_heap));
622         Rooted::new(store, gc_ref)
623     }
624 }
625 
626 unsafe impl WasmTy for Rooted<StructRef> {
627     #[inline]
628     fn valtype() -> ValType {
629         ValType::Ref(RefType::new(false, HeapType::Struct))
630     }
631 
632     #[inline]
633     fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
634         self.comes_from_same_store(store)
635     }
636 
637     #[inline]
638     fn dynamic_concrete_type_check(
639         &self,
640         store: &StoreOpaque,
641         _nullable: bool,
642         ty: &HeapType,
643     ) -> Result<()> {
644         match ty {
645             HeapType::Any | HeapType::Eq | HeapType::Struct => Ok(()),
646             HeapType::ConcreteStruct(ty) => self.ensure_matches_ty(store, ty),
647 
648             HeapType::Extern
649             | HeapType::NoExtern
650             | HeapType::Func
651             | HeapType::ConcreteFunc(_)
652             | HeapType::NoFunc
653             | HeapType::I31
654             | HeapType::Array
655             | HeapType::ConcreteArray(_)
656             | HeapType::None
657             | HeapType::NoCont
658             | HeapType::Cont
659             | HeapType::ConcreteCont(_)
660             | HeapType::NoExn
661             | HeapType::Exn
662             | HeapType::ConcreteExn(_) => bail!(
663                 "type mismatch: expected `(ref {ty})`, got `(ref {})`",
664                 self._ty(store)?,
665             ),
666         }
667     }
668 
669     fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
670         self.wasm_ty_store(store, ptr, ValRaw::anyref)
671     }
672 
673     unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
674         Self::wasm_ty_load(store, ptr.get_anyref(), StructRef::from_cloned_gc_ref)
675     }
676 }
677 
678 unsafe impl WasmTy for Option<Rooted<StructRef>> {
679     #[inline]
680     fn valtype() -> ValType {
681         ValType::STRUCTREF
682     }
683 
684     #[inline]
685     fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
686         self.map_or(true, |x| x.comes_from_same_store(store))
687     }
688 
689     #[inline]
690     fn dynamic_concrete_type_check(
691         &self,
692         store: &StoreOpaque,
693         nullable: bool,
694         ty: &HeapType,
695     ) -> Result<()> {
696         match self {
697             Some(s) => Rooted::<StructRef>::dynamic_concrete_type_check(s, store, nullable, ty),
698             None => {
699                 ensure!(
700                     nullable,
701                     "expected a non-null reference, but found a null reference"
702                 );
703                 Ok(())
704             }
705         }
706     }
707 
708     #[inline]
709     fn is_vmgcref_and_points_to_object(&self) -> bool {
710         self.is_some()
711     }
712 
713     fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
714         <Rooted<StructRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
715     }
716 
717     unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
718         <Rooted<StructRef>>::wasm_ty_option_load(
719             store,
720             ptr.get_anyref(),
721             StructRef::from_cloned_gc_ref,
722         )
723     }
724 }
725 
726 unsafe impl WasmTy for OwnedRooted<StructRef> {
727     #[inline]
728     fn valtype() -> ValType {
729         ValType::Ref(RefType::new(false, HeapType::Struct))
730     }
731 
732     #[inline]
733     fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
734         self.comes_from_same_store(store)
735     }
736 
737     #[inline]
738     fn dynamic_concrete_type_check(
739         &self,
740         store: &StoreOpaque,
741         _: bool,
742         ty: &HeapType,
743     ) -> Result<()> {
744         match ty {
745             HeapType::Any | HeapType::Eq | HeapType::Struct => Ok(()),
746             HeapType::ConcreteStruct(ty) => self.ensure_matches_ty(store, ty),
747 
748             HeapType::Extern
749             | HeapType::NoExtern
750             | HeapType::Func
751             | HeapType::ConcreteFunc(_)
752             | HeapType::NoFunc
753             | HeapType::I31
754             | HeapType::Array
755             | HeapType::ConcreteArray(_)
756             | HeapType::None
757             | HeapType::NoCont
758             | HeapType::Cont
759             | HeapType::ConcreteCont(_)
760             | HeapType::NoExn
761             | HeapType::Exn
762             | HeapType::ConcreteExn(_) => bail!(
763                 "type mismatch: expected `(ref {ty})`, got `(ref {})`",
764                 self._ty(store)?,
765             ),
766         }
767     }
768 
769     fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
770         self.wasm_ty_store(store, ptr, ValRaw::anyref)
771     }
772 
773     unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
774         Self::wasm_ty_load(store, ptr.get_anyref(), StructRef::from_cloned_gc_ref)
775     }
776 }
777 
778 unsafe impl WasmTy for Option<OwnedRooted<StructRef>> {
779     #[inline]
780     fn valtype() -> ValType {
781         ValType::STRUCTREF
782     }
783 
784     #[inline]
785     fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
786         self.as_ref()
787             .map_or(true, |x| x.comes_from_same_store(store))
788     }
789 
790     #[inline]
791     fn dynamic_concrete_type_check(
792         &self,
793         store: &StoreOpaque,
794         nullable: bool,
795         ty: &HeapType,
796     ) -> Result<()> {
797         match self {
798             Some(s) => {
799                 OwnedRooted::<StructRef>::dynamic_concrete_type_check(s, store, nullable, ty)
800             }
801             None => {
802                 ensure!(
803                     nullable,
804                     "expected a non-null reference, but found a null reference"
805                 );
806                 Ok(())
807             }
808         }
809     }
810 
811     #[inline]
812     fn is_vmgcref_and_points_to_object(&self) -> bool {
813         self.is_some()
814     }
815 
816     fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
817         <OwnedRooted<StructRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
818     }
819 
820     unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
821         <OwnedRooted<StructRef>>::wasm_ty_option_load(
822             store,
823             ptr.get_anyref(),
824             StructRef::from_cloned_gc_ref,
825         )
826     }
827 }
828