From ba6016560401e492d8c2e2bcbcce411797e50275 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 19 Aug 2025 12:09:10 -0700 Subject: [PATCH 1/4] Make garbage collection an `async` function This commit is similar to #11442 and #11460 except it's applied to the garbage collection phase of Wasmtime's GC. Specifically the functions that actually perform a GC are no longer duplicated across sync, async, and maybe async versions. There's only one "always async" version and the root-level crate entrypoints for sync versions assert that async support is disabled and then use the async version. Worth noting here is that GC suffers from a preexisting issue described in #11409 where it's not sound how a `StoreOpaque` is widened to acquire a resource limiter. This commit seemingly makes the issue worse by adding a few more `unsafe` blocks, but they're all fundamentally doing the same thing as before. Fully solving this issue will require making memory/table creation an `async` function that takes the limiter as an argument. Doing this will require further refactoring/code movement so my goal is to effectively maintain the status quo, but in a slightly different location, and enable knocking out the `unsafe` in the future. In the meantime the previous `unsafe` block is "lifted higher up" so it's not quite so deep and should be easier to remove in the future. --- crates/wasmtime/src/runtime/func.rs | 4 +- crates/wasmtime/src/runtime/store.rs | 49 +++--- crates/wasmtime/src/runtime/store/async_.rs | 83 +---------- crates/wasmtime/src/runtime/store/gc.rs | 139 +++++++++--------- crates/wasmtime/src/runtime/vm/gc.rs | 11 +- .../wasmtime/src/runtime/vm/gc/gc_runtime.rs | 14 +- crates/wasmtime/src/runtime/vm/libcalls.rs | 10 +- 7 files changed, 122 insertions(+), 188 deletions(-) diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs index df1c76f04dc1..43f264ddc8c1 100644 --- a/crates/wasmtime/src/runtime/func.rs +++ b/crates/wasmtime/src/runtime/func.rs @@ -2206,11 +2206,11 @@ impl Caller<'_, T> { /// /// Same as [`Store::gc_async`](crate::Store::gc_async). #[cfg(all(feature = "async", feature = "gc"))] - pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> + pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) where T: Send + 'static, { - self.store.gc_async(why).await + self.store.gc_async(why).await; } /// Returns the remaining fuel in the store. diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs index 73a33e2a4910..00677769dba9 100644 --- a/crates/wasmtime/src/runtime/store.rs +++ b/crates/wasmtime/src/runtime/store.rs @@ -93,7 +93,8 @@ use crate::runtime::vm::mpk::ProtectionKey; use crate::runtime::vm::{ self, GcStore, Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle, Interpreter, InterpreterRef, ModuleRuntimeInfo, OnDemandInstanceAllocator, SendSyncPtr, - SignalHandler, StoreBox, StorePtr, Unwind, VMContext, VMFuncRef, VMGcRef, VMStoreContext, + SignalHandler, StoreBox, StorePtr, Unwind, VMContext, VMFuncRef, VMGcRef, VMStore, + VMStoreContext, }; use crate::trampoline::VMHostGlobalContext; use crate::{Engine, Module, Trap, Val, ValRaw, module::ModuleRegistry}; @@ -881,8 +882,7 @@ impl Store { /// This method is only available when the `gc` Cargo feature is enabled. #[cfg(feature = "gc")] pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) { - assert!(!self.inner.async_support()); - self.inner.gc(why); + StoreContextMut(&mut self.inner).gc(why) } /// Returns the amount fuel in this [`Store`]. When fuel is enabled, it must @@ -1101,7 +1101,9 @@ impl<'a, T> StoreContextMut<'a, T> { /// This method is only available when the `gc` Cargo feature is enabled. #[cfg(feature = "gc")] pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) { - self.0.gc(why); + assert!(!self.0.async_support()); + let (mut limiter, store) = self.0.resource_limiter_and_store_opaque(); + vm::assert_ready(store.gc(limiter.as_mut(), None, why.map(|e| e.bytes_needed()))); } /// Returns remaining fuel in this store. @@ -1511,7 +1513,7 @@ impl StoreOpaque { #[cfg(feature = "gc")] fn allocate_gc_store( engine: &Engine, - vmstore: NonNull, + vmstore: NonNull, pkey: Option, ) -> Result { use wasmtime_environ::packed_option::ReservedValue; @@ -1558,7 +1560,7 @@ impl StoreOpaque { #[cfg(not(feature = "gc"))] fn allocate_gc_store( _engine: &Engine, - _vmstore: NonNull, + _vmstore: NonNull, _pkey: Option, ) -> Result { bail!("cannot allocate a GC store: the `gc` feature was disabled at compile time") @@ -1656,12 +1658,7 @@ impl StoreOpaque { } #[cfg(feature = "gc")] - fn do_gc(&mut self) { - assert!( - !self.async_support(), - "must use `store.gc_async()` instead of `store.gc()` for async stores" - ); - + async fn do_gc(&mut self) { // If the GC heap hasn't been initialized, there is nothing to collect. if self.gc_store.is_none() { return; @@ -1673,8 +1670,11 @@ impl StoreOpaque { // call mutable methods on `self`. let mut roots = core::mem::take(&mut self.gc_roots_list); - self.trace_roots(&mut roots); - self.unwrap_gc_store_mut().gc(unsafe { roots.iter() }); + self.trace_roots(&mut roots).await; + let async_yield = self.async_support(); + self.unwrap_gc_store_mut() + .gc(async_yield, unsafe { roots.iter() }) + .await; // Restore the GC roots for the next GC. roots.clear(); @@ -1684,16 +1684,27 @@ impl StoreOpaque { } #[cfg(feature = "gc")] - fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList) { + async fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList) { log::trace!("Begin trace GC roots"); // We shouldn't have any leftover, stale GC roots. assert!(gc_roots_list.is_empty()); self.trace_wasm_stack_roots(gc_roots_list); + if self.async_support() { + vm::Yield::new().await; + } #[cfg(feature = "stack-switching")] - self.trace_wasm_continuation_roots(gc_roots_list); + { + self.trace_wasm_continuation_roots(gc_roots_list); + if self.async_support() { + vm::Yield::new().await; + } + } self.trace_vmctx_roots(gc_roots_list); + if self.async_support() { + vm::Yield::new().await; + } self.trace_user_roots(gc_roots_list); log::trace!("End trace GC roots") @@ -1927,7 +1938,7 @@ impl StoreOpaque { } #[inline] - pub fn traitobj(&self) -> NonNull { + pub fn traitobj(&self) -> NonNull { self.traitobj.as_raw().unwrap() } @@ -2271,7 +2282,7 @@ pub(crate) enum AllocateInstanceKind<'a> { }, } -unsafe impl vm::VMStore for StoreInner { +unsafe impl VMStore for StoreInner { #[cfg(feature = "component-model-async")] fn component_async_store( &mut self, @@ -2559,7 +2570,7 @@ impl AsStoreOpaque for StoreOpaque { } } -impl AsStoreOpaque for dyn vm::VMStore { +impl AsStoreOpaque for dyn VMStore { fn as_store_opaque(&mut self) -> &mut StoreOpaque { self } diff --git a/crates/wasmtime/src/runtime/store/async_.rs b/crates/wasmtime/src/runtime/store/async_.rs index bb2d8f93bfb5..0962bb30128f 100644 --- a/crates/wasmtime/src/runtime/store/async_.rs +++ b/crates/wasmtime/src/runtime/store/async_.rs @@ -2,6 +2,7 @@ use crate::CallHook; use crate::fiber::{self}; use crate::prelude::*; +use crate::runtime::vm::VMStore; use crate::store::{ResourceLimiterInner, StoreInner, StoreOpaque}; use crate::{Store, StoreContextMut, UpdateDeadline}; @@ -89,11 +90,11 @@ impl Store { /// /// This method is only available when the `gc` Cargo feature is enabled. #[cfg(feature = "gc")] - pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> + pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) where T: Send, { - self.inner.gc_async(why).await + StoreContextMut(&mut self.inner).gc_async(why).await } /// Configures epoch-deadline expiration to yield to the async @@ -132,11 +133,14 @@ impl<'a, T> StoreContextMut<'a, T> { /// /// This method is only available when the `gc` Cargo feature is enabled. #[cfg(feature = "gc")] - pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()> + pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) where T: Send + 'static, { - self.0.gc_async(why).await + let (mut limiter, store) = self.0.resource_limiter_and_store_opaque(); + store + .gc(limiter.as_mut(), None, why.map(|e| e.bytes_needed())) + .await; } /// Configures epoch-deadline expiration to yield to the async @@ -164,77 +168,6 @@ impl StoreInner { #[doc(hidden)] impl StoreOpaque { - /// Executes a synchronous computation `func` asynchronously on a new fiber. - /// - /// This function will convert the synchronous `func` into an asynchronous - /// future. This is done by running `func` in a fiber on a separate native - /// stack which can be suspended and resumed from. - #[cfg(feature = "gc")] - pub(crate) async fn on_fiber( - &mut self, - func: impl FnOnce(&mut Self) -> R + Send + Sync, - ) -> Result { - fiber::on_fiber(self, func).await - } - - #[cfg(feature = "gc")] - pub(super) async fn do_gc_async(&mut self) { - assert!( - self.async_support(), - "cannot use `gc_async` without enabling async support in the config", - ); - - // If the GC heap hasn't been initialized, there is nothing to collect. - if self.gc_store.is_none() { - return; - } - - log::trace!("============ Begin Async GC ==========="); - - // Take the GC roots out of `self` so we can borrow it mutably but still - // call mutable methods on `self`. - let mut roots = core::mem::take(&mut self.gc_roots_list); - - self.trace_roots_async(&mut roots).await; - self.unwrap_gc_store_mut() - .gc_async(unsafe { roots.iter() }) - .await; - - // Restore the GC roots for the next GC. - roots.clear(); - self.gc_roots_list = roots; - - log::trace!("============ End Async GC ==========="); - } - - #[inline] - #[cfg(not(feature = "gc"))] - pub async fn gc_async(&mut self) { - // Nothing to collect. - // - // Note that this is *not* a public method, this is just defined for the - // crate-internal `StoreOpaque` type. This is a convenience so that we - // don't have to `cfg` every call site. - } - - #[cfg(feature = "gc")] - async fn trace_roots_async(&mut self, gc_roots_list: &mut crate::runtime::vm::GcRootsList) { - use crate::runtime::vm::Yield; - - log::trace!("Begin trace GC roots"); - - // We shouldn't have any leftover, stale GC roots. - assert!(gc_roots_list.is_empty()); - - self.trace_wasm_stack_roots(gc_roots_list); - Yield::new().await; - self.trace_vmctx_roots(gc_roots_list); - Yield::new().await; - self.trace_user_roots(gc_roots_list); - - log::trace!("End trace GC roots") - } - /// Yields execution to the caller on out-of-gas or epoch interruption. /// /// This only works on async futures and stores, and assumes that we're diff --git a/crates/wasmtime/src/runtime/store/gc.rs b/crates/wasmtime/src/runtime/store/gc.rs index fc33cf3b9c82..093bf1709aa7 100644 --- a/crates/wasmtime/src/runtime/store/gc.rs +++ b/crates/wasmtime/src/runtime/store/gc.rs @@ -1,78 +1,77 @@ //! GC-related methods for stores. use super::*; -use crate::GcHeapOutOfMemory; use crate::runtime::vm::VMGcRef; impl StoreOpaque { - /// Collect garbage, potentially growing the GC heap. - pub(crate) fn gc(&mut self, why: Option<&GcHeapOutOfMemory<()>>) { - assert!(!self.async_support()); - unsafe { - self.maybe_async_gc(None, why.map(|oom| oom.bytes_needed())) - .expect("infallible when not async"); - } - } - /// Attempt to grow the GC heap by `bytes_needed` or, if that fails, perform /// a garbage collection. /// - /// Cooperative, async-yielding (if configured) is completely transparent. - /// - /// Note that even when this function returns `Ok(())`, it is not guaranteed + /// Note that even when this function returns it is not guaranteed /// that a GC allocation of size `bytes_needed` will succeed. Growing the GC /// heap could fail, and then performing a collection could succeed but /// might not free up enough space. Therefore, callers should not assume /// that a retried allocation will always succeed. /// - /// # Safety - /// - /// When async is enabled, it is the caller's responsibility to ensure that - /// this is called on a fiber stack. - pub(crate) unsafe fn maybe_async_gc( + /// The `root` argument passed in is considered a root for this GC operation + /// and its new value is returned as well. + pub(crate) async fn gc( &mut self, + limiter: Option<&mut StoreResourceLimiter<'_>>, root: Option, bytes_needed: Option, - ) -> Result> { + ) -> Option { let mut scope = crate::OpaqueRootScope::new(self); let store_id = scope.id(); let root = root.map(|r| scope.gc_roots_mut().push_lifo_root(store_id, r)); - if scope.async_support() { - #[cfg(feature = "async")] - scope.block_on(|scope| Box::pin(scope.grow_or_collect_gc_heap_async(bytes_needed)))?; - } else { - scope.grow_or_collect_gc_heap(bytes_needed); - } + scope.grow_or_collect_gc_heap(limiter, bytes_needed).await; - let root = match root { - None => None, - Some(r) => { - let r = r - .get_gc_ref(&scope) - .expect("still in scope") - .unchecked_copy(); - Some(scope.clone_gc_ref(&r)) - } - }; + root.map(|r| { + let r = r + .get_gc_ref(&scope) + .expect("still in scope") + .unchecked_copy(); + scope.clone_gc_ref(&r) + }) + } - Ok(root) + /// Same as [`Self::gc`], but less safe. + /// + /// FIXME(#11409) this method should not need to exist, but performing such + /// a refactoring will require making memory creation async. + async unsafe fn gc_unsafe_get_limiter( + &mut self, + root: Option, + bytes_needed: Option, + ) -> Option { + // SAFETY: this isn't safe, see #11409 + let (mut limiter, store) = + unsafe { self.traitobj().as_mut().resource_limiter_and_store_opaque() }; + store.gc(limiter.as_mut(), root, bytes_needed).await } - fn grow_or_collect_gc_heap(&mut self, bytes_needed: Option) { - assert!(!self.async_support()); + async fn grow_or_collect_gc_heap( + &mut self, + limiter: Option<&mut StoreResourceLimiter<'_>>, + bytes_needed: Option, + ) { if let Some(n) = bytes_needed { - if vm::assert_ready(self.grow_gc_heap(n)).is_ok() { + if self.grow_gc_heap(limiter, n).await.is_ok() { return; } } - self.do_gc(); + self.do_gc().await; } /// Attempt to grow the GC heap by `bytes_needed` bytes. /// /// Returns an error if growing the GC heap fails. - async fn grow_gc_heap(&mut self, bytes_needed: u64) -> Result<()> { + async fn grow_gc_heap( + &mut self, + limiter: Option<&mut StoreResourceLimiter<'_>>, + bytes_needed: u64, + ) -> Result<()> { log::trace!("Attempting to grow the GC heap by {bytes_needed} bytes"); assert!(bytes_needed > 0); @@ -115,14 +114,8 @@ impl StoreOpaque { // `VMMemoryDefinition` in the `VMStoreContext` immediately // afterwards. unsafe { - // FIXME(#11409) this is not sound to widen the borrow - let (mut limiter, _) = heap - .store - .traitobj() - .as_mut() - .resource_limiter_and_store_opaque(); heap.memory - .grow(delta_pages_for_alloc, limiter.as_mut()) + .grow(delta_pages_for_alloc, limiter) .await? .ok_or_else(|| anyhow!("failed to grow GC heap"))?; } @@ -192,7 +185,12 @@ impl StoreOpaque { Err(e) => match e.downcast::>() { Ok(oom) => { let (value, oom) = oom.take_inner(); - self.gc(Some(&oom)); + // SAFETY: FIXME(#11409) + unsafe { + vm::assert_ready( + self.gc_unsafe_get_limiter(None, Some(oom.bytes_needed())), + ); + } alloc_func(self, value) } Err(e) => Err(e), @@ -220,10 +218,22 @@ impl StoreOpaque { Err(e) => match e.downcast::>() { Ok(oom) => { let (value, oom) = oom.take_inner(); - // SAFETY: it's the caller's responsibility to ensure that + // Note it's the caller's responsibility to ensure that // this is on a fiber stack if necessary. + // + // SAFETY: FIXME(#11409) unsafe { - self.maybe_async_gc(None, Some(oom.bytes_needed()))?; + if self.async_support() { + self.block_on(|store| { + Box::pin( + store.gc_unsafe_get_limiter(None, Some(oom.bytes_needed())), + ) + })?; + } else { + vm::assert_ready( + self.gc_unsafe_get_limiter(None, Some(oom.bytes_needed())), + ); + } } alloc_func(self, value) } @@ -235,27 +245,6 @@ impl StoreOpaque { #[cfg(feature = "async")] impl StoreOpaque { - /// Asynchronously collect garbage, potentially growing the GC heap. - pub(crate) async fn gc_async(&mut self, why: Option<&GcHeapOutOfMemory<()>>) -> Result<()> { - assert!(self.async_support()); - self.on_fiber(|store| unsafe { - store.maybe_async_gc(None, why.map(|oom| oom.bytes_needed())) - }) - .await??; - Ok(()) - } - - async fn grow_or_collect_gc_heap_async(&mut self, bytes_needed: Option) { - assert!(self.async_support()); - if let Some(bytes_needed) = bytes_needed { - if self.grow_gc_heap(bytes_needed).await.is_ok() { - return; - } - } - - self.do_gc_async().await; - } - /// Attempt an allocation, if it fails due to GC OOM, then do a GC and /// retry. pub(crate) async fn retry_after_gc_async( @@ -276,7 +265,11 @@ impl StoreOpaque { Err(e) => match e.downcast::>() { Ok(oom) => { let (value, oom) = oom.take_inner(); - self.gc_async(Some(&oom)).await?; + // SAFETY: FIXME(#11409) + unsafe { + self.gc_unsafe_get_limiter(None, Some(oom.bytes_needed())) + .await; + } alloc_func(self, value) } Err(e) => Err(e), diff --git a/crates/wasmtime/src/runtime/vm/gc.rs b/crates/wasmtime/src/runtime/vm/gc.rs index 9eb361b74d67..a264e2dc42fd 100644 --- a/crates/wasmtime/src/runtime/vm/gc.rs +++ b/crates/wasmtime/src/runtime/vm/gc.rs @@ -69,17 +69,10 @@ impl GcStore { self.gc_heap.vmmemory() } - /// Perform garbage collection within this heap. - pub fn gc(&mut self, roots: GcRootsIter<'_>) { - let mut collection = self.gc_heap.gc(roots, &mut self.host_data_table); - collection.collect(); - } - /// Asynchronously perform garbage collection within this heap. - #[cfg(feature = "async")] - pub async fn gc_async(&mut self, roots: GcRootsIter<'_>) { + pub async fn gc(&mut self, async_yield: bool, roots: GcRootsIter<'_>) { let collection = self.gc_heap.gc(roots, &mut self.host_data_table); - collect_async(collection).await; + collect_async(collection, async_yield).await; } /// Get the kind of the given GC reference. diff --git a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs index 2cde32594592..bb8bb4268b11 100644 --- a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs +++ b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs @@ -789,11 +789,17 @@ pub enum GcProgress { /// Asynchronously run the given garbage collection process to completion, /// cooperatively yielding back to the event loop after each increment of work. -#[cfg(feature = "async")] -pub async fn collect_async<'a>(mut collection: Box + 'a>) { +pub async fn collect_async<'a>( + mut collection: Box + 'a>, + async_yield: bool, +) { loop { match collection.collect_increment() { - GcProgress::Continue => crate::runtime::vm::Yield::new().await, + GcProgress::Continue => { + if async_yield { + crate::runtime::vm::Yield::new().await + } + } GcProgress::Complete => return, } } @@ -808,7 +814,7 @@ mod collect_async_tests { fn _assert_send_sync(_: T) {} fn _foo<'a>(collection: Box>) { - _assert_send_sync(collect_async(collection)); + _assert_send_sync(collect_async(collection, true)); } } } diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs index f22ee2e85fe1..f69e670138d6 100644 --- a/crates/wasmtime/src/runtime/vm/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/libcalls.rs @@ -609,12 +609,10 @@ unsafe fn grow_gc_heap( ) .unwrap(); - unsafe { - store - .maybe_async_gc(None, Some(bytes_needed)) - .context("failed to grow the GC heap") - .context(crate::Trap::AllocationTooLarge)?; - } + let (mut limiter, store) = store.resource_limiter_and_store_opaque(); + block_on!(store, async |store| { + store.gc(limiter.as_mut(), None, Some(bytes_needed)).await; + })?; // JIT code relies on the memory having grown by `bytes_needed` bytes if // this libcall returns successfully, so trap if we didn't grow that much. From d553f17730f9cab0fb75747d16ebdeaf2f246aed Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 19 Aug 2025 14:07:41 -0700 Subject: [PATCH 2/4] Review comments --- crates/wasmtime/src/runtime/store/gc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasmtime/src/runtime/store/gc.rs b/crates/wasmtime/src/runtime/store/gc.rs index 093bf1709aa7..805f1628e380 100644 --- a/crates/wasmtime/src/runtime/store/gc.rs +++ b/crates/wasmtime/src/runtime/store/gc.rs @@ -45,7 +45,7 @@ impl StoreOpaque { root: Option, bytes_needed: Option, ) -> Option { - // SAFETY: this isn't safe, see #11409 + // SAFETY: this isn't sound, see #11409 let (mut limiter, store) = unsafe { self.traitobj().as_mut().resource_limiter_and_store_opaque() }; store.gc(limiter.as_mut(), root, bytes_needed).await From 8d0fb80caa9d843c210d8f4fa402833a150b3177 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 19 Aug 2025 14:11:01 -0700 Subject: [PATCH 3/4] Fix configured build --- crates/wasmtime/src/runtime/store/async_.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/wasmtime/src/runtime/store/async_.rs b/crates/wasmtime/src/runtime/store/async_.rs index 0962bb30128f..a0d089075dfe 100644 --- a/crates/wasmtime/src/runtime/store/async_.rs +++ b/crates/wasmtime/src/runtime/store/async_.rs @@ -2,6 +2,7 @@ use crate::CallHook; use crate::fiber::{self}; use crate::prelude::*; +#[cfg(feature = "gc")] use crate::runtime::vm::VMStore; use crate::store::{ResourceLimiterInner, StoreInner, StoreOpaque}; use crate::{Store, StoreContextMut, UpdateDeadline}; From 0d0ffb2554c3083578db5dea0097d9cf6abed268 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 19 Aug 2025 14:44:09 -0700 Subject: [PATCH 4/4] More configured build fixes --- crates/wasmtime/src/runtime/store.rs | 3 +++ crates/wasmtime/src/runtime/store/gc.rs | 3 +++ crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs | 1 + 3 files changed, 7 insertions(+) diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs index 00677769dba9..d7c99f8cb3ca 100644 --- a/crates/wasmtime/src/runtime/store.rs +++ b/crates/wasmtime/src/runtime/store.rs @@ -1691,17 +1691,20 @@ impl StoreOpaque { assert!(gc_roots_list.is_empty()); self.trace_wasm_stack_roots(gc_roots_list); + #[cfg(feature = "async")] if self.async_support() { vm::Yield::new().await; } #[cfg(feature = "stack-switching")] { self.trace_wasm_continuation_roots(gc_roots_list); + #[cfg(feature = "async")] if self.async_support() { vm::Yield::new().await; } } self.trace_vmctx_roots(gc_roots_list); + #[cfg(feature = "async")] if self.async_support() { vm::Yield::new().await; } diff --git a/crates/wasmtime/src/runtime/store/gc.rs b/crates/wasmtime/src/runtime/store/gc.rs index 805f1628e380..6cbc7bc095be 100644 --- a/crates/wasmtime/src/runtime/store/gc.rs +++ b/crates/wasmtime/src/runtime/store/gc.rs @@ -224,11 +224,14 @@ impl StoreOpaque { // SAFETY: FIXME(#11409) unsafe { if self.async_support() { + #[cfg(feature = "async")] self.block_on(|store| { Box::pin( store.gc_unsafe_get_limiter(None, Some(oom.bytes_needed())), ) })?; + #[cfg(not(feature = "async"))] + unreachable!(); } else { vm::assert_ready( self.gc_unsafe_get_limiter(None, Some(oom.bytes_needed())), diff --git a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs index bb8bb4268b11..4fbdadf6844b 100644 --- a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs +++ b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs @@ -797,6 +797,7 @@ pub async fn collect_async<'a>( match collection.collect_increment() { GcProgress::Continue => { if async_yield { + #[cfg(feature = "async")] crate::runtime::vm::Yield::new().await } }