1 #[cfg(feature = "gc")]
2 mod enabled;
3 #[cfg(feature = "gc")]
4 pub use enabled::*;
5 
6 #[cfg(not(feature = "gc"))]
7 mod disabled;
8 #[cfg(not(feature = "gc"))]
9 pub use disabled::*;
10 
11 mod func_ref;
12 mod gc_ref;
13 mod gc_runtime;
14 mod host_data;
15 mod i31;
16 
17 pub use func_ref::*;
18 pub use gc_ref::*;
19 pub use gc_runtime::*;
20 pub use host_data::*;
21 pub use i31::*;
22 
23 use crate::prelude::*;
24 use crate::runtime::vm::{GcHeapAllocationIndex, VMMemoryDefinition};
25 use crate::store::Asyncness;
26 use core::any::Any;
27 use core::mem::MaybeUninit;
28 use core::{alloc::Layout, num::NonZeroU32};
29 use wasmtime_environ::{GcArrayLayout, GcStructLayout, VMGcKind, VMSharedTypeIndex};
30 
31 /// GC-related data that is one-to-one with a `wasmtime::Store`.
32 ///
33 /// Contains everything we need to do collections, invoke barriers, etc...
34 ///
35 /// In general, exposes a very similar interface to `GcHeap`, but fills in some
36 /// of the context arguments for callers (such as the `ExternRefHostDataTable`)
37 /// since they are all stored together inside `GcStore`.
38 pub struct GcStore {
39     /// This GC heap's allocation index (primarily used for integrating with the
40     /// pooling allocator).
41     pub allocation_index: GcHeapAllocationIndex,
42 
43     /// The actual GC heap.
44     pub gc_heap: Box<dyn GcHeap>,
45 
46     /// The `externref` host data table for this GC heap.
47     pub host_data_table: ExternRefHostDataTable,
48 
49     /// The function-references table for this GC heap.
50     pub func_ref_table: FuncRefTable,
51 
52     /// An allocation counter that triggers GC when it reaches zero.
53     ///
54     /// Initialized from the `WASMTIME_GC_ZEAL_ALLOC_COUNTER` environment
55     /// variable. Decremented on every allocation and when it hits zero, a GC is
56     /// forced and the counter is reset.
57     #[cfg(all(gc_zeal, feature = "std"))]
58     gc_zeal_alloc_counter: Option<NonZeroU32>,
59 
60     /// The initial value to reset the counter to after it triggers.
61     #[cfg(all(gc_zeal, feature = "std"))]
62     gc_zeal_alloc_counter_init: Option<NonZeroU32>,
63 }
64 
65 impl GcStore {
66     /// Create a new `GcStore`.
new(allocation_index: GcHeapAllocationIndex, gc_heap: Box<dyn GcHeap>) -> Self67     pub fn new(allocation_index: GcHeapAllocationIndex, gc_heap: Box<dyn GcHeap>) -> Self {
68         let host_data_table = ExternRefHostDataTable::default();
69         let func_ref_table = FuncRefTable::default();
70 
71         #[cfg(all(gc_zeal, feature = "std"))]
72         let gc_zeal_alloc_counter_init =
73             std::env::var("WASMTIME_GC_ZEAL_ALLOC_COUNTER")
74                 .ok()
75                 .map(|v| {
76                     v.parse::<NonZeroU32>().unwrap_or_else(|_| {
77                         panic!(
78                             "`WASMTIME_GC_ZEAL_ALLOC_COUNTER` must be a non-zero \
79                              `u32` value, got: {v}"
80                         )
81                     })
82                 });
83 
84         Self {
85             allocation_index,
86             gc_heap,
87             host_data_table,
88             func_ref_table,
89             #[cfg(all(gc_zeal, feature = "std"))]
90             gc_zeal_alloc_counter: gc_zeal_alloc_counter_init,
91             #[cfg(all(gc_zeal, feature = "std"))]
92             gc_zeal_alloc_counter_init,
93         }
94     }
95 
96     /// Get the `VMMemoryDefinition` for this GC heap.
vmmemory_definition(&self) -> VMMemoryDefinition97     pub fn vmmemory_definition(&self) -> VMMemoryDefinition {
98         self.gc_heap.vmmemory()
99     }
100 
101     /// Asynchronously perform garbage collection within this heap.
gc(&mut self, asyncness: Asyncness, roots: GcRootsIter<'_>)102     pub async fn gc(&mut self, asyncness: Asyncness, roots: GcRootsIter<'_>) {
103         let collection = self.gc_heap.gc(roots, &mut self.host_data_table);
104         collect_async(collection, asyncness).await;
105     }
106 
107     /// Get the kind of the given GC reference.
kind(&self, gc_ref: &VMGcRef) -> VMGcKind108     pub fn kind(&self, gc_ref: &VMGcRef) -> VMGcKind {
109         debug_assert!(!gc_ref.is_i31());
110         self.header(gc_ref).kind()
111     }
112 
113     /// Get the header of the given GC reference.
header(&self, gc_ref: &VMGcRef) -> &VMGcHeader114     pub fn header(&self, gc_ref: &VMGcRef) -> &VMGcHeader {
115         debug_assert!(!gc_ref.is_i31());
116         self.gc_heap.header(gc_ref)
117     }
118 
119     /// Clone a GC reference, calling GC write barriers as necessary.
clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef120     pub fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef {
121         if gc_ref.is_i31() {
122             gc_ref.copy_i31()
123         } else {
124             self.gc_heap.clone_gc_ref(gc_ref)
125         }
126     }
127 
128     /// Write the `source` GC reference into the uninitialized `destination`
129     /// slot, performing write barriers as necessary.
init_gc_ref( &mut self, destination: &mut MaybeUninit<Option<VMGcRef>>, source: Option<&VMGcRef>, )130     pub fn init_gc_ref(
131         &mut self,
132         destination: &mut MaybeUninit<Option<VMGcRef>>,
133         source: Option<&VMGcRef>,
134     ) {
135         // Initialize the destination to `None`, at which point the regular GC
136         // write barrier is safe to reuse.
137         let destination = destination.write(None);
138         self.write_gc_ref(destination, source);
139     }
140 
141     /// Dynamically tests whether a `init_gc_ref` is needed to write `gc_ref`
142     /// into an uninitialized destination.
needs_init_barrier(gc_ref: Option<&VMGcRef>) -> bool143     pub(crate) fn needs_init_barrier(gc_ref: Option<&VMGcRef>) -> bool {
144         assert!(cfg!(feature = "gc") || gc_ref.is_none());
145         gc_ref.is_some_and(|r| !r.is_i31())
146     }
147 
148     /// Dynamically tests whether a `write_gc_ref` is needed to write `gc_ref`
149     /// into `dest`.
needs_write_barrier( dest: &mut Option<VMGcRef>, gc_ref: Option<&VMGcRef>, ) -> bool150     pub(crate) fn needs_write_barrier(
151         dest: &mut Option<VMGcRef>,
152         gc_ref: Option<&VMGcRef>,
153     ) -> bool {
154         assert!(cfg!(feature = "gc") || gc_ref.is_none());
155         assert!(cfg!(feature = "gc") || dest.is_none());
156         dest.as_ref().is_some_and(|r| !r.is_i31()) || gc_ref.is_some_and(|r| !r.is_i31())
157     }
158 
159     /// Same as [`Self::write_gc_ref`] but doesn't require a `store` when
160     /// possible.
161     ///
162     /// # Panics
163     ///
164     /// Panics if `store` is `None` and one of `dest` or `gc_ref` requires a
165     /// write barrier.
write_gc_ref_optional_store( store: Option<&mut Self>, dest: &mut Option<VMGcRef>, gc_ref: Option<&VMGcRef>, )166     pub(crate) fn write_gc_ref_optional_store(
167         store: Option<&mut Self>,
168         dest: &mut Option<VMGcRef>,
169         gc_ref: Option<&VMGcRef>,
170     ) {
171         if Self::needs_write_barrier(dest, gc_ref) {
172             store.unwrap().write_gc_ref(dest, gc_ref)
173         } else {
174             *dest = gc_ref.map(|r| r.copy_i31());
175         }
176     }
177 
178     /// Write the `source` GC reference into the `destination` slot, performing
179     /// write barriers as necessary.
write_gc_ref(&mut self, destination: &mut Option<VMGcRef>, source: Option<&VMGcRef>)180     pub fn write_gc_ref(&mut self, destination: &mut Option<VMGcRef>, source: Option<&VMGcRef>) {
181         // If neither the source nor destination actually point to a GC object
182         // (that is, they are both either null or `i31ref`s) then we can skip
183         // the GC barrier.
184         if Self::needs_write_barrier(destination, source) {
185             self.gc_heap
186                 .write_gc_ref(&mut self.host_data_table, destination, source);
187         } else {
188             *destination = source.map(|s| s.copy_i31());
189         }
190     }
191 
192     /// Drop the given GC reference, performing drop barriers as necessary.
drop_gc_ref(&mut self, gc_ref: VMGcRef)193     pub fn drop_gc_ref(&mut self, gc_ref: VMGcRef) {
194         if !gc_ref.is_i31() {
195             self.gc_heap.drop_gc_ref(&mut self.host_data_table, gc_ref);
196         }
197     }
198 
199     /// Hook to call whenever a GC reference is about to be exposed to Wasm.
200     ///
201     /// Returns the raw representation of this GC ref, ready to be passed to
202     /// Wasm.
203     #[must_use]
expose_gc_ref_to_wasm(&mut self, gc_ref: VMGcRef) -> NonZeroU32204     pub fn expose_gc_ref_to_wasm(&mut self, gc_ref: VMGcRef) -> NonZeroU32 {
205         let raw = gc_ref.as_raw_non_zero_u32();
206         if !gc_ref.is_i31() {
207             log::trace!("exposing GC ref to Wasm: {gc_ref:p}");
208             self.gc_heap.expose_gc_ref_to_wasm(gc_ref);
209         }
210         raw
211     }
212 
213     /// Allocate a new `externref`.
214     ///
215     /// Returns:
216     ///
217     /// * `Ok(Ok(_))`: Successfully allocated the `externref`.
218     ///
219     /// * `Ok(Err((value, n)))`: Failed to allocate the `externref`, but doing a GC
220     ///   and then trying again may succeed. Returns the given `value` as the
221     ///   error payload, along with the size of the failed allocation.
222     ///
223     /// * `Err(_)`: Unrecoverable allocation failure.
alloc_externref( &mut self, value: Box<dyn Any + Send + Sync>, ) -> Result<Result<VMExternRef, (Box<dyn Any + Send + Sync>, u64)>>224     pub fn alloc_externref(
225         &mut self,
226         value: Box<dyn Any + Send + Sync>,
227     ) -> Result<Result<VMExternRef, (Box<dyn Any + Send + Sync>, u64)>> {
228         let host_data_id = self.host_data_table.alloc(value);
229         match self.gc_heap.alloc_externref(host_data_id)? {
230             Ok(x) => Ok(Ok(x)),
231             Err(n) => Ok(Err((self.host_data_table.dealloc(host_data_id), n))),
232         }
233     }
234 
235     /// Get a shared borrow of the given `externref`'s host data.
236     ///
237     /// Passing invalid `VMExternRef`s (eg garbage values or `externref`s
238     /// associated with a different heap is memory safe but will lead to general
239     /// incorrectness such as panics and wrong results.
externref_host_data(&self, externref: &VMExternRef) -> &(dyn Any + Send + Sync)240     pub fn externref_host_data(&self, externref: &VMExternRef) -> &(dyn Any + Send + Sync) {
241         let host_data_id = self.gc_heap.externref_host_data(externref);
242         self.host_data_table.get(host_data_id)
243     }
244 
245     /// Get a mutable borrow of the given `externref`'s host data.
246     ///
247     /// Passing invalid `VMExternRef`s (eg garbage values or `externref`s
248     /// associated with a different heap is memory safe but will lead to general
249     /// incorrectness such as panics and wrong results.
externref_host_data_mut( &mut self, externref: &VMExternRef, ) -> &mut (dyn Any + Send + Sync)250     pub fn externref_host_data_mut(
251         &mut self,
252         externref: &VMExternRef,
253     ) -> &mut (dyn Any + Send + Sync) {
254         let host_data_id = self.gc_heap.externref_host_data(externref);
255         self.host_data_table.get_mut(host_data_id)
256     }
257 
258     /// Allocate a raw object with the given header and layout.
alloc_raw( &mut self, header: VMGcHeader, layout: Layout, ) -> Result<Result<VMGcRef, u64>>259     pub fn alloc_raw(
260         &mut self,
261         header: VMGcHeader,
262         layout: Layout,
263     ) -> Result<Result<VMGcRef, u64>> {
264         // When gc_zeal is enabled with an allocation counter, decrement it and
265         // force a GC cycle when it reaches zero by returning a fake OOM.
266         #[cfg(all(gc_zeal, feature = "std"))]
267         if let Some(counter) = self.gc_zeal_alloc_counter.take() {
268             match NonZeroU32::new(counter.get() - 1) {
269                 Some(c) => self.gc_zeal_alloc_counter = Some(c),
270                 None => {
271                     log::trace!("gc_zeal: allocation counter reached zero, forcing GC");
272                     self.gc_zeal_alloc_counter = self.gc_zeal_alloc_counter_init;
273                     return Ok(Err(0));
274                 }
275             }
276         }
277 
278         self.gc_heap.alloc_raw(header, layout)
279     }
280 
281     /// Allocate an uninitialized struct with the given type index and layout.
282     ///
283     /// This does NOT check that the index is currently allocated in the types
284     /// registry or that the layout matches the index's type. Failure to uphold
285     /// those invariants is memory safe, but will lead to general incorrectness
286     /// such as panics and wrong results.
alloc_uninit_struct( &mut self, ty: VMSharedTypeIndex, layout: &GcStructLayout, ) -> Result<Result<VMStructRef, u64>>287     pub fn alloc_uninit_struct(
288         &mut self,
289         ty: VMSharedTypeIndex,
290         layout: &GcStructLayout,
291     ) -> Result<Result<VMStructRef, u64>> {
292         self.gc_heap
293             .alloc_uninit_struct_or_exn(ty, layout)
294             .map(|r| r.map(|r| r.into_structref_unchecked()))
295     }
296 
297     /// Deallocate an uninitialized struct.
dealloc_uninit_struct(&mut self, structref: VMStructRef)298     pub fn dealloc_uninit_struct(&mut self, structref: VMStructRef) {
299         self.gc_heap.dealloc_uninit_struct_or_exn(structref.into())
300     }
301 
302     /// Get the data for the given object reference.
303     ///
304     /// Panics when the structref and its size is out of the GC heap bounds.
gc_object_data(&mut self, gc_ref: &VMGcRef) -> &mut VMGcObjectData305     pub fn gc_object_data(&mut self, gc_ref: &VMGcRef) -> &mut VMGcObjectData {
306         self.gc_heap.gc_object_data_mut(gc_ref)
307     }
308 
309     /// Get the object datas for the given pair of object references.
310     ///
311     /// Panics if `a` and `b` are the same reference or either is out of bounds.
gc_object_data_pair( &mut self, a: &VMGcRef, b: &VMGcRef, ) -> (&mut VMGcObjectData, &mut VMGcObjectData)312     pub fn gc_object_data_pair(
313         &mut self,
314         a: &VMGcRef,
315         b: &VMGcRef,
316     ) -> (&mut VMGcObjectData, &mut VMGcObjectData) {
317         assert_ne!(a, b);
318         self.gc_heap.gc_object_data_pair(a, b)
319     }
320 
321     /// Allocate an uninitialized array with the given type index.
322     ///
323     /// This does NOT check that the index is currently allocated in the types
324     /// registry or that the layout matches the index's type. Failure to uphold
325     /// those invariants is memory safe, but will lead to general incorrectness
326     /// such as panics and wrong results.
alloc_uninit_array( &mut self, ty: VMSharedTypeIndex, len: u32, layout: &GcArrayLayout, ) -> Result<Result<VMArrayRef, u64>>327     pub fn alloc_uninit_array(
328         &mut self,
329         ty: VMSharedTypeIndex,
330         len: u32,
331         layout: &GcArrayLayout,
332     ) -> Result<Result<VMArrayRef, u64>> {
333         self.gc_heap.alloc_uninit_array(ty, len, layout)
334     }
335 
336     /// Deallocate an uninitialized array.
dealloc_uninit_array(&mut self, arrayref: VMArrayRef)337     pub fn dealloc_uninit_array(&mut self, arrayref: VMArrayRef) {
338         self.gc_heap.dealloc_uninit_array(arrayref);
339     }
340 
341     /// Get the length of the given array.
array_len(&self, arrayref: &VMArrayRef) -> u32342     pub fn array_len(&self, arrayref: &VMArrayRef) -> u32 {
343         self.gc_heap.array_len(arrayref)
344     }
345 
346     /// Allocate an uninitialized exception object with the given type
347     /// index.
348     ///
349     /// This does NOT check that the index is currently allocated in the types
350     /// registry or that the layout matches the index's type. Failure to uphold
351     /// those invariants is memory safe, but will lead to general incorrectness
352     /// such as panics and wrong results.
alloc_uninit_exn( &mut self, ty: VMSharedTypeIndex, layout: &GcStructLayout, ) -> Result<Result<VMExnRef, u64>>353     pub fn alloc_uninit_exn(
354         &mut self,
355         ty: VMSharedTypeIndex,
356         layout: &GcStructLayout,
357     ) -> Result<Result<VMExnRef, u64>> {
358         self.gc_heap
359             .alloc_uninit_struct_or_exn(ty, layout)
360             .map(|r| r.map(|r| r.into_exnref_unchecked()))
361     }
362 
363     /// Deallocate an uninitialized exception object.
dealloc_uninit_exn(&mut self, exnref: VMExnRef)364     pub fn dealloc_uninit_exn(&mut self, exnref: VMExnRef) {
365         self.gc_heap.dealloc_uninit_struct_or_exn(exnref.into());
366     }
367 }
368