Skip to main content

wasmtime/runtime/store/
gc.rs

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