Skip to content

Commit 6c440d1

Browse files
committed
Make garbage collection an async function
This commit is similar to bytecodealliance#11442 and bytecodealliance#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 bytecodealliance#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.
1 parent 71b8453 commit 6c440d1

File tree

7 files changed

+122
-188
lines changed

7 files changed

+122
-188
lines changed

crates/wasmtime/src/runtime/func.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,11 +2206,11 @@ impl<T> Caller<'_, T> {
22062206
///
22072207
/// Same as [`Store::gc_async`](crate::Store::gc_async).
22082208
#[cfg(all(feature = "async", feature = "gc"))]
2209-
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
2209+
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
22102210
where
22112211
T: Send + 'static,
22122212
{
2213-
self.store.gc_async(why).await
2213+
self.store.gc_async(why).await;
22142214
}
22152215

22162216
/// Returns the remaining fuel in the store.

crates/wasmtime/src/runtime/store.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ use crate::runtime::vm::mpk::ProtectionKey;
9393
use crate::runtime::vm::{
9494
self, GcStore, Imports, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
9595
Interpreter, InterpreterRef, ModuleRuntimeInfo, OnDemandInstanceAllocator, SendSyncPtr,
96-
SignalHandler, StoreBox, StorePtr, Unwind, VMContext, VMFuncRef, VMGcRef, VMStoreContext,
96+
SignalHandler, StoreBox, StorePtr, Unwind, VMContext, VMFuncRef, VMGcRef, VMStore,
97+
VMStoreContext,
9798
};
9899
use crate::trampoline::VMHostGlobalContext;
99100
use crate::{Engine, Module, Trap, Val, ValRaw, module::ModuleRegistry};
@@ -881,8 +882,7 @@ impl<T> Store<T> {
881882
/// This method is only available when the `gc` Cargo feature is enabled.
882883
#[cfg(feature = "gc")]
883884
pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) {
884-
assert!(!self.inner.async_support());
885-
self.inner.gc(why);
885+
StoreContextMut(&mut self.inner).gc(why)
886886
}
887887

