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, prelude::*};
14use alloc::sync::Arc;
15use core::mem;
16use core::mem::MaybeUninit;
17use wasmtime_environ::{GcLayout, 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.kind().matches(VMGcKind::ExnRef));
378        Ok(header.ty().expect("exnrefs should have concrete types"))
379    }
380
381    /// Create a new `Rooted<ExnRef>` from the given GC reference.
382    ///
383    /// `gc_ref` should point to a valid `exnref` and should belong to
384    /// the store's GC heap. Failure to uphold these invariants is
385    /// memory safe but will lead to general incorrectness such as
386    /// panics or wrong results.
387    pub(crate) fn from_cloned_gc_ref(
388        store: &mut AutoAssertNoGc<'_>,
389        gc_ref: VMGcRef,
390    ) -> Rooted<Self> {
391        debug_assert!(
392            store
393                .unwrap_gc_store()
394                .kind(&gc_ref)
395                .unwrap()
396                .matches(VMGcKind::ExnRef)
397        );
398        Rooted::new(store, gc_ref)
399    }
400
401    #[inline]
402    pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
403        self.inner.comes_from_same_store(store)
404    }
405
406    /// Converts this [`ExnRef`] to a raw value suitable to store within a
407    /// [`ValRaw`].
408    ///
409    /// Returns an error if this `exnref` has been unrooted.
410    ///
411    /// # Correctness
412    ///
413    /// Produces a raw value which is only valid to pass into a store if a GC
414    /// doesn't happen between when the value is produce and when it's passed
415    /// into the store.
416    ///
417    /// [`ValRaw`]: crate::ValRaw
418    pub fn to_raw(&self, mut store: impl AsContextMut) -> Result<u32> {
419        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
420        self._to_raw(&mut store)
421    }
422
423    pub(crate) fn _to_raw(&self, store: &mut AutoAssertNoGc<'_>) -> Result<u32> {
424        self.inner.expose_gc_ref_to_wasm(store).map(|r| r.get())
425    }
426
427    /// Get the type of this reference.
428    ///
429    /// # Errors
430    ///
431    /// Return an error if this reference has been unrooted.
432    ///
433    /// # Panics
434    ///
435    /// Panics if this reference is associated with a different store.
436    pub fn ty(&self, store: impl AsContext) -> Result<ExnType> {
437        self._ty(store.as_context().0)
438    }
439
440    pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<ExnType> {
441        assert!(self.comes_from_same_store(store));
442        let index = self.type_index(store)?;
443        Ok(ExnType::from_shared_type_index(store.engine(), index))
444    }
445
446    /// Does this `exnref` match the given type?
447    ///
448    /// That is, is this object's type a subtype of the given type?
449    ///
450    /// # Errors
451    ///
452    /// Return an error if this reference has been unrooted.
453    ///
454    /// # Panics
455    ///
456    /// Panics if this reference is associated with a different store.
457    pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result<bool> {
458        self._matches_ty(store.as_context().0, ty)
459    }
460
461    pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<bool> {
462        assert!(self.comes_from_same_store(store));
463        Ok(HeapType::from(self._ty(store)?).matches(ty))
464    }
465
466    pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
467        if !self.comes_from_same_store(store) {
468            bail!("function used with wrong store");
469        }
470        if self._matches_ty(store, ty)? {
471            Ok(())
472        } else {
473            let actual_ty = self._ty(store)?;
474            bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
475        }
476    }
477
478    /// Get the values of this exception object's fields.
479    ///
480    /// # Errors
481    ///
482    /// Return an error if this reference has been unrooted.
483    ///
484    /// # Panics
485    ///
486    /// Panics if this reference is associated with a different store.
487    pub fn fields<'a, T: 'static>(
488        &'a self,
489        store: impl Into<StoreContextMut<'a, T>>,
490    ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
491        self._fields(store.into().0)
492    }
493
494    pub(crate) fn _fields<'a>(
495        &'a self,
496        store: &'a mut StoreOpaque,
497    ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
498        assert!(self.comes_from_same_store(store));
499        let store = AutoAssertNoGc::new(store);
500
501        let gc_ref = self.inner.try_gc_ref(&store)?;
502        let header = store.require_gc_store()?.header(gc_ref)?;
503        debug_assert!(header.kind().matches(VMGcKind::ExnRef));
504
505        let index = header.ty().expect("exnrefs should have concrete types");
506        let ty = ExnType::from_shared_type_index(store.engine(), index);
507        let len = ty.fields().len();
508
509        return Ok(Fields {
510            exnref: self,
511            store,
512            index: 0,
513            len,
514        });
515
516        struct Fields<'a, 'b> {
517            exnref: &'a ExnRef,
518            store: AutoAssertNoGc<'b>,
519            index: usize,
520            len: usize,
521        }
522
523        impl Iterator for Fields<'_, '_> {
524            type Item = Val;
525
526            #[inline]
527            fn next(&mut self) -> Option<Self::Item> {
528                let i = self.index;
529                debug_assert!(i <= self.len);
530                if i >= self.len {
531                    return None;
532                }
533                self.index += 1;
534                self.exnref._field(&mut self.store, i).ok()
535            }
536
537            #[inline]
538            fn size_hint(&self) -> (usize, Option<usize>) {
539                let len = self.len - self.index;
540                (len, Some(len))
541            }
542        }
543
544        impl ExactSizeIterator for Fields<'_, '_> {
545            #[inline]
546            fn len(&self) -> usize {
547                self.len - self.index
548            }
549        }
550    }
551
552    fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
553        assert!(self.comes_from_same_store(&store));
554        let gc_ref = self.inner.try_gc_ref(store)?;
555        Ok(store.require_gc_store()?.header(gc_ref)?)
556    }
557
558    fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> {
559        assert!(self.comes_from_same_store(&store));
560        let gc_ref = self.inner.try_gc_ref(store)?;
561        debug_assert!(self.header(store)?.kind().matches(VMGcKind::ExnRef));
562        Ok(gc_ref.as_exnref_unchecked())
563    }
564
565    fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<Arc<GcStructLayout>> {
566        assert!(self.comes_from_same_store(&store));
567        let type_index = self.type_index(store)?;
568        let layout = store
569            .engine()
570            .signatures()
571            .layout(type_index)
572            .expect("exn types should have GC layouts");
573        match layout {
574            GcLayout::Struct(s) => Ok(s),
575            GcLayout::Array(_) => unreachable!(),
576        }
577    }
578
579    fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
580        let ty = self._ty(store)?;
581        match ty.field(field) {
582            Some(f) => Ok(f),
583            None => {
584                let len = ty.fields().len();
585                bail!("cannot access field {field}: exn only has {len} fields")
586            }
587        }
588    }
589
590    /// Get this exception object's `index`th field.
591    ///
592    /// # Errors
593    ///
594    /// Returns an `Err(_)` if the index is out of bounds or this reference has
595    /// been unrooted.
596    ///
597    /// # Panics
598    ///
599    /// Panics if this reference is associated with a different store.
600    pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
601        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
602        self._field(&mut store, index)
603    }
604
605    pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
606        assert!(self.comes_from_same_store(store));
607        let exnref = self.exnref(store)?.unchecked_copy();
608        let field_ty = self.field_ty(store, index)?;
609        let layout = self.layout(store)?;
610        exnref.read_field(store, &layout, field_ty.element_type(), index)
611    }
612
613    /// Get this exception object's associated tag.
614    ///
615    /// # Errors
616    ///
617    /// Returns an `Err(_)` if this reference has been unrooted.
618    ///
619    /// # Panics
620    ///
621    /// Panics if this reference is associated with a different store.
622    pub fn tag(&self, mut store: impl AsContextMut) -> Result<Tag> {
623        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
624        assert!(self.comes_from_same_store(&store));
625        let exnref = self.exnref(&store)?.unchecked_copy();
626        let (instance, index) = exnref.tag(&mut store)?;
627        Ok(Tag::from_raw_indices(&*store, instance, index))
628    }
629}
630
631unsafe impl WasmTy for Rooted<ExnRef> {
632    #[inline]
633    fn valtype() -> ValType {
634        ValType::Ref(RefType::new(false, HeapType::Exn))
635    }
636
637    #[inline]
638    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
639        self.comes_from_same_store(store)
640    }
641
642    #[inline]
643    fn dynamic_concrete_type_check(
644        &self,
645        _store: &StoreOpaque,
646        _nullable: bool,
647        _ty: &HeapType,
648    ) -> Result<()> {
649        // Wasm can't specify a concrete exn type, so there are no
650        // dynamic checks.
651        Ok(())
652    }
653
654    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
655        self.wasm_ty_store(store, ptr, ValRaw::anyref)
656    }
657
658    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
659        Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
660    }
661}
662
663unsafe impl WasmTy for Option<Rooted<ExnRef>> {
664    #[inline]
665    fn valtype() -> ValType {
666        ValType::EXNREF
667    }
668
669    #[inline]
670    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
671        self.map_or(true, |x| x.comes_from_same_store(store))
672    }
673
674    #[inline]
675    fn dynamic_concrete_type_check(
676        &self,
677        store: &StoreOpaque,
678        nullable: bool,
679        ty: &HeapType,
680    ) -> Result<()> {
681        match self {
682            Some(a) => a.ensure_matches_ty(store, ty),
683            None => {
684                ensure!(
685                    nullable,
686                    "expected a non-null reference, but found a null reference"
687                );
688                Ok(())
689            }
690        }
691    }
692
693    #[inline]
694    fn is_vmgcref_and_points_to_object(&self) -> bool {
695        self.is_some()
696    }
697
698    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
699        <Rooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
700    }
701
702    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
703        <Rooted<ExnRef>>::wasm_ty_option_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
704    }
705}
706
707unsafe impl WasmTy for OwnedRooted<ExnRef> {
708    #[inline]
709    fn valtype() -> ValType {
710        ValType::Ref(RefType::new(false, HeapType::Exn))
711    }
712
713    #[inline]
714    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
715        self.comes_from_same_store(store)
716    }
717
718    #[inline]
719    fn dynamic_concrete_type_check(
720        &self,
721        store: &StoreOpaque,
722        _nullable: bool,
723        ty: &HeapType,
724    ) -> Result<()> {
725        self.ensure_matches_ty(store, ty)
726    }
727
728    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
729        self.wasm_ty_store(store, ptr, ValRaw::anyref)
730    }
731
732    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
733        Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
734    }
735}
736
737unsafe impl WasmTy for Option<OwnedRooted<ExnRef>> {
738    #[inline]
739    fn valtype() -> ValType {
740        ValType::EXNREF
741    }
742
743    #[inline]
744    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
745        self.as_ref()
746            .map_or(true, |x| x.comes_from_same_store(store))
747    }
748
749    #[inline]
750    fn dynamic_concrete_type_check(
751        &self,
752        store: &StoreOpaque,
753        nullable: bool,
754        ty: &HeapType,
755    ) -> Result<()> {
756        match self {
757            Some(a) => a.ensure_matches_ty(store, ty),
758            None => {
759                ensure!(
760                    nullable,
761                    "expected a non-null reference, but found a null reference"
762                );
763                Ok(())
764            }
765        }
766    }
767
768    #[inline]
769    fn is_vmgcref_and_points_to_object(&self) -> bool {
770        self.is_some()
771    }
772
773    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
774        <OwnedRooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
775    }
776
777    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
778        <OwnedRooted<ExnRef>>::wasm_ty_option_load(
779            store,
780            ptr.get_anyref(),
781            ExnRef::from_cloned_gc_ref,
782        )
783    }
784}