Skip to main content

wasmtime/runtime/store/
gc.rs

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