Skip to content

Commit e1f50aa

Browse files
authored
Make table/memory creation async functions (#11470)
* Make core instance allocation an `async` function This commit is a step in preparation for #11430, notably core instance allocation, or `StoreOpaque::allocate_instance` is now an `async fn`. This function does not actually use the `async`-ness just yet so it's a noop from that point of view, but this propagates outwards to enough locations that I wanted to split this off to make future changes more digestable. Notably some creation functions here such as making an `Instance`, `Table`, or `Memory` are refactored internally to use this new `async` function. Annotations of `assert_ready` or `one_poll` are used as appropriate as well. For reference this commit was benchmarked with our `instantiation.rs` benchmark in the pooling allocator and shows no changes relative to the original baseline from before-`async`-PRs. * Make table/memory creation `async` functions This commit is a large-ish refactor which is made possible by the many previous refactorings to internals w.r.t. async-in-Wasmtime. The end goal of this change is that table and memory allocation are both `async` functions. Achieving this, however, required some refactoring to enable it to work: * To work with `Send` neither function can close over `dyn VMStore`. This required changing their `Option<&mut dyn VMStore>` arugment to `Option<&mut StoreResourceLimiter<'_>>` * Somehow a `StoreResourceLimiter` needed to be acquired from an `InstanceAllocationRequest`. Previously the store was stored here as an unsafe raw pointer, but I've refactored this now so `InstanceAllocationRequest` directly stores `&StoreOpaque` and `Option<&mut StoreResourceLimiter>` meaning it's trivial to acquire them. This additionally means no more `unsafe` access of the store during instance allocation (yay!). * Now-redundant fields of `InstanceAllocationRequest` were removed since they can be safely inferred from `&StoreOpaque`. For example passing around `&Tunables` is now all gone. * Methods upwards from table/memory allocation to the `InstanceAllocator` trait needed to be made `async`. This includes new `#[async_trait]` methods for example. * `StoreOpaque::ensure_gc_store` is now an `async` function. This internally carries a new `unsafe` block carried over from before with the raw point passed around in `InstanceAllocationRequest`. A future PR will delete this `unsafe` block, it's just temporary. I attempted a few times to split this PR up into separate commits but everything is relatively intertwined here so this is the smallest "atomic" unit I could manage to land these changes and refactorings. * Shuffle `async-trait` dep * Fix configured build
1 parent ad8d55a commit e1f50aa

File tree

22 files changed

+313
-497
lines changed

22 files changed

+313
-497
lines changed

crates/wasmtime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ cache = ["dep:wasmtime-cache", "std"]
196196
# `async fn` and calling functions asynchronously.
197197
async = [
198198
"dep:wasmtime-fiber",
199-
"dep:async-trait",
200199
"wasmtime-component-macro?/async",
201200
"runtime",
202201
]
@@ -261,6 +260,7 @@ runtime = [
261260
"dep:windows-sys",
262261
"pulley-interpreter/interp",
263262
"dep:wasmtime-unwinder",
263+
"dep:async-trait",
264264
]
265265

266266
# Enable support for garbage collection-related things.

crates/wasmtime/src/runtime/component/instance.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ impl<'a> Instantiator<'a> {
634634
}
635635
}
636636

637-
fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
637+
async fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
638638
let env_component = self.component.env_component();
639639

640640
// Before all initializers are processed configure all destructors for
@@ -714,7 +714,7 @@ impl<'a> Instantiator<'a> {
714714
// if required.
715715

716716
let i = unsafe {
717-
crate::Instance::new_started_impl(store, module, imports.as_ref())?
717+
crate::Instance::new_started(store, module, imports.as_ref()).await?
718718
};
719719
self.instance_mut(store.0).push_instance_id(i.id());
720720
}
@@ -991,37 +991,26 @@ impl<T: 'static> InstancePre<T> {
991991
!store.as_context().async_support(),
992992
"must use async instantiation when async support is enabled"
993993
);
994-
self.instantiate_impl(store)
994+
vm::assert_ready(self._instantiate(store))
995995
}
996996
/// Performs the instantiation process into the store specified.
997997
///
998998
/// Exactly like [`Self::instantiate`] except for use on async stores.
999999
//
10001000
// TODO: needs more docs
10011001
#[cfg(feature = "async")]
1002-
pub async fn instantiate_async(
1003-
&self,
1004-
mut store: impl AsContextMut<Data = T>,
1005-
) -> Result<Instance>
1006-
where
1007-
T: Send,
1008-
{
1009-
let mut store = store.as_context_mut();
1010-
assert!(
1011-
store.0.async_support(),
1012-
"must use sync instantiation when async support is disabled"
1013-
);
1014-
store.on_fiber(|store| self.instantiate_impl(store)).await?
1002+
pub async fn instantiate_async(&self, store: impl AsContextMut<Data = T>) -> Result<Instance> {
1003+
self._instantiate(store).await
10151004
}
10161005

1017-
fn instantiate_impl(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
1006+
async fn _instantiate(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
10181007
let mut store = store.as_context_mut();
10191008
store
10201009
.engine()
10211010
.allocator()
10221011
.increment_component_instance_count()?;
10231012
let mut instantiator = Instantiator::new(&self.component, store.0, &self.imports);
1024-
instantiator.run(&mut store).map_err(|e| {
1013+
instantiator.run(&mut store).await.map_err(|e| {
10251014
store
10261015
.engine()
10271016
.allocator()

crates/wasmtime/src/runtime/externals/table.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ impl Table {
9494
/// # }
9595
/// ```
9696
pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> {
97-
Table::_new(store.as_context_mut().0, ty, init)
97+
vm::one_poll(Table::_new(store.as_context_mut().0, ty, init))
98+
.expect("must use `new_async` when async resource limiters are in use")
9899
}
99100

100101
/// Async variant of [`Table::new`]. You must use this variant with
@@ -111,18 +112,11 @@ impl Table {
111112
ty: TableType,
112113
init: Ref,
113114
) -> Result<Table> {
114-
let mut store = store.as_context_mut();
115-
assert!(
116-
store.0.async_support(),
117-
"cannot use `new_async` without enabling async support on the config"
118-
);
119-
store
120-
.on_fiber(|store| Table::_new(store.0, ty, init))
121-
.await?
115+
Table::_new(store.as_context_mut().0, ty, init).await
122116
}
123117

124-
fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> {
125-
let table = generate_table_export(store, &ty)?;
118+
async fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> {
119+
let table = generate_table_export(store, &ty).await?;
126120
table._fill(store, 0, init, ty.minimum())?;
127121
Ok(table)
128122
}

crates/wasmtime/src/runtime/instance.rs

Lines changed: 32 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ impl Instance {
117117
// Note that the unsafety here should be satisfied by the call to
118118
// `typecheck_externs` above which satisfies the condition that all
119119
// the imports are valid for this module.
120-
unsafe { Instance::new_started(&mut store, module, imports.as_ref()) }
120+
assert!(!store.0.async_support());
121+
vm::assert_ready(unsafe { Instance::new_started(&mut store, module, imports.as_ref()) })
121122
}
122123

123124
/// Same as [`Instance::new`], except for usage in [asynchronous stores].
@@ -200,7 +201,7 @@ impl Instance {
200201
let mut store = store.as_context_mut();
201202
let imports = Instance::typecheck_externs(store.0, module, imports)?;
202203
// See `new` for notes on this unsafety
203-
unsafe { Instance::new_started_async(&mut store, module, imports.as_ref()).await }
204+
unsafe { Instance::new_started(&mut store, module, imports.as_ref()).await }
204205
}
205206

206207
fn typecheck_externs(
@@ -242,62 +243,31 @@ impl Instance {
242243
/// Internal function to create an instance and run the start function.
243244
///
244245
/// This function's unsafety is the same as `Instance::new_raw`.
245-
pub(crate) unsafe fn new_started<T>(
246-
store: &mut StoreContextMut<'_, T>,
247-
module: &Module,
248-
imports: Imports<'_>,
249-
) -> Result<Instance> {
250-
assert!(
251-
!store.0.async_support(),
252-
"must use async instantiation when async support is enabled",
253-
);
254-
255-
// SAFETY: the safety contract of `new_started_impl` is the same as this
256-
// function.
257-
unsafe { Self::new_started_impl(store, module, imports) }
258-
}
259-
260-
/// Internal function to create an instance and run the start function.
261-
///
262-
/// ONLY CALL THIS IF YOU HAVE ALREADY CHECKED FOR ASYNCNESS AND HANDLED
263-
/// THE FIBER NONSENSE
264-
pub(crate) unsafe fn new_started_impl<T>(
246+
pub(crate) async unsafe fn new_started<T>(
265247
store: &mut StoreContextMut<'_, T>,
266248
module: &Module,
267249
imports: Imports<'_>,
268250
) -> Result<Instance> {
269251
// SAFETY: the safety contract of `new_raw` is the same as this
270252
// function.
271-
let (instance, start) = unsafe { Instance::new_raw(store.0, module, imports)? };
253+
let (instance, start) = unsafe { Instance::new_raw(store.0, module, imports).await? };
272254
if let Some(start) = start {
273-
instance.start_raw(store, start)?;
255+
if store.0.async_support() {
256+
#[cfg(feature = "async")]
257+
{
258+
store
259+
.on_fiber(|store| instance.start_raw(store, start))
260+
.await??;
261+
}
262+
#[cfg(not(feature = "async"))]
263+
unreachable!();
264+
} else {
265+
instance.start_raw(store, start)?;
266+
}
274267
}
275268
Ok(instance)
276269
}
277270

278-
/// Internal function to create an instance and run the start function.
279-
///
280-
/// This function's unsafety is the same as `Instance::new_raw`.
281-
#[cfg(feature = "async")]
282-
async unsafe fn new_started_async<T>(
283-
store: &mut StoreContextMut<'_, T>,
284-
module: &Module,
285-
imports: Imports<'_>,
286-
) -> Result<Instance> {
287-
assert!(
288-
store.0.async_support(),
289-
"must use sync instantiation when async support is disabled",
290-
);
291-
292-
store
293-
.on_fiber(|store| {
294-
// SAFETY: the unsafe contract of `new_started_impl` is the same
295-
// as this function.
296-
unsafe { Self::new_started_impl(store, module, imports) }
297-
})
298-
.await?
299-
}
300-
301271
/// Internal function to create an instance which doesn't have its `start`
302272
/// function run yet.
303273
///
@@ -313,7 +283,7 @@ impl Instance {
313283
/// This method is unsafe because it does not type-check the `imports`
314284
/// provided. The `imports` provided must be suitable for the module
315285
/// provided as well.
316-
unsafe fn new_raw(
286+
async unsafe fn new_raw(
317287
store: &mut StoreOpaque,
318288
module: &Module,
319289
imports: Imports<'_>,
@@ -325,7 +295,7 @@ impl Instance {
325295

326296
// Allocate the GC heap, if necessary.
327297
if module.env_module().needs_gc_heap {
328-
store.ensure_gc_store()?;
298+
store.ensure_gc_store().await?;
329299
}
330300

331301
let compiled_module = module.compiled_module();
@@ -341,11 +311,13 @@ impl Instance {
341311
// SAFETY: this module, by construction, was already validated within
342312
// the store.
343313
let id = unsafe {
344-
store.allocate_instance(
345-
AllocateInstanceKind::Module(module_id),
346-
&ModuleRuntimeInfo::Module(module.clone()),
347-
imports,
348-
)?
314+
store
315+
.allocate_instance(
316+
AllocateInstanceKind::Module(module_id),
317+
&ModuleRuntimeInfo::Module(module.clone()),
318+
imports,
319+
)
320+
.await?
349321
};
350322

351323
// Additionally, before we start doing fallible instantiation, we
@@ -377,24 +349,7 @@ impl Instance {
377349
.features()
378350
.contains(WasmFeatures::BULK_MEMORY);
379351

380-
if store.async_support() {
381-
#[cfg(feature = "async")]
382-
store.block_on(|store| {
383-
let module = compiled_module.module().clone();
384-
Box::pin(
385-
async move { vm::initialize_instance(store, id, &module, bulk_memory).await },
386-
)
387-
})??;
388-
#[cfg(not(feature = "async"))]
389-
unreachable!();
390-
} else {
391-
vm::assert_ready(vm::initialize_instance(
392-
store,
393-
id,
394-
compiled_module.module(),
395-
bulk_memory,
396-
))?;
397-
}
352+
vm::initialize_instance(store, id, compiled_module.module(), bulk_memory).await?;
398353

399354
Ok((instance, compiled_module.module().start_func))
400355
}
@@ -905,7 +860,10 @@ impl<T: 'static> InstancePre<T> {
905860
// This unsafety should be handled by the type-checking performed by the
906861
// constructor of `InstancePre` to assert that all the imports we're passing
907862
// in match the module we're instantiating.
908-
unsafe { Instance::new_started(&mut store, &self.module, imports.as_ref()) }
863+
assert!(!store.0.async_support());
864+
vm::assert_ready(unsafe {
865+
Instance::new_started(&mut store, &self.module, imports.as_ref())
866+
})
909867
}
910868

911869
/// Creates a new instance, running the start function asynchronously
@@ -935,7 +893,7 @@ impl<T: 'static> InstancePre<T> {
935893
// This unsafety should be handled by the type-checking performed by the
936894
// constructor of `InstancePre` to assert that all the imports we're passing
937895
// in match the module we're instantiating.
938-
unsafe { Instance::new_started_async(&mut store, &self.module, imports.as_ref()).await }
896+
unsafe { Instance::new_started(&mut store, &self.module, imports.as_ref()).await }
939897
}
940898
}
941899

crates/wasmtime/src/runtime/memory.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ impl Memory {
260260
/// # }
261261
/// ```
262262
pub fn new(mut store: impl AsContextMut, ty: MemoryType) -> Result<Memory> {
263-
Self::_new(store.as_context_mut().0, ty)
263+
vm::one_poll(Self::_new(store.as_context_mut().0, ty))
264+
.expect("must use `new_async` when async resource limiters are in use")
264265
}
265266

266267
/// Async variant of [`Memory::new`]. You must use this variant with
@@ -273,17 +274,12 @@ impl Memory {
273274
/// [`Store`](`crate::Store`).
274275
#[cfg(feature = "async")]
275276
pub async fn new_async(mut store: impl AsContextMut, ty: MemoryType) -> Result<Memory> {
276-
let mut store = store.as_context_mut();
277-
assert!(
278-
store.0.async_support(),
279-
"cannot use `new_async` without enabling async support on the config"
280-
);
281-
store.on_fiber(|store| Self::_new(store.0, ty)).await?
277+
Self::_new(store.as_context_mut().0, ty).await
282278
}
283279

284280
/// Helper function for attaching the memory to a "frankenstein" instance
285-
fn _new(store: &mut StoreOpaque, ty: MemoryType) -> Result<Memory> {
286-
generate_memory_export(store, &ty, None)
281+
async fn _new(store: &mut StoreOpaque, ty: MemoryType) -> Result<Memory> {
282+
generate_memory_export(store, &ty, None).await
287283
}
288284

289285
/// Returns the underlying type of this memory.
@@ -1005,7 +1001,10 @@ impl SharedMemory {
10051001
/// Construct a single-memory instance to provide a way to import
10061002
/// [`SharedMemory`] into other modules.
10071003
pub(crate) fn vmimport(&self, store: &mut StoreOpaque) -> crate::runtime::vm::VMMemoryImport {
1008-
generate_memory_export(store, &self.ty(), Some(&self.vm))
1004+
// Note `vm::assert_ready` shouldn't panic here because this isn't
1005+
// actually allocating any new memory so resource limiting shouldn't
1006+
// kick in.
1007+
vm::assert_ready(generate_memory_export(store, &self.ty(), Some(&self.vm)))
10091008
.unwrap()
10101009
.vmimport(store)
10111010
}

0 commit comments

Comments
 (0)