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        self.inner.try_gc_ref(store)?;
470        Ok(HeapType::Exn.matches(ty))
471    }
472
473    pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
474        if !self.comes_from_same_store(store) {
475            bail!("function used with wrong store");
476        }
477        if self._matches_ty(store, ty)? {
478            Ok(())
479        } else {
480            let actual_ty = self._ty(store)?;
481            bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
482        }
483    }
484
485    /// Get the values of this exception object's fields.
486    ///
487    /// # Errors
488    ///
489    /// Return an error if this reference has been unrooted.
490    ///
491    /// # Panics
492    ///
493    /// Panics if this reference is associated with a different store.
494    pub fn fields<'a, T: 'static>(
495        &'a self,
496        store: impl Into<StoreContextMut<'a, T>>,
497    ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
498        self._fields(store.into().0)
499    }
500
501    pub(crate) fn _fields<'a>(
502        &'a self,
503        store: &'a mut StoreOpaque,
504    ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
505        assert!(self.comes_from_same_store(store));
506        let store = AutoAssertNoGc::new(store);
507
508        let gc_ref = self.inner.try_gc_ref(&store)?;
509        let header = store.require_gc_store()?.header(gc_ref)?;
510        debug_assert!(header.matches_kind(VMGcKind::ExnRef));
511
512        let index = match header.ty() {
513            Some(index) => index,
514            None => bail_bug!("exnrefs should have concrete types"),
515        };
516        let ty = match ExnType::from_shared_type_index(store.engine(), index) {
517            Some(ty) => ty,
518            None => bail_bug!("invalid exception type index"),
519        };
520        let len = ty.fields().len();
521
522        return Ok(Fields {
523            exnref: self,
524            store,
525            index: 0,
526            len,
527        });
528
529        struct Fields<'a, 'b> {
530            exnref: &'a ExnRef,
531            store: AutoAssertNoGc<'b>,
532            index: usize,
533            len: usize,
534        }
535
536        impl Iterator for Fields<'_, '_> {
537            type Item = Val;
538
539            #[inline]
540            fn next(&mut self) -> Option<Self::Item> {
541                let i = self.index;
542                debug_assert!(i <= self.len);
543                if i >= self.len {
544                    return None;
545                }
546                self.index += 1;
547                self.exnref._field(&mut self.store, i).ok()
548            }
549
550            #[inline]
551            fn size_hint(&self) -> (usize, Option<usize>) {
552                let len = self.len - self.index;
553                (len, Some(len))
554            }
555        }
556
557        impl ExactSizeIterator for Fields<'_, '_> {
558            #[inline]
559            fn len(&self) -> usize {
560                self.len - self.index
561            }
562        }
563    }
564
565    fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
566        assert!(self.comes_from_same_store(&store));
567        let gc_ref = self.inner.try_gc_ref(store)?;
568        Ok(store.require_gc_store()?.header(gc_ref)?)
569    }
570
571    fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> {
572        assert!(self.comes_from_same_store(&store));
573        let gc_ref = self.inner.try_gc_ref(store)?;
574        debug_assert!(self.header(store)?.matches_kind(VMGcKind::ExnRef));
575        Ok(gc_ref.as_exnref_unchecked())
576    }
577
578    fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<Arc<GcStructLayout>> {
579        assert!(self.comes_from_same_store(&store));
580        let type_index = self.type_index(store)?;
581        super::gc_struct_layout(store.engine(), type_index)
582    }
583
584    fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
585        let ty = self._ty(store)?;
586        match ty.field(field) {
587            Some(f) => Ok(f),
588            None => {
589                let len = ty.fields().len();
590                bail!("cannot access field {field}: exn only has {len} fields")
591            }
592        }
593    }
594
595    /// Get this exception object's `index`th field.
596    ///
597    /// # Errors
598    ///
599    /// Returns an `Err(_)` if the index is out of bounds or this reference has
600    /// been unrooted.
601    ///
602    /// # Panics
603    ///
604    /// Panics if this reference is associated with a different store.
605    pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
606        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
607        self._field(&mut store, index)
608    }
609
610    pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
611        assert!(self.comes_from_same_store(store));
612        let exnref = self.exnref(store)?.unchecked_copy();
613        let field_ty = self.field_ty(store, index)?;
614        let layout = self.layout(store)?;
615        exnref.read_field(store, &layout, field_ty.element_type(), index)
616    }
617
618    /// Get this exception object's associated tag.
619    ///
620    /// # Errors
621    ///
622    /// Returns an `Err(_)` if this reference has been unrooted.
623    ///
624    /// # Panics
625    ///
626    /// Panics if this reference is associated with a different store.
627    pub fn tag(&self, mut store: impl AsContextMut) -> Result<Tag> {
628        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
629        assert!(self.comes_from_same_store(&store));
630        let exnref = self.exnref(&store)?.unchecked_copy();
631        let (instance, index) = exnref.tag(&mut store)?;
632        match Tag::from_raw_indices(&*store, instance, index) {
633            Some(tag) => Ok(tag),
634            None => bail_bug!("invalid tag indices in exception object"),
635        }
636    }
637}
638
639unsafe impl WasmTy for Rooted<ExnRef> {
640    #[inline]
641    fn valtype() -> ValType {
642        ValType::Ref(RefType::new(false, HeapType::Exn))
643    }
644
645    #[inline]
646    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
647        self.comes_from_same_store(store)
648    }
649
650    #[inline]
651    fn dynamic_concrete_type_check(
652        &self,
653        store: &StoreOpaque,
654        _nullable: bool,
655        ty: &HeapType,
656    ) -> Result<()> {
657        self.ensure_matches_ty(store, ty)
658    }
659
660    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
661        self.wasm_ty_store(store, ptr, ValRaw::anyref)
662    }
663
664    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
665        Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
666    }
667}
668
669unsafe impl WasmTy for Option<Rooted<ExnRef>> {
670    #[inline]
671    fn valtype() -> ValType {
672        ValType::EXNREF
673    }
674
675    #[inline]
676    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
677        self.map_or(true, |x| x.comes_from_same_store(store))
678    }
679
680    #[inline]
681    fn dynamic_concrete_type_check(
682        &self,
683        store: &StoreOpaque,
684        nullable: bool,
685        ty: &HeapType,
686    ) -> Result<()> {
687        match self {
688            Some(a) => a.ensure_matches_ty(store, ty),
689            None => {
690                ensure!(
691                    nullable,
692                    "expected a non-null reference, but found a null reference"
693                );
694                Ok(())
695            }
696        }
697    }
698
699    #[inline]
700    fn is_vmgcref_and_points_to_object(&self) -> bool {
701        self.is_some()
702    }
703
704    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
705        <Rooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
706    }
707
708    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
709        <Rooted<ExnRef>>::wasm_ty_option_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
710    }
711}
712
713unsafe impl WasmTy for OwnedRooted<ExnRef> {
714    #[inline]
715    fn valtype() -> ValType {
716        ValType::Ref(RefType::new(false, HeapType::Exn))
717    }
718
719    #[inline]
720    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
721        self.comes_from_same_store(store)
722    }
723
724    #[inline]
725    fn dynamic_concrete_type_check(
726        &self,
727        store: &StoreOpaque,
728        _nullable: bool,
729        ty: &HeapType,
730    ) -> Result<()> {
731        self.ensure_matches_ty(store, ty)
732    }
733
734    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
735        self.wasm_ty_store(store, ptr, ValRaw::anyref)
736    }
737
738    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
739        Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
740    }
741}
742
743unsafe impl WasmTy for Option<OwnedRooted<ExnRef>> {
744    #[inline]
745    fn valtype() -> ValType {
746        ValType::EXNREF
747    }
748
749    #[inline]
750    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
751        self.as_ref()
752            .map_or(true, |x| x.comes_from_same_store(store))
753    }
754
755    #[inline]
756    fn dynamic_concrete_type_check(
757        &self,
758        store: &StoreOpaque,
759        nullable: bool,
760        ty: &HeapType,
761    ) -> Result<()> {
762        match self {
763            Some(a) => a.ensure_matches_ty(store, ty),
764            None => {
765                ensure!(
766                    nullable,
767                    "expected a non-null reference, but found a null reference"
768                );
769                Ok(())
770            }
771        }
772    }
773
774    #[inline]
775    fn is_vmgcref_and_points_to_object(&self) -> bool {
776        self.is_some()
777    }
778
779    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
780        <OwnedRooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
781    }
782
783    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
784        <OwnedRooted<ExnRef>>::wasm_ty_option_load(
785            store,
786            ptr.get_anyref(),
787            ExnRef::from_cloned_gc_ref,
788        )
789    }
790}