888888
/// Returns the amount fuel in this [`Store`]. When fuel is enabled, it must
@@ -1101,7 +1101,9 @@ impl<'a, T> StoreContextMut<'a, T> {
11011101
/// This method is only available when the `gc` Cargo feature is enabled.
11021102
#[cfg(feature = "gc")]
11031103
pub fn gc(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) {
1104-
self.0.gc(why);
1104+
assert!(!self.0.async_support());
1105+
let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
1106+
vm::assert_ready(store.gc(limiter.as_mut(), None, why.map(|e| e.bytes_needed())));
11051107
}
11061108

11071109
/// Returns remaining fuel in this store.
@@ -1511,7 +1513,7 @@ impl StoreOpaque {
15111513
#[cfg(feature = "gc")]
15121514
fn allocate_gc_store(
15131515
engine: &Engine,
1514-
vmstore: NonNull<dyn vm::VMStore>,
1516+
vmstore: NonNull<dyn VMStore>,
15151517
pkey: Option<ProtectionKey>,
15161518
) -> Result<GcStore> {
15171519
use wasmtime_environ::packed_option::ReservedValue;
@@ -1558,7 +1560,7 @@ impl StoreOpaque {
15581560
#[cfg(not(feature = "gc"))]
15591561
fn allocate_gc_store(
15601562
_engine: &Engine,
1561-
_vmstore: NonNull<dyn vm::VMStore>,
1563+
_vmstore: NonNull<dyn VMStore>,
15621564
_pkey: Option<ProtectionKey>,
15631565
) -> Result<GcStore> {
15641566
bail!("cannot allocate a GC store: the `gc` feature was disabled at compile time")
@@ -1656,12 +1658,7 @@ impl StoreOpaque {
16561658
}
16571659

16581660
#[cfg(feature = "gc")]
1659-
fn do_gc(&mut self) {
1660-
assert!(
1661-
!self.async_support(),
1662-
"must use `store.gc_async()` instead of `store.gc()` for async stores"
1663-
);
1664-
1661+
async fn do_gc(&mut self) {
16651662
// If the GC heap hasn't been initialized, there is nothing to collect.
16661663
if self.gc_store.is_none() {
16671664
return;
@@ -1673,8 +1670,11 @@ impl StoreOpaque {
16731670
// call mutable methods on `self`.
16741671
let mut roots = core::mem::take(&mut self.gc_roots_list);
16751672

1676-
self.trace_roots(&mut roots);
1677-
self.unwrap_gc_store_mut().gc(unsafe { roots.iter() });
1673+
self.trace_roots(&mut roots).await;
1674+
let async_yield = self.async_support();
1675+
self.unwrap_gc_store_mut()
1676+
.gc(async_yield, unsafe { roots.iter() })
1677+
.await;
16781678

16791679
// Restore the GC roots for the next GC.
16801680
roots.clear();
@@ -1684,16 +1684,27 @@ impl StoreOpaque {
16841684
}
16851685

16861686
#[cfg(feature = "gc")]
1687-
fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList) {
1687+
async fn trace_roots(&mut self, gc_roots_list: &mut GcRootsList) {
16881688
log::trace!("Begin trace GC roots");
16891689

16901690
// We shouldn't have any leftover, stale GC roots.
16911691
assert!(gc_roots_list.is_empty());
16921692

16931693
self.trace_wasm_stack_roots(gc_roots_list);
1694+
if self.async_support() {
1695+
vm::Yield::new().await;
1696+
}
16941697
#[cfg(feature = "stack-switching")]
1695-
self.trace_wasm_continuation_roots(gc_roots_list);
1698+
{
1699+
self.trace_wasm_continuation_roots(gc_roots_list);
1700+
if self.async_support() {
1701+
vm::Yield::new().await;
1702+
}
1703+
}
16961704
self.trace_vmctx_roots(gc_roots_list);
1705+
if self.async_support() {
1706+
vm::Yield::new().await;
1707+
}
16971708
self.trace_user_roots(gc_roots_list);
16981709

16991710
log::trace!("End trace GC roots")
@@ -1927,7 +1938,7 @@ impl StoreOpaque {
19271938
}
19281939

19291940
#[inline]
1930-
pub fn traitobj(&self) -> NonNull<dyn vm::VMStore> {
1941+
pub fn traitobj(&self) -> NonNull<dyn VMStore> {
19311942
self.traitobj.as_raw().unwrap()
19321943
}
19331944

@@ -2271,7 +2282,7 @@ pub(crate) enum AllocateInstanceKind<'a> {
22712282
},
22722283
}
22732284

2274-
unsafe impl<T> vm::VMStore for StoreInner<T> {
2285+
unsafe impl<T> VMStore for StoreInner<T> {
22752286
#[cfg(feature = "component-model-async")]
22762287
fn component_async_store(
22772288
&mut self,
@@ -2559,7 +2570,7 @@ impl AsStoreOpaque for StoreOpaque {
25592570
}
25602571
}
25612572

2562-
impl AsStoreOpaque for dyn vm::VMStore {
2573+
impl AsStoreOpaque for dyn VMStore {
25632574
fn as_store_opaque(&mut self) -> &mut StoreOpaque {
25642575
self
25652576
}

crates/wasmtime/src/runtime/store/async_.rs

Lines changed: 8 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
use crate::CallHook;
33
use crate::fiber::{self};
44
use crate::prelude::*;
5+
use crate::runtime::vm::VMStore;
56
use crate::store::{ResourceLimiterInner, StoreInner, StoreOpaque};
67
use crate::{Store, StoreContextMut, UpdateDeadline};
78

@@ -89,11 +90,11 @@ impl<T> Store<T> {
8990
///
9091
/// This method is only available when the `gc` Cargo feature is enabled.
9192
#[cfg(feature = "gc")]
92-
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
93+
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
9394
where
9495
T: Send,
9596
{
96-
self.inner.gc_async(why).await
97+
StoreContextMut(&mut self.inner).gc_async(why).await
9798
}
9899

99100
/// Configures epoch-deadline expiration to yield to the async
@@ -132,11 +133,14 @@ impl<'a, T> StoreContextMut<'a, T> {
132133
///
133134
/// This method is only available when the `gc` Cargo feature is enabled.
134135
#[cfg(feature = "gc")]
135-
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>) -> Result<()>
136+
pub async fn gc_async(&mut self, why: Option<&crate::GcHeapOutOfMemory<()>>)
136137
where
137138
T: Send + 'static,
138139
{
139-
self.0.gc_async(why).await
140+
let (mut limiter, store) = self.0.resource_limiter_and_store_opaque();
141+
store
142+
.gc(limiter.as_mut(), None, why.map(|e| e.bytes_needed()))
143+
.await;
140144
}
141145

142146
/// Configures epoch-deadline expiration to yield to the async
@@ -164,77 +168,6 @@ impl<T> StoreInner<T> {
164168

165169
#[doc(hidden)]
166170
impl StoreOpaque {
167-
/// Executes a synchronous computation `func` asynchronously on a new fiber.
168-
///
169-
/// This function will convert the synchronous `func` into an asynchronous
170-
/// future. This is done by running `func` in a fiber on a separate native
171-
/// stack which can be suspended and resumed from.
172-
#[cfg(feature = "gc")]
173-
pub(crate) async fn on_fiber<R: Send + Sync>(
174-
&mut self,
175-
func: impl FnOnce(&mut Self) -> R + Send + Sync,
176-
) -> Result<R> {
177-
fiber::on_fiber(self, func).await
178-
}
179-
180-
#[cfg(feature = "gc")]
181-
pub(super) async fn do_gc_async(&mut self) {
182-
assert!(
183-
self.async_support(),
184-
"cannot use `gc_async` without enabling async support in the config",
185-
);
186-
187-
// If the GC heap hasn't been initialized, there is nothing to collect.
188-
if self.gc_store.is_none() {
189-
return;
190-
}
191-
192-
log::trace!("============ Begin Async GC ===========");
193-
194-
// Take the GC roots out of `self` so we can borrow it mutably but still
195-
// call mutable methods on `self`.
196-
let mut roots = core::mem::take(&mut self.gc_roots_list);
197-
198-
self.trace_roots_async(&mut roots).await;
199-
self.unwrap_gc_store_mut()
200-
.gc_async(unsafe { roots.iter() })
201-
.await;
202-
203-
// Restore the GC roots for the next GC.
204-
roots.clear();
205-
self.gc_roots_list = roots;
206-
207-
log::trace!("============ End Async GC ===========");
208-
}
209-
210-
#[inline]
211-
#[cfg(not(feature = "gc"))]
212-
pub async fn gc_async(&mut self) {
213-
// Nothing to collect.
214-
//
215-
// Note that this is *not* a public method, this is just defined for the
216-
// crate-internal `StoreOpaque` type. This is a convenience so that we
217-
// don't have to `cfg` every call site.
218-
}
219-
220-
#[cfg(feature = "gc")]
221-
async fn trace_roots_async(&mut self, gc_roots_list: &mut crate::runtime::vm::GcRootsList) {
222-
use crate::runtime::vm::Yield;
223-
224-
log::trace!("Begin trace GC roots");
225-
226-
// We shouldn't have any leftover, stale GC roots.
227-
assert!(gc_roots_list.is_empty());
228-
229-
self.trace_wasm_stack_roots(gc_roots_list);
230-
Yield::new().await;
231-
self.trace_vmctx_roots(gc_roots_list);
232-
Yield::new().await;
233-
self.trace_user_roots(gc_roots_list);
234-
235-
log::trace!("End trace GC roots")
236-
}
237-
238171
/// Yields execution to the caller on out-of-gas or epoch interruption.
239172
///
240173
/// This only works on async futures and stores, and assumes that we're

0 commit comments

Comments
 (0)