Skip to main content

wasmtime/runtime/vm/gc/
gc_runtime.rs

1//! Traits for abstracting over our different garbage collectors.
2
3use crate::bail_bug;
4use crate::prelude::*;
5use crate::runtime::vm::{
6    ExternRefHostDataId, GcHeapObject, GcStoreTraceState, SendSyncPtr, TypedGcRef, VMArrayRef,
7    VMExternRef, VMGcHeader, VMGcObjectData, VMGcRef, ValRaw,
8};
9use crate::store::Asyncness;
10use crate::vm::VMMemoryDefinition;
11use core::ptr::NonNull;
12use core::slice;
13use core::{alloc::Layout, any::Any, marker, mem, ops::Range, ptr};
14use wasmtime_environ::{GcArrayLayout, GcStructLayout, GcTypeLayouts, VMSharedTypeIndex};
15
16/// Trait for integrating a garbage collector with the runtime.
17///
18/// This trait is responsible for:
19///
20/// * GC barriers used by runtime code (as opposed to compiled Wasm code)
21///
22/// * Creating and managing GC heaps for individual stores
23///
24/// * Running garbage collection
25///
26/// # Safety
27///
28/// The collector, its GC heaps, and GC barriers when taken together as a whole
29/// must be safe. Additionally, they must work with the GC barriers emitted into
30/// compiled Wasm code via the collector's corresponding `GcCompiler`
31/// implementation. That is, if callers only call safe methods on this trait
32/// (while pairing it with its associated `GcCompiler`, `GcHeap`, and etc...)
33/// and uphold all the documented safety invariants of this trait's unsafe
34/// methods, then it must be impossible for callers to violate memory
35/// safety. Implementations of this trait may not add new safety invariants, not
36/// already documented in this trait's interface, that callers need to uphold.
37pub unsafe trait GcRuntime: 'static + Send + Sync {
38    /// Get this collector's GC type layouts.
39    fn layouts(&self) -> &dyn GcTypeLayouts;
40
41    /// Construct a new GC heap.
42    #[cfg(feature = "gc")]
43    fn new_gc_heap(&self, engine: &crate::Engine) -> Result<Box<dyn GcHeap>>;
44}
45
46/// A heap that manages garbage-collected objects.
47///
48/// Each `wasmtime::Store` is associated with a single `GcHeap`, and a `GcHeap`
49/// is only ever used with one store at a time, but `GcHeap`s may be reused with
50/// new stores after its original store is dropped. The `reset` method will be
51/// called in between each such reuse. (This reuse allows for better integration
52/// with the pooling allocator).
53///
54/// If a `GcHeap` mapped any memory, its `Drop` implementation should unmap that
55/// memory.
56///
57/// # Safety
58///
59/// The trait methods below are all safe: implementations of this trait must
60/// ensure that these methods cannot be misused to create memory unsafety. The
61/// expectation is that -- given that `VMGcRef` is a newtype over an index --
62/// implementations perform similar tricks as Wasm linear memory
63/// implementations. The heap should internally be a contiguous region of memory
64/// and `VMGcRef` indices into the heap must be bounds checked (explicitly or
65/// implicitly via virtual memory tricks).
66///
67/// Furthermore, if heap corruption occurs because (for example) a `VMGcRef`
68/// from a different heap is used with this heap, then that corruption must be
69/// limited to within this heap. Every heap is a mini sandbox. It follows that
70/// native pointers should never be written into or read out from the GC heap,
71/// since that could spread corruption from inside the GC heap out to the native
72/// host heap. The host data for an `externref`, therefore, is stored in a side
73/// table (`ExternRefHostDataTable`) and never inside the heap. Only an id
74/// referencing a slot in that table should ever be written into the GC heap.
75///
76/// These constraints give us great amounts of safety compared to working with
77/// raw pointers. The worst that could happen is corruption local to heap and a
78/// panic, or perhaps reading stale heap data from a previous Wasm instance. A
79/// corrupt `GcHeap` can *never* result in the native host's corruption.
80///
81/// The downside is that we are introducing `heap_base + index` computations and
82/// bounds checking to access GC memory, adding performance overhead. This is
83/// deemed to be a worthy trade off. Furthermore, it isn't even a clear cut
84/// performance degradation since this allows us to use 32-bit "pointers",
85/// giving us more compact data representations and the improved cache
86/// utilization that implies.
87pub unsafe trait GcHeap: 'static + Send + Sync {
88    ////////////////////////////////////////////////////////////////////////////
89    // Life Cycle GC Heap Methods
90
91    /// Is this GC heap currently attached to a memory?
92    fn is_attached(&self) -> bool;
93
94    /// Attach this GC heap to a memory.
95    ///
96    /// Once attached, this GC heap can be used with Wasm.
97    fn attach(&mut self, memory: crate::vm::Memory);
98
99    /// Reset this heap.
100    ///
101    /// Calling this method unassociates this heap with the store that it has
102    /// been associated with, making it available to be associated with a new
103    /// heap.
104    ///
105    /// This should refill free lists, reset bump pointers, and etc... as if
106    /// nothing were allocated in this heap (because nothing is allocated in
107    /// this heap anymore).
108    ///
109    /// This should retain any allocated memory from the global allocator and
110    /// any virtual memory mappings.
111    fn detach(&mut self) -> crate::vm::Memory;
112
113    ////////////////////////////////////////////////////////////////////////////
114    // `Any` methods
115
116    /// Get this heap as an `&Any`.
117    fn as_any(&self) -> &dyn Any;
118
119    /// Get this heap as an `&mut Any`.
120    fn as_any_mut(&mut self) -> &mut dyn Any;
121
122    ////////////////////////////////////////////////////////////////////////////
123    // No-GC Scope Methods
124
125    /// Enter a no-GC scope.
126    ///
127    /// Calling the `gc` method when we are inside a no-GC scope should panic.
128    ///
129    /// We can enter multiple, nested no-GC scopes and this method should
130    /// account for that.
131    fn enter_no_gc_scope(&mut self);
132
133    /// Exit a no-GC scope.
134    ///
135    /// Dual to `enter_no_gc_scope`.
136    fn exit_no_gc_scope(&mut self);
137
138    ////////////////////////////////////////////////////////////////////////////
139    // GC Barriers
140
141    /// Read barrier called every time the runtime clones a GC reference.
142    ///
143    /// Callers should pass a valid `VMGcRef` that belongs to the given
144    /// heap. Failure to do so is memory safe, but may result in general
145    /// failures such as panics or incorrect results.
146    fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef;
147
148    /// Write barrier called whenever the runtime is nulling out a GC reference.
149    ///
150    /// Default implemented in terms of the `write_gc_ref` barrier.
151    ///
152    /// If an `externref` is reclaimed, then its associated entry in the
153    /// `host_data_table` should be removed.
154    ///
155    /// Callers should pass a valid `VMGcRef` that belongs to the given
156    /// heap. Failure to do so is memory safe, but may result in general
157    /// failures such as panics or incorrect results.
158    ///
159    /// The given `gc_ref` should not be used again.
160    fn drop_gc_ref(&mut self, gc_ref: VMGcRef) {
161        let mut dest = Some(gc_ref);
162
163        // Similar to `clone_gc_ref` not being fallible this method,
164        // `drop_gc_ref`, is also not fallible as it's seen as too painful to
165        // propagate this result. The consequence is that if the GC heap is
166        // corrupted at this point it'll get a ltitle more corrupted from this
167        // operation, but that's the tradeoff we're making ignoring the error
168        // here.
169        if let Err(e) = self.write_gc_ref(&mut dest, None) {
170            if cfg!(debug_assertions) {
171                panic!("heap corruption detected: {e}");
172            }
173        }
174    }
175
176    /// Write barrier called every time the runtime overwrites a GC reference.
177    ///
178    /// The `source` is a borrowed GC reference, and should not have been cloned
179    /// already for this write operation. This allows implementations to fuse
180    /// the `source`'s read barrier into this write barrier.
181    ///
182    /// If an `externref` is reclaimed, then its associated entry in the
183    /// `host_data_table` should be removed.
184    ///
185    /// Callers should pass a valid `VMGcRef` that belongs to the given heap for
186    /// both the `source` and `destination`. Failure to do so is memory safe,
187    /// but may result in general failures such as panics or incorrect results.
188    fn write_gc_ref(
189        &mut self,
190        destination: &mut Option<VMGcRef>,
191        source: Option<&VMGcRef>,
192    ) -> Result<()>;
193
194    /// Read barrier called whenever a GC reference is passed from the runtime
195    /// to Wasm: an argument to a host-to-Wasm call, or a return from a
196    /// Wasm-to-host call.
197    ///
198    /// Callers should pass a valid `VMGcRef` that belongs to the given
199    /// heap. Failure to do so is memory safe, but may result in general
200    /// failures such as panics or incorrect results.
201    fn expose_gc_ref_to_wasm(&mut self, gc_ref: VMGcRef) -> Result<()>;
202
203    ////////////////////////////////////////////////////////////////////////////
204    // `externref` Methods
205
206    /// Allocate a `VMExternRef` with space for host data described by the given
207    /// layout.
208    ///
209    /// Return values:
210    ///
211    /// * `Ok(Ok(_))`: The allocation was successful.
212    ///
213    /// * `Ok(Err(n))`: There is currently not enough available space for this
214    ///   allocation of size `n`. The caller should either grow the heap or run
215    ///   a collection to reclaim space, and then try allocating again.
216    ///
217    /// * `Err(_)`: The collector cannot satisfy this allocation request, and
218    ///   would not be able to even after the caller were to trigger a
219    ///   collection. This could be because, for example, the requested
220    ///   allocation is larger than this collector's implementation limit for
221    ///   object size.
222    fn alloc_externref(
223        &mut self,
224        host_data: ExternRefHostDataId,
225    ) -> Result<Result<VMExternRef, u64>>;
226
227    /// Get the host data ID associated with the given `externref`.
228    ///
229    /// Callers should pass a valid `externref` that belongs to the given
230    /// heap. Failure to do so is memory safe, but may result in general
231    /// failures such as panics or incorrect results.
232    fn externref_host_data(&self, externref: &VMExternRef) -> Result<ExternRefHostDataId>;
233
234    ////////////////////////////////////////////////////////////////////////////
235    // Struct, array, and general GC object methods
236
237    /// Get the header of the object that `gc_ref` points to.
238    ///
239    /// Implementations may assert that the header's kind is valid, so
240    /// allocation paths that have not written the header yet must reach for the
241    /// raw object data instead of these accessors.
242    fn header(&self, gc_ref: &VMGcRef) -> Result<&VMGcHeader>;
243
244    /// Get the header of the object that `gc_ref` points to.
245    ///
246    /// See `header` for the restriction on uninitialized objects.
247    fn header_mut(&mut self, gc_ref: &VMGcRef) -> Result<&mut VMGcHeader>;
248
249    /// Get the size (in bytes) of the object referenced by `gc_ref`.
250    ///
251    /// # Errors
252    ///
253    /// Returns an error on out of bounds or if the `gc_ref` is an `i31ref`.
254    fn object_size(&self, gc_ref: &VMGcRef) -> Result<usize>;
255
256    /// Allocate a raw, uninitialized GC-managed object with the given header
257    /// and layout.
258    ///
259    /// The object's fields and elements are left uninitialized. It is the
260    /// caller's responsibility to initialize them before exposing the struct to
261    /// Wasm or triggering a GC.
262    ///
263    /// The header's described type and layout must match *for this
264    /// collector*. That is, if this collector adds an extra header word to all
265    /// objects, the given layout must already include space for that header
266    /// word. Therefore, this method is effectively only usable with layouts
267    /// derived from a `Gc{Struct,Array}Layout` returned by this collector.
268    ///
269    /// Failure to uphold any of the above is memory safe, but may result in
270    /// general failures such as panics or incorrect results.
271    ///
272    /// Return values:
273    ///
274    /// * `Ok(Ok(_))`: The allocation was successful.
275    ///
276    /// * `Ok(Err(n))`: There is currently not enough available space for this
277    ///   allocation of size `n`. The caller should either grow the heap or run
278    ///   a collection to reclaim space, and then try allocating again.
279    ///
280    /// * `Err(_)`: The collector cannot satisfy this allocation request, and
281    ///   would not be able to even after the caller were to trigger a
282    ///   collection. This could be because, for example, the requested
283    ///   alignment is larger than this collector's implementation limit.
284    fn alloc_raw(&mut self, header: VMGcHeader, layout: Layout) -> Result<Result<VMGcRef, u64>>;
285
286    /// Allocate a GC-managed struct of the given type and layout.
287    ///
288    /// The struct's fields are left uninitialized. It is the caller's
289    /// responsibility to initialize them before exposing the struct to Wasm or
290    /// triggering a GC.
291    ///
292    /// The `ty` and `layout` must match.
293    ///
294    /// Failure to do either of the above is memory safe, but may result in
295    /// general failures such as panics or incorrect results.
296    ///
297    /// Return values:
298    ///
299    /// * `Ok(Ok(_))`: The allocation was successful.
300    ///
301    /// * `Ok(Err(n))`: There is currently not enough available space for this
302    ///   allocation of size `n`. The caller should either grow the heap or run
303    ///   a collection to reclaim space, and then try allocating again.
304    ///
305    /// * `Err(_)`: The collector cannot satisfy this allocation request, and
306    ///   would not be able to even after the caller were to trigger a
307    ///   collection. This could be because, for example, the requested
308    ///   allocation is larger than this collector's implementation limit for
309    ///   object size.
310    fn alloc_uninit_struct_or_exn(
311        &mut self,
312        ty: VMSharedTypeIndex,
313        layout: &GcStructLayout,
314    ) -> Result<Result<VMGcRef, u64>>;
315
316    /// Deallocate an uninitialized, GC-managed struct or exception.
317    ///
318    /// This is useful for if initialization of the struct's fields fails, so
319    /// that the struct's allocation can be eagerly reclaimed, and so that the
320    /// collector doesn't attempt to treat any of the uninitialized fields as
321    /// valid GC references, or something like that.
322    fn dealloc_uninit_struct_or_exn(&mut self, structref: VMGcRef) -> Result<()>;
323
324    /// * `Ok(Ok(_))`: The allocation was successful.
325    ///
326    /// * `Ok(Err(n))`: There is currently not enough available space for this
327    ///   allocation of size `n`. The caller should either grow the heap or run
328    ///   a collection to reclaim space, and then try allocating again.
329    ///
330    /// * `Err(_)`: The collector cannot satisfy this allocation request, and
331    ///   would not be able to even after the caller were to trigger a
332    ///   collection. This could be because, for example, the requested
333    ///   allocation is larger than this collector's implementation limit for
334    ///   object size.
335    fn alloc_uninit_array(
336        &mut self,
337        ty: VMSharedTypeIndex,
338        len: u32,
339        layout: &GcArrayLayout,
340    ) -> Result<Result<VMArrayRef, u64>>;
341
342    /// Deallocate an uninitialized, GC-managed array.
343    ///
344    /// This is useful for if initialization of the array's fields fails, so
345    /// that the array's allocation can be eagerly reclaimed, and so that the
346    /// collector doesn't attempt to treat any of the uninitialized fields as
347    /// valid GC references, or something like that.
348    fn dealloc_uninit_array(&mut self, arrayref: VMArrayRef) -> Result<()>;
349
350    /// Get the length of the given array.
351    ///
352    /// Returns an error on out-of-bounds accesses.
353    ///
354    /// The given `arrayref` should be valid and of the given size. Failure to
355    /// do so is memory safe, but may result in general failures such as panics
356    /// or incorrect results.
357    fn array_len(&self, arrayref: &VMArrayRef) -> Result<u32>;
358
359    ////////////////////////////////////////////////////////////////////////////
360    // Garbage Collection Methods
361
362    /// Get the total number of bytes currently allocated (live or
363    /// dead-but-not-collected) in this heap.
364    ///
365    /// This is distinct from the heap capacity.
366    fn allocated_bytes(&self) -> usize;
367
368    /// Whether a GC should be performed before the next heap growth.
369    ///
370    /// Some collectors (e.g. the copying collector) need to perform a GC before
371    /// growing the heap in certain states, to ensure that the semi-spaces remain
372    /// properly balanced.
373    ///
374    /// Defaults to `false`.
375    fn needs_gc_before_next_growth(&self) -> bool {
376        false
377    }
378
379    /// Start a new garbage collection process.
380    ///
381    /// The given `roots` are GC roots and should not be collected (nor anything
382    /// transitively reachable from them).
383    ///
384    /// Upon reclaiming an `externref`, its associated entry in the
385    /// `host_data_table` is removed.
386    ///
387    /// Callers should pass valid GC roots that belongs to this heap, and the
388    /// host data table associated with this heap's `externref`s. Failure to do
389    /// so is memory safe, but may result in general failures such as panics or
390    /// incorrect results.
391    ///
392    /// This method should panic if we are in a no-GC scope.
393    fn gc<'a, 'b>(
394        &'a mut self,
395        roots: GcRootsIter<'a>,
396        trace_state: &'a mut GcStoreTraceState<'b>,
397    ) -> Box<dyn GarbageCollection + 'a>
398    where
399        'b: 'a;
400
401    ////////////////////////////////////////////////////////////////////////////
402    // JIT-Code Interaction Methods
403
404    /// Get the pointer that will be stored in the `VMContext::gc_heap_data`
405    /// field and be accessible from JIT code via collaboration with the
406    /// corresponding `GcCompiler` trait.
407    ///
408    /// # Safety
409    ///
410    /// The returned pointer, if any, must remain valid as long as `self` is not
411    /// dropped.
412    unsafe fn vmctx_gc_heap_data(&self) -> NonNull<u8>;
413
414    ////////////////////////////////////////////////////////////////////////////
415    // Accessors for the raw bytes of the GC heap
416
417    /// Take the underlying memory storage out of this GC heap.
418    ///
419    /// # Panics
420    ///
421    /// If this GC heap is used while the memory is taken then a panic will
422    /// occur. This will also panic if the memory is already taken.
423    fn take_memory(&mut self) -> crate::vm::Memory;
424
425    /// Replace this GC heap's underlying memory storage.
426    ///
427    /// # Safety
428    ///
429    /// The `memory` must have been taken via `take_memory` and the GC heap must
430    /// not have been used at all since the memory was taken. The memory must be
431    /// the same size or larger than it was.
432    unsafe fn replace_memory(&mut self, memory: crate::vm::Memory, delta_bytes_grown: u64);
433
434    /// Get a raw `VMMemoryDefinition` for this heap's underlying memory storage.
435    ///
436    /// If/when exposing this `VMMemoryDefinition` to Wasm, it is your
437    /// responsibility to ensure that you do not do that in such a way as to
438    /// violate Rust's borrowing rules (e.g. make sure there is no active
439    /// `heap_slice_mut()` call at the same time) and that if this GC heap is
440    /// resized (and its base potentially moves) then that Wasm gets a new,
441    /// updated `VMMemoryDefinition` record.
442    fn vmmemory(&self) -> VMMemoryDefinition;
443
444    /// Get a slice of the raw bytes of the GC heap.
445    #[inline]
446    fn heap_slice(&self) -> &[u8] {
447        let vmmemory = self.vmmemory();
448        let ptr = vmmemory.base.as_ptr().cast_const();
449        let len = vmmemory.current_length();
450        unsafe { slice::from_raw_parts(ptr, len) }
451    }
452
453    /// Get a mutable slice of the raw bytes of the GC heap.
454    #[inline]
455    fn heap_slice_mut(&mut self) -> &mut [u8] {
456        let vmmemory = self.vmmemory();
457        let ptr = vmmemory.base.as_ptr();
458        let len = vmmemory.current_length();
459        unsafe { slice::from_raw_parts_mut(ptr, len) }
460    }
461
462    ////////////////////////////////////////////////////////////////////////////
463    // Provided helper methods.
464
465    /// Index into this heap and get a shared reference to the `T` that `gc_ref`
466    /// points to.
467    ///
468    /// # Errors
469    ///
470    /// Returns an error on out of bounds or if the `gc_ref` is an `i31ref`.
471    #[inline(always)]
472    fn index<T>(&self, gc_ref: &TypedGcRef<T>) -> Result<&T>
473    where
474        Self: Sized,
475        T: GcHeapObject,
476    {
477        assert!(!mem::needs_drop::<T>());
478        let gc_ref = gc_ref.as_untyped();
479        let start = gc_ref.heap_index()?.get();
480        let start = usize::try_from(start)?;
481        let len = mem::size_of::<T>();
482        let slice = match self.heap_slice().get(start..).and_then(|s| s.get(..len)) {
483            Some(slice) => slice,
484            None => bail_bug!("gc object out-of-bounds"),
485        };
486        if slice.as_ptr().addr() % mem::align_of::<T>() != 0 {
487            bail_bug!("gc object and/or heap misaligned");
488        }
489        Ok(unsafe { &*(slice.as_ptr().cast::<T>()) })
490    }
491
492    /// Index into this heap and get an exclusive reference to the `T` that
493    /// `gc_ref` points to.
494    ///
495    /// # Errors
496    ///
497    /// Returns an error on out of bounds or if the `gc_ref` is an `i31ref`.
498    #[inline(always)]
499    fn index_mut<T>(&mut self, gc_ref: &TypedGcRef<T>) -> Result<&mut T>
500    where
501        Self: Sized,
502        T: GcHeapObject,
503    {
504        assert!(!mem::needs_drop::<T>());
505        let gc_ref = gc_ref.as_untyped();
506        let start = gc_ref.heap_index()?.get();
507        let start = usize::try_from(start)?;
508        let len = mem::size_of::<T>();
509        let slice = match self
510            .heap_slice_mut()
511            .get_mut(start..)
512            .and_then(|s| s.get_mut(..len))
513        {
514            Some(slice) => slice,
515            None => bail_bug!("gc object out-of-bounds"),
516        };
517        assert!(slice.as_ptr().addr() % mem::align_of::<T>() == 0);
518        Ok(unsafe { &mut *(slice.as_mut_ptr().cast::<T>()) })
519    }
520
521    /// Get the range of bytes that the given object occupies in the heap.
522    ///
523    /// # Errors
524    ///
525    /// Returns an error on out of bounds or if the `gc_ref` is an `i31ref`.
526    fn object_range(&self, gc_ref: &VMGcRef) -> Result<Range<usize>> {
527        let start = gc_ref.heap_index()?.get();
528        let start = usize::try_from(start)?;
529        let size = self.object_size(gc_ref)?;
530        let end = match start.checked_add(size) {
531            Some(end) => end,
532            None => bail_bug!("object size overflow"),
533        };
534        Ok(start..end)
535    }
536
537    /// Get a mutable borrow of the given object's data.
538    ///
539    /// # Errors
540    ///
541    /// Returns an error on out-of-bounds accesses or if the `gc_ref` is an
542    /// `i31ref`.
543    fn gc_object_data(&self, gc_ref: &VMGcRef) -> Result<&VMGcObjectData> {
544        let range = self.object_range(gc_ref)?;
545        let data = match self.heap_slice().get(range) {
546            Some(data) => data,
547            None => bail_bug!("gc object out of bounds"),
548        };
549        Ok(data.into())
550    }
551
552    /// Get a mutable borrow of the given object's data.
553    ///
554    /// # Errors
555    ///
556    /// Returns an error on out-of-bounds accesses or if the `gc_ref` is an
557    /// `i31ref`.
558    fn gc_object_data_mut(&mut self, gc_ref: &VMGcRef) -> Result<&mut VMGcObjectData> {
559        let range = self.object_range(gc_ref)?;
560        let data = match self.heap_slice_mut().get_mut(range) {
561            Some(data) => data,
562            None => bail_bug!("gc object out of bounds"),
563        };
564        Ok(data.into())
565    }
566}
567
568/// A list of GC roots.
569///
570/// This is effectively a builder for a `GcRootsIter` that will be given to a GC
571/// heap when it is time to perform garbage collection.
572#[derive(Default)]
573pub struct GcRootsList(Vec<RawGcRoot>);
574
575// Ideally these `*mut`s would be `&mut`s and we wouldn't need as much of this
576// machinery around `GcRootsList`, `RawGcRoot`, `GcRoot`, and `GcRootIter` but
577// if we try that then we run into two different kinds of lifetime issues:
578//
579// 1. When collecting the various roots from a `&mut StoreOpaque`, we borrow
580//    from `self` to push new GC roots onto the roots list. But then we want to
581//    call helper methods like `self.for_each_global(...)`, but we can't because
582//    there are active borrows of `self` preventing it.
583//
584// 2. We want to reuse the roots list and its backing storage across GCs, rather
585//    than reallocate on every GC. But the only place for the roots list to live
586//    such that it is easily reusable across GCs is in the store itself. But the
587//    contents of the roots list (when it is non-empty, during GCs) borrow from
588//    the store, which creates self-references.
589#[derive(Clone, Copy, Debug)]
590#[cfg_attr(
591    not(feature = "gc"),
592    expect(
593        dead_code,
594        reason = "not worth it at this time to #[cfg] away these variants",
595    )
596)]
597enum RawGcRoot {
598    Stack(SendSyncPtr<u32>),
599    VMGcRef(SendSyncPtr<VMGcRef>),
600    ValRaw(SendSyncPtr<ValRaw>),
601}
602
603#[cfg(feature = "gc")]
604impl GcRootsList {
605    /// Add a GC root that is inside a Wasm stack frame to this list.
606    ///
607    /// # Safety
608    ///
609    /// The pointer must be to a valid stack-map slot on the Wasm stack and must
610    /// remain valid while registered within this `GcRootsList`.
611    #[inline]
612    pub unsafe fn add_wasm_stack_root(&mut self, ptr_to_root: SendSyncPtr<u32>) {
613        unsafe {
614            log::trace!(
615                "Adding Wasm stack root: {:#p} -> {:#p}",
616                ptr_to_root,
617                VMGcRef::from_raw_u32(*ptr_to_root.as_ref()).unwrap()
618            );
619            debug_assert!(VMGcRef::from_raw_u32(*ptr_to_root.as_ref()).is_some());
620        }
621        self.0.push(RawGcRoot::Stack(ptr_to_root));
622    }
623
624    /// Add a GC root to this list.
625    ///
626    /// # Safety
627    ///
628    /// The pointer must be to a valid `VMGcRef` and must remain valid while
629    /// registered within this `GcRootsList`.
630    #[inline]
631    pub unsafe fn add_vmgcref_root(&mut self, ptr_to_root: SendSyncPtr<VMGcRef>, why: &str) {
632        unsafe {
633            log::trace!(
634                "Adding VMGcRef root: {why}: {:#p}",
635                ptr_to_root.as_ref().unchecked_copy()
636            );
637        }
638        self.0.push(RawGcRoot::VMGcRef(ptr_to_root))
639    }
640
641    /// Add a GC root to this list.
642    ///
643    /// # Safety
644    ///
645    /// The pointer must be to a valid `ValRaw` that is a GC reference and must
646    /// remain valid while registered within this `GcRootsList`.
647    #[inline]
648    pub unsafe fn add_val_raw_root(&mut self, ptr_to_root: SendSyncPtr<ValRaw>, why: &str) {
649        unsafe {
650            log::trace!(
651                "Adding ValRaw root: {why}: {:#x}",
652                ptr_to_root.as_ref().get_anyref()
653            );
654        }
655        self.0.push(RawGcRoot::ValRaw(ptr_to_root))
656    }
657
658    /// Get an iterator over all roots in this list.
659    ///
660    /// # Safety
661    ///
662    /// Callers must ensure that all the pointers to GC roots that have been
663    /// added to this list are valid for the duration of the `'a` lifetime.
664    #[inline]
665    pub unsafe fn iter<'a>(&'a mut self) -> GcRootsIter<'a> {
666        GcRootsIter {
667            list: self,
668            index: 0,
669        }
670    }
671
672    /// Is this list empty?
673    pub fn is_empty(&self) -> bool {
674        self.0.is_empty()
675    }
676
677    /// Clear this GC roots list.
678    #[inline]
679    pub fn clear(&mut self) {
680        self.0.clear();
681    }
682}
683
684/// An iterator over all the roots in a `GcRootsList`.
685pub struct GcRootsIter<'a> {
686    list: &'a mut GcRootsList,
687    index: usize,
688}
689
690impl<'a> Iterator for GcRootsIter<'a> {
691    type Item = GcRoot<'a>;
692
693    #[inline]
694    fn next(&mut self) -> Option<Self::Item> {
695        let root = GcRoot {
696            raw: self.list.0.get(self.index).copied()?,
697            _phantom: marker::PhantomData,
698        };
699        self.index += 1;
700        Some(root)
701    }
702}
703
704/// A GC root.
705///
706/// This is, effectively, a mutable reference to a `VMGcRef`.
707///
708/// Collector implementations should update the `VMGcRef` if they move the
709/// `VMGcRef`'s referent during the course of a GC.
710#[derive(Debug)]
711pub struct GcRoot<'a> {
712    raw: RawGcRoot,
713    _phantom: marker::PhantomData<&'a mut VMGcRef>,
714}
715
716impl GcRoot<'_> {
717    /// Is this root from inside a Wasm stack frame?
718    #[inline]
719    pub fn is_on_wasm_stack(&self) -> bool {
720        matches!(self.raw, RawGcRoot::Stack(_))
721    }
722
723    /// Get this GC root.
724    ///
725    /// Does NOT run GC barriers.
726    #[inline]
727    pub fn get(&self) -> Result<VMGcRef> {
728        match self.raw {
729            RawGcRoot::VMGcRef(ptr) => Ok(unsafe { ptr::read(ptr.as_ptr()) }),
730            RawGcRoot::Stack(ptr) => unsafe {
731                let raw: u32 = ptr::read(ptr.as_ptr());
732                match VMGcRef::from_raw_u32(raw) {
733                    Some(r) => Ok(r),
734                    None => bail_bug!("stacked contained null gcref"),
735                }
736            },
737            RawGcRoot::ValRaw(ptr) => unsafe {
738                let val: ValRaw = ptr::read(ptr.as_ptr());
739                match val.get_vmgcref() {
740                    Some(r) => Ok(r),
741                    None => bail_bug!("val contained null gcref"),
742                }
743            },
744        }
745    }
746
747    /// Set this GC root.
748    ///
749    /// Does NOT run GC barriers.
750    ///
751    /// Collector implementations should use this method to update GC root
752    /// pointers after the collector moves the GC object that the root is
753    /// referencing.
754    pub fn set(&mut self, new_ref: VMGcRef) {
755        match self.raw {
756            RawGcRoot::VMGcRef(ptr) => unsafe {
757                ptr::write(ptr.as_ptr(), new_ref);
758            },
759            RawGcRoot::Stack(ptr) => unsafe {
760                ptr::write(ptr.as_ptr(), new_ref.as_raw_u32());
761            },
762            RawGcRoot::ValRaw(ptr) => unsafe {
763                let val = ValRaw::vmgcref(Some(new_ref));
764                ptr::write(ptr.as_ptr(), val);
765            },
766        }
767    }
768}
769
770/// A garbage collection process.
771///
772/// Implementations define the `collect_increment` method, and then consumers
773/// can either use
774///
775/// * `GarbageCollection::collect` for synchronous code, or
776///
777/// * `collect_async(Box<dyn GarbageCollection>)` for async code.
778///
779/// When using fuel and/or epochs, consumers can also use `collect_increment`
780/// directly and choose to abandon further execution in this GC's heap's whole
781/// store if the GC is taking too long to complete.
782pub trait GarbageCollection: Send + Sync {
783    /// Perform an incremental slice of this garbage collection process.
784    ///
785    /// Upon completion of the slice, a `GcProgress` is returned which informs
786    /// the caller whether to continue driving this GC process forward and
787    /// executing more slices (`GcProgress::Continue`) or whether the GC process
788    /// has finished (`GcProgress::Complete`).
789    ///
790    /// The mutator does *not* run in between increments. This method exists
791    /// solely to allow cooperative yielding
792    fn collect_increment(&mut self) -> Result<GcProgress>;
793
794    /// Run this GC process to completion.
795    ///
796    /// Keeps calling `collect_increment` in a loop until the GC process is
797    /// complete.
798    fn collect(&mut self) -> Result<()> {
799        loop {
800            match self.collect_increment()? {
801                GcProgress::Continue => continue,
802                GcProgress::Complete => return Ok(()),
803            }
804        }
805    }
806}
807
808/// The result of doing an incremental amount of GC.
809pub enum GcProgress {
810    /// There is still more work to do.
811    Continue,
812    /// The GC is complete.
813    Complete,
814}
815
816/// Asynchronously run the given garbage collection process to completion,
817/// cooperatively yielding back to the event loop after each increment of work.
818pub async fn collect_async(
819    mut collection: Box<dyn GarbageCollection + '_>,
820    asyncness: Asyncness,
821    yield_fn: impl AsyncFn(),
822) -> Result<()> {
823    #[cfg(not(feature = "async"))]
824    {
825        _ = yield_fn;
826    }
827
828    loop {
829        match collection.collect_increment()? {
830            GcProgress::Continue => {
831                if asyncness != Asyncness::No {
832                    #[cfg(feature = "async")]
833                    yield_fn().await
834                }
835            }
836            GcProgress::Complete => return Ok(()),
837        }
838    }
839}
840
841#[cfg(all(test, feature = "async"))]
842mod collect_async_tests {
843    use super::*;
844
845    #[test]
846    fn is_send_and_sync() {
847        fn _assert_send_sync<T: Send + Sync>(_: T) {}
848
849        fn _foo(collection: Box<dyn GarbageCollection>) {
850            _assert_send_sync(collect_async(collection, Asyncness::Yes, async || ()));
851        }
852    }
853}