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.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() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::StructRef))
324    }
325
326    /// Downcast this `eqref` to a `structref`.
327    ///
328    /// If this `eqref` is a `structref`, then `Some(_)` is returned.
329    ///
330    /// If this `eqref` is not a `structref`, then `None` is returned.
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.
339    pub fn as_struct(&self, store: impl AsContext) -> Result<Option<Rooted<StructRef>>> {
340        self._as_struct(store.as_context().0)
341    }
342
343    pub(crate) fn _as_struct(&self, store: &StoreOpaque) -> Result<Option<Rooted<StructRef>>> {
344        if self._is_struct(store)? {
345            Ok(Some(Rooted::from_gc_root_index(self.inner)))
346        } else {
347            Ok(None)
348        }
349    }
350
351    /// Downcast this `eqref` to a `structref`, panicking if this `eqref` is
352    /// not a `structref`.
353    ///
354    /// # Errors
355    ///
356    /// Return an error if this reference has been unrooted.
357    ///
358    /// # Panics
359    ///
360    /// Panics if this reference is associated with a different store, or if
361    /// this `eqref` is not a `struct`.
362    pub fn unwrap_struct(&self, store: impl AsContext) -> Result<Rooted<StructRef>> {
363        self._unwrap_struct(store.as_context().0)
364    }
365
366    pub(crate) fn _unwrap_struct(&self, store: &StoreOpaque) -> Result<Rooted<StructRef>> {
367        Ok(self
368            ._as_struct(store)?
369            .expect("EqRef::unwrap_struct on non-structref"))
370    }
371
372    /// Is this `eqref` an `arrayref`?
373    ///
374    /// # Errors
375    ///
376    /// Return an error if this reference has been unrooted.
377    ///
378    /// # Panics
379    ///
380    /// Panics if this reference is associated with a different store.
381    pub fn is_array(&self, store: impl AsContext) -> Result<bool> {
382        self._is_array(store.as_context().0)
383    }
384
385    pub(crate) fn _is_array(&self, store: &StoreOpaque) -> Result<bool> {
386        let gc_ref = self.inner.try_gc_ref(store)?;
387        Ok(!gc_ref.is_i31() && store.gc_store()?.kind(gc_ref).matches(VMGcKind::ArrayRef))
388    }
389
390    /// Downcast this `eqref` to an `arrayref`.
391    ///
392    /// If this `eqref` is an `arrayref`, then `Some(_)` is returned.
393    ///
394    /// If this `eqref` is not an `arrayref`, then `None` is returned.
395    ///
396    /// # Errors
397    ///
398    /// Return an error if this reference has been unrooted.
399    ///
400    /// # Panics
401    ///
402    /// Panics if this reference is associated with a different store.
403    pub fn as_array(&self, store: impl AsContext) -> Result<Option<Rooted<ArrayRef>>> {
404        self._as_array(store.as_context().0)
405    }
406
407    pub(crate) fn _as_array(&self, store: &StoreOpaque) -> Result<Option<Rooted<ArrayRef>>> {
408        if self._is_array(store)? {
409            Ok(Some(Rooted::from_gc_root_index(self.inner)))
410        } else {
411            Ok(None)
412        }
413    }
414
415    /// Downcast this `eqref` to an `arrayref`, panicking if this `eqref` is
416    /// not an `arrayref`.
417    ///
418    /// # Errors
419    ///
420    /// Return an error if this reference has been unrooted.
421    ///
422    /// # Panics
423    ///
424    /// Panics if this reference is associated with a different store, or if
425    /// this `eqref` is not an `array`.
426    pub fn unwrap_array(&self, store: impl AsContext) -> Result<Rooted<ArrayRef>> {
427        self._unwrap_array(store.as_context().0)
428    }
429
430    pub(crate) fn _unwrap_array(&self, store: &StoreOpaque) -> Result<Rooted<ArrayRef>> {
431        Ok(self
432            ._as_array(store)?
433            .expect("EqRef::unwrap_array on non-arrayref"))
434    }
435}
436
437unsafe impl WasmTy for Rooted<EqRef> {
438    #[inline]
439    fn valtype() -> ValType {
440        ValType::Ref(RefType::new(false, HeapType::Eq))
441    }
442
443    #[inline]
444    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
445        self.comes_from_same_store(store)
446    }
447
448    #[inline]
449    fn dynamic_concrete_type_check(
450        &self,
451        store: &StoreOpaque,
452        _nullable: bool,
453        ty: &HeapType,
454    ) -> Result<()> {
455        self.ensure_matches_ty(store, ty)
456    }
457
458    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
459        self.wasm_ty_store(store, ptr, ValRaw::anyref)
460    }
461
462    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
463        Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
464    }
465}
466
467unsafe impl WasmTy for Option<Rooted<EqRef>> {
468    #[inline]
469    fn valtype() -> ValType {
470        ValType::EQREF
471    }
472
473    #[inline]
474    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
475        self.map_or(true, |x| x.comes_from_same_store(store))
476    }
477
478    #[inline]
479    fn dynamic_concrete_type_check(
480        &self,
481        store: &StoreOpaque,
482        nullable: bool,
483        ty: &HeapType,
484    ) -> Result<()> {
485        match self {
486            Some(s) => Rooted::<EqRef>::dynamic_concrete_type_check(s, store, nullable, ty),
487            None => {
488                ensure!(
489                    nullable,
490                    "expected a non-null reference, but found a null reference"
491                );
492                Ok(())
493            }
494        }
495    }
496
497    #[inline]
498    fn is_vmgcref_and_points_to_object(&self) -> bool {
499        self.is_some()
500    }
501
502    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
503        <Rooted<EqRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
504    }
505
506    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
507        <Rooted<EqRef>>::wasm_ty_option_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
508    }
509}
510
511unsafe impl WasmTy for ManuallyRooted<EqRef> {
512    #[inline]
513    fn valtype() -> ValType {
514        ValType::Ref(RefType::new(false, HeapType::Eq))
515    }
516
517    #[inline]
518    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
519        self.comes_from_same_store(store)
520    }
521
522    #[inline]
523    fn dynamic_concrete_type_check(
524        &self,
525        store: &StoreOpaque,
526        _: bool,
527        ty: &HeapType,
528    ) -> Result<()> {
529        self.ensure_matches_ty(store, ty)
530    }
531
532    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
533        self.wasm_ty_store(store, ptr, ValRaw::anyref)
534    }
535
536    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
537        Self::wasm_ty_load(store, ptr.get_anyref(), EqRef::from_cloned_gc_ref)
538    }
539}
540
541unsafe impl WasmTy for Option<ManuallyRooted<EqRef>> {
542    #[inline]
543    fn valtype() -> ValType {
544        ValType::EQREF
545    }
546
547    #[inline]
548    fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
549        self.as_ref()
550            .map_or(true, |x| x.comes_from_same_store(store))
551    }
552
553    #[inline]
554    fn dynamic_concrete_type_check(
555        &self,
556        store: &StoreOpaque,
557        nullable: bool,
558        ty: &HeapType,
559    ) -> Result<()> {
560        match self {
561            Some(s) => ManuallyRooted::<EqRef>::dynamic_concrete_type_check(s, store, nullable, ty),
562            None => {
563                ensure!(
564                    nullable,
565                    "expected a non-null reference, but found a null reference"
566                );
567                Ok(())
568            }
569        }
570    }
571
572    #[inline]
573    fn is_vmgcref_and_points_to_object(&self) -> bool {
574        self.is_some()
575    }
576
577    fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
578        <ManuallyRooted<EqRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
579    }
580
581    unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
582        <ManuallyRooted<EqRef>>::wasm_ty_option_load(
583            store,
584            ptr.get_anyref(),
585            EqRef::from_cloned_gc_ref,
586        )
587    }
588}