Skip to content

Commit f881ab2

Browse files
authored
Make memory growth an async function (bytecodealliance#11460)
* Make memory growth an `async` function This is an analog of bytecodealliance#11442 but for memories. This had a little more impact due to memories being hooked into GC operations. Further refactoring of GC operations to make them safer/more-async is deferred to a future PR and for now it's "no worse than before". This is another step towards bytecodealliance#11430 and enables removing a longstanding `unsafe` block in `runtime/memory.rs` which previously could not be removed. One semantic change from this is that growth of a shared memory no longer uses an async limiter. This is done to keep growth of a shared memory consistent with creation of a shared memory where no limits are applied. This is due to the cross-store nature of shared memories which means that we can't tie growth to any one particular store. This additionally fixes an issue where an rwlock write guard was otherwise held across a `.await` point which creates a non-`Send` future, closing a possible soundness hole in Wasmtime. * Fix threads-disabled build * Review comments
1 parent dcedcbf commit f881ab2

File tree

8 files changed

+95
-68
lines changed

8 files changed

+95
-68
lines changed

crates/wasmtime/src/runtime/memory.rs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::Trap;
22
use crate::prelude::*;
3-
use crate::store::{StoreInstanceId, StoreOpaque};
3+
use crate::runtime::vm::{self, VMStore};
4+
use crate::store::{StoreInstanceId, StoreOpaque, StoreResourceLimiter};
45
use crate::trampoline::generate_memory_export;
56
use crate::{AsContext, AsContextMut, Engine, MemoryType, StoreContext, StoreContextMut};
67
use core::cell::UnsafeCell;
@@ -596,20 +597,9 @@ impl Memory {
596597
/// ```
597598
pub fn grow(&self, mut store: impl AsContextMut, delta: u64) -> Result<u64> {
598599
let store = store.as_context_mut().0;
599-
// FIXME(#11179) shouldn't use a raw pointer to work around the borrow
600-
// checker here.
601-
let mem: *mut _ = self.wasmtime_memory(store);
602-
unsafe {
603-
match (*mem).grow(delta, Some(store))? {
604-
Some(size) => {
605-
let vm = (*mem).vmmemory();
606-
store[self.instance].memory_ptr(self.index).write(vm);
607-
let page_size = (*mem).page_size();
608-
Ok(u64::try_from(size).unwrap() / page_size)
609-
}
610-
None => bail!("failed to grow memory by `{}`", delta),
611-
}
612-
}
600+
let (mut limiter, store) = store.resource_limiter_and_store_opaque();
601+
vm::one_poll(self._grow(store, limiter.as_mut(), delta))
602+
.expect("must use `grow_async` if an async resource limiter is used")
613603
}
614604

615605
/// Async variant of [`Memory::grow`]. Required when using a
@@ -621,21 +611,29 @@ impl Memory {
621611
/// [`Store`](`crate::Store`).
622612
#[cfg(feature = "async")]
623613
pub async fn grow_async(&self, mut store: impl AsContextMut, delta: u64) -> Result<u64> {
624-
let mut store = store.as_context_mut();
625-
assert!(
626-
store.0.async_support(),
627-
"cannot use `grow_async` without enabling async support on the config"
628-
);
629-
store.on_fiber(|store| self.grow(store, delta)).await?
614+
let store = store.as_context_mut();
615+
let (mut limiter, store) = store.0.resource_limiter_and_store_opaque();
616+
self._grow(store, limiter.as_mut(), delta).await
630617
}
631618

632-
fn wasmtime_memory<'a>(
619+
async fn _grow(
633620
&self,
634-
store: &'a mut StoreOpaque,
635-
) -> &'a mut crate::runtime::vm::Memory {
636-
self.instance
621+
store: &mut StoreOpaque,
622+
limiter: Option<&mut StoreResourceLimiter<'_>>,
623+
delta: u64,
624+
) -> Result<u64> {
625+
let result = self
626+
.instance
637627
.get_mut(store)
638-
.get_defined_memory_mut(self.index)
628+
.memory_grow(limiter, self.index, delta)
629+
.await?;
630+
match result {
631+
Some(size) => {
632+
let page_size = self.wasmtime_ty(store).page_size();
633+
Ok(u64::try_from(size).unwrap() / page_size)
634+
}
635+
None => bail!("failed to grow memory by `{}`", delta),
636+
}
639637
}
640638

641639
pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedMemoryIndex) -> Memory {
@@ -908,7 +906,7 @@ impl SharedMemory {
908906
/// [`ResourceLimiter`](crate::ResourceLimiter) is another example of
909907
/// preventing a memory to grow.
910908
pub fn grow(&self, delta: u64) -> Result<u64> {
911-
match self.vm.grow(delta, None)? {
909+
match self.vm.grow(delta)? {
912910
Some((old_size, _new_size)) => {
913911
// For shared memory, the `VMMemoryDefinition` is updated inside
914912
// the locked region.

crates/wasmtime/src/runtime/store.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,27 @@ pub enum StoreResourceLimiter<'a> {
262262
}
263263

264264
impl StoreResourceLimiter<'_> {
265+
pub(crate) async fn memory_growing(
266+
&mut self,
267+
current: usize,
268+
desired: usize,
269+
maximum: Option<usize>,
270+
) -> Result<bool, Error> {
271+
match self {
272+
Self::Sync(s) => s.memory_growing(current, desired, maximum),
273+
#[cfg(feature = "async")]
274+
Self::Async(s) => s.memory_growing(current, desired, maximum).await,
275+
}
276+
}
277+
278+
pub(crate) fn memory_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
279+
match self {
280+
Self::Sync(s) => s.memory_grow_failed(error),
281+
#[cfg(feature = "async")]
282+
Self::Async(s) => s.memory_grow_failed(error),
283+
}
284+
}
285+
265286
pub(crate) async fn table_growing(
266287
&mut self,
267288
current: usize,

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ impl StoreOpaque {
6262
fn grow_or_collect_gc_heap(&mut self, bytes_needed: Option<u64>) {
6363
assert!(!self.async_support());
6464
if let Some(n) = bytes_needed {
65-
if unsafe { self.maybe_async_grow_gc_heap(n).is_ok() } {
65+
if vm::assert_ready(self.grow_gc_heap(n)).is_ok() {
6666
return;
6767
}
6868
}
@@ -72,12 +72,7 @@ impl StoreOpaque {
7272
/// Attempt to grow the GC heap by `bytes_needed` bytes.
7373
///
7474
/// Returns an error if growing the GC heap fails.
75-
///
76-
/// # Safety
77-
///
78-
/// When async is enabled, it is the caller's responsibility to ensure that
79-
/// this is called on a fiber stack.
80-
unsafe fn maybe_async_grow_gc_heap(&mut self, bytes_needed: u64) -> Result<()> {
75+
async fn grow_gc_heap(&mut self, bytes_needed: u64) -> Result<()> {
8176
log::trace!("Attempting to grow the GC heap by {bytes_needed} bytes");
8277
assert!(bytes_needed > 0);
8378

@@ -120,8 +115,15 @@ impl StoreOpaque {
120115
// `VMMemoryDefinition` in the `VMStoreContext` immediately
121116
// afterwards.
122117
unsafe {
118+
// FIXME(#11409) this is not sound to widen the borrow
119+
let (mut limiter, _) = heap
120+
.store
121+
.traitobj()
122+
.as_mut()
123+
.resource_limiter_and_store_opaque();
123124
heap.memory
124-
.grow(delta_pages_for_alloc, Some(heap.store.traitobj().as_mut()))?
125+
.grow(delta_pages_for_alloc, limiter.as_mut())
126+
.await?
125127
.ok_or_else(|| anyhow!("failed to grow GC heap"))?;
126128
}
127129
heap.store.vm_store_context.gc_heap = heap.memory.vmmemory();
@@ -246,7 +248,7 @@ impl StoreOpaque {
246248
async fn grow_or_collect_gc_heap_async(&mut self, bytes_needed: Option<u64>) {
247249
assert!(self.async_support());
248250
if let Some(bytes_needed) = bytes_needed {
249-
if unsafe { self.maybe_async_grow_gc_heap(bytes_needed).is_ok() } {
251+
if self.grow_gc_heap(bytes_needed).await.is_ok() {
250252
return;
251253
}
252254
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::runtime::vm::{
1717
GcStore, HostResult, Imports, ModuleRuntimeInfo, SendSyncPtr, VMGlobalKind, VMStore,
1818
VMStoreRawPtr, VmPtr, VmSafe, WasmFault, catch_unwind_and_record_trap,
1919
};
20-
use crate::store::{InstanceId, StoreId, StoreInstanceId, StoreOpaque};
20+
use crate::store::{InstanceId, StoreId, StoreInstanceId, StoreOpaque, StoreResourceLimiter};
2121
use alloc::sync::Arc;
2222
use core::alloc::Layout;
2323
use core::marker;
@@ -673,15 +673,18 @@ impl Instance {
673673
/// Returns `None` if memory can't be grown by the specified amount
674674
/// of pages. Returns `Some` with the old size in bytes if growth was
675675
/// successful.
676-
pub(crate) fn memory_grow(
676+
pub(crate) async fn memory_grow(
677677
mut self: Pin<&mut Self>,
678-
store: &mut dyn VMStore,
678+
limiter: Option<&mut StoreResourceLimiter<'_>>,
679679
idx: DefinedMemoryIndex,
680680
delta: u64,
681681
) -> Result<Option<usize>, Error> {
682682
let memory = &mut self.as_mut().memories_mut()[idx].1;
683683

684-
let result = unsafe { memory.grow(delta, Some(store)) };
684+
// SAFETY: this is the safe wrapper around `Memory::grow` because it
685+
// automatically updates the `VMMemoryDefinition` in this instance after
686+
// a growth operation below.
687+
let result = unsafe { memory.grow(delta, limiter).await };
685688

686689
// Update the state used by a non-shared Wasm memory in case the base
687690
// pointer and/or the length changed.

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,17 @@ fn memory_grow(
220220
let module = instance.env_module();
221221
let page_size_log2 = module.memories[module.memory_index(memory_index)].page_size_log2;
222222

223-
let result = instance
224-
.as_mut()
225-
.memory_grow(store, memory_index, delta)?
226-
.map(|size_in_bytes| AllocationSize(size_in_bytes >> page_size_log2));
223+
let (mut limiter, store) = store.resource_limiter_and_store_opaque();
224+
let limiter = limiter.as_mut();
225+
block_on!(store, async |_store| {
226+
let result = instance
227+
.as_mut()
228+
.memory_grow(limiter, memory_index, delta)
229+
.await?
230+
.map(|size_in_bytes| AllocationSize(size_in_bytes >> page_size_log2));
227231

228-
Ok(result)
232+
Ok(result)
233+
})?
229234
}
230235

231236
/// A helper structure to represent the return value of a memory or table growth

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
//! they should be merged together.
7676
7777
use crate::prelude::*;
78+
use crate::runtime::store::StoreResourceLimiter;
7879
use crate::runtime::vm::vmcontext::VMMemoryDefinition;
7980
#[cfg(has_virtual_memory)]
8081
use crate::runtime::vm::{HostAlignedByteCount, MmapOffset};
@@ -394,14 +395,14 @@ impl Memory {
394395
///
395396
/// Ensure that the provided Store is not used to get access any Memory
396397
/// which lives inside it.
397-
pub unsafe fn grow(
398+
pub async unsafe fn grow(
398399
&mut self,
399400
delta_pages: u64,
400-
store: Option<&mut dyn VMStore>,
401+
limiter: Option<&mut StoreResourceLimiter<'_>>,
401402
) -> Result<Option<usize>, Error> {
402403
let result = match self {
403-
Memory::Local(mem) => mem.grow(delta_pages, store)?,
404-
Memory::Shared(mem) => mem.grow(delta_pages, store)?,
404+
Memory::Local(mem) => mem.grow(delta_pages, limiter).await?,
405+
Memory::Shared(mem) => mem.grow(delta_pages)?,
405406
};
406407
match result {
407408
Some((old, _new)) => Ok(Some(old)),
@@ -600,10 +601,10 @@ impl LocalMemory {
600601
/// the underlying `grow_to` implementation.
601602
///
602603
/// The `store` is used only for error reporting.
603-
pub fn grow(
604+
pub async fn grow(
604605
&mut self,
605606
delta_pages: u64,
606-
mut store: Option<&mut dyn VMStore>,
607+
mut limiter: Option<&mut StoreResourceLimiter<'_>>,
607608
) -> Result<Option<(usize, usize)>, Error> {
608609
let old_byte_size = self.alloc.byte_size();
609610

@@ -634,8 +635,11 @@ impl LocalMemory {
634635
.and_then(|n| usize::try_from(n).ok());
635636

636637
// Store limiter gets first chance to reject memory_growing.
637-
if let Some(store) = &mut store {
638-
if !store.memory_growing(old_byte_size, new_byte_size, maximum)? {
638+
if let Some(limiter) = &mut limiter {
639+
if !limiter
640+
.memory_growing(old_byte_size, new_byte_size, maximum)
641+
.await?
642+
{
639643
return Ok(None);
640644
}
641645
}
@@ -701,8 +705,8 @@ impl LocalMemory {
701705
// report the growth failure to but the error should not be
702706
// dropped
703707
// (https://github.com/bytecodealliance/wasmtime/issues/4240).
704-
if let Some(store) = store {
705-
store.memory_grow_failed(e)?;
708+
if let Some(limiter) = limiter {
709+
limiter.memory_grow_failed(e)?;
706710
}
707711
Ok(None)
708712
}

crates/wasmtime/src/runtime/vm/memory/shared_memory.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::prelude::*;
22
use crate::runtime::vm::memory::{LocalMemory, MmapMemory, validate_atomic_addr};
33
use crate::runtime::vm::parking_spot::{ParkingSpot, Waiter};
4-
use crate::runtime::vm::{Memory, VMMemoryDefinition, VMStore, WaitResult};
4+
use crate::runtime::vm::{self, Memory, VMMemoryDefinition, WaitResult};
55
use std::cell::RefCell;
66
use std::ops::Range;
77
use std::ptr::NonNull;
@@ -68,13 +68,11 @@ impl SharedMemory {
6868
}
6969

7070
/// Same as `RuntimeLinearMemory::grow`, except with `&self`.
71-
pub fn grow(
72-
&self,
73-
delta_pages: u64,
74-
store: Option<&mut dyn VMStore>,
75-
) -> Result<Option<(usize, usize)>, Error> {
71+
pub fn grow(&self, delta_pages: u64) -> Result<Option<(usize, usize)>, Error> {
7672
let mut memory = self.0.memory.write().unwrap();
77-
let result = memory.grow(delta_pages, store)?;
73+
// Without a limiter being passed in this shouldn't have an await point,
74+
// so it should be safe to assert that it's ready.
75+
let result = vm::assert_ready(memory.grow(delta_pages, None))?;
7876
if let Some((_old_size_in_bytes, new_size_in_bytes)) = result {
7977
// Store the new size to the `VMMemoryDefinition` for JIT-generated
8078
// code (and runtime functions) to access. No other code can be

crates/wasmtime/src/runtime/vm/memory/shared_memory_disabled.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::prelude::*;
22
use crate::runtime::vm::memory::LocalMemory;
3-
use crate::runtime::vm::{VMMemoryDefinition, VMStore, WaitResult};
3+
use crate::runtime::vm::{VMMemoryDefinition, WaitResult};
44
use core::ops::Range;
55
use core::ptr::NonNull;
66
use core::time::Duration;
@@ -26,11 +26,7 @@ impl SharedMemory {
2626
match *self {}
2727
}
2828

29-
pub fn grow(
30-
&self,
31-
_delta_pages: u64,
32-
_store: Option<&mut dyn VMStore>,
33-
) -> Result<Option<(usize, usize)>> {
29+
pub fn grow(&self, _delta_pages: u64) -> Result<Option<(usize, usize)>> {
3430
match *self {}
3531
}
3632

0 commit comments

Comments
 (0)