wasmtime/runtime/vm/gc/enabled/structref.rs
1use crate::{
2 StorageType, Val,
3 prelude::*,
4 runtime::vm::{GcHeap, GcStore, VMGcRef},
5 store::AutoAssertNoGc,
6};
7use core::fmt;
8use wasmtime_environ::{GcStructLayout, VMGcKind};
9
10/// A `VMGcRef` that we know points to a `struct`.
11///
12/// Create a `VMStructRef` via `VMGcRef::into_structref` and
13/// `VMGcRef::as_structref`, or their untyped equivalents
14/// `VMGcRef::into_structref_unchecked` and `VMGcRef::as_structref_unchecked`.
15///
16/// Note: This is not a `TypedGcRef<_>` because each collector can have a
17/// different concrete representation of `structref` that they allocate inside
18/// their heaps.
19#[derive(Debug, PartialEq, Eq, Hash)]
20#[repr(transparent)]
21pub struct VMStructRef(VMGcRef);
22
23impl fmt::Pointer for VMStructRef {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 fmt::Pointer::fmt(&self.0, f)
26 }
27}
28
29impl From<VMStructRef> for VMGcRef {
30 #[inline]
31 fn from(x: VMStructRef) -> Self {
32 x.0
33 }
34}
35
36impl VMGcRef {
37 /// Is this `VMGcRef` pointing to a `struct`?
38 pub fn is_structref(&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::StructRef),
45 Err(_) => false,
46 }
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
98impl 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 ) -> Result<Val> {
142 let offset = layout.fields[field].offset;
143 self.as_gc_ref().read_val(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 self.as_gc_ref().write_val(store, ty, offset, val)
169 }
170
171 /// Initialize a field in this structref that is currently uninitialized.
172 ///
173 /// The difference between this method and `write_field` is that GC barriers
174 /// are handled differently. When overwriting an initialized field (aka
175 /// `write_field`) we need to call the full write GC write barrier, which
176 /// logically drops the old GC reference and clones the new GC
177 /// reference. When we are initializing a field for the first time, there is
178 /// no old GC reference that is being overwritten and which we need to drop,
179 /// so we only need to clone the new GC reference.
180 ///
181 /// Calling this method on a structref that has already had the associated
182 /// field initialized will result in GC bugs. These are memory safe but will
183 /// lead to generally incorrect behavior such as panics, leaks, and
184 /// incorrect results.
185 ///
186 /// Does not check that `val` matches `ty`, nor that the field is actually
187 /// of type `ty`. Checking those things is the caller's responsibility.
188 /// Failure to do so is memory safe, but will lead to general incorrectness
189 /// such as panics and wrong results.
190 ///
191 /// Returns an error if `val` is a GC reference that has since been
192 /// unrooted.
193 ///
194 /// Panics on out-of-bounds accesses.
195 pub fn initialize_field(
196 &self,
197 store: &mut AutoAssertNoGc,
198 layout: &GcStructLayout,
199 ty: &StorageType,
200 field: usize,
201 val: Val,
202 ) -> Result<()> {
203 debug_assert!(val._matches_ty(&store, &ty.unpack())?);
204 let offset = layout.fields[field].offset;
205 self.as_gc_ref().initialize_val(store, ty, offset, val)
206 }
207}