Skip to main content

wasmtime/runtime/store/
gc.rs

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