Skip to main content

wasmtime/runtime/store/
gc.rs

1//! GC-related methods for stores.
2
3use crate::RootSet;
4use crate::error::{Context, ensure};
5use crate::hash_map::HashMap;
6use crate::module::ModuleRegistry;
7use crate::store::{
8    Asyncness, AutoAssertNoGc, InstanceId, StoreOpaque, StoreResourceLimiter, yield_now,
9};
10use crate::type_registry::RegisteredType;
11use crate::vm::{
12    self, Backtrace, Frame, GcRootsList, GcStore, InstanceAllocationRequest, NopHasher,
13    SendSyncPtr, StoreGcHostAllocTypes, TraceInfo, VMGcRef,
14};
15use crate::{
16    ExnRef, GcHeapOutOfMemory, Result, Rooted, Store, StoreContextMut, ThrownException, bail,
17};
18use core::fmt;
19use core::mem::ManuallyDrop;
20use core::num::NonZeroU32;
21use core::ops::{Deref, DerefMut};
22use core::ptr::NonNull;
23use wasmtime_environ::DefinedTagIndex;
24use wasmtime_environ::packed_option::ReservedValue;
25
26#[derive(Default)]
27pub(crate) struct StoreGcData {
28    gc_roots: RootSet,
29    gc_roots_list: GcRootsList,
30    // Types for which the embedder has created an allocator for.
31    gc_host_alloc_types: StoreGcHostAllocTypes,
32    /// Pending exception, if any. This is also a GC root, because it
33    /// needs to be rooted somewhere between the time that a pending
34    /// exception is set and the time that the handling code takes the
35    /// exception object. We use this rooting strategy rather than a
36    /// root in an `Err` branch of a `Result` on the host side because
37    /// it is less error-prone with respect to rooting behavior. See
38    /// `throw()`, `take_pending_exception()`,
39    /// `peek_pending_exception()`, `has_pending_exception()`, and
40    /// `catch()`.
41    ///
42    /// Also note that the underlying reference here is a `VMExnRef`, a
43    /// refinement of `VMGcRef`, but rooting APIs right now make it difficult to
44    /// work with that directly so this is stored as `VMGcRef` instead.
45    pending_exception: Option<VMGcRef>,
46
47    /// A store-local cache of engine-level subtype checks.
48    ///
49    /// Dynamic subtype checks (e.g. for `ref.cast` instructions) consult the
50    /// engine's type registry, which is protected by a read-write lock.
51    /// Testing locally shows that parallel readers take a significant
52    /// performance hit when using this lock (as measured with `wasmtime
53    /// serve`). Thus this cache is intended to serve as a fast path where the
54    /// lock need not be hit at all.
55    ///
56    /// Note that the size of this cache is currently bounded to a fixed size.
57    /// Once this cache is full then all future consultations which aren't in
58    /// the cache go upstream to the engine itself (slow). In the future
59    /// this'll either be removed entirely (see #13484) or will have some sort
60    /// of LRU-like behavior.
61    ///
62    /// FIXME(#13484) this field is a temporary workaround for a "true
63    /// solution" where supertypes are stored inline in a compiled-code-visible
64    /// location which means that a libcall isn't needed at all (nor
65    /// synchronization) to determine subtype/supertype relationships.
66    subtype_check_cache: HashMap<u64, bool, NopHasher>,
67}
68
69impl<T> Store<T> {
70    /// Perform garbage collection.
71    ///
72    /// Note that it is not required to actively call this function. GC will
73    /// automatically happen according to various internal heuristics. This is
74    /// provided if fine-grained control over the GC is desired.
75    ///
76    /// If you are calling this method after an attempted allocation failed, you
77    /// may pass in the [`GcHeapOutOfMemory`][crate::GcHeapOutOfMemory] error.
78    /// When you do so, this method will attempt to create enough space in the
79    /// GC heap for that allocation, so that it will succeed on the next
80    /// attempt.
81    ///
82    /// # Errors
83    ///
84    /// This method will fail if an [async limiter is
85    /// configured](Store::limiter_async) in which case [`Store::gc_async`] must
86    /// be used instead.
87    pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> {
88        StoreContextMut(&mut self.inner).gc(why)
89    }
90
91    /// Returns the current capacity of the GC heap in bytes, or 0 if the GC
92    /// heap has not been initialized yet.
93    pub fn gc_heap_capacity(&self) -> usize {
94        self.inner.gc_heap_capacity()
95    }
96
97    /// Set an exception as the currently pending exception, and
98    /// return an error that propagates the throw.
99    ///
100    /// This method takes an exception object and stores it in the
101    /// `Store` as the currently pending exception. This is a special
102    /// rooted slot that holds the exception as long as it is
103    /// propagating. This method then returns a `ThrownException`
104    /// error, which is a special type that indicates a pending
105    /// exception exists. When this type propagates as an error
106    /// returned from a Wasm-to-host call, the pending exception is
107    /// thrown within the Wasm context, and either caught or
108    /// propagated further to the host-to-Wasm call boundary. If an
109    /// exception is thrown out of Wasm (or across Wasm from a
110    /// hostcall) back to the host-to-Wasm call boundary, *that*
111    /// invocation returns a `ThrownException`, and the pending
112    /// exception slot is again set. In other words, the
113    /// `ThrownException` error type should propagate upward exactly
114    /// and only when a pending exception is set.
115    ///
116    /// To take the pending exception, use [`Self::take_pending_exception`].
117    ///
118    /// This method is parameterized over `R` for convenience, but
119    /// will always return an `Err`.
120    ///
121    /// If there is already a pending exception in the store then the previous
122    /// one will be overwritten.
123    ///
124    /// # Errors
125    ///
126    /// This method will return an error if `exception` is unrooted. Otherwise
127    /// this method will always return `ThrownException`.
128    pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
129        self.inner.throw_impl(exception)
130    }
131
132    /// Take the currently pending exception, if any, and return it,
133    /// removing it from the "pending exception" slot.
134    ///
135    /// If there is no pending exception, returns `None`.
136    ///
137    /// Note: the returned exception is a LIFO root (see
138    /// [`crate::Rooted`]), rooted in the current handle scope. Take
139    /// care to ensure that it is re-rooted or otherwise does not
140    /// escape this scope! It is usually best to allow an exception
141    /// object to be rooted in the store's "pending exception" slot
142    /// until the final consumer has taken it, rather than root it and
143    /// pass it up the callstack in some other way.
144    ///
145    /// This method is useful to implement ad-hoc exception plumbing
146    /// in various ways, but for the most idiomatic handling, see
147    /// [`StoreContextMut::throw`].
148    pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
149        self.inner.take_pending_exception_rooted()
150    }
151}
152
153impl<'a, T> StoreContextMut<'a, T> {
154    /// Perform garbage collection.
155    ///
156    /// Same as [`Store::gc`].
157    pub fn gc(&mut self, why: Option<&GcHeapOutOfMemory<()>>) -> Result<()> {
158        let (mut limiter, store) = self.0.validate_sync_resource_limiter_and_store_opaque()?;
159        vm::assert_ready(store.gc(
160            limiter.as_mut(),
161            None,
162            why.map(|e| e.bytes_needed()),
163            Asyncness::No,
164        ))?;
165        Ok(())
166    }
167
168    /// Set an exception as the currently pending exception, and
169    /// return an error that propagates the throw.
170    ///
171    /// See [`Store::throw`] for more details.
172    #[cfg(feature = "gc")]
173    pub fn throw<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
174        self.0.inner.throw_impl(exception)
175    }
176
177    /// Take the currently pending exception, if any, and return it,
178    /// removing it from the "pending exception" slot.
179    ///
180    /// See [`Store::take_pending_exception`] for more details.
181    #[cfg(feature = "gc")]
182    pub fn take_pending_exception(&mut self) -> Option<Rooted<ExnRef>> {
183        self.0.inner.take_pending_exception_rooted()
184    }
185}
186
187#[derive(Debug)]
188struct GcHeapGrowthFailed;
189
190impl fmt::Display for GcHeapGrowthFailed {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        f.write_str("GC heap growth failed")
193    }
194}
195
196impl core::error::Error for GcHeapGrowthFailed {}
197
198impl StoreOpaque {
199    /// Perform any growth or GC needed to allocate `bytes_needed` bytes.
200    ///
201    /// Note that even when this function returns it is not guaranteed
202    /// that a GC allocation of size `bytes_needed` will succeed. Growing the GC
203    /// heap could fail, and then performing a collection could succeed but
204    /// might not free up enough space. Therefore, callers should not assume
205    /// that a retried allocation will always succeed.
206    ///
207    /// The `root` argument passed in is considered a root for this GC operation
208    /// and its new value is returned as well.
209    pub(crate) async fn gc(
210        &mut self,
211        limiter: Option<&mut StoreResourceLimiter<'_>>,
212        root: Option<VMGcRef>,
213        bytes_needed: Option<u64>,
214        asyncness: Asyncness,
215    ) -> Result<Option<VMGcRef>> {
216        let mut scope = crate::OpaqueRootScope::new(self);
217        scope.trim_gc_liveness_flags(true);
218        let store_id = scope.id();
219        let root = root.map(|r| scope.gc_roots_mut().push_lifo_root(store_id, r));
220
221        scope
222            .collect_and_maybe_grow_gc_heap(limiter, bytes_needed, asyncness)
223            .await?;
224
225        Ok(root.map(|r| {
226            let r = r
227                .get_gc_ref(&scope)
228                .expect("still in scope")
229                .unchecked_copy();
230            scope.clone_gc_ref(&r)
231        }))
232    }
233
234    // This lives on the Store because it must simultaneously borrow
235    // `gc_store` and `gc_roots`, and is invoked from other modules to
236    // which we do not want to expose the raw fields for piecewise
237    // borrows.
238    pub(crate) fn trim_gc_liveness_flags(&mut self, eager: bool) {
239        if let Some(gc_store) = self.gc_store.as_mut() {
240            self.gc_data.gc_roots.trim_liveness_flags(gc_store, eager);
241        }
242    }
243
244    /// Helper invoked as part of `gc`, whose purpose is to GC and
245    /// maybe grow for a pending allocation of a given size.
246    async fn collect_and_maybe_grow_gc_heap(
247        &mut self,
248        limiter: Option<&mut StoreResourceLimiter<'_>>,
249        bytes_needed: Option<u64>,
250        asyncness: Asyncness,
251    ) -> Result<()> {
252        log::trace!("collect_and_maybe_grow_gc_heap(bytes_needed = {bytes_needed:#x?})");
253        self.do_gc(asyncness).await?;
254        if let Some(n) = bytes_needed
255            && n > u64::try_from(self.gc_heap_capacity())?.saturating_sub(
256                self.gc_store.as_ref().map_or(0, |gc| {
257                    u64::try_from(gc.last_post_gc_allocated_bytes.unwrap_or(0)).unwrap()
258                }),
259            )
260        {
261            if let Err(e) = self.grow_gc_heap(limiter, n, asyncness).await {
262                if e.is::<GcHeapGrowthFailed>() {
263                    log::trace!("ignoring GC heap growth failure: {e}");
264                } else {
265                    return Err(e);
266                }
267            }
268        }
269        Ok(())
270    }
271
272    /// Attempt to grow the GC heap by `bytes_needed` bytes.
273    ///
274    /// Returns an error if growing the GC heap fails.
275    pub(crate) async fn grow_gc_heap(
276        &mut self,
277        limiter: Option<&mut StoreResourceLimiter<'_>>,
278        bytes_needed: u64,
279        asyncness: Asyncness,
280    ) -> Result<()> {
281        log::trace!("Attempting to grow the GC heap by at least {bytes_needed:#x} bytes");
282
283        if bytes_needed == 0 {
284            return Ok(());
285        }
286
287        // If the GC heap needs a collection before growth (e.g. the copying
288        // collector's active space is the second half), do a GC first.
289        if self
290            .gc_store
291            .as_ref()
292            .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth())
293        {
294            self.do_gc(asyncness).await?;
295            debug_assert!(
296                !self
297                    .gc_store
298                    .as_ref()
299                    .map_or(false, |gc| gc.gc_heap.needs_gc_before_next_growth()),
300                "needs_gc_before_next_growth should return false after a GC"
301            );
302        }
303
304        let page_size = self.engine().tunables().gc_heap_memory_type().page_size();
305
306        // Take the GC heap's underlying memory out of the GC heap, attempt to
307        // grow it, then replace it.
308        let mut heap = TakenGcHeap::new(self);
309
310        let current_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
311        let current_size_in_pages = current_size_in_bytes / page_size;
312
313        // Aim to double the heap size, amortizing the cost of growth.
314        let doubled_size_in_pages = current_size_in_pages.saturating_mul(2);
315        assert!(doubled_size_in_pages >= current_size_in_pages);
316        let delta_pages_for_doubling = doubled_size_in_pages - current_size_in_pages;
317
318        // When doubling our size, saturate at the maximum memory size in pages.
319        //
320        // TODO: we should consult the instance allocator for its configured
321        // maximum memory size, if any, rather than assuming the index
322        // type's maximum size.
323        let max_size_in_bytes = 1 << 32;
324        let max_size_in_pages = max_size_in_bytes / page_size;
325        let delta_to_max_size_in_pages = max_size_in_pages - current_size_in_pages;
326        let delta_pages_for_alloc = delta_pages_for_doubling.min(delta_to_max_size_in_pages);
327
328        // But always make sure we are attempting to grow at least as many pages
329        // as needed by the requested allocation. This must happen *after* the
330        // max-size saturation, so that if we are at the max already, we do not
331        // succeed in growing by zero delta pages, and then return successfully
332        // to our caller, who would be assuming that there is now capacity for
333        // their allocation.
334        let pages_needed = bytes_needed.div_ceil(page_size);
335        assert!(pages_needed > 0);
336        let delta_pages_for_alloc = delta_pages_for_alloc.max(pages_needed);
337        assert!(delta_pages_for_alloc > 0);
338
339        // Safety: we pair growing the GC heap with updating its associated
340        // `VMMemoryDefinition` in the `VMStoreContext` immediately
341        // afterwards.
342        unsafe {
343            heap.memory
344                .grow(delta_pages_for_alloc, limiter)
345                .await
346                .context(GcHeapGrowthFailed)?
347                .ok_or(GcHeapGrowthFailed)?;
348        }
349        *heap.store.vm_store_context.gc_heap.get_mut() = heap.memory.vmmemory();
350
351        let new_size_in_bytes = u64::try_from(heap.memory.byte_size())?;
352        assert!(new_size_in_bytes > current_size_in_bytes);
353        heap.delta_bytes_grown = new_size_in_bytes - current_size_in_bytes;
354        let delta_bytes_for_alloc = delta_pages_for_alloc.checked_mul(page_size).unwrap();
355        assert!(
356            heap.delta_bytes_grown >= delta_bytes_for_alloc,
357            "{} should be greater than or equal to {delta_bytes_for_alloc}",
358            heap.delta_bytes_grown,
359        );
360        log::trace!(
361            "  -> grew GC heap by {:#x} bytes: new size is {new_size_in_bytes:#x} bytes",
362            heap.delta_bytes_grown
363        );
364        return Ok(());
365
366        struct TakenGcHeap<'a> {
367            store: &'a mut StoreOpaque,
368            memory: ManuallyDrop<vm::Memory>,
369            delta_bytes_grown: u64,
370        }
371
372        impl<'a> TakenGcHeap<'a> {
373            fn new(store: &'a mut StoreOpaque) -> TakenGcHeap<'a> {
374                TakenGcHeap {
375                    memory: ManuallyDrop::new(store.unwrap_gc_store_mut().gc_heap.take_memory()),
376                    store,
377                    delta_bytes_grown: 0,
378                }
379            }
380        }
381
382        impl Drop for TakenGcHeap<'_> {
383            fn drop(&mut self) {
384                // SAFETY: this `Drop` guard ensures that this has exclusive
385                // ownership of fields and is thus safe to take `self.memory`.
386                // Additionally for `replace_memory` the memory was previously
387                // taken when this was created so it should be safe to place
388                // back inside the GC heap.
389                unsafe {
390                    self.store.unwrap_gc_store_mut().gc_heap.replace_memory(
391                        ManuallyDrop::take(&mut self.memory),
392                        self.delta_bytes_grown,
393                    );
394                }
395            }
396        }
397    }
398
399    fn replace_gc_zeal_alloc_counter(
400        &mut self,
401        new_value: Option<NonZeroU32>,
402    ) -> Option<NonZeroU32> {
403        if let Some(gc_store) = &mut self.gc_store {
404            gc_store.replace_gc_zeal_alloc_counter(new_value)
405        } else {
406            None
407        }
408    }
409
410    /// Attempt an allocation, if it fails due to GC OOM, apply the
411    /// grow-or-collect heuristic and retry.
412    ///
413    /// The heuristic is:
414    /// - If the last post-collection heap usage is less than half the current
415    ///   capacity, collect first, then retry. If that still fails, grow and
416    ///   retry one final time.
417    /// - Otherwise, grow first and retry.
418    pub(crate) async fn retry_after_gc_async<T, U>(
419        &mut self,
420        mut limiter: Option<&mut StoreResourceLimiter<'_>>,
421        value: T,
422        asyncness: Asyncness,
423        alloc_func: impl Fn(&mut Self, T) -> Result<U>,
424    ) -> Result<U>
425    where
426        T: Send + Sync + 'static,
427    {
428        self.ensure_gc_store(limiter.as_deref_mut()).await?;
429
430        match alloc_func(self, value) {
431            Ok(x) => Ok(x),
432            Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
433                Ok(oom) => {
434                    log::trace!("Got GC heap OOM: {oom}");
435
436                    let (value, oom) = oom.take_inner();
437                    let bytes_needed = oom.bytes_needed();
438
439                    let mut store = WithoutGcZealAllocCounter::new(self);
440
441                    let gc_heap_capacity = store
442                        .gc_store
443                        .as_ref()
444                        .map_or(0, |gc_store| gc_store.gc_heap_capacity());
445                    let last_gc_heap_usage = store.gc_store.as_ref().map_or(0, |gc_store| {
446                        gc_store.last_post_gc_allocated_bytes.unwrap_or(0)
447                    });
448
449                    if should_collect_first(bytes_needed, gc_heap_capacity, last_gc_heap_usage) {
450                        log::trace!(
451                            "Collecting first, then retrying; growing GC heap if collecting didn't \
452                             free up enough space, then retrying again"
453                        );
454                        store
455                            .gc(limiter.as_deref_mut(), None, None, asyncness)
456                            .await?;
457
458                        match alloc_func(&mut store, value) {
459                            Ok(x) => Ok(x),
460                            Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
461                                Ok(oom2) => {
462                                    // Collection wasn't enough; grow and try
463                                    // one final time.
464                                    let (value, _) = oom2.take_inner();
465                                    // Ignore error; we'll get one from
466                                    // `alloc_func` below if growth failed and
467                                    // failure to grow was fatal.
468                                    let _ =
469                                        store.grow_gc_heap(limiter, bytes_needed, asyncness).await;
470
471                                    alloc_func(&mut store, value)
472                                }
473                                Err(e) => Err(e),
474                            },
475                        }
476                    } else {
477                        log::trace!(
478                            "Grow GC heap first, collecting if growth failed, then retrying"
479                        );
480
481                        if let Err(e) = store
482                            .grow_gc_heap(limiter.as_deref_mut(), bytes_needed.max(1), asyncness)
483                            .await
484                        {
485                            log::trace!("growing GC heap failed: {e}");
486                            store.gc(limiter, None, None, asyncness).await?;
487                        }
488
489                        alloc_func(&mut store, value)
490                    }
491                }
492                Err(e) => Err(e),
493            },
494        }
495    }
496
497    /// Set a pending exception.
498    ///
499    /// The `exnref` is cloned internally and held on this store to be fetched
500    /// later by an unwind. This method does *not* set up an unwind request on
501    /// the TLS call state; that must be done separately.
502    ///
503    /// GC barriers are not required by the caller of this function.
504    pub(crate) fn set_pending_exception(&mut self, exnref: &VMGcRef) -> crate::Error {
505        debug_assert!(exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
506        let gc_store = self.gc_store.as_mut().unwrap();
507        match gc_store.write_gc_ref(&mut self.gc_data.pending_exception, Some(exnref)) {
508            Ok(()) => ThrownException.into(),
509            Err(e) => e,
510        }
511    }
512
513    /// Takes the pending exception from this store, if any, and exposes it to
514    /// WebAssembly, returning the raw representation.
515    pub(crate) fn expose_pending_exception_to_wasm(&mut self) -> Option<NonZeroU32> {
516        let exnref = self.gc_data.pending_exception.take()?;
517        let gc_store = self.unwrap_gc_store_mut();
518        debug_assert!(exnref.is_exnref(&*gc_store.gc_heap));
519        Some(gc_store.expose_gc_ref_to_wasm(exnref).unwrap())
520    }
521
522    /// Takes the pending exception of the store, yielding ownership of its
523    /// reference to the `Rooted` that's returned.
524    fn take_pending_exception_rooted(&mut self) -> Option<Rooted<ExnRef>> {
525        let vmexnref = self.gc_data.pending_exception.take()?;
526        debug_assert!(vmexnref.is_exnref(&*self.unwrap_gc_store().gc_heap));
527        let mut nogc = AutoAssertNoGc::new(self);
528        Some(Rooted::new(&mut nogc, vmexnref))
529    }
530
531    /// Returns the (instance,tag) pair that the pending exception in this
532    /// store, if any, references.
533    pub(crate) fn pending_exception_tag_and_instance(
534        &mut self,
535    ) -> Option<(InstanceId, DefinedTagIndex)> {
536        let pending_exnref = self.gc_data.pending_exception.as_ref()?.unchecked_copy();
537        debug_assert!(pending_exnref.is_exnref(&*self.unwrap_gc_store_mut().gc_heap));
538        let mut store = AutoAssertNoGc::new(self);
539
540        // Note that if the GC heap is corrupt this will return an error, and in
541        // such as situation we return `None` here pretending that there's no
542        // pending exception. This defers the GC heap corruption to get detected
543        // later. This method is primarily called right now to determine if
544        // there's a handler for an exception, and by returning `None` here this
545        // turns into just any old embedder error.
546        pending_exnref.into_exnref_unchecked().tag(&mut store).ok()
547    }
548
549    /// Get an owned rooted reference to the pending exception,
550    /// without taking it off the store.
551    #[cfg(feature = "debug")]
552    pub(crate) fn pending_exception_owned_rooted(
553        &mut self,
554    ) -> Result<Option<crate::OwnedRooted<ExnRef>>, crate::OutOfMemory> {
555        let pending = match &self.gc_data.pending_exception {
556            Some(r) => r,
557            None => return Ok(None),
558        };
559        let cloned = self.gc_store.as_mut().unwrap().clone_gc_ref(pending);
560        let mut nogc = AutoAssertNoGc::new(self);
561        Ok(Some(crate::OwnedRooted::new(&mut nogc, cloned)?))
562    }
563
564    /// Stores `exception` within the store to later get thrown.
565    ///
566    /// Delegates to `self.set_pending_exception` after accessing the internal
567    /// exception pointer.
568    fn throw_impl<R>(&mut self, exception: Rooted<ExnRef>) -> Result<R> {
569        let exception = exception.try_gc_ref(self)?.unchecked_copy();
570        Err(self.set_pending_exception(&exception))
571    }
572
573    /// Helper method to require that a `GcStore` was previously allocated for
574    /// this store, failing if it has not yet been allocated.
575    ///
576    /// Note that this should only be used in a context where allocation of a
577    /// `GcStore` is sure to have already happened prior, otherwise this may
578    /// return a confusing error to embedders which is a bug in Wasmtime.
579    ///
580    /// Some situations where it's safe to call this method:
581    ///
582    /// * There's already a non-null and non-i31 `VMGcRef` in scope. By existing
583    ///   this shows proof that the `GcStore` was previously allocated.
584    /// * During instantiation and instance's `needs_gc_heap` flag will be
585    ///   handled and instantiation will automatically create a GC store.
586    #[inline]
587    pub(crate) fn require_gc_store(&self) -> Result<&GcStore> {
588        match &self.gc_store {
589            Some(gc_store) => Ok(gc_store),
590            None => bail!("GC heap not initialized yet"),
591        }
592    }
593
594    /// Same as [`Self::require_gc_store`], but mutable.
595    #[inline]
596    pub(crate) fn require_gc_store_mut(&mut self) -> Result<&mut GcStore> {
597        match &mut self.gc_store {
598            Some(gc_store) => Ok(gc_store),
599            None => bail!("GC heap not initialized yet"),
600        }
601    }
602
603    /// Returns the current capacity of the GC heap in bytes, or 0 if the GC
604    /// heap has not been initialized yet.
605    pub(crate) fn gc_heap_capacity(&self) -> usize {
606        match self.gc_store.as_ref() {
607            Some(gc_store) => gc_store.gc_heap_capacity(),
608            None => 0,
609        }
610    }
611
612    async fn do_gc(&mut self, asyncness: Asyncness) -> Result<()> {
613        // If the GC heap hasn't been initialized, there is nothing to collect.
614        if self.gc_store.is_none() {
615            return Ok(());
616        }
617
618        if log::log_enabled!(log::Level::Trace) {
619            let gc_store = self.gc_store.as_ref().unwrap();
620            let capacity = gc_store.gc_heap_capacity();
621            let live_set_size = gc_store.last_post_gc_allocated_bytes.unwrap_or(0);
622            let utilization = live_set_size as f64 / capacity as f64 * 100.0;
623            log::trace!(
624                "============ Begin GC ===========\n\
625                 \t          GC heap capacity = {capacity:#010x} bytes\n\
626                 \tlast post-GC live-set size = {live_set_size:#010x} bytes\n\
627                 \t       GC heap utilization = {utilization:.02}%",
628            );
629        }
630
631        // Take the GC roots out of `self` so we can borrow it mutably but still
632        // call mutable methods on `self`.
633        let mut roots = core::mem::take(&mut self.gc_data.gc_roots_list);
634
635        self.trace_roots(&mut roots, asyncness).await;
636        self.gc_store
637            .as_mut()
638            .unwrap()
639            .gc(
640                asyncness,
641                unsafe { roots.iter() },
642                &self.modules,
643                &self.gc_data.gc_host_alloc_types,
644                // TODO: Once `Config` has an optional `AsyncFn` field for
645                // yielding to the current async runtime
646                // (e.g. `tokio::task::yield_now`), use that if set; otherwise
647                // fall back to the runtime-agnostic code.
648                yield_now,
649            )
650            .await?;
651
652        // Restore the GC roots for the next GC.
653        roots.clear();
654        self.gc_data.gc_roots_list = roots;
655
656        if log::log_enabled!(log::Level::Trace) {
657            let gc_store = self.gc_store.as_ref().unwrap();
658            let capacity = gc_store.gc_heap_capacity();
659            let live_set_size = gc_store.last_post_gc_allocated_bytes.unwrap_or(0);
660            let utilization = live_set_size as f64 / capacity as f64 * 100.0;
661            log::trace!(
662                "============ End GC ===========\n\
663                 \t     GC heap capacity = {capacity:#010x} bytes\n\
664                 \tpost-GC live-set size = {live_set_size:#010x} bytes\n\
665                 \t  GC heap utilization = {utilization:.02}%",
666            );
667        }
668        Ok(())
669    }
670
671    async fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList, asyncness: Asyncness) {
672        log::trace!("Begin trace GC roots");
673
674        // We shouldn't have any leftover, stale GC roots.
675        assert!(gc_roots_list.is_empty());
676
677        self.trace_wasm_stack_roots(gc_roots_list);
678        if asyncness != Asyncness::No {
679            self.yield_now().await;
680        }
681
682        #[cfg(feature = "stack-switching")]
683        {
684            self.trace_wasm_continuation_roots(gc_roots_list);
685            if asyncness != Asyncness::No {
686                self.yield_now().await;
687            }
688        }
689
690        self.trace_vmctx_roots(gc_roots_list);
691        if asyncness != Asyncness::No {
692            self.yield_now().await;
693        }
694
695        self.trace_instance_roots(gc_roots_list);
696        if asyncness != Asyncness::No {
697            self.yield_now().await;
698        }
699
700        self.trace_user_roots(gc_roots_list);
701        if asyncness != Asyncness::No {
702            self.yield_now().await;
703        }
704
705        self.trace_pending_exception_roots(gc_roots_list);
706
707        log::trace!("End trace GC roots")
708    }
709
710    pub(crate) fn trace_wasm_stack_frame(
711        modules: &ModuleRegistry,
712        gc_roots_list: &mut GcRootsList,
713        frame: Frame,
714    ) {
715        let pc = frame.pc();
716        debug_assert!(pc != 0, "we should always get a valid PC for Wasm frames");
717
718        let fp = frame.fp() as *mut usize;
719        debug_assert!(
720            !fp.is_null(),
721            "we should always get a valid frame pointer for Wasm frames"
722        );
723
724        let (store_code, offset) = modules
725            .store_code_by_pc(pc)
726            .expect("should have store code for Wasm frame");
727        let offset = u32::try_from(offset).unwrap();
728
729        let stack_map =
730            wasmtime_environ::StackMap::lookup(offset, store_code.code_memory().stack_map_data());
731
732        if let Some(stack_map) = stack_map {
733            log::trace!(
734                "We have a stack map that maps {} bytes in this Wasm frame",
735                stack_map.frame_size()
736            );
737
738            let sp = unsafe { stack_map.sp(fp) };
739            for stack_slot in unsafe { stack_map.live_gc_refs(sp) } {
740                unsafe {
741                    Self::trace_wasm_stack_slot(gc_roots_list, stack_slot);
742                }
743            }
744        }
745
746        #[cfg(feature = "debug")]
747        if let Some(frame_table) = store_code.code_memory().frame_table() {
748            for stack_slot in crate::debug::gc_refs_in_frame(frame_table, offset, fp) {
749                unsafe {
750                    Self::trace_wasm_stack_slot(gc_roots_list, stack_slot);
751                }
752            }
753        }
754    }
755
756    unsafe fn trace_wasm_stack_slot(gc_roots_list: &mut GcRootsList, stack_slot: *mut u32) {
757        let raw: u32 = unsafe { core::ptr::read(stack_slot) };
758        log::trace!("Stack slot @ {stack_slot:p} = {raw:#x}");
759
760        let gc_ref = vm::VMGcRef::from_raw_u32(raw);
761        if gc_ref.is_some() {
762            unsafe {
763                gc_roots_list
764                    .add_wasm_stack_root(SendSyncPtr::new(NonNull::new(stack_slot).unwrap()));
765            }
766        }
767    }
768
769    fn trace_wasm_stack_roots(&mut self, gc_roots_list: &mut GcRootsList) {
770        log::trace!("Begin trace GC roots :: Wasm stack");
771
772        Backtrace::trace(self, |frame| {
773            Self::trace_wasm_stack_frame(self.modules(), gc_roots_list, frame);
774            core::ops::ControlFlow::Continue(())
775        });
776
777        #[cfg(feature = "component-model-async")]
778        if self.concurrency_support() {
779            let unwind = self.unwinder();
780            let StoreOpaque {
781                modules,
782                store_data,
783                ..
784            } = self;
785            store_data
786                .components
787                .task_state_mut()
788                .concurrent_state_mut()
789                .trace_fiber_roots(modules, unwind, gc_roots_list);
790        }
791
792        log::trace!("End trace GC roots :: Wasm stack");
793    }
794
795    #[cfg(feature = "stack-switching")]
796    fn trace_wasm_continuation_roots(&mut self, gc_roots_list: &mut GcRootsList) {
797        use crate::vm::VMStackState;
798
799        log::trace!("Begin trace GC roots :: continuations");
800
801        for continuation in &self.continuations {
802            let state = continuation.common_stack_information.state;
803
804            // FIXME(frank-emrich) In general, it is not enough to just trace
805            // through the stacks of continuations; we also need to look through
806            // their `cont.bind` arguments. However, we don't currently have
807            // enough RTTI information to check if any of the values in the
808            // buffers used by `cont.bind` are GC values. As a workaround, note
809            // that we currently disallow cont.bind-ing GC values altogether.
810            // This way, it is okay not to check them here.
811            match state {
812                VMStackState::Suspended => {
813                    Backtrace::trace_suspended_continuation(self, continuation.deref(), |frame| {
814                        Self::trace_wasm_stack_frame(self.modules(), gc_roots_list, frame);
815                        core::ops::ControlFlow::Continue(())
816                    });
817                }
818                VMStackState::Running => {
819                    // Handled by `trace_wasm_stack_roots`.
820                }
821                VMStackState::Parent => {
822                    // We don't know whether our child is suspended or running, but in
823                    // either case things should be handled correctly when traversing
824                    // further along in the chain, nothing required at this point.
825                }
826                VMStackState::Fresh | VMStackState::Returned => {
827                    // Fresh/Returned continuations have no gc values on their stack.
828                }
829            }
830        }
831
832        log::trace!("End trace GC roots :: continuations");
833    }
834
835    fn trace_vmctx_roots(&mut self, gc_roots_list: &mut GcRootsList) {
836        log::trace!("Begin trace GC roots :: vmctx");
837        self.for_each_global(|store, global| global.trace_root(store, gc_roots_list));
838        self.for_each_table(|store, table| table.trace_roots(store, gc_roots_list));
839        log::trace!("End trace GC roots :: vmctx");
840    }
841
842    fn trace_instance_roots(&mut self, gc_roots_list: &mut GcRootsList) {
843        log::trace!("Begin trace GC roots :: instance");
844        for (_id, instance) in &mut self.instances {
845            // SAFETY: the instance's GC roots will remain valid for the
846            // duration of this GC cycle.
847            unsafe {
848                instance
849                    .handle
850                    .get_mut()
851                    .trace_element_segment_roots(gc_roots_list);
852            }
853        }
854        log::trace!("End trace GC roots :: instance");
855    }
856
857    fn trace_user_roots(&mut self, gc_roots_list: &mut GcRootsList) {
858        log::trace!("Begin trace GC roots :: user");
859        self.gc_data.gc_roots.trace_roots(gc_roots_list);
860        log::trace!("End trace GC roots :: user");
861    }
862
863    fn trace_pending_exception_roots(&mut self, gc_roots_list: &mut GcRootsList) {
864        log::trace!("Begin trace GC roots :: pending exception");
865        if let Some(pending_exception) = self.gc_data.pending_exception.as_mut() {
866            unsafe {
867                gc_roots_list.add_vmgcref_root(pending_exception.into(), "Pending exception");
868            }
869        }
870        log::trace!("End trace GC roots :: pending exception");
871    }
872
873    /// Insert a host-allocated GC type into this store.
874    ///
875    /// This makes it suitable for the embedder to allocate instances of this
876    /// type in this store, and we don't have to worry about the type being
877    /// reclaimed (since it is possible that none of the Wasm modules in this
878    /// store are holding it alive).
879    pub(crate) fn insert_gc_host_alloc_type(&mut self, ty: RegisteredType) {
880        let trace_info = ty.layout().map(TraceInfo::new);
881        self.gc_data
882            .gc_host_alloc_types
883            .insert(ty.index(), (ty, trace_info));
884    }
885
886    /// Performs a lazy allocation of the `GcStore` within this store, returning
887    /// the previous allocation if it's already present.
888    ///
889    /// This method will, if necessary, allocate a new `GcStore` -- linear
890    /// memory and all. This is a blocking operation due to
891    /// `ResourceLimiterAsync` which means that this should only be executed
892    /// in a fiber context at this time.
893    #[inline]
894    pub(crate) async fn ensure_gc_store(
895        &mut self,
896        limiter: Option<&mut StoreResourceLimiter<'_>>,
897    ) -> Result<&mut GcStore> {
898        if self.gc_store.is_some() {
899            return Ok(self.gc_store.as_mut().unwrap());
900        }
901        self.allocate_gc_store(limiter).await
902    }
903
904    #[inline(never)]
905    async fn allocate_gc_store(
906        &mut self,
907        limiter: Option<&mut StoreResourceLimiter<'_>>,
908    ) -> Result<&mut GcStore> {
909        log::trace!("allocating GC heap for store {:?}", self.id());
910
911        assert!(self.gc_store.is_none());
912        assert_eq!(
913            self.vm_store_context.gc_heap.get_mut().base.as_non_null(),
914            NonNull::dangling(),
915        );
916        assert_eq!(self.vm_store_context.gc_heap.get_mut().current_length(), 0);
917
918        let engine = self.engine();
919        let mem_ty = engine.tunables().gc_heap_memory_type();
920        ensure!(
921            engine.features().gc_types(),
922            "cannot allocate a GC store when GC is disabled at configuration time"
923        );
924        let gc_runtime = engine
925            .gc_runtime()
926            .context("no GC runtime: GC disabled at compile time or configuration time")?;
927
928        // First, allocate the memory that will be our GC heap's storage.
929        let mut request = InstanceAllocationRequest {
930            id: InstanceId::reserved_value(),
931            runtime_info: engine.empty_module_runtime_info(),
932            imports: vm::Imports::default(),
933            store: self,
934            limiter,
935        };
936
937        let (mem_alloc_index, mem) = engine
938            .allocator()
939            .allocate_memory(
940                &mut request,
941                &mem_ty,
942                None,
943                wasmtime_environ::MemoryKind::GcHeap,
944            )
945            .await?;
946
947        // Then, allocate the actual GC heap, passing in that memory
948        // storage.
949        let (index, mut heap) =
950            match engine
951                .allocator()
952                .allocate_gc_heap(engine, &**gc_runtime, mem_alloc_index)
953            {
954                Ok(pair) => pair,
955                Err(e) => unsafe {
956                    engine
957                        .allocator()
958                        .deallocate_memory(None, mem_alloc_index, mem);
959                    return Err(e);
960                },
961            };
962        heap.attach(mem);
963
964        let gc_store = GcStore::new(index, heap, engine.tunables().gc_zeal_alloc_counter);
965        *self.vm_store_context.gc_heap.get_mut() = gc_store.vmmemory_definition();
966        Ok(self.gc_store.insert(gc_store))
967    }
968
969    /// Tests whether there is a pending exception.
970    pub fn has_pending_exception(&self) -> bool {
971        self.gc_data.pending_exception.is_some()
972    }
973
974    #[inline]
975    pub(crate) fn gc_roots(&self) -> &RootSet {
976        &self.gc_data.gc_roots
977    }
978
979    #[inline]
980    pub(crate) fn gc_roots_mut(&mut self) -> &mut RootSet {
981        &mut self.gc_data.gc_roots
982    }
983
984    #[inline]
985    pub(crate) fn enter_gc_lifo_scope(&self) -> usize {
986        self.gc_data.gc_roots.enter_lifo_scope()
987    }
988
989    #[inline]
990    pub(crate) fn exit_gc_lifo_scope(&mut self, scope: usize) {
991        self.gc_data
992            .gc_roots
993            .exit_lifo_scope(self.gc_store.as_mut(), scope);
994    }
995
996    /// Is type `sub` a subtype of `sup`?
997    ///
998    /// Equivalent to `self.engine().signatures().is_subtype(sub, sup)` but
999    /// caches results store-locally to avoid contention on the engine's type
1000    /// registry lock. See the documentation of the `subtype_check_cache` field
1001    /// for details.
1002    pub(crate) fn is_subtype_cached(
1003        &mut self,
1004        sub: wasmtime_environ::VMSharedTypeIndex,
1005        sup: wasmtime_environ::VMSharedTypeIndex,
1006    ) -> bool {
1007        const MAX_SIZE: usize = 1 << 16; // 64k entries
1008
1009        let key = (u64::from(sub.as_u32()) << 32) | u64::from(sup.as_u32());
1010        let engine_answer = || self.engine.signatures().is_subtype(sub, sup);
1011        if self.gc_data.subtype_check_cache.len() < MAX_SIZE {
1012            *self
1013                .gc_data
1014                .subtype_check_cache
1015                .entry(key)
1016                .or_insert_with(engine_answer)
1017        } else {
1018            self.gc_data
1019                .subtype_check_cache
1020                .get(&key)
1021                .copied()
1022                .unwrap_or_else(engine_answer)
1023        }
1024    }
1025}
1026
1027/// RAII type to temporarily disable the GC zeal allocation counter.
1028struct WithoutGcZealAllocCounter<'a> {
1029    store: &'a mut StoreOpaque,
1030    counter: Option<NonZeroU32>,
1031}
1032
1033impl Deref for WithoutGcZealAllocCounter<'_> {
1034    type Target = StoreOpaque;
1035
1036    fn deref(&self) -> &Self::Target {
1037        &self.store
1038    }
1039}
1040
1041impl DerefMut for WithoutGcZealAllocCounter<'_> {
1042    fn deref_mut(&mut self) -> &mut Self::Target {
1043        &mut self.store
1044    }
1045}
1046
1047impl Drop for WithoutGcZealAllocCounter<'_> {
1048    fn drop(&mut self) {
1049        self.store.replace_gc_zeal_alloc_counter(self.counter);
1050    }
1051}
1052
1053impl<'a> WithoutGcZealAllocCounter<'a> {
1054    pub fn new(store: &'a mut StoreOpaque) -> Self {
1055        let counter = store.replace_gc_zeal_alloc_counter(None);
1056        WithoutGcZealAllocCounter { store, counter }
1057    }
1058}
1059
1060/// Given that we've hit a `GcHeapOutOfMemory` error, should we try freeing up
1061/// space by collecting first or by growing the GC heap first?
1062///
1063/// * `bytes_needed`: the number of bytes the mutator wants to allocate
1064///
1065/// * `gc_heap_capacity`: The current size of the GC heap.
1066///
1067/// * `last_gc_heap_usage`: The precise GC heap usage after the last collection.
1068#[track_caller]
1069fn should_collect_first(
1070    bytes_needed: u64,
1071    gc_heap_capacity: usize,
1072    last_gc_heap_usage: usize,
1073) -> bool {
1074    debug_assert!(last_gc_heap_usage <= gc_heap_capacity);
1075
1076    // If we haven't allocated the GC heap yet, there's nothing to collect.
1077    //
1078    // Make sure to grow in this scenario even when the GC zeal infrastructure
1079    // passes `bytes_needed = 0`. This way our retry-after-gc logic doesn't
1080    // auto-fail on its second attempt, which would be bad because it doesn't
1081    // necessarily retry more than once.
1082    if gc_heap_capacity == 0 {
1083        return false;
1084    }
1085
1086    // The GC zeal infrastructure will use `bytes_needed = 0` to trigger extra
1087    // collections.
1088    if bytes_needed == 0 {
1089        return true;
1090    }
1091
1092    let Ok(bytes_needed) = usize::try_from(bytes_needed) else {
1093        // No point wasting time on collection if we will never be able to
1094        // satisfy the allocation.
1095        return false;
1096    };
1097
1098    if bytes_needed > isize::MAX.cast_unsigned() {
1099        // Similarly, no allocation can be larger than `isize::MAX` in Rust (or
1100        // LLVM), so don't bother wasting time on collection if we will never be
1101        // able to satisfy the allocation.
1102        return false;
1103    }
1104
1105    let Some(predicted_usage) = last_gc_heap_usage.checked_add(bytes_needed) else {
1106        // If we can't represent our predicted usage as a `usize`, we won't be
1107        // able to grow the GC heap to that size, so try collecting first to
1108        // free up space.
1109        return true;
1110    };
1111
1112    // Common case: to balance collection frequency (and its time overhead) with
1113    // GC heap growth (and its space overhead), only prefer growing first if the
1114    // predicted GC heap utilization is greater than half the GC heap's
1115    // capacity.
1116    predicted_usage < gc_heap_capacity / 2
1117}
1118
1119#[cfg(test)]
1120mod tests {
1121    use super::should_collect_first;
1122    use crate::{AsContextMut, Config, Engine, ExternRef, Result, Store};
1123
1124    #[test]
1125    fn test_should_collect_first() {
1126        // No GC heap yet special case.
1127        for bytes_needed in 0..256 {
1128            assert_eq!(should_collect_first(bytes_needed, 0, 0), false);
1129        }
1130
1131        // GC zeal special case.
1132        for cap in 1..256 {
1133            for usage in 0..=cap {
1134                assert_eq!(should_collect_first(0, cap, usage), true);
1135            }
1136        }
1137
1138        let max_alloc_usize = isize::MAX.cast_unsigned();
1139        let max_alloc_u64 = u64::try_from(max_alloc_usize).unwrap();
1140
1141        // Allocation size larger than `isize::MAX` --> will never succeed, do
1142        // not bother collecting.
1143        assert_eq!(
1144            should_collect_first(max_alloc_u64 + 1, max_alloc_usize, 0),
1145            false,
1146        );
1147
1148        // Predicted usage overflow --> growth will likely fail, collect first.
1149        assert_eq!(should_collect_first(1, usize::MAX, usize::MAX), true);
1150
1151        // Common case: predicted usage is low --> we likely have more than
1152        // enough space already, so collect first.
1153        assert_eq!(should_collect_first(16, 1024, 64), true);
1154
1155        // Common case: predicted usage is high --> plausible we may not have
1156        // enough space, and we want to amortize the cost of collections, so
1157        // grow first.
1158        assert_eq!(should_collect_first(16, 1024, 512), false);
1159    }
1160
1161    #[test]
1162    fn gc_heap_initial_size() -> Result<()> {
1163        let mut config = Config::new();
1164        config.gc_heap_initial_size(1 << 20);
1165        let engine = Engine::new(&config)?;
1166        let mut store = Store::new(&engine, ());
1167        ExternRef::new(&mut store, 1)?;
1168
1169        let gc_store = store.as_context_mut().0.unwrap_gc_store();
1170        assert_eq!(gc_store.gc_heap_capacity(), 1 << 20);
1171        Ok(())
1172    }
1173}