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