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