Skip to main content

wasmtime/runtime/gc/enabled/
exnref.rs

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