Skip to content

Commit 67ce06a

Browse files
alexcrichtonbongjunj
authored andcommitted
Relax the Send bound on fiber creation (bytecodealliance#11454)
* Relax the `Send` bound on fiber creation This commit removes the need for `Send` for all usages of fiber-related bits. The goal is that the future returned from `async` functions is `Send` if all the inputs are `Send`, but there's no actual need to require that in the function signatures themselves. This property was identified in upcoming work to make more internals of Wasmtime `async` where `async` functions are going to be used to implement the synchronous path through Wasmtime where `Send` isn't a bound today, nor ideally do we want to have it there. The way this commit works is: * First `make_fiber` now has a `Send` bound which makes it sound. Before bytecodealliance#11444 it took `&mut dyn VMStore` which is never `Send` and the refactoring there missed adding this bound. This change required a few `Send` bounds in `concurrent.rs` where it's expected to have no impact as everything there is basically already `Send`. * Next `make_fiber_unchecked` is added which is the same as `make_fiber` except without a `Send` bound. The `on_fiber` function is then updated to use this `*_unchecked` variant. This means that fibers can now be used with non-`Send` stores. Crucially though the structure of internals in play means that future produced is still only `Send` if `T: Send` meaning that there is no loss in safety. * Next some `Send` bounds were dropped throughout async functions in Wasmtime as a proof-of-concept to require these changes to `on_fiber`. This means that these entrypoints into Wasmtime no longer require `Send`, but still naturally require `Send` if the result future is sent to various threads. * Finally a new doctest is added to `Instance::new_async` with a `compile_fail` example to ensure that this fails. This is unfortunately a very brittle test because all we can assert is that compilation failed, not why compilation failed. That means that this is likely to regress over time, but it's the best we can do at this time. Note that the goal here is NOT to remove `Send` bounds throughout Wasmtime. Many of them are still required, for example all the ones related to `Linker`. The realization is that we can remove some of the "edge" bounds on entrypoints where the resulting future can be conditionally `Send` depending on `T` meaning we don't actually have to write down any bounds ourselves. This refactoring will enable future refactorings to have sync-and-async functions use the same internals and neither entrypoint needs to have a `Send` bound. * Review comments
1 parent 3c04e0a commit 67ce06a

7 files changed

Lines changed: 106 additions & 34 deletions

File tree

crates/wasmtime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ tempfile = { workspace = true }
103103
libtest-mimic = { workspace = true }
104104
cranelift-native = { workspace = true }
105105
wasmtime-test-util = { workspace = true }
106-
tokio = { workspace = true, features = ["macros", "sync"] }
106+
tokio = { workspace = true, features = ["macros", "sync", "rt-multi-thread"] }
107107

108108
[build-dependencies]
109109
cc = { workspace = true, optional = true }

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,7 +1075,7 @@ impl Instance {
10751075
fun: impl AsyncFnOnce(&Accessor<T>) -> R,
10761076
) -> Result<R>
10771077
where
1078-
T: 'static,
1078+
T: Send + 'static,
10791079
{
10801080
check_recursive_run();
10811081
let mut store = store.as_context_mut();
@@ -1165,7 +1165,10 @@ impl Instance {
11651165
self,
11661166
mut store: StoreContextMut<'_, T>,
11671167
mut future: Pin<&mut impl Future<Output = R>>,
1168-
) -> Result<R> {
1168+
) -> Result<R>
1169+
where
1170+
T: Send,
1171+
{
11691172
loop {
11701173
// Take `ConcurrentState::futures` out of the instance so we can
11711174
// poll it while also safely giving any of the futures inside access
@@ -1320,7 +1323,7 @@ impl Instance {
13201323
}
13211324

13221325
/// Handle the specified work item, possibly resuming a fiber if applicable.
1323-
async fn handle_work_item<T>(
1326+
async fn handle_work_item<T: Send>(
13241327
self,
13251328
store: StoreContextMut<'_, T>,
13261329
item: WorkItem,
@@ -1438,7 +1441,11 @@ impl Instance {
14381441
}
14391442

14401443
/// Execute the specified guest call on a worker fiber.
1441-
async fn run_on_worker<T>(self, store: StoreContextMut<'_, T>, item: WorkerItem) -> Result<()> {
1444+
async fn run_on_worker<T: Send>(
1445+
self,
1446+
store: StoreContextMut<'_, T>,
1447+
item: WorkerItem,
1448+
) -> Result<()> {
14421449
let worker = if let Some(fiber) = self.concurrent_state_mut(store.0).worker.take() {
14431450
fiber
14441451
} else {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl Table {
107107
/// [`Store`](`crate::Store`)
108108
#[cfg(feature = "async")]
109109
pub async fn new_async(
110-
mut store: impl AsContextMut<Data: Send>,
110+
mut store: impl AsContextMut,
111111
ty: TableType,
112112
init: Ref,
113113
) -> Result<Table> {
@@ -350,7 +350,7 @@ impl Table {
350350
#[cfg(feature = "async")]
351351
pub async fn grow_async(
352352
&self,
353-
mut store: impl AsContextMut<Data: Send>,
353+
mut store: impl AsContextMut,
354354
delta: u64,
355355
init: Ref,
356356
) -> Result<u64> {

crates/wasmtime/src/runtime/fiber.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,13 @@ fn resume_fiber<'a>(
769769
}
770770

771771
/// Create a new `StoreFiber` which runs the specified closure.
772-
pub(crate) fn make_fiber<'a, S>(
772+
///
773+
/// # Safety
774+
///
775+
/// The returned `StoreFiber<'a>` structure is unconditionally `Send` but the
776+
/// send-ness is actually a function of `S`. When `S` is statically known to be
777+
/// `Send` then use the safe [`make_fiber`] function.
778+
pub(crate) unsafe fn make_fiber_unchecked<'a, S>(
773779
store: &mut S,
774780
fun: impl FnOnce(&mut S) -> Result<()> + Send + Sync + 'a,
775781
) -> Result<StoreFiber<'a>>
@@ -852,6 +858,19 @@ where
852858
})
853859
}
854860

861+
/// Safe wrapper around [`make_fiber_unchecked`] which requires that `S` is
862+
/// `Send`.
863+
#[cfg(feature = "component-model-async")]
864+
pub(crate) fn make_fiber<'a, S>(
865+
store: &mut S,
866+
fun: impl FnOnce(&mut S) -> Result<()> + Send + Sync + 'a,
867+
) -> Result<StoreFiber<'a>>
868+
where
869+
S: AsStoreOpaque + Send + ?Sized + 'a,
870+
{
871+
unsafe { make_fiber_unchecked(store, fun) }
872+
}
873+
855874
/// Run the specified function on a newly-created fiber and `.await` its
856875
/// completion.
857876
pub(crate) async fn on_fiber<S, R>(
@@ -868,10 +887,18 @@ where
868887
debug_assert!(config.async_stack_size > 0);
869888

870889
let mut result = None;
871-
let fiber = make_fiber(store, |store| {
872-
result = Some(func(store));
873-
Ok(())
874-
})?;
890+
891+
// SAFETY: the `StoreFiber` returned by `make_fiber_unchecked` is `Send`
892+
// despite we not actually knowing here whether `S` is `Send` or not. That
893+
// is safe here, however, because this function is already conditionally
894+
// `Send` based on `S`. Additionally `fiber` doesn't escape this function,
895+
// so the future-of-this-function is still correctly `Send`-vs-not.
896+
let fiber = unsafe {
897+
make_fiber_unchecked(store, |store| {
898+
result = Some(func(store));
899+
Ok(())
900+
})?
901+
};
875902

876903
{
877904
let fiber = FiberFuture {

crates/wasmtime/src/runtime/instance.rs

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,63 @@ impl Instance {
137137
///
138138
/// This function will also panic, like [`Instance::new`], if any [`Extern`]
139139
/// specified does not belong to `store`.
140+
///
141+
/// # Examples
142+
///
143+
/// An example of using this function:
144+
///
145+
/// ```
146+
/// use wasmtime::{Result, Store, Engine, Config, Module, Instance};
147+
///
148+
/// #[tokio::main]
149+
/// async fn main() -> Result<()> {
150+
/// let mut config = Config::new();
151+
/// config.async_support(true);
152+
/// let engine = Engine::new(&config)?;
153+
///
154+
/// // For this example, a module with no imports is being used hence
155+
/// // the empty array to `Instance::new_async`.
156+
/// let module = Module::new(&engine, "(module)")?;
157+
/// let mut store = Store::new(&engine, ());
158+
/// let instance = Instance::new_async(&mut store, &module, &[]).await?;
159+
///
160+
/// // ... use `instance` and exports and such ...
161+
///
162+
/// Ok(())
163+
/// }
164+
/// ```
165+
///
166+
/// Note, though, that the future returned from this function is only
167+
/// `Send` if the store's own data is `Send` meaning that this does not
168+
/// compile for example:
169+
///
170+
/// ```compile_fail
171+
/// use wasmtime::{Result, Store, Engine, Config, Module, Instance};
172+
/// use std::rc::Rc;
173+
///
174+
/// #[tokio::main]
175+
/// async fn main() -> Result<()> {
176+
/// let mut config = Config::new();
177+
/// config.async_support(true);
178+
/// let engine = Engine::new(&config)?;
179+
///
180+
/// let module = Module::new(&engine, "(module)")?;
181+
///
182+
/// // Note that `Rc<()>` is NOT `Send`, which is what many future
183+
/// // runtimes require and below will cause a failure.
184+
/// let mut store = Store::new(&engine, Rc::new(()));
185+
///
186+
/// // Compile failure because `Store<Rc<()>>` is not `Send`
187+
/// assert_send(Instance::new_async(&mut store, &module, &[])).await?;
188+
///
189+
/// Ok(())
190+
/// }
191+
///
192+
/// fn assert_send<T: Send>(t: T) -> T { t }
193+
/// ```
140194
#[cfg(feature = "async")]
141195
pub async fn new_async(
142-
mut store: impl AsContextMut<Data: Send>,
196+
mut store: impl AsContextMut,
143197
module: &Module,
144198
imports: &[Extern],
145199
) -> Result<Instance> {
@@ -229,10 +283,7 @@ impl Instance {
229283
store: &mut StoreContextMut<'_, T>,
230284
module: &Module,
231285
imports: Imports<'_>,
232-
) -> Result<Instance>
233-
where
234-
T: Send + 'static,
235-
{
286+
) -> Result<Instance> {
236287
assert!(
237288
store.0.async_support(),
238289
"must use sync instantiation when async support is disabled",
@@ -854,10 +905,7 @@ impl<T: 'static> InstancePre<T> {
854905
pub async fn instantiate_async(
855906
&self,
856907
mut store: impl AsContextMut<Data = T>,
857-
) -> Result<Instance>
858-
where
859-
T: Send,
860-
{
908+
) -> Result<Instance> {
861909
let mut store = store.as_context_mut();
862910
let imports = pre_instantiate_raw(
863911
&mut store.0,

crates/wasmtime/src/runtime/memory.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,7 @@ impl Memory {
271271
/// This function will panic when used with a non-async
272272
/// [`Store`](`crate::Store`).
273273
#[cfg(feature = "async")]
274-
pub async fn new_async(
275-
mut store: impl AsContextMut<Data: Send>,
276-
ty: MemoryType,
277-
) -> Result<Memory> {
274+
pub async fn new_async(mut store: impl AsContextMut, ty: MemoryType) -> Result<Memory> {
278275
let mut store = store.as_context_mut();
279276
assert!(
280277
store.0.async_support(),
@@ -623,11 +620,7 @@ impl Memory {
623620
/// This function will panic when used with a non-async
624621
/// [`Store`](`crate::Store`).
625622
#[cfg(feature = "async")]
626-
pub async fn grow_async(
627-
&self,
628-
mut store: impl AsContextMut<Data: Send>,
629-
delta: u64,
630-
) -> Result<u64> {
623+
pub async fn grow_async(&self, mut store: impl AsContextMut, delta: u64) -> Result<u64> {
631624
let mut store = store.as_context_mut();
632625
assert!(
633626
store.0.async_support(),

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,7 @@ impl<T> StoreContextMut<'_, T> {
278278
pub(crate) async fn on_fiber<R: Send + Sync>(
279279
&mut self,
280280
func: impl FnOnce(&mut StoreContextMut<'_, T>) -> R + Send + Sync,
281-
) -> Result<R>
282-
where
283-
T: Send + 'static,
284-
{
281+
) -> Result<R> {
285282
fiber::on_fiber(self.0, |me| func(&mut StoreContextMut(me))).await
286283
}
287284
}

0 commit comments

Comments
 (0)