wasmtime/runtime/gc/enabled/
exnref.rs

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