1 use super::{truncate_i32_to_i8, truncate_i32_to_i16};
2 use crate::{
3     AnyRef, ExnRef, ExternRef, Func, HeapType, RootedGcRefImpl, StorageType, Val, ValType,
4     prelude::*,
5     runtime::vm::{GcHeap, GcStore, VMGcRef},
6     store::AutoAssertNoGc,
7     vm::{FuncRefTableId, SendSyncPtr},
8 };
9 use core::fmt;
10 use wasmtime_environ::{GcStructLayout, VMGcKind};
11 
12 /// A `VMGcRef` that we know points to a `struct`.
13 ///
14 /// Create a `VMStructRef` via `VMGcRef::into_structref` and
15 /// `VMGcRef::as_structref`, or their untyped equivalents
16 /// `VMGcRef::into_structref_unchecked` and `VMGcRef::as_structref_unchecked`.
17 ///
18 /// Note: This is not a `TypedGcRef<_>` because each collector can have a
19 /// different concrete representation of `structref` that they allocate inside
20 /// their heaps.
21 #[derive(Debug, PartialEq, Eq, Hash)]
22 #[repr(transparent)]
23 pub struct VMStructRef(VMGcRef);
24 
25 impl fmt::Pointer for VMStructRef {
26     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27         fmt::Pointer::fmt(&self.0, f)
28     }
29 }
30 
31 impl From<VMStructRef> for VMGcRef {
32     #[inline]
33     fn from(x: VMStructRef) -> Self {
34         x.0
35     }
36 }
37 
38 impl VMGcRef {
39     /// Is this `VMGcRef` pointing to a `struct`?
40     pub fn is_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool {
41         if self.is_i31() {
42             return false;
43         }
44 
45         let header = gc_heap.header(&self);
46         header.kind().matches(VMGcKind::StructRef)
47     }
48 
49     /// Create a new `VMStructRef` from the given `gc_ref`.
50     ///
51     /// If this is not a GC reference to an `structref`, `Err(self)` is
52     /// returned.
53     pub fn into_structref(self, gc_heap: &impl GcHeap) -> Result<VMStructRef, VMGcRef> {
54         if self.is_structref(gc_heap) {
55             Ok(self.into_structref_unchecked())
56         } else {
57             Err(self)
58         }
59     }
60 
61     /// Create a new `VMStructRef` from `self` without actually checking that
62     /// `self` is an `structref`.
63     ///
64     /// This method does not check that `self` is actually an `structref`, 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_structref_unchecked(self) -> VMStructRef {
70         debug_assert!(!self.is_i31());
71         VMStructRef(self)
72     }
73 
74     /// Get this GC reference as an `structref` reference, if it actually is an
75     /// `structref` reference.
76     pub fn as_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMStructRef> {
77         if self.is_structref(gc_heap) {
78             Some(self.as_structref_unchecked())
79         } else {
80             None
81         }
82     }
83 
84     /// Get this GC reference as an `structref` reference without checking if it
85     /// actually is an `structref` reference.
86     ///
87     /// Calling this method on a non-`structref` reference is memory safe, but
88     /// will lead to general incorrectness like panics and wrong results.
89     pub fn as_structref_unchecked(&self) -> &VMStructRef {
90         debug_assert!(!self.is_i31());
91         let ptr = self as *const VMGcRef;
92         let ret = unsafe { &*ptr.cast() };
93         assert!(matches!(ret, VMStructRef(VMGcRef { .. })));
94         ret
95     }
96 }
97 
98 impl VMStructRef {
99     /// Get the underlying `VMGcRef`.
100     pub fn as_gc_ref(&self) -> &VMGcRef {
101         &self.0
102     }
103 
104     /// Clone this `VMStructRef`, 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 `structref`, 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 `VMStructRef` 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     /// Read a field of the given `StorageType` into a `Val`.
127     ///
128     /// `i8` and `i16` fields are zero-extended into `Val::I32(_)`s.
129     ///
130     /// Does not check that the field is actually of type `ty`. That is the
131     /// caller's responsibility. Failure to do so is memory safe, but will lead
132     /// to general incorrectness such as panics and wrong results.
133     ///
134     /// Panics on out-of-bounds accesses.
135     pub fn read_field(
136         &self,
137         store: &mut AutoAssertNoGc,
138         layout: &GcStructLayout,
139         ty: &StorageType,
140         field: usize,
141     ) -> Val {
142         let offset = layout.fields[field].offset;
143         read_field_impl(self.as_gc_ref(), store, ty, offset)
144     }
145 
146     /// Write the given value into this struct at the given offset.
147     ///
148     /// Returns an error if `val` is a GC reference that has since been
149     /// unrooted.
150     ///
151     /// Does not check that `val` matches `ty`, nor that the field is actually
152     /// of type `ty`. Checking those things is the caller's responsibility.
153     /// Failure to do so is memory safe, but will lead to general incorrectness
154     /// such as panics and wrong results.
155     ///
156     /// Panics on out-of-bounds accesses.
157     pub fn write_field(
158         &self,
159         store: &mut AutoAssertNoGc,
160         layout: &GcStructLayout,
161         ty: &StorageType,
162         field: usize,
163         val: Val,
164     ) -> Result<()> {
165         debug_assert!(val._matches_ty(&store, &ty.unpack())?);
166 
167         let offset = layout.fields[field].offset;
168         let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
169         match val {
170             Val::I32(i) if ty.is_i8() => data.write_i8(offset, truncate_i32_to_i8(i)),
171             Val::I32(i) if ty.is_i16() => data.write_i16(offset, truncate_i32_to_i16(i)),
172             Val::I32(i) => data.write_i32(offset, i),
173             Val::I64(i) => data.write_i64(offset, i),
174             Val::F32(f) => data.write_u32(offset, f),
175             Val::F64(f) => data.write_u64(offset, f),
176             Val::V128(v) => data.write_v128(offset, v),
177 
178             // For GC-managed references, we need to take care to run the
179             // appropriate barriers, even when we are writing null references
180             // into the struct.
181             //
182             // POD-read the old value into a local copy, run the GC write
183             // barrier on that local copy, and then POD-write the updated
184             // value back into the struct. This avoids transmuting the inner
185             // data, which would probably be fine, but this approach is
186             // Obviously Correct and should get us by for now. If LLVM isn't
187             // able to elide some of these unnecessary copies, and this
188             // method is ever hot enough, we can always come back and clean
189             // it up in the future.
190             Val::ExternRef(e) => {
191                 let raw = data.read_u32(offset);
192                 let mut gc_ref = VMGcRef::from_raw_u32(raw);
193                 let e = match e {
194                     Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()),
195                     None => None,
196                 };
197                 store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref());
198                 let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
199                 data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
200             }
201             Val::AnyRef(a) => {
202                 let raw = data.read_u32(offset);
203                 let mut gc_ref = VMGcRef::from_raw_u32(raw);
204                 let a = match a {
205                     Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()),
206                     None => None,
207                 };
208                 store.gc_store_mut()?.write_gc_ref(&mut gc_ref, a.as_ref());
209                 let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
210                 data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
211             }
212             Val::ExnRef(e) => {
213                 let raw = data.read_u32(offset);
214                 let mut gc_ref = VMGcRef::from_raw_u32(raw);
215                 let e = match e {
216                     Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()),
217                     None => None,
218                 };
219                 store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref());
220                 let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
221                 data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
222             }
223 
224             Val::FuncRef(f) => {
225                 let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store)));
226                 let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) };
227                 store
228                     .gc_store_mut()?
229                     .gc_object_data(self.as_gc_ref())
230                     .write_u32(offset, id.into_raw());
231             }
232         }
233         Ok(())
234     }
235 
236     /// Initialize a field in this structref that is currently uninitialized.
237     ///
238     /// The difference between this method and `write_field` is that GC barriers
239     /// are handled differently. When overwriting an initialized field (aka
240     /// `write_field`) we need to call the full write GC write barrier, which
241     /// logically drops the old GC reference and clones the new GC
242     /// reference. When we are initializing a field for the first time, there is
243     /// no old GC reference that is being overwritten and which we need to drop,
244     /// so we only need to clone the new GC reference.
245     ///
246     /// Calling this method on a structref that has already had the associated
247     /// field initialized will result in GC bugs. These are memory safe but will
248     /// lead to generally incorrect behavior such as panics, leaks, and
249     /// incorrect results.
250     ///
251     /// Does not check that `val` matches `ty`, nor that the field is actually
252     /// of type `ty`. Checking those things is the caller's responsibility.
253     /// Failure to do so is memory safe, but will lead to general incorrectness
254     /// such as panics and wrong results.
255     ///
256     /// Returns an error if `val` is a GC reference that has since been
257     /// unrooted.
258     ///
259     /// Panics on out-of-bounds accesses.
260     pub fn initialize_field(
261         &self,
262         store: &mut AutoAssertNoGc,
263         layout: &GcStructLayout,
264         ty: &StorageType,
265         field: usize,
266         val: Val,
267     ) -> Result<()> {
268         debug_assert!(val._matches_ty(&store, &ty.unpack())?);
269         let offset = layout.fields[field].offset;
270         initialize_field_impl(self.as_gc_ref(), store, ty, offset, val)
271     }
272 }
273 
274 /// Read a field from a GC object at a given offset.
275 ///
276 /// This factored-out function allows a shared implementation for both
277 /// structs (this module) and exception objects.
278 pub(crate) fn read_field_impl(
279     gc_ref: &VMGcRef,
280     store: &mut AutoAssertNoGc,
281     ty: &StorageType,
282     offset: u32,
283 ) -> Val {
284     let data = store.unwrap_gc_store_mut().gc_object_data(gc_ref);
285     match ty {
286         StorageType::I8 => Val::I32(data.read_u8(offset).into()),
287         StorageType::I16 => Val::I32(data.read_u16(offset).into()),
288         StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)),
289         StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)),
290         StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)),
291         StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)),
292         StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)),
293         StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() {
294             HeapType::Extern => {
295                 let raw = data.read_u32(offset);
296                 Val::ExternRef(ExternRef::_from_raw(store, raw))
297             }
298             HeapType::Any => {
299                 let raw = data.read_u32(offset);
300                 Val::AnyRef(AnyRef::_from_raw(store, raw))
301             }
302             HeapType::Exn => {
303                 let raw = data.read_u32(offset);
304                 Val::ExnRef(ExnRef::_from_raw(store, raw))
305             }
306             HeapType::Func => {
307                 let func_ref_id = data.read_u32(offset);
308                 let func_ref_id = FuncRefTableId::from_raw(func_ref_id);
309                 let func_ref = store
310                     .unwrap_gc_store()
311                     .func_ref_table
312                     .get_untyped(func_ref_id);
313                 Val::FuncRef(unsafe {
314                     func_ref.map(|p| Func::from_vm_func_ref(store.id(), p.as_non_null()))
315                 })
316             }
317             otherwise => unreachable!("not a top type: {otherwise:?}"),
318         },
319     }
320 }
321 
322 pub(crate) fn initialize_field_impl(
323     gc_ref: &VMGcRef,
324     store: &mut AutoAssertNoGc,
325     ty: &StorageType,
326     offset: u32,
327     val: Val,
328 ) -> Result<()> {
329     match val {
330         Val::I32(i) if ty.is_i8() => store
331             .gc_store_mut()?
332             .gc_object_data(gc_ref)
333             .write_i8(offset, truncate_i32_to_i8(i)),
334         Val::I32(i) if ty.is_i16() => store
335             .gc_store_mut()?
336             .gc_object_data(gc_ref)
337             .write_i16(offset, truncate_i32_to_i16(i)),
338         Val::I32(i) => store
339             .gc_store_mut()?
340             .gc_object_data(gc_ref)
341             .write_i32(offset, i),
342         Val::I64(i) => store
343             .gc_store_mut()?
344             .gc_object_data(gc_ref)
345             .write_i64(offset, i),
346         Val::F32(f) => store
347             .gc_store_mut()?
348             .gc_object_data(gc_ref)
349             .write_u32(offset, f),
350         Val::F64(f) => store
351             .gc_store_mut()?
352             .gc_object_data(gc_ref)
353             .write_u64(offset, f),
354         Val::V128(v) => store
355             .gc_store_mut()?
356             .gc_object_data(gc_ref)
357             .write_v128(offset, v),
358 
359         // NB: We don't need to do a write barrier when initializing a
360         // field, because there is nothing being overwritten. Therefore, we
361         // just the clone barrier.
362         Val::ExternRef(x) => {
363             let x = match x {
364                 None => 0,
365                 Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
366             };
367             store
368                 .gc_store_mut()?
369                 .gc_object_data(gc_ref)
370                 .write_u32(offset, x);
371         }
372         Val::AnyRef(x) => {
373             let x = match x {
374                 None => 0,
375                 Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
376             };
377             store
378                 .gc_store_mut()?
379                 .gc_object_data(gc_ref)
380                 .write_u32(offset, x);
381         }
382         Val::ExnRef(x) => {
383             let x = match x {
384                 None => 0,
385                 Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
386             };
387             store
388                 .gc_store_mut()?
389                 .gc_object_data(gc_ref)
390                 .write_u32(offset, x);
391         }
392 
393         Val::FuncRef(f) => {
394             let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store)));
395             let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) };
396             store
397                 .gc_store_mut()?
398                 .gc_object_data(gc_ref)
399                 .write_u32(offset, id.into_raw());
400         }
401     }
402     Ok(())
403 }
404