wasmtime/runtime/gc/enabled/
eqref.rs

1//! Working with GC `eqref`s.
2
3use crate::{
4    prelude::*,
5    runtime::vm::VMGcRef,
6    store::{AutoAssertNoGc, StoreOpaque},
7    AnyRef, ArrayRef, ArrayType, AsContext, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted,
8    RefType, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy, I31,
9};
10use core::mem::{self, MaybeUninit};
11use wasmtime_environ::VMGcKind;
12
13/// A reference to a GC-managed object that can be tested for equality.
14///
15/// The WebAssembly reference types that can be tested for equality, and
16/// therefore are `eqref`s, include `structref`s, `arrayref`s, and
17/// `i31ref`s. `funcref`s, `exnref`s, and `externref`s cannot be tested for
18/// equality by Wasm, and are not `eqref`s.
19///
20/// Use the [`Rooted::ref_eq`][Rooted::ref_eq] method to actually test two
21/// references for equality.
22///
23/// Like all WebAssembly references, these are opaque to and unforgeable by
24/// Wasm: they cannot be faked and Wasm cannot, for example, cast the integer
25/// `0x12345678` into a reference, pretend it is a valid `eqref`, and trick the
26/// host into dereferencing it and segfaulting or worse.
27///
28/// Note that you can also use `Rooted<EqRef>` and `ManuallyRooted<EqRef>` as a
29/// type parameter with [`Func::typed`][crate::Func::typed]- and
30/// [`Func::wrap`][crate::Func::wrap]-style APIs.
31///
32/// # Example
33///
34/// ```
35/// use wasmtime::*;
36///
37/// # fn foo() -> Result<()> {
38/// let mut config = Config::new();
39/// config.wasm_function_references(true);
40/// config.wasm_gc(true);
41///
42/// let engine = Engine::new(&config)?;
43/// let mut store = Store::new(&engine, ());
44///
45/// // Define a module that exports a function that returns a new `eqref` each
46/// // time it is invoked.
47/// let module = Module::new(&engine, r#"
48///     (module
49///         (global $g (mut i32) (i32.const 0))
50///         (func (export "new-eqref") (result (ref eq))
51///             ;; Increment $g.
52///             global.get $g
53///             i32.const 1
54///             i32.add
55///             global.set $g
56///
57///             ;; Create an `i31ref`, which is a kind of `eqref`, from $g.
58///             global.get $g
59///             ref.i31
60///         )
61///     )
62/// "#)?;
63///
64/// // Instantiate the module.
65/// let instance = Instance::new(&mut store, &module, &[])?;
66///
67/// // Get the exported function.
68/// let new_eqref = instance.get_typed_func::<(), Rooted<EqRef>>(&mut store, "new-eqref")?;
69///
70/// {
71///     let mut scope = RootScope::new(&mut store);
72///
73///     // Call the function to get an `eqref`.
74///     let x = new_eqref.call(&mut scope, ())?;
75///
76///     // `x` is equal to itself!
77///     assert!(Rooted::ref_eq(&scope, &x, &x)?);
78///
79///     // Call the function again to get a new, different `eqref`.
80///     let y = new_eqref.call(&mut scope, ())?;
81///
82///     // `x` is not equal to `y`!
83///     assert!(!Rooted::ref_eq(&scope, &x, &y)?);
84/// }
85/// # Ok(())
86/// # }
87/// # foo().unwrap();
88/// ```
89#[derive(Debug)]
90#[repr(transparent)]
91pub struct EqRef {
92    pub(super) inner: GcRootIndex,
93}
94
95impl From<Rooted<StructRef>> for Rooted<EqRef> {
96    #[inline]
97    fn from(s: Rooted<StructRef>) -> Self {
98        s.to_eqref()
99    }
100}
101
102impl From<ManuallyRooted<StructRef>> for ManuallyRooted<EqRef> {
103    #[inline]
104    fn from(s: ManuallyRooted<StructRef>) -> Self {
105        s.to_eqref()
106    }
107}
108
109impl From<Rooted<ArrayRef>> for Rooted<EqRef> {
110    #[inline]
111    fn from(s: Rooted<ArrayRef>) -> Self {
112        s.to_eqref()
113    }
114}
115
116impl From<ManuallyRooted<ArrayRef>> for ManuallyRooted<EqRef> {
117    #[inline]
118    fn from(s: ManuallyRooted<ArrayRef>) -> Self {
119        s.to_eqref()
120    }
121}
122
123unsafe impl GcRefImpl for EqRef {
124    #[allow(private_interfaces)]
125    fn transmute_ref(index: &GcRootIndex) -> &Self {
126        // Safety: `EqRef` is a newtype of a `GcRootIndex`.
127        let me: &Self = unsafe { mem::transmute(index) };
128
129        // Assert we really are just a newtype of a `GcRootIndex`.
130        assert!(matches!(
131            me,
132            Self {
133                inner: GcRootIndex { .. },
134            }
135        ));
136
137        me
138    }
139}
140
141impl Rooted<EqRef> {
142    /// Upcast this `eqref` into an `anyref`.
143    #[inline]
144    pub fn to_anyref(self) -> Rooted<AnyRef> {
145        self.unchecked_cast()
146    }
147}
148
149impl ManuallyRooted<EqRef> {
150    /// Upcast this `eqref` into an `anyref`.
151    #[inline]
152    pub fn to_anyref(self) -> ManuallyRooted<AnyRef> {
153        self.unchecked_cast()
154    }
155}
156
157impl EqRef {
158    /// Create a new `Rooted<AnyRef>` from the given GC reference.
159    ///
160    /// `gc_ref` should point to a valid `anyref` and should belong to the
161    /// store's GC heap. Failure to uphold these invariants is memory safe but
162    /// will lead to general incorrectness such as panics or wrong results.
163    pub(crate) fn from_cloned_gc_ref(
164        store: &mut AutoAssertNoGc<'_>,
165        gc_ref: VMGcRef,
166    ) -> Rooted<Self> {
167        debug_assert!(
168            gc_ref.is_i31()
169                || store
170                    .unwrap_gc_store()
171                    .header(&gc_ref)
172                    .kind()
173                    .matches(VMGcKind::EqRef)
174        );
175        Rooted::new(store, gc_ref)
176    }
177
178    #[inline]
179    pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
180        self.inner.comes_from_same_store(store)
181    }
182
183    /// Get the type of this reference.
184    ///
185    /// # Errors
186    ///
187    /// Return an error if this reference has been unrooted.
188    ///
189    /// # Panics
190    ///
191    /// Panics if this reference is associated with a different store.
192    pub fn ty(&self, store: impl AsContext) -> Result<HeapType> {
193        self._ty(store.as_context().0)
194    }
195
196    pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<HeapType> {
197        let gc_ref = self.inner.try_gc_ref(store)?;
198        if gc_ref.is_i31() {
199            return Ok(HeapType::I31);
200        }
201
202        let header = store.gc_store()?.header(gc_ref);
203
204        if header.kind().matches(VMGcKind::StructRef) {
205            return Ok(HeapType::ConcreteStruct(
206                StructType::from_shared_type_index(store.engine(), header.ty().unwrap()),
207            ));
208        }
209
210        if header.kind().matches(VMGcKind::ArrayRef) {
211            return Ok(HeapType::ConcreteArray(ArrayType::from_shared_type_index(
212                store.engine(),
213                header.ty().unwrap(),
214            )));
215        }
216
217        unreachable!("no other kinds of `eqref`s")
218    }
219
220    /// Does this `eqref` match the given type?
221    ///
222    /// That is, is this object's type a subtype of the given type?
223    ///
224    /// # Errors
225    ///
226    /// Return an error if this reference has been unrooted.
227    ///
228    /// # Panics
229    ///
230    /// Panics if this reference is associated with a different store.
231    pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result<bool> {
232        self._matches_ty(store.as_context().0, ty)
233    }
234
235    pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<bool> {
236        assert!(self.comes_from_same_store(store));
237        Ok(self._ty(store)?.matches(ty))
238    }
239
240    pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
241        if !self.comes_from_same_store(store) {
242            bail!("function used with wrong store");
243        }
244        if self._matches_ty(store, ty)? {
245            Ok(())
246        } else {
247            let actual_ty = self._ty(store)?;
248            bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
249        }
250    }
251
252    /// Is this `eqref` an `i31`?
253    ///
254    /// # Errors
255    ///
256    /// Return an error if this reference has been unrooted.
257    ///
258    /// # Panics
259    ///
260    /// Panics if this reference is associated with a different store.
261    pub fn is_i31(&self, store: impl AsContext) -> Result<bool> {
262        self._is_i31(store.as_context().0)
263    }
264
265    pub(crate) fn _is_i31(&self, store: &StoreOpaque) -> Result<bool> {
266        assert!(self.comes_from_same_store(store));
267        let gc_ref = self.inner.try_gc_ref(store)?;
268        Ok(gc_ref.is_i31())
269    }
270
271    /// Downcast this `eqref` to an `i31`.
272    ///
273    /// If this `eqref` is an `i31`, then `Some(_)` is returned.
274    ///
275    /// If this `eqref` is not an `i31`, then `None` is returned.
276    ///
277    /// # Errors
278    ///
279    /// Return an error if this reference has been unrooted.
280    ///
281    /// # Panics
282    ///
283    /// Panics if this reference is associated with a different store.
284    pub fn as_i31(&self, store: impl AsContext) -> Result<Option<I31>> {
285        self._as_i31(store.as_context().0)
286    }
287
288    pub(crate) fn _as_i31(&self, store: &StoreOpaque) -> Result<Option<I31>> {
289        assert!(self.comes_from_same_store(store));
290        let gc_ref = self.inner.try_gc_ref(store)?;
291        Ok(gc_ref.as_i31().map(Into::into))
292    }
293
294    /// Downcast this `eqref` to an `i31`, panicking if this `eqref` is not an
295    /// `i31`.
296    ///
297    /// # Errors
298    ///
299    /// Return an error if this reference has been unrooted.
300    ///
301    /// # Panics
302    ///
303    /// Panics if this reference is associated with a different store, or if
304    /// this `eqref` is not an `i31`.
305    pub fn unwrap_i31(&self, store: impl AsContext) -> Result<I31> {
306        Ok(self.as_i31(store)?.expect("EqRef::unwrap_i31 on non-i31"))
307    }
308
309    /// Is this `eqref` a `structref`?
310    ///
311    /// # Errors
312    ///
313    /// Return an error if this reference has been unrooted.
314    ///
315    /// # Panics
316    ///
317    /// Panics if this reference is associated with a different store.
318    pub fn is_struct(&self, store: impl AsContext) -> Result<bool> {
319        self._is_struct(store.as_context().0)
320    }
321
322    pub(crate) fn _is_struct(&self, store: &StoreOpaque) -> Result<bool> {
323        let gc_ref = self.inner.try_gc_ref(store)?;
324        Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::StructRef))
325    }
326
327    /// Downcast this `eqref` to a `structref`.
328    ///
329    /// If this `eqref` is a `structref`, then `Some(_)` is returned.
330    ///
331    /// If this `eqref` is not a `structref`, then `None` is returned.
332    ///
333    /// # Errors
334    ///
335    /// Return an error if this reference has been unrooted.
336    ///
337    /// # Panics
338    ///
339    /// Panics if this reference is associated with a different store.
340    pub fn as_struct(&self, store: impl AsContext) -> Result<Option<Rooted<StructRef>>> {
341        self._as_struct(store.as_context().0)
342    }
343
344    pub(crate) fn _as_struct(&self, store: &StoreOpaque) -> Result<Option<Rooted<StructRef>>> {
345        if self._is_struct(store)? {
346            Ok(Some(Rooted::from_gc_root_index(self.inner)))
347        } else {
348            Ok(None)
349        }
350    }
351
352    /// Downcast this `eqref` to a `structref`, panicking if this `eqref` is
353    /// not a `structref`.
354    ///
355    /// # Errors
356    ///
357    /// Return an error if this reference has been unrooted.
358    ///
359    /// # Panics
360    ///
361    /// Panics if this reference is associated with a different store, or if
362    /// this `eqref` is not a `struct`.
363    pub fn unwrap_struct(&self, store: impl AsContext) -> Result<Rooted<StructRef>> {
364        self._unwrap_struct(store.as_context().0)
365    }
366
367    pub(crate) fn _unwrap_struct(&self, store: &StoreOpaque) -> Result<Rooted<StructRef>> {
368        Ok(self
369            ._as_struct(store)?
370            .expect("EqRef::unwrap_struct on non-structref"))
371    }
372
373    /// Is this `eqref` an `arrayref`?
374    ///
375    /// # Errors
376    ///
377    /// Return an error if this reference has been unrooted.
378    ///
379    /// # Panics
380    ///
381    /// Panics if this reference is associated with a different store.
382    pub fn is_array(&self, store: impl AsContext) -> Result<bool> {
383        self._is_array(store.as_context().0)
384    }
385
386    pub(crate) fn _is_array(&self, store: &StoreOpaque) -> Result<bool> {
387        let gc_ref = self.inner.try_gc_ref(store)?;
388        Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::ArrayRef))
389    }
390
391    /// Downcast this `eqref` to an `arrayref`.
392    ///
393    /// If this `eqref` is an `arrayref`, then `Some(_)` is returned.
394    ///
395    /// If this `eqref` is not an `arrayref`, then `None` is returned.
396    ///
397    /// # Errors
398    ///
399    /// Return an error if this reference has been unrooted.
400    ///
401    /// # Panics
402    ///
403    /// Panics if this reference is associated with a different store.
404    pub fn as_array(&self, store: impl AsContext) -> Result<Option<Rooted<ArrayRef>>> {
405        self._as_array(store.as_context().0)
406    }
407
408    pub(crate) fn _as_array(&self, store: &StoreOpaque) -> Result<Option<Rooted<ArrayRef>>> {
409        if self._is_array(store)? {
410            Ok(Some(Rooted::from_gc_root_index(self.inner)))
411        } else {
412            Ok(None)
413        }
414    }
415
416    /// Downcast this `eqref` to an `arrayref`, panicking if this `eqref` is
417    /// not an `arrayref`.
418    ///
419    /// # Errors
420    ///
421    /// Return an error if this reference has been unrooted.
422    ///
423    /// # Panics
424    ///
425    /// Panics if this reference is associated with a different store, or if
426    /// this `eqref` is not an `array`.
427    pub fn unwrap_array(&self, store: impl AsContext) -> Result<Rooted<ArrayRef>> {
428        self._unwrap_array(store.as_context().0)
429    }
430
431    pub(crate) fn _unwrap_array(&self, store: &StoreOpaque) -> Result<Rooted<ArrayRef>> {
432        Ok(self
433            ._as_array(store)?
434            .expect("EqRef::unwrap_array on non-arrayref"))
435    }
436}
437
438unsafe impl WasmTy for Rooted<EqRef> {
439    #[inline]
440    fn valtype() -> ValType {
441        ValType::Ref(RefType::new(false, HeapType::Eq))
442    }
443
444    #[inline]
445    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
446        self.comes_from_same_store(store)
447    }
448
449    #[inline]
450    fn dynamic_concrete_type_check(
451        &self,
452        store: &StoreOpaque,
453        _nullable: bool,
454        ty: &HeapType,
455    ) -> Result<()> {
456        self.ensure_matches_ty(store, ty)
457    }
458
459    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
460        self.wasm_ty_store(store, ptr, ValRaw::anyref)
461    }
462
463    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
464        Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
465    }
466}
467
468unsafe impl WasmTy for Option<Rooted<EqRef>> {
469    #[inline]
470    fn valtype() -> ValType {
471        ValType::EQREF
472    }
473
474    #[inline]
475    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
476        self.map_or(true, |x| x.comes_from_same_store(store))
477    }
478
479    #[inline]
480    fn dynamic_concrete_type_check(
481        &self,
482        store: &StoreOpaque,
483        nullable: bool,
484        ty: &HeapType,
485    ) -> Result<()> {
486        match self {
487            Some(s) => Rooted::<EqRef>::dynamic_concrete_type_check(s, store, nullable, ty),
488            None => {
489                ensure!(
490                    nullable,
491                    "expected a non-null reference, but found a null reference"
492                );
493                Ok(())
494            }
495        }
496    }
497
498    #[inline]
499    fn is_vmgcref_and_points_to_object(&self) -> bool {
500        self.is_some()
501    }
502
503    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
504        <Rooted<EqRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
505    }
506
507    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
508        <Rooted<EqRef>>::wasm_ty_option_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
509    }
510}
511
512unsafe impl WasmTy for ManuallyRooted<EqRef> {
513    #[inline]
514    fn valtype() -> ValType {
515        ValType::Ref(RefType::new(false, HeapType::Eq))
516    }
517
518    #[inline]
519    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
520        self.comes_from_same_store(store)
521    }
522
523    #[inline]
524    fn dynamic_concrete_type_check(
525        &self,
526        store: &StoreOpaque,
527        _: bool,
528        ty: &HeapType,
529    ) -> Result<()> {
530        self.ensure_matches_ty(store, ty)
531    }
532
533    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
534        self.wasm_ty_store(store, ptr, ValRaw::anyref)
535    }
536
537    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
538        Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
539    }
540}
541
542unsafe impl WasmTy for Option<ManuallyRooted<EqRef>> {
543    #[inline]
544    fn valtype() -> ValType {
545        ValType::EQREF
546    }
547
548    #[inline]
549    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
550        self.as_ref()
551            .map_or(true, |x| x.comes_from_same_store(store))
552    }
553
554    #[inline]
555    fn dynamic_concrete_type_check(
556        &self,
557        store: &StoreOpaque,
558        nullable: bool,
559        ty: &HeapType,
560    ) -> Result<()> {
561        match self {
562            Some(s) => ManuallyRooted::<EqRef>::dynamic_concrete_type_check(s, store, nullable, ty),
563            None => {
564                ensure!(
565                    nullable,
566                    "expected a non-null reference, but found a null reference"
567                );
568                Ok(())
569            }
570        }
571    }
572
573    #[inline]
574    fn is_vmgcref_and_points_to_object(&self) -> bool {
575        self.is_some()
576    }
577
578    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
579        <ManuallyRooted<EqRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
580    }
581
582    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
583        <ManuallyRooted<EqRef>>::wasm_ty_option_load(
584            store,
585            ptr.get_anyref(),
586            EqRef::from_cloned_gc_ref,
587        )
588    }
589}