wasmtime/runtime/gc/enabled/
eqref.rs

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