Skip to main content

wasmtime/runtime/vm/gc/enabled/
arrayref.rs

1use crate::{
2    StorageType, Val, bail_bug,
3    prelude::*,
4    runtime::vm::{GcHeap, GcStore, VMGcRef},
5    store::{AutoAssertNoGc, StoreOpaque},
6};
7use core::fmt;
8use wasmtime_environ::{GcArrayLayout, VMGcKind};
9
10/// A `VMGcRef` that we know points to a `array`.
11///
12/// Create a `VMArrayRef` via `VMGcRef::into_arrayref` and
13/// `VMGcRef::as_arrayref`, or their untyped equivalents
14/// `VMGcRef::into_arrayref_unchecked` and `VMGcRef::as_arrayref_unchecked`.
15///
16/// Note: This is not a `TypedGcRef<_>` because each collector can have a
17/// different concrete representation of `arrayref` that they allocate inside
18/// their heaps.
19#[derive(Debug, PartialEq, Eq, Hash)]
20#[repr(transparent)]
21pub struct VMArrayRef(VMGcRef);
22
23impl fmt::Pointer for VMArrayRef {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        fmt::Pointer::fmt(&self.0, f)
26    }
27}
28
29impl From<VMArrayRef> for VMGcRef {
30    #[inline]
31    fn from(x: VMArrayRef) -> Self {
32        x.0
33    }
34}
35
36impl VMGcRef {
37    /// Is this `VMGcRef` pointing to a `array`?
38    pub fn is_arrayref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool {
39        if self.is_i31() {
40            return false;
41        }
42
43        match gc_heap.header(&self) {
44            Ok(header) => header.matches_kind(VMGcKind::ArrayRef),
45            Err(_) => false,
46        }
47    }
48
49    /// Create a new `VMArrayRef` from the given `gc_ref`.
50    ///
51    /// If this is not a GC reference to an `arrayref`, `Err(self)` is
52    /// returned.
53    pub fn into_arrayref(self, gc_heap: &(impl GcHeap + ?Sized)) -> Result<VMArrayRef, VMGcRef> {
54        if self.is_arrayref(gc_heap) {
55            Ok(self.into_arrayref_unchecked())
56        } else {
57            Err(self)
58        }
59    }
60
61    /// Create a new `VMArrayRef` from `self` without actually checking that
62    /// `self` is an `arrayref`.
63    ///
64    /// This method does not check that `self` is actually an `arrayref`, but
65    /// it should be. Failure to uphold this invariant is memory safe but will
66    /// result in general incorrectness down the line such as panics or wrong
67    /// results.
68    #[inline]
69    pub fn into_arrayref_unchecked(self) -> VMArrayRef {
70        debug_assert!(!self.is_i31());
71        VMArrayRef(self)
72    }
73
74    /// Get this GC reference as an `arrayref` reference, if it actually is an
75    /// `arrayref` reference.
76    pub fn as_arrayref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMArrayRef> {
77        if self.is_arrayref(gc_heap) {
78            Some(self.as_arrayref_unchecked())
79        } else {
80            None
81        }
82    }
83
84    /// Get this GC reference as an `arrayref` reference without checking if it
85    /// actually is an `arrayref` reference.
86    ///
87    /// Calling this method on a non-`arrayref` reference is memory safe, but
88    /// will lead to general incorrectness like panics and wrong results.
89    pub fn as_arrayref_unchecked(&self) -> &VMArrayRef {
90        debug_assert!(!self.is_i31());
91        let ptr = self as *const VMGcRef;
92        let ret = unsafe { &*ptr.cast() };
93        assert!(matches!(ret, VMArrayRef(VMGcRef { .. })));
94        ret
95    }
96}
97
98impl VMArrayRef {
99    /// Get the underlying `VMGcRef`.
100    pub fn as_gc_ref(&self) -> &VMGcRef {
101        &self.0
102    }
103
104    /// Clone this `VMArrayRef`, running any GC barriers as necessary.
105    pub fn clone(&self, gc_store: &mut GcStore) -> Self {
106        Self(gc_store.clone_gc_ref(&self.0))
107    }
108
109    /// Explicitly drop this `arrayref`, running GC drop barriers as necessary.
110    pub fn drop(self, gc_store: &mut GcStore) {
111        gc_store.drop_gc_ref(self.0);
112    }
113
114    /// Copy this `VMArrayRef` without running the GC's clone barriers.
115    ///
116    /// Prefer calling `clone(&mut GcStore)` instead! This is mostly an internal
117    /// escape hatch for collector implementations.
118    ///
119    /// Failure to run GC barriers when they would otherwise be necessary can
120    /// lead to leaks, panics, and wrong results. It cannot lead to memory
121    /// unsafety, however.
122    pub fn unchecked_copy(&self) -> Self {
123        Self(self.0.unchecked_copy())
124    }
125
126    /// Get the length of this array.
127    pub fn len(&self, store: &StoreOpaque) -> Result<u32> {
128        store.unwrap_gc_store().array_len(self)
129    }
130
131    /// Read an element of the given `StorageType` into a `Val`.
132    ///
133    /// `i8` and `i16` fields are zero-extended into `Val::I32(_)`s.
134    ///
135    /// Does not check that this array's elements are actually of type
136    /// `ty`. That is the caller's responsibility. Failure to do so is memory
137    /// safe, but will lead to general incorrectness such as panics and wrong
138    /// results.
139    ///
140    /// Returns an error on out-of-bounds accesses.
141    pub fn read_elem(
142        &self,
143        store: &mut AutoAssertNoGc,
144        layout: &GcArrayLayout,
145        ty: &StorageType,
146        index: u32,
147    ) -> Result<Val> {
148        let offset = match layout.elem_offset(index) {
149            Some(offset) => offset,
150            None => bail_bug!("out-of-bounds array element index"),
151        };
152        self.as_gc_ref().read_val(store, ty, offset)
153    }
154
155    /// Write the given value into this array at the given offset.
156    ///
157    /// Returns an error if `val` is a GC reference that has since been
158    /// unrooted.
159    ///
160    /// Does not check that `val` matches `ty`, nor that the field is actually
161    /// of type `ty`. Checking those things is the caller's responsibility.
162    /// Failure to do so is memory safe, but will lead to general incorrectness
163    /// such as panics and wrong results.
164    ///
165    /// Returns an error on out-of-bounds accesses.
166    pub fn write_elem(
167        &self,
168        store: &mut AutoAssertNoGc,
169        layout: &GcArrayLayout,
170        ty: &StorageType,
171        index: u32,
172        val: Val,
173    ) -> Result<()> {
174        debug_assert!(val._matches_ty(&store, &ty.unpack())?);
175
176        let offset = match layout.elem_offset(index) {
177            Some(offset) => offset,
178            None => bail_bug!("out-of-bounds array element index"),
179        };
180        self.as_gc_ref().write_val(store, ty, offset, val)
181    }
182
183    /// Initialize an element in this arrayref that is currently uninitialized.
184    ///
185    /// The difference between this method and `write_elem` is that GC barriers
186    /// are handled differently. When overwriting an initialized element (aka
187    /// `write_elem`) we need to call the full write GC write barrier, which
188    /// logically drops the old GC reference and clones the new GC
189    /// reference. When we are initializing an element for the first time, there
190    /// is no old GC reference that is being overwritten and which we need to
191    /// drop, so we only need to clone the new GC reference.
192    ///
193    /// Calling this method on a arrayref that has already had the associated
194    /// element initialized will result in GC bugs. These are memory safe but
195    /// will lead to generally incorrect behavior such as panics, leaks, and
196    /// incorrect results.
197    ///
198    /// Does not check that `val` matches `ty`, nor that the field is actually
199    /// of type `ty`. Checking those things is the caller's responsibility.
200    /// Failure to do so is memory safe, but will lead to general incorrectness
201    /// such as panics and wrong results.
202    ///
203    /// Returns an error if `val` is a GC reference that has since been
204    /// unrooted.
205    ///
206    /// Returns an error on out-of-bounds accesses.
207    pub fn initialize_elem(
208        &self,
209        store: &mut AutoAssertNoGc,
210        layout: &GcArrayLayout,
211        ty: &StorageType,
212        index: u32,
213        val: Val,
214    ) -> Result<()> {
215        debug_assert!(val._matches_ty(&store, &ty.unpack())?);
216        let offset = match layout.elem_offset(index) {
217            Some(offset) => offset,
218            None => bail_bug!("out-of-bounds array element index"),
219        };
220        self.as_gc_ref().initialize_val(store, ty, offset, val)
221    }
222}