Skip to main content

wasmtime/runtime/store/
gc.rs

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