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