Skip to main content

wasmtime/runtime/gc/enabled/
eqref.rs

1//! Working with GC `eqref`s.
2#![cfg(feature = "gc")]
3use crate::{
4    AnyRef, ArrayRef, ArrayType, AsContext, AsContextMut, GcRefImpl, GcRootIndex, HeapType, I31,
5    OwnedRooted, RefType, Rooted, StructRef, StructType, ValRaw, ValType, WasmTy, bail_bug,
6    prelude::*,
7    runtime::vm::VMGcRef,
8    store::{AutoAssertNoGc, StoreOpaque},
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 `OwnedRooted<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<OwnedRooted<StructRef>> for OwnedRooted<EqRef> {
103    #[inline]
104    fn from(s: OwnedRooted<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<OwnedRooted<ArrayRef>> for OwnedRooted<EqRef> {
117    #[inline]
118    fn from(s: OwnedRooted<ArrayRef>) -> Self {
119        s.to_eqref()
120    }
121}
122
123unsafe impl GcRefImpl for EqRef {
124    fn transmute_ref(index: &GcRootIndex) -> &Self {
125        // Safety: `EqRef` is a newtype of a `GcRootIndex`.
126        let me: &Self = unsafe { mem::transmute(index) };
127
128        // Assert we really are just a newtype of a `GcRootIndex`.
129        assert!(matches!(
130            me,
131            Self {
132                inner: GcRootIndex { .. },
133            }
134        ));
135
136        me
137    }
138}
139
140impl Rooted<EqRef> {
141    /// Upcast this `eqref` into an `anyref`.
142    #[inline]
143    pub fn to_anyref(self) -> Rooted<AnyRef> {
144        self.unchecked_cast()
145    }
146}
147
148impl OwnedRooted<EqRef> {
149    /// Upcast this `eqref` into an `anyref`.
150    #[inline]
151    pub fn to_anyref(self) -> OwnedRooted<AnyRef> {
152        self.unchecked_cast()
153    }
154}
155
156impl EqRef {
157    /// Create a new `Rooted<AnyRef>` from the given GC reference.
158    ///
159    /// `gc_ref` should point to a valid `anyref` and should belong to the
160    /// store's GC heap. Failure to uphold these invariants is memory safe but
161    /// will lead to general incorrectness such as panics or wrong results.
162    pub(crate) fn from_cloned_gc_ref(
163        store: &mut AutoAssertNoGc<'_>,
164        gc_ref: VMGcRef,
165    ) -> Rooted<Self> {
166        debug_assert!(
167            gc_ref.is_i31()
168                || store
169                    .unwrap_gc_store()
170                    .header(&gc_ref)
171                    .unwrap()
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.require_gc_store()?.header(gc_ref)?;
203        let ty = match header.ty() {
204            Some(ty) => ty,
205            None => bail_bug!("ty should be present"),
206        };
207
208        if header.kind().matches(VMGcKind::StructRef) {
209            return Ok(HeapType::ConcreteStruct(
210                StructType::from_shared_type_index(store.engine(), ty),
211            ));
212        }
213
214        if header.kind().matches(VMGcKind::ArrayRef) {
215            return Ok(HeapType::ConcreteArray(ArrayType::from_shared_type_index(
216                store.engine(),
217                ty,
218            )));
219        }
220
221        bail_bug!("no other kinds of `eqref`s")
222    }
223
224    /// Does this `eqref` match the given type?
225    ///
226    /// That is, is this object's type a subtype of the given type?
227    ///
228    /// # Errors
229    ///
230    /// Return an error if this reference has been unrooted.
231    ///
232    /// # Panics
233    ///
234    /// Panics if this reference is associated with a different store.
235    pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result<bool> {
236        self._matches_ty(store.as_context().0, ty)
237    }
238
239    pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<bool> {
240        assert!(self.comes_from_same_store(store));
241        Ok(self._ty(store)?.matches(ty))
242    }
243
244    pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
245        if !self.comes_from_same_store(store) {
246            bail!("function used with wrong store");
247        }
248        if self._matches_ty(store, ty)? {
249            Ok(())
250        } else {
251            let actual_ty = self._ty(store)?;
252            bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
253        }
254    }
255
256    /// Construct an `eqref` from an `i31`.
257    ///
258    /// # Example
259    ///
260    /// ```
261    /// # use wasmtime::*;
262    /// # fn _foo() -> Result<()> {
263    /// let mut store = Store::<()>::default();
264    ///
265    /// // Create an `i31`.
266    /// let i31 = I31::wrapping_u32(999);
267    ///
268    /// // Convert it into an `eqref`.
269    /// let eqref = EqRef::from_i31(&mut store, i31);
270    /// # Ok(())
271    /// # }
272    /// ```
273    pub fn from_i31(mut store: impl AsContextMut, value: I31) -> Rooted<Self> {
274        let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
275        Self::_from_i31(&mut store, value)
276    }
277
278    pub(crate) fn _from_i31(store: &mut AutoAssertNoGc<'_>, value: I31) -> Rooted<Self> {
279        let gc_ref = VMGcRef::from_i31(value.runtime_i31());
280        Rooted::new(store, gc_ref)
281    }
282
283    /// Is this `eqref` an `i31`?
284    ///
285    /// # Errors
286    ///
287    /// Return an error if this reference has been unrooted.
288    ///
289    /// # Panics
290    ///
291    /// Panics if this reference is associated with a different store.
292    pub fn is_i31(&self, store: impl AsContext) -> Result<bool> {
293        self._is_i31(store.as_context().0)
294    }
295
296    pub(crate) fn _is_i31(&self, store: &StoreOpaque) -> Result<bool> {
297        assert!(self.comes_from_same_store(store));
298        let gc_ref = self.inner.try_gc_ref(store)?;
299        Ok(gc_ref.is_i31())
300    }
301
302    /// Downcast this `eqref` to an `i31`.
303    ///
304    /// If this `eqref` is an `i31`, then `Some(_)` is returned.
305    ///
306    /// If this `eqref` is not an `i31`, then `None` is returned.
307    ///
308    /// # Errors
309    ///
310    /// Return an error if this reference has been unrooted.
311    ///
312    /// # Panics
313    ///
314    /// Panics if this reference is associated with a different store.
315    pub fn as_i31(&self, store: impl AsContext) -> Result<Option<I31>> {
316        self._as_i31(store.as_context().0)
317    }
318
319    pub(crate) fn _as_i31(&self, store: &StoreOpaque) -> Result<Option<I31>> {
320        assert!(self.comes_from_same_store(store));
321        let gc_ref = self.inner.try_gc_ref(store)?;
322        Ok(gc_ref.as_i31().map(Into::into))
323    }
324
325    /// Downcast this `eqref` to an `i31`, panicking if this `eqref` is not an
326    /// `i31`.
327    ///
328    /// # Errors
329    ///
330    /// Return an error if this reference has been unrooted.
331    ///
332    /// # Panics
333    ///
334    /// Panics if this reference is associated with a different store, or if
335    /// this `eqref` is not an `i31`.
336    pub fn unwrap_i31(&self, store: impl AsContext) -> Result<I31> {
337        Ok(self.as_i31(store)?.expect("EqRef::unwrap_i31 on non-i31"))
338    }
339
340    /// Is this `eqref` a `structref`?
341    ///
342    /// # Errors
343    ///
344    /// Return an error if this reference has been unrooted.
345    ///
346    /// # Panics
347    ///
348    /// Panics if this reference is associated with a different store.
349    pub fn is_struct(&self, store: impl AsContext) -> Result<bool> {
350        self._is_struct(store.as_context().0)
351    }
352
353    pub(crate) fn _is_struct(&self, store: &StoreOpaque) -> Result<bool> {
354        let gc_ref = self.inner.try_gc_ref(store)?;
355        Ok(!gc_ref.is_i31()
356            && store
357                .require_gc_store()?
358                .kind(gc_ref)?
359                .matches(VMGcKind::StructRef))
360    }
361
362    /// Downcast this `eqref` to a `structref`.
363    ///
364    /// If this `eqref` is a `structref`, then `Some(_)` is returned.
365    ///
366    /// If this `eqref` is not a `structref`, then `None` is returned.
367    ///
368    /// # Errors
369    ///
370    /// Return an error if this reference has been unrooted.
371    ///
372    /// # Panics
373    ///
374    /// Panics if this reference is associated with a different store.
375    pub fn as_struct(&self, store: impl AsContext) -> Result<Option<Rooted<StructRef>>> {
376        self._as_struct(store.as_context().0)
377    }
378
379    pub(crate) fn _as_struct(&self, store: &StoreOpaque) -> Result<Option<Rooted<StructRef>>> {
380        if self._is_struct(store)? {
381            Ok(Some(Rooted::from_gc_root_index(self.inner)))
382        } else {
383            Ok(None)
384        }
385    }
386
387    /// Downcast this `eqref` to a `structref`, panicking if this `eqref` is
388    /// not a `structref`.
389    ///
390    /// # Errors
391    ///
392    /// Return an error if this reference has been unrooted.
393    ///
394    /// # Panics
395    ///
396    /// Panics if this reference is associated with a different store, or if
397    /// this `eqref` is not a `struct`.
398    pub fn unwrap_struct(&self, store: impl AsContext) -> Result<Rooted<StructRef>> {
399        self._unwrap_struct(store.as_context().0)
400    }
401
402    pub(crate) fn _unwrap_struct(&self, store: &StoreOpaque) -> Result<Rooted<StructRef>> {
403        Ok(self
404            ._as_struct(store)?
405            .expect("EqRef::unwrap_struct on non-structref"))
406    }
407
408    /// Is this `eqref` an `arrayref`?
409    ///
410    /// # Errors
411    ///
412    /// Return an error if this reference has been unrooted.
413    ///
414    /// # Panics
415    ///
416    /// Panics if this reference is associated with a different store.
417    pub fn is_array(&self, store: impl AsContext) -> Result<bool> {
418        self._is_array(store.as_context().0)
419    }
420
421    pub(crate) fn _is_array(&self, store: &StoreOpaque) -> Result<bool> {
422        let gc_ref = self.inner.try_gc_ref(store)?;
423        Ok(!gc_ref.is_i31()
424            && store
425                .require_gc_store()?
426                .kind(gc_ref)?
427                .matches(VMGcKind::ArrayRef))
428    }
429
430    /// Downcast this `eqref` to an `arrayref`.
431    ///
432    /// If this `eqref` is an `arrayref`, then `Some(_)` is returned.
433    ///
434    /// If this `eqref` is not an `arrayref`, then `None` is returned.
435    ///
436    /// # Errors
437    ///
438    /// Return an error if this reference has been unrooted.
439    ///
440    /// # Panics
441    ///
442    /// Panics if this reference is associated with a different store.
443    pub fn as_array(&self, store: impl AsContext) -> Result<Option<Rooted<ArrayRef>>> {
444        self._as_array(store.as_context().0)
445    }
446
447    pub(crate) fn _as_array(&self, store: &StoreOpaque) -> Result<Option<Rooted<ArrayRef>>> {
448        if self._is_array(store)? {
449            Ok(Some(Rooted::from_gc_root_index(self.inner)))
450        } else {
451            Ok(None)
452        }
453    }
454
455    /// Downcast this `eqref` to an `arrayref`, panicking if this `eqref` is
456    /// not an `arrayref`.
457    ///
458    /// # Errors
459    ///
460    /// Return an error if this reference has been unrooted.
461    ///
462    /// # Panics
463    ///
464    /// Panics if this reference is associated with a different store, or if
465    /// this `eqref` is not an `array`.
466    pub fn unwrap_array(&self, store: impl AsContext) -> Result<Rooted<ArrayRef>> {
467        self._unwrap_array(store.as_context().0)
468    }
469
470    pub(crate) fn _unwrap_array(&self, store: &StoreOpaque) -> Result<Rooted<ArrayRef>> {
471        Ok(self
472            ._as_array(store)?
473            .expect("EqRef::unwrap_array on non-arrayref"))
474    }
475}
476
477unsafe impl WasmTy for Rooted<EqRef> {
478    #[inline]
479    fn valtype() -> ValType {
480        ValType::Ref(RefType::new(false, HeapType::Eq))
481    }
482
483    #[inline]
484    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
485        self.comes_from_same_store(store)
486    }
487
488    #[inline]
489    fn dynamic_concrete_type_check(
490        &self,
491        store: &StoreOpaque,
492        _nullable: bool,
493        ty: &HeapType,
494    ) -> Result<()> {
495        self.ensure_matches_ty(store, ty)
496    }
497
498    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
499        self.wasm_ty_store(store, ptr, ValRaw::anyref)
500    }
501
502    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
503        Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
504    }
505}
506
507unsafe impl WasmTy for Option<Rooted<EqRef>> {
508    #[inline]
509    fn valtype() -> ValType {
510        ValType::EQREF
511    }
512
513    #[inline]
514    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
515        self.map_or(true, |x| x.comes_from_same_store(store))
516    }
517
518    #[inline]
519    fn dynamic_concrete_type_check(
520        &self,
521        store: &StoreOpaque,
522        nullable: bool,
523        ty: &HeapType,
524    ) -> Result<()> {
525        match self {
526            Some(s) => Rooted::<EqRef>::dynamic_concrete_type_check(s, store, nullable, ty),
527            None => {
528                ensure!(
529                    nullable,
530                    "expected a non-null reference, but found a null reference"
531                );
532                Ok(())
533            }
534        }
535    }
536
537    #[inline]
538    fn is_vmgcref_and_points_to_object(&self) -> bool {
539        self.is_some()
540    }
541
542    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
543        <Rooted<EqRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
544    }
545
546    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
547        <Rooted<EqRef>>::wasm_ty_option_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
548    }
549}
550
551unsafe impl WasmTy for OwnedRooted<EqRef> {
552    #[inline]
553    fn valtype() -> ValType {
554        ValType::Ref(RefType::new(false, HeapType::Eq))
555    }
556
557    #[inline]
558    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
559        self.comes_from_same_store(store)
560    }
561
562    #[inline]
563    fn dynamic_concrete_type_check(
564        &self,
565        store: &StoreOpaque,
566        _: bool,
567        ty: &HeapType,
568    ) -> Result<()> {
569        self.ensure_matches_ty(store, ty)
570    }
571
572    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
573        self.wasm_ty_store(store, ptr, ValRaw::anyref)
574    }
575
576    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
577        Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
578    }
579}
580
581unsafe impl WasmTy for Option<OwnedRooted<EqRef>> {
582    #[inline]
583    fn valtype() -> ValType {
584        ValType::EQREF
585    }
586
587    #[inline]
588    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
589        self.as_ref()
590            .map_or(true, |x| x.comes_from_same_store(store))
591    }
592
593    #[inline]
594    fn dynamic_concrete_type_check(
595        &self,
596        store: &StoreOpaque,
597        nullable: bool,
598        ty: &HeapType,
599    ) -> Result<()> {
600        match self {
601            Some(s) => OwnedRooted::<EqRef>::dynamic_concrete_type_check(s, store, nullable, ty),
602            None => {
603                ensure!(
604                    nullable,
605                    "expected a non-null reference, but found a null reference"
606                );
607                Ok(())
608            }
609        }
610    }
611
612    #[inline]
613    fn is_vmgcref_and_points_to_object(&self) -> bool {
614        self.is_some()
615    }
616
617    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
618        <OwnedRooted<EqRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
619    }
620
621    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
622        <OwnedRooted<EqRef>>::wasm_ty_option_load(
623            store,
624            ptr.get_anyref(),
625            EqRef::from_cloned_gc_ref,
626        )
627    }
628}