Skip to content

Commit 45969a7

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 6177710 commit 45969a7

15 files changed

Lines changed: 221 additions & 242 deletions

File tree

crates/wasmtime/src/runtime/instance.rs

Lines changed: 1 addition & 1 deletion
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();

crates/wasmtime/src/runtime/store.rs

Lines changed: 51 additions & 41 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+
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
@@ -2225,16 +2234,17 @@ at https://bytecodealliance.org/security.
22252234
// SAFETY: this function's own contract is the same as
22262235
// `allocate_module`, namely the imports provided are valid.
22272236
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-
})?
2237+
// FIXME(#11409) this is not a sound widening borrow
2238+
let (mut limiter, store) = self.traitobj().as_mut().resource_limiter_and_store_opaque();
2239+
allocator
2240+
.allocate_module(InstanceAllocationRequest {
2241+
id,
2242+
runtime_info,
2243+
imports,
2244+
store,
2245+
limiter: limiter.as_mut(),
2246+
})
2247+
.await?
22382248
};
22392249

22402250
let actual = match kind {

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

Lines changed: 12 additions & 3 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>>() {
@@ -212,7 +212,16 @@ impl StoreOpaque {
212212
where
213213
T: Send + Sync + 'static,
214214
{
215-
self.ensure_gc_store()?;
215+
if self.async_support() {
216+
self.block_on(|store| {
217+
Box::pin(async move {
218+
store.ensure_gc_store().await?;
219+
anyhow::Ok(())
220+
})
221+
})??;
222+
} else {
223+
vm::assert_ready(self.ensure_gc_store())?;
224+
}
216225
match alloc_func(self, value) {
217226
Ok(x) => Ok(x),
218227
Err(e) => match e.downcast::<crate::GcHeapOutOfMemory<T>>() {
@@ -262,7 +271,7 @@ impl StoreOpaque {
262271
self.async_support(),
263272
"you must configure async to use the `*_async` versions of methods"
264273
);
265-
self.ensure_gc_store()?;
274+
self.ensure_gc_store().await?;
266275
match alloc_func(self, value) {
267276
Ok(x) => Ok(x),
268277
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(

crates/wasmtime/src/runtime/vm.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ pub use crate::runtime::vm::gc::*;
9999
pub use crate::runtime::vm::imports::Imports;
100100
pub use crate::runtime::vm::instance::{
101101
GcHeapAllocationIndex, Instance, InstanceAllocationRequest, InstanceAllocator, InstanceHandle,
102-
MemoryAllocationIndex, OnDemandInstanceAllocator, StorePtr, TableAllocationIndex,
103-
initialize_instance,
102+
MemoryAllocationIndex, OnDemandInstanceAllocator, TableAllocationIndex, initialize_instance,
104103
};
105104
#[cfg(feature = "pooling-allocator")]
106105
pub use crate::runtime::vm::instance::{

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

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -487,32 +487,22 @@ impl Instance {
487487
unsafe { self.vmctx_plus_offset_mut(offset) }
488488
}
489489

490-
pub(crate) unsafe fn set_store(mut self: Pin<&mut Self>, store: Option<NonNull<dyn VMStore>>) {
490+
pub(crate) unsafe fn set_store(mut self: Pin<&mut Self>, store: &StoreOpaque) {
491491
// FIXME: should be more targeted ideally with the `unsafe` than just
492492
// throwing this entire function in a large `unsafe` block.
493493
unsafe {
494-
*self.as_mut().store_mut() = store.map(VMStoreRawPtr);
495-
if let Some(mut store) = store {
496-
let store = store.as_mut();
497-
self.vm_store_context()
498-
.write(Some(store.vm_store_context_ptr().into()));
499-
#[cfg(target_has_atomic = "64")]
500-
{
501-
*self.as_mut().epoch_ptr() =
502-
Some(NonNull::from(store.engine().epoch_counter()).into());
503-
}
494+
*self.as_mut().store_mut() = Some(VMStoreRawPtr(store.traitobj()));
495+
self.vm_store_context()
496+
.write(Some(store.vm_store_context_ptr().into()));
497+
#[cfg(target_has_atomic = "64")]
498+
{
499+
*self.as_mut().epoch_ptr() =
500+
Some(NonNull::from(store.engine().epoch_counter()).into());
501+
}
504502

505-
if self.env_module().needs_gc_heap {
506-
self.as_mut().set_gc_heap(Some(store.unwrap_gc_store()));
507-
} else {
508-
self.as_mut().set_gc_heap(None);
509-
}
503+
if self.env_module().needs_gc_heap {
504+
self.as_mut().set_gc_heap(Some(store.unwrap_gc_store()));
510505
} else {
511-
self.vm_store_context().write(None);
512-
#[cfg(target_has_atomic = "64")]
513-
{
514-
*self.as_mut().epoch_ptr() = None;
515-
}
516506
self.as_mut().set_gc_heap(None);
517507
}
518508
}
@@ -1246,7 +1236,7 @@ impl Instance {
12461236
mut self: Pin<&mut Self>,
12471237
module: &Module,
12481238
offsets: &VMOffsets<HostPtr>,
1249-
store: StorePtr,
1239+
store: &StoreOpaque,
12501240
imports: Imports,
12511241
) {
12521242
assert!(ptr::eq(module, self.env_module().as_ref()));
@@ -1260,7 +1250,7 @@ impl Instance {
12601250

12611251
// SAFETY: it's up to the caller to provide a valid store pointer here.
12621252
unsafe {
1263-
self.as_mut().set_store(store.as_raw());
1253+
self.as_mut().set_store(store);
12641254
}
12651255

12661256
// Initialize shared types

0 commit comments

Comments
 (0)