1#[cfg(feature = "gc")]
2mod enabled;
3#[cfg(feature = "gc")]
4pub use enabled::*;
5
6#[cfg(not(feature = "gc"))]
7mod disabled;
8#[cfg(not(feature = "gc"))]
9pub use disabled::*;
10
11#[cfg(feature = "stack-switching")]
12mod cont_ref;
13mod data;
14mod func_ref;
15mod gc_ref;
16mod gc_runtime;
17mod host_data;
18mod i31;
19
20#[cfg(feature = "stack-switching")]
21pub use cont_ref::*;
22pub use data::*;
23pub use func_ref::*;
24pub use gc_ref::*;
25pub use gc_runtime::*;
26pub use host_data::*;
27pub use i31::*;
28
29use crate::bail_bug;
30use crate::hash_map::HashMap;
31use crate::module::ModuleRegistry;
32use crate::prelude::*;
33use crate::runtime::vm::{GcHeapAllocationIndex, VMMemoryDefinition};
34use crate::store::Asyncness;
35use crate::type_registry::RegisteredType;
36use core::any::Any;
37use core::mem::MaybeUninit;
38use core::{alloc::Layout, num::NonZeroU32};
39use wasmtime_environ::{GcArrayLayout, GcLayout, GcStructLayout, VMGcKind, VMSharedTypeIndex};
40
41pub struct GcStore {
49 pub allocation_index: GcHeapAllocationIndex,
52
53 pub gc_heap: Box<dyn GcHeap>,
55
56 pub host_data_table: ExternRefHostDataTable,
58
59 pub func_ref_table: FuncRefTable,
61
62 #[cfg(feature = "stack-switching")]
64 pub cont_ref_table: ContRefTable,
65
66 pub last_post_gc_allocated_bytes: Option<usize>,
70
71 #[cfg(gc_zeal)]
76 gc_zeal_alloc_counter: Option<NonZeroU32>,
77
78 #[cfg(gc_zeal)]
80 gc_zeal_alloc_counter_init: Option<NonZeroU32>,
81}
82
83pub type StoreGcHostAllocTypes = HashMap<VMSharedTypeIndex, (RegisteredType, Option<TraceInfo>)>;
86
87pub struct GcStoreTraceState<'a> {
90 pub host_data_table: &'a mut ExternRefHostDataTable,
93 pub modules: &'a ModuleRegistry,
95 pub gc_host_alloc_types: &'a StoreGcHostAllocTypes,
97}
98
99impl GcStore {
100 pub fn new(
102 allocation_index: GcHeapAllocationIndex,
103 gc_heap: Box<dyn GcHeap>,
104 gc_zeal_alloc_counter: Option<NonZeroU32>,
105 ) -> Self {
106 let host_data_table = ExternRefHostDataTable::default();
107 let func_ref_table = FuncRefTable::default();
108 #[cfg(feature = "stack-switching")]
109 let cont_ref_table = ContRefTable::default();
110
111 let _ = &gc_zeal_alloc_counter;
112
113 Self {
114 allocation_index,
115 gc_heap,
116 host_data_table,
117 func_ref_table,
118 #[cfg(feature = "stack-switching")]
119 cont_ref_table,
120 last_post_gc_allocated_bytes: None,
121 #[cfg(gc_zeal)]
122 gc_zeal_alloc_counter,
123 #[cfg(gc_zeal)]
124 gc_zeal_alloc_counter_init: gc_zeal_alloc_counter,
125 }
126 }
127
128 pub fn vmmemory_definition(&self) -> VMMemoryDefinition {
130 self.gc_heap.vmmemory()
131 }
132
133 pub fn gc_heap_capacity(&self) -> usize {
135 self.gc_heap.heap_slice().len()
136 }
137
138 pub async fn gc(
140 &mut self,
141 asyncness: Asyncness,
142 roots: GcRootsIter<'_>,
143 modules: &ModuleRegistry,
144 gc_host_alloc_types: &StoreGcHostAllocTypes,
145 yield_fn: impl AsyncFn(),
146 ) -> Result<()> {
147 let mut trace_state = GcStoreTraceState {
148 host_data_table: &mut self.host_data_table,
149 modules,
150 gc_host_alloc_types,
151 };
152 let collection = self.gc_heap.gc(roots, &mut trace_state);
153 collect_async(collection, asyncness, yield_fn).await?;
154 self.last_post_gc_allocated_bytes = Some({
155 let size = self.gc_heap.allocated_bytes();
156 log::trace!("After collection, GC heap's allocated bytes = {size:#x} bytes");
157 size
158 });
159 Ok(())
160 }
161
162 pub fn kind(&self, gc_ref: &VMGcRef) -> Result<VMGcKind> {
164 debug_assert!(!gc_ref.is_i31());
165 match self.header(gc_ref)?.kind() {
166 Some(kind) => Ok(kind),
167 None => bail_bug!("invalid `VMGcKind`"),
168 }
169 }
170
171 pub fn header(&self, gc_ref: &VMGcRef) -> Result<&VMGcHeader> {
173 debug_assert!(!gc_ref.is_i31());
174 self.gc_heap.header(gc_ref)
175 }
176
177 pub fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef {
179 if gc_ref.is_i31() {
180 gc_ref.copy_i31()
181 } else {
182 self.gc_heap.clone_gc_ref(gc_ref)
183 }
184 }
185
186 pub fn init_gc_ref(
189 &mut self,
190 destination: &mut MaybeUninit<Option<VMGcRef>>,
191 source: Option<&VMGcRef>,
192 ) -> Result<()> {
193 let destination = destination.write(None);
196 self.write_gc_ref(destination, source)
197 }
198
199 pub(crate) fn needs_init_barrier(gc_ref: Option<&VMGcRef>) -> bool {
202 assert!(cfg!(feature = "gc") || gc_ref.is_none());
203 gc_ref.is_some_and(|r| !r.is_i31())
204 }
205
206 pub(crate) fn needs_write_barrier(
209 dest: &mut Option<VMGcRef>,
210 gc_ref: Option<&VMGcRef>,
211 ) -> bool {
212 assert!(cfg!(feature = "gc") || gc_ref.is_none());
213 assert!(cfg!(feature = "gc") || dest.is_none());
214 dest.as_ref().is_some_and(|r| !r.is_i31()) || gc_ref.is_some_and(|r| !r.is_i31())
215 }
216
217 pub(crate) fn write_gc_ref_optional_store(
225 store: Option<&mut Self>,
226 dest: &mut Option<VMGcRef>,
227 gc_ref: Option<&VMGcRef>,
228 ) -> Result<()> {
229 if Self::needs_write_barrier(dest, gc_ref) {
230 store.unwrap().write_gc_ref(dest, gc_ref)
231 } else {
232 *dest = gc_ref.map(|r| r.copy_i31());
233 Ok(())
234 }
235 }
236
237 pub fn write_gc_ref(
240 &mut self,
241 destination: &mut Option<VMGcRef>,
242 source: Option<&VMGcRef>,
243 ) -> Result<()> {
244 if Self::needs_write_barrier(destination, source) {
248 self.gc_heap.write_gc_ref(destination, source)?;
249 } else {
250 *destination = source.map(|s| s.copy_i31());
251 }
252 Ok(())
253 }
254
255 pub fn drop_gc_ref(&mut self, gc_ref: VMGcRef) {
257 if !gc_ref.is_i31() {
258 self.gc_heap.drop_gc_ref(gc_ref);
259 }
260 }
261
262 #[must_use]
267 pub fn expose_gc_ref_to_wasm(&mut self, gc_ref: VMGcRef) -> Result<NonZeroU32> {
268 let raw = gc_ref.as_raw_non_zero_u32();
269 if !gc_ref.is_i31() {
270 log::trace!("exposing GC ref to Wasm: {gc_ref:p}");
271 self.gc_heap.expose_gc_ref_to_wasm(gc_ref)?;
272 }
273 Ok(raw)
274 }
275
276 pub fn alloc_externref(
288 &mut self,
289 value: Box<dyn Any + Send + Sync>,
290 ) -> Result<Result<VMExternRef, (Box<dyn Any + Send + Sync>, u64)>> {
291 let host_data_id = self.host_data_table.alloc(value);
292 match self.gc_heap.alloc_externref(host_data_id)? {
293 Ok(x) => Ok(Ok(x)),
294 Err(n) => Ok(Err((self.host_data_table.dealloc(host_data_id)?, n))),
295 }
296 }
297
298 pub fn externref_host_data(&self, externref: &VMExternRef) -> Result<&(dyn Any + Send + Sync)> {
304 let host_data_id = self.gc_heap.externref_host_data(externref)?;
305 self.host_data_table.get(host_data_id)
306 }
307
308 pub fn externref_host_data_mut(
314 &mut self,
315 externref: &VMExternRef,
316 ) -> Result<&mut (dyn Any + Send + Sync)> {
317 let host_data_id = self.gc_heap.externref_host_data(externref)?;
318 self.host_data_table.get_mut(host_data_id)
319 }
320
321 pub fn alloc_raw(
323 &mut self,
324 header: VMGcHeader,
325 layout: Layout,
326 ) -> Result<Result<VMGcRef, u64>> {
327 #[cfg(gc_zeal)]
330 if let Some(counter) = self.gc_zeal_alloc_counter.take() {
331 match NonZeroU32::new(counter.get() - 1) {
332 Some(c) => self.gc_zeal_alloc_counter = Some(c),
333 None => {
334 log::trace!("gc_zeal: allocation counter reached zero, forcing GC");
335 self.gc_zeal_alloc_counter = self.gc_zeal_alloc_counter_init;
336 return Ok(Err(0));
337 }
338 }
339 }
340
341 self.gc_heap.alloc_raw(header, layout)
342 }
343
344 pub fn alloc_uninit_struct(
351 &mut self,
352 ty: VMSharedTypeIndex,
353 layout: &GcStructLayout,
354 ) -> Result<Result<VMStructRef, u64>> {
355 self.gc_heap
356 .alloc_uninit_struct_or_exn(ty, layout)
357 .map(|r| r.map(|r| r.into_structref_unchecked()))
358 }
359
360 pub fn dealloc_uninit_struct(&mut self, structref: VMStructRef) -> Result<()> {
362 self.gc_heap.dealloc_uninit_struct_or_exn(structref.into())
363 }
364
365 pub fn gc_object_data(&mut self, gc_ref: &VMGcRef) -> Result<&mut VMGcObjectData> {
369 self.gc_heap.gc_object_data_mut(gc_ref)
370 }
371
372 pub fn alloc_uninit_array(
379 &mut self,
380 ty: VMSharedTypeIndex,
381 len: u32,
382 layout: &GcArrayLayout,
383 ) -> Result<Result<VMArrayRef, u64>> {
384 self.gc_heap.alloc_uninit_array(ty, len, layout)
385 }
386
387 pub fn dealloc_uninit_array(&mut self, arrayref: VMArrayRef) -> Result<()> {
389 self.gc_heap.dealloc_uninit_array(arrayref)
390 }
391
392 pub fn array_len(&self, arrayref: &VMArrayRef) -> Result<u32> {
394 self.gc_heap.array_len(arrayref)
395 }
396
397 pub fn alloc_uninit_exn(
405 &mut self,
406 ty: VMSharedTypeIndex,
407 layout: &GcStructLayout,
408 ) -> Result<Result<VMExnRef, u64>> {
409 self.gc_heap
410 .alloc_uninit_struct_or_exn(ty, layout)
411 .map(|r| r.map(|r| r.into_exnref_unchecked()))
412 }
413
414 pub fn dealloc_uninit_exn(&mut self, exnref: VMExnRef) -> Result<()> {
416 self.gc_heap.dealloc_uninit_struct_or_exn(exnref.into())
417 }
418
419 #[cfg(feature = "gc")]
420 pub(crate) fn replace_gc_zeal_alloc_counter(
421 &mut self,
422 new_value: Option<NonZeroU32>,
423 ) -> Option<NonZeroU32> {
424 #[cfg(gc_zeal)]
425 return core::mem::replace(&mut self.gc_zeal_alloc_counter, new_value);
426
427 #[cfg(not(gc_zeal))]
428 {
429 let _ = new_value;
430 return None;
431 }
432 }
433}
434
435#[derive(Debug)]
437pub enum TraceInfo {
438 Array {
440 #[cfg_attr(
443 not(feature = "gc-drc"),
444 allow(dead_code, reason = "easier not to cfg on/off")
445 )]
446 gc_ref_elems: bool,
447 },
448
449 Struct {
451 gc_ref_offsets: Box<[u32]>,
454 },
455}
456
457impl TraceInfo {
458 pub(crate) fn new(gc_layout: &GcLayout) -> Self {
459 match gc_layout {
460 GcLayout::Array(l) => TraceInfo::Array {
461 gc_ref_elems: l.elems_are_gc_refs,
462 },
463 GcLayout::Struct(l) => TraceInfo::Struct {
464 gc_ref_offsets: l
465 .fields
466 .iter()
467 .filter_map(|f| if f.is_gc_ref { Some(f.offset) } else { None })
468 .collect(),
469 },
470 }
471 }
472}