Skip to content

Commit cfc2cea

Browse files
committed
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.
1 parent 683d4c1 commit cfc2cea

File tree

15 files changed

+214
-366
lines changed

15 files changed

+214
-366
lines changed

crates/wasmtime/src/runtime/instance.rs

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ impl Instance {
295295

296296
// Allocate the GC heap, if necessary.
297297
if module.env_module().needs_gc_heap {
298-
store.ensure_gc_store()?;
298+
store.ensure_gc_store().await?;
299299
}
300300

301301
let compiled_module = module.compiled_module();
@@ -349,24 +349,7 @@ impl Instance {
349349
.features()
350350
.contains(WasmFeatures::BULK_MEMORY);
351351

352-
if store.async_support() {
353-
#[cfg(feature = "async")]
354-
store.block_on(|store| {
355-
let module = compiled_module.module().clone();
356-
Box::pin(
357-
async move { vm::initialize_instance(store, id, &module, bulk_memory).await },
358-
)
359-
})??;
360-
#[cfg(not(feature = "async"))]
361-
unreachable!();
362-
} else {
363-
vm::assert_ready(vm::initialize_instance(
364-
store,
365-
id,
366-
compiled_module.module(),
367-
bulk_memory,
368-
))?;
369-
}
352+
vm::initialize_instance(store, id, compiled_module.module(), bulk_memory).await?;
370353

371354
Ok((instance, compiled_module.module().start_func))
372355
}

crates/wasmtime/src/runtime/store.rs

Lines changed: 52 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ 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, VMStore,
97-
VMStoreContext,
96+
SignalHandler, StoreBox, Unwind, VMContext, VMFuncRef, VMGcRef, VMStore, VMStoreContext,
9897
};
9998
use crate::trampoline::VMHostGlobalContext;
10099
use crate::{Engine, Module, Trap, Val, ValRaw, module::ModuleRegistry};
@@ -470,6 +469,20 @@ pub struct StoreOpaque {
470469
executor: Executor,
471470
}
472471

472+
/// Self-pointer to `StoreInner<T>` from within a `StoreOpaque` which is chiefly
473+
/// used to copy into instances during instantiation.
474+
///
475+
/// FIXME: ideally this type would get deleted and Wasmtime's reliance on it
476+
/// would go away.
477+
struct StorePtr(Option<NonNull<dyn VMStore>>);
478+
479+
// We can't make `VMStore: Send + Sync` because that requires making all of
480+
// Wastime's internals generic over the `Store`'s `T`. So instead, we take care
481+
// in the whole VM layer to only use the `VMStore` in ways that are `Send`- and
482+
// `Sync`-safe and we have to have these unsafe impls.
483+
unsafe impl Send for StorePtr {}
484+
unsafe impl Sync for StorePtr {}
485+
473486
/// Executor state within `StoreOpaque`.
474487
///
475488
/// Effectively stores Pulley interpreter state and handles conditional support
@@ -646,7 +659,7 @@ impl<T> Store<T> {
646659
fuel_reserve: 0,
647660
fuel_yield_interval: None,
648661
store_data,
649-
traitobj: StorePtr::empty(),
662+
traitobj: StorePtr(None),
650663
default_caller_vmctx: SendSyncPtr::new(NonNull::dangling()),
651664
hostcall_val_storage: Vec::new(),
652665
wasm_val_raw_storage: Vec::new(),
@@ -670,7 +683,7 @@ impl<T> Store<T> {
670683
data: ManuallyDrop::new(data),
671684
});
672685

673-
inner.traitobj = StorePtr::new(NonNull::from(&mut *inner));
686+
inner.traitobj = StorePtr(Some(NonNull::from(&mut *inner)));
674687

675688
// Wasmtime uses the callee argument to host functions to learn about
676689
// the original pointer to the `Store` itself, allowing it to
@@ -1489,15 +1502,15 @@ impl StoreOpaque {
14891502
/// `ResourceLimiterAsync` which means that this should only be executed
14901503
/// in a fiber context at this time.
14911504
#[inline]
1492-
pub(crate) fn ensure_gc_store(&mut self) -> Result<&mut GcStore> {
1505+
pub(crate) async fn ensure_gc_store(&mut self) -> Result<&mut GcStore> {
14931506
if self.gc_store.is_some() {
14941507
return Ok(self.gc_store.as_mut().unwrap());
14951508
}
1496-
self.allocate_gc_store()
1509+
self.allocate_gc_store().await
14971510
}
14981511

14991512
#[inline(never)]
1500-
fn allocate_gc_store(&mut self) -> Result<&mut GcStore> {
1513+
async fn allocate_gc_store(&mut self) -> Result<&mut GcStore> {
15011514
log::trace!("allocating GC heap for store {:?}", self.id());
15021515

15031516
assert!(self.gc_store.is_none());
@@ -1507,19 +1520,24 @@ impl StoreOpaque {
15071520
);
15081521
assert_eq!(self.vm_store_context.gc_heap.current_length(), 0);
15091522

1510-
let vmstore = self.traitobj();
1511-
let gc_store = allocate_gc_store(self.engine(), vmstore, self.get_pkey())?;
1523+
let gc_store = allocate_gc_store(self).await?;
15121524
self.vm_store_context.gc_heap = gc_store.vmmemory_definition();
15131525
return Ok(self.gc_store.insert(gc_store));
15141526

15151527
#[cfg(feature = "gc")]
1516-
fn allocate_gc_store(
1517-
engine: &Engine,
1518-
vmstore: NonNull<dyn VMStore>,
1519-
pkey: Option<ProtectionKey>,
1520-
) -> Result<GcStore> {
1528+
async fn allocate_gc_store(store: &mut StoreOpaque) -> Result<GcStore> {
15211529
use wasmtime_environ::packed_option::ReservedValue;
15221530

1531+
// FIXME(#11409) this is not a sound widening borrow
1532+
let (mut limiter, store) = unsafe {
1533+
store
1534+
.traitobj()
1535+
.as_mut()
1536+
.resource_limiter_and_store_opaque()
1537+
};
1538+
1539+
let engine = store.engine();
1540+
let mem_ty = engine.tunables().gc_heap_memory_type();
15231541
ensure!(
15241542
engine.features().gc_types(),
15251543
"cannot allocate a GC store when GC is disabled at configuration time"
@@ -1532,19 +1550,14 @@ impl StoreOpaque {
15321550
wasmtime_environ::Module::default(),
15331551
)),
15341552
imports: vm::Imports::default(),
1535-
store: StorePtr::new(vmstore),
1536-
#[cfg(feature = "wmemcheck")]
1537-
wmemcheck: false,
1538-
pkey,
1539-
tunables: engine.tunables(),
1553+
store,
1554+
limiter: limiter.as_mut(),
15401555
};
1541-
let mem_ty = engine.tunables().gc_heap_memory_type();
1542-
let tunables = engine.tunables();
15431556

1544-
let (mem_alloc_index, mem) =
1545-
engine
1546-
.allocator()
1547-
.allocate_memory(&mut request, &mem_ty, tunables, None)?;
1557+
let (mem_alloc_index, mem) = engine
1558+
.allocator()
1559+
.allocate_memory(&mut request, &mem_ty, None)
1560+
.await?;
15481561

15491562
// Then, allocate the actual GC heap, passing in that memory
15501563
// storage.
@@ -1560,11 +1573,7 @@ impl StoreOpaque {
15601573
}
15611574

15621575
#[cfg(not(feature = "gc"))]
1563-
fn allocate_gc_store(
1564-
_engine: &Engine,
1565-
_vmstore: NonNull<dyn VMStore>,
1566-
_pkey: Option<ProtectionKey>,
1567-
) -> Result<GcStore> {
1576+
async fn allocate_gc_store(_: &mut StoreOpaque) -> Result<GcStore> {
15681577
bail!("cannot allocate a GC store: the `gc` feature was disabled at compile time")
15691578
}
15701579
}
@@ -1944,7 +1953,7 @@ impl StoreOpaque {
19441953

19451954
#[inline]
19461955
pub fn traitobj(&self) -> NonNull<dyn VMStore> {
1947-
self.traitobj.as_raw().unwrap()
1956+
self.traitobj.0.unwrap()
19481957
}
19491958

19501959
/// Takes the cached `Vec<Val>` stored internally across hostcalls to get
@@ -2091,6 +2100,7 @@ at https://bytecodealliance.org/security.
20912100

20922101
/// Retrieve the store's protection key.
20932102
#[inline]
2103+
#[cfg(feature = "pooling-allocator")]
20942104
pub(crate) fn get_pkey(&self) -> Option<ProtectionKey> {
20952105
self.pkey
20962106
}
@@ -2225,16 +2235,17 @@ at https://bytecodealliance.org/security.
22252235
// SAFETY: this function's own contract is the same as
22262236
// `allocate_module`, namely the imports provided are valid.
22272237
let handle = unsafe {
2228-
allocator.allocate_module(InstanceAllocationRequest {
2229-
id,
2230-
runtime_info,
2231-
imports,
2232-
store: StorePtr::new(self.traitobj()),
2233-
#[cfg(feature = "wmemcheck")]
2234-
wmemcheck: self.engine().config().wmemcheck,
2235-
pkey: self.get_pkey(),
2236-
tunables: self.engine().tunables(),
2237-
})?
2238+
// FIXME(#11409) this is not a sound widening borrow
2239+
let (mut limiter, store) = self.traitobj().as_mut().resource_limiter_and_store_opaque();
2240+
allocator
2241+
.allocate_module(InstanceAllocationRequest {
2242+
id,
2243+
runtime_info,
2244+
imports,
2245+
store,
2246+
limiter: limiter.as_mut(),
2247+
})
2248+
.await?
22382249
};
22392250

22402251
let actual = match kind {
@@ -2316,82 +2327,6 @@ unsafe impl<T> VMStore for StoreInner<T> {
23162327
)
23172328
}
23182329

2319-
fn memory_growing(
2320-
&mut self,
2321-
current: usize,
2322-
desired: usize,
2323-
maximum: Option<usize>,
2324-
) -> Result<bool, anyhow::Error> {
2325-
match self.limiter {
2326-
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
2327-
limiter(&mut self.data).memory_growing(current, desired, maximum)
2328-
}
2329-
#[cfg(feature = "async")]
2330-
Some(ResourceLimiterInner::Async(_)) => self.block_on(|store| {
2331-
let limiter = match &mut store.0.limiter {
2332-
Some(ResourceLimiterInner::Async(limiter)) => limiter,
2333-
_ => unreachable!(),
2334-
};
2335-
limiter(&mut store.0.data).memory_growing(current, desired, maximum)
2336-
})?,
2337-
None => Ok(true),
2338-
}
2339-
}
2340-
2341-
fn memory_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
2342-
match self.limiter {
2343-
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
2344-
limiter(&mut self.data).memory_grow_failed(error)
2345-
}
2346-
#[cfg(feature = "async")]
2347-
Some(ResourceLimiterInner::Async(ref mut limiter)) => {
2348-
limiter(&mut self.data).memory_grow_failed(error)
2349-
}
2350-
None => {
2351-
log::debug!("ignoring memory growth failure error: {error:?}");
2352-
Ok(())
2353-
}
2354-
}
2355-
}
2356-
2357-
fn table_growing(
2358-
&mut self,
2359-
current: usize,
2360-
desired: usize,
2361-
maximum: Option<usize>,
2362-
) -> Result<bool, anyhow::Error> {
2363-
match self.limiter {
2364-
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
2365-
limiter(&mut self.data).table_growing(current, desired, maximum)
2366-
}
2367-
#[cfg(feature = "async")]
2368-
Some(ResourceLimiterInner::Async(_)) => self.block_on(|store| {
2369-
let limiter = match &mut store.0.limiter {
2370-
Some(ResourceLimiterInner::Async(limiter)) => limiter,
2371-
_ => unreachable!(),
2372-
};
2373-
limiter(&mut store.0.data).table_growing(current, desired, maximum)
2374-
})?,
2375-
None => Ok(true),
2376-
}
2377-
}
2378-
2379-
fn table_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
2380-
match self.limiter {
2381-
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
2382-
limiter(&mut self.data).table_grow_failed(error)
2383-
}
2384-
#[cfg(feature = "async")]
2385-
Some(ResourceLimiterInner::Async(ref mut limiter)) => {
2386-
limiter(&mut self.data).table_grow_failed(error)
2387-
}
2388-
None => {
2389-
log::debug!("ignoring table growth failure: {error:?}");
2390-
Ok(())
2391-
}
2392-
}
2393-
}
2394-
23952330
fn out_of_gas(&mut self) -> Result<()> {
23962331
if !self.refuel() {
23972332
return Err(Trap::OutOfFuel.into());

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ impl StoreOpaque {
179179
!self.async_support(),
180180
"use the `*_async` versions of methods when async is configured"
181181
);
182-
self.ensure_gc_store()?;
182+
vm::assert_ready(self.ensure_gc_store())?;
183183
match alloc_func(self, value) {
184184
Ok(x) => Ok(x),
185185
Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
@@ -208,7 +208,7 @@ impl StoreOpaque {
208208
where
209209
T: Send + Sync + 'static,
210210
{
211-
self.ensure_gc_store()?;
211+
self.ensure_gc_store().await?;
212212
match alloc_func(self, value) {
213213
Ok(x) => Ok(x),
214214
Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {

crates/wasmtime/src/runtime/trampoline/memory.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ struct SingleMemoryInstance<'a> {
124124
ondemand: OnDemandInstanceAllocator,
125125
}
126126

127+
#[async_trait::async_trait]
127128
unsafe impl InstanceAllocator for SingleMemoryInstance<'_> {
128129
#[cfg(feature = "component-model")]
129130
fn validate_component<'a>(
@@ -167,11 +168,10 @@ unsafe impl InstanceAllocator for SingleMemoryInstance<'_> {
167168
self.ondemand.decrement_core_instance_count();
168169
}
169170

170-
fn allocate_memory(
171+
async fn allocate_memory(
171172
&self,
172-
request: &mut InstanceAllocationRequest,
173+
request: &mut InstanceAllocationRequest<'_>,
173174
ty: &wasmtime_environ::Memory,
174-
tunables: &Tunables,
175175
memory_index: Option<DefinedMemoryIndex>,
176176
) -> Result<(MemoryAllocationIndex, Memory)> {
177177
if cfg!(debug_assertions) {
@@ -186,9 +186,11 @@ unsafe impl InstanceAllocator for SingleMemoryInstance<'_> {
186186
MemoryAllocationIndex::default(),
187187
shared_memory.clone().as_memory(),
188188
)),
189-
None => self
190-
.ondemand
191-
.allocate_memory(request, ty, tunables, memory_index),
189+
None => {
190+
self.ondemand
191+
.allocate_memory(request, ty, memory_index)
192+
.await
193+
}
192194
}
193195
}
194196

@@ -204,14 +206,13 @@ unsafe impl InstanceAllocator for SingleMemoryInstance<'_> {
204206
}
205207
}
206208

207-
fn allocate_table(
209+
async fn allocate_table(
208210
&self,
209-
req: &mut InstanceAllocationRequest,
211+
req: &mut InstanceAllocationRequest<'_>,
210212
ty: &wasmtime_environ::Table,
211-
tunables: &Tunables,
212213
table_index: DefinedTableIndex,
213214
) -> Result<(TableAllocationIndex, Table)> {
214-
self.ondemand.allocate_table(req, ty, tunables, table_index)
215+
self.ondemand.allocate_table(req, ty, table_index).await
215216
}
216217

217218
unsafe fn deallocate_table(

0 commit comments

Comments
 (0)