Skip to content

Commit 715d439

Browse files
alexcrichtonbongjunj
authored andcommitted
Add a helper always-Sync utility to Wasmtime (bytecodealliance#11453)
* Add a helper always-`Sync` utility to Wasmtime This commit adds a newtype wrapper to Wasmtime, `AlwaysMut`, which is unconditionally `Sync` if the stored type is `Send`. This is similar to a `Mutex<T>` where it promotes a `Send` bound to a `Sync` bound, but it's unlike `Mutex<T>` in that `AlwaysMut<T>` has no synchronization. The reason that this is safe is that `AlwaysMut<T>` completely disallows access to the underlying data through `&self` and requires `&mut self`. This is similar to how `Mutex::get_mut` is safe, for example. This type cleans up a preexisting `unsafe impl Sync` block in funcref management around `SendSyncBump` (`bumpalo::Bump` is `Send`, not `Sync`, but we only access it through `&mut self`). This then additionally removes `unsafe impl Sync for StoreFiber` which, upon reflection, is not sound because we don't ever constraint the store's `T` type to `Sync`, only `Send`. This is effectively no change throughout Wasmtime, however, as fibers are only accessed with `&mut`. Overall this is mostly just internal refactoring to reduce the amount of `unsafe` inside of Wasmtime and to add a new utility to use in the future too. * Fix configured build * Review comments * Fix configured build
1 parent e9c5de8 commit 715d439

5 files changed

Lines changed: 118 additions & 55 deletions

File tree

crates/wasmtime/src/runtime/fiber.rs

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
use crate::prelude::*;
22
use crate::store::{Executor, StoreId, StoreInner, StoreOpaque};
33
use crate::vm::mpk::{self, ProtectionMask};
4-
use crate::vm::{AsyncWasmCallState, VMStore};
4+
use crate::vm::{AlwaysMut, AsyncWasmCallState, VMStore};
55
use crate::{Engine, StoreContextMut};
66
use anyhow::{Result, anyhow};
77
use core::mem;
88
use core::ops::Range;
99
use core::pin::Pin;
1010
use core::ptr::{self, NonNull};
1111
use core::task::{Context, Poll};
12-
use wasmtime_fiber::{Fiber, Suspend};
12+
use wasmtime_fiber::{Fiber, FiberStack, Suspend};
1313

1414
type WasmtimeResume = Result<NonNull<Context<'static>>>;
1515
type WasmtimeYield = StoreFiberYield;
1616
type WasmtimeComplete = Result<()>;
1717
type WasmtimeSuspend = Suspend<WasmtimeResume, WasmtimeYield, WasmtimeComplete>;
18+
type WasmtimeFiber<'a> = Fiber<'a, WasmtimeResume, WasmtimeYield, WasmtimeComplete>;
1819

1920
/// State related to asynchronous computations stored within a `Store<T>`.
2021
///
@@ -434,9 +435,9 @@ pub(crate) struct StoreFiber<'a> {
434435
///
435436
/// Note also that every `StoreFiber` is implicitly granted exclusive access
436437
/// to the store when it is resumed.
437-
fiber: Option<Fiber<'a, WasmtimeResume, WasmtimeYield, WasmtimeComplete>>,
438+
fiber: Option<AlwaysMut<RawFiber<'a>>>,
438439
/// See `FiberResumeState`
439-
state: Option<FiberResumeState>,
440+
state: Option<AlwaysMut<FiberResumeState>>,
440441
/// The Wasmtime `Engine` to which this fiber belongs.
441442
engine: Engine,
442443
/// The id of the store with which this fiber was created.
@@ -446,9 +447,23 @@ pub(crate) struct StoreFiber<'a> {
446447
id: StoreId,
447448
}
448449

449-
impl StoreFiber<'_> {
450+
struct RawFiber<'a>(WasmtimeFiber<'a>);
451+
452+
impl<'a> StoreFiber<'a> {
453+
/// Convenience method to peel off some layers of abstraction around the raw
454+
/// `wasmtime_fiber::Fiber`.
455+
fn fiber(&mut self) -> Option<&mut WasmtimeFiber<'a>> {
456+
Some(&mut self.fiber.as_mut()?.get_mut().0)
457+
}
458+
459+
/// Convenience method take the internal fiber and consume it, yielding its
460+
/// original stack.
461+
fn take_fiber_stack(&mut self) -> Option<FiberStack> {
462+
self.fiber.take().map(|f| f.into_inner().0.into_stack())
463+
}
464+
450465
pub(crate) fn dispose(&mut self, store: &mut StoreOpaque) {
451-
if let Some(fiber) = &mut self.fiber {
466+
if let Some(fiber) = self.fiber() {
452467
if !fiber.done() {
453468
let result = resume_fiber(store, self, Err(anyhow!("future dropped")));
454469
debug_assert!(result.is_ok());
@@ -469,16 +484,15 @@ impl Drop for StoreFiber<'_> {
469484
}
470485

471486
assert!(
472-
self.fiber.as_ref().unwrap().done(),
487+
self.fiber().unwrap().done(),
473488
"attempted to drop in-progress fiber without first calling `StoreFiber::dispose`"
474489
);
475490

476-
self.state.take().unwrap().dispose();
491+
self.state.take().unwrap().into_inner().dispose();
477492

478493
unsafe {
479-
self.engine
480-
.allocator()
481-
.deallocate_fiber_stack(self.fiber.take().unwrap().into_stack());
494+
let stack = self.take_fiber_stack().unwrap();
495+
self.engine.allocator().deallocate_fiber_stack(stack);
482496
}
483497
}
484498
}
@@ -537,11 +551,7 @@ impl Drop for StoreFiber<'_> {
537551
// declare the fiber as only containing Send data on its stack, despite not
538552
// knowing for sure at compile time that this is correct. That's what `unsafe`
539553
// in Rust is all about, though, right?
540-
unsafe impl Send for StoreFiber<'_> {}
541-
// See the docs about the `Send` impl above, which also apply to this `Sync`
542-
// impl. `Sync` is needed since we store `StoreFiber`s and switch between them
543-
// when executing components that export async-lifted functions.
544-
unsafe impl Sync for StoreFiber<'_> {}
554+
unsafe impl Send for RawFiber<'_> {}
545555

546556
/// State of the world when a fiber last suspended.
547557
///
@@ -596,8 +606,7 @@ impl FiberResumeState {
596606
let tls = unsafe { self.tls.push() };
597607
let mpk = swap_mpk_states(self.mpk);
598608
let async_guard_range = fiber
599-
.fiber
600-
.as_ref()
609+
.fiber()
601610
.unwrap()
602611
.stack()
603612
.guard_range()
@@ -723,25 +732,30 @@ fn resume_fiber<'a>(
723732

724733
impl Drop for Restore<'_, '_> {
725734
fn drop(&mut self) {
726-
self.fiber.state = Some(unsafe { self.state.take().unwrap().replace(self.store) });
735+
self.fiber.state =
736+
Some(unsafe { self.state.take().unwrap().replace(self.store).into() });
727737
}
728738
}
729739
let result = unsafe {
730-
let prev = fiber.state.take().unwrap().replace(store, fiber);
740+
let prev = fiber
741+
.state
742+
.take()
743+
.unwrap()
744+
.into_inner()
745+
.replace(store, fiber);
731746
let restore = Restore {
732747
store,
733748
fiber,
734749
state: Some(prev),
735750
};
736-
restore.fiber.fiber.as_ref().unwrap().resume(result)
751+
restore.fiber.fiber().unwrap().resume(result)
737752
};
738753

739754
match &result {
740755
// The fiber has finished, so recycle its stack by disposing of the
741756
// underlying fiber itself.
742757
Ok(_) => {
743-
let stack = fiber.fiber.take().map(|f| f.into_stack());
744-
if let Some(stack) = stack {
758+
if let Some(stack) = fiber.take_fiber_stack() {
745759
store.deallocate_fiber_stack(stack);
746760
}
747761
}
@@ -761,7 +775,7 @@ fn resume_fiber<'a>(
761775
// pointer Wasmtime uses is not pointing anywhere within the
762776
// stack. If it is then that's a bug indicating that TLS management
763777
// in Wasmtime is incorrect.
764-
if let Some(range) = fiber.fiber.as_ref().unwrap().stack().range() {
778+
if let Some(range) = fiber.fiber().unwrap().stack().range() {
765779
AsyncWasmCallState::assert_current_state_not_in_range(range);
766780
}
767781
}
@@ -826,19 +840,22 @@ pub(crate) fn make_fiber<'a>(
826840
fun(reset.0)
827841
})?;
828842
Ok(StoreFiber {
829-
state: Some(FiberResumeState {
830-
tls: crate::runtime::vm::AsyncWasmCallState::new(),
831-
mpk: if track_pkey_context_switch {
832-
Some(ProtectionMask::all())
833-
} else {
834-
None
835-
},
836-
stack_limit: usize::MAX,
837-
executor,
838-
}),
843+
state: Some(
844+
FiberResumeState {
845+
tls: crate::runtime::vm::AsyncWasmCallState::new(),
846+
mpk: if track_pkey_context_switch {
847+
Some(ProtectionMask::all())
848+
} else {
849+
None
850+
},
851+
stack_limit: usize::MAX,
852+
executor,
853+
}
854+
.into(),
855+
),
839856
engine,
840857
id,
841-
fiber: Some(fiber),
858+
fiber: Some(RawFiber(fiber).into()),
842859
})
843860
}
844861

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::Definition;
55
use crate::module::ModuleRegistry;
66
use crate::prelude::*;
77
use crate::runtime::HostFunc;
8-
use crate::runtime::vm::{SendSyncPtr, VMArrayCallHostFuncContext, VMFuncRef};
8+
use crate::runtime::vm::{AlwaysMut, SendSyncPtr, VMArrayCallHostFuncContext, VMFuncRef};
99
use alloc::sync::Arc;
1010
use core::ptr::NonNull;
1111

@@ -18,7 +18,7 @@ use core::ptr::NonNull;
1818
pub struct FuncRefs {
1919
/// A bump allocation arena where we allocate `VMFuncRef`s such
2020
/// that they are pinned and owned.
21-
bump: SendSyncBump,
21+
bump: AlwaysMut<bumpalo::Bump>,
2222

2323
/// Pointers into `self.bump` for entries that need `wasm_call` field filled
2424
/// in.
@@ -77,23 +77,6 @@ enum Storage {
7777
},
7878
}
7979

80-
use send_sync_bump::SendSyncBump;
81-
mod send_sync_bump {
82-
#[derive(Default)]
83-
pub struct SendSyncBump(bumpalo::Bump);
84-
85-
impl SendSyncBump {
86-
pub fn alloc<T>(&mut self, val: T) -> &mut T {
87-
self.0.alloc(val)
88-
}
89-
}
90-
91-
// Safety: We require `&mut self` on the only public method, which means it
92-
// is safe to send `&SendSyncBump` references across threads because they
93-
// can't actually do anything with it.
94-
unsafe impl Sync for SendSyncBump {}
95-
}
96-
9780
impl FuncRefs {
9881
/// Push the given `VMFuncRef` into this arena, returning a
9982
/// pinned pointer to it.
@@ -109,7 +92,7 @@ impl FuncRefs {
10992
modules: &ModuleRegistry,
11093
) -> NonNull<VMFuncRef> {
11194
debug_assert!(func_ref.wasm_call.is_none());
112-
let func_ref = self.bump.alloc(func_ref);
95+
let func_ref = self.bump.get_mut().alloc(func_ref);
11396
// SAFETY: it's a contract of this function itself that `func_ref` has a
11497
// valid vmctx field to read.
11598
let has_hole = unsafe { !try_fill(func_ref, modules) };

crates/wasmtime/src/runtime/vm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ use wasmtime_environ::{
5151
#[cfg(feature = "gc")]
5252
use wasmtime_environ::ModuleInternedTypeIndex;
5353

54+
mod always_mut;
5455
#[cfg(feature = "component-model")]
5556
pub mod component;
5657
mod const_expr;
@@ -92,6 +93,7 @@ pub(crate) use interpreter_disabled as interpreter;
9293
#[cfg(feature = "debug-builtins")]
9394
pub use wasmtime_jit_debug::gdb_jit_int::GdbJitImageRegistration;
9495

96+
pub use crate::runtime::vm::always_mut::*;
9597
pub use crate::runtime::vm::export::*;
9698
pub use crate::runtime::vm::gc::*;
9799
pub use crate::runtime::vm::imports::Imports;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use core::fmt;
2+
3+
/// A helper types that is `Send` if `T` is `Sync`.
4+
///
5+
/// This structure is a newtype wrapper around the `T` type parameter. What
6+
/// makes this a utility is the fact that it contains an `unsafe impl Sync`
7+
/// implementation for the type when `T` is `Send`. This is then coupled with
8+
/// the fact that there is no ability to access a shared reference, `&T`, from
9+
/// this type. Instead all access is done through `&mut T`.
10+
///
11+
/// This means that accessing the `T` in this data structure always requires
12+
/// exclusive `&mut self` access. This provides the trivial guarantee that this
13+
/// type is safe to share across threads because if you do so then you're just
14+
/// not able to do anything with it.
15+
#[derive(Default /* Do not derive traits with &self here, that's not sound */)]
16+
#[repr(transparent)]
17+
pub struct AlwaysMut<T>(T);
18+
19+
// SAFETY: this is the purpose for existence of this type, meaning that if `T`
20+
// is `Send` then this type is `Sync` because it statically disallows shared
21+
// access to `T`.
22+
unsafe impl<T: Send> Sync for AlwaysMut<T> {}
23+
24+
impl<T> AlwaysMut<T> {
25+
/// Creates a new [`AlwaysMut`] with the provided value.
26+
pub fn new(value: T) -> AlwaysMut<T> {
27+
AlwaysMut(value)
28+
}
29+
30+
/// Return a mutable reference to the underlying data in this [`AlwaysMut`]
31+
pub fn get_mut(&mut self) -> &mut T {
32+
&mut self.0
33+
}
34+
35+
/// Consume this [`AlwaysMut`], returning the underlying data.
36+
#[cfg(feature = "async")]
37+
pub fn into_inner(self) -> T {
38+
self.0
39+
}
40+
}
41+
42+
impl<T> From<T> for AlwaysMut<T> {
43+
fn from(val: T) -> AlwaysMut<T> {
44+
AlwaysMut::new(val)
45+
}
46+
}
47+
48+
impl<T> fmt::Debug for AlwaysMut<T> {
49+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50+
f.debug_struct("AlwaysMut").finish_non_exhaustive()
51+
}
52+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,15 @@ pub(crate) mod tls {
995995
state: raw::Ptr,
996996
}
997997

998+
// SAFETY: This is a relatively unsafe unsafe block and not really all that
999+
// well audited. The general idea is that the linked list of activations
1000+
// owned by `self.state` are safe to send to other threads, but that relies
1001+
// on everything internally being safe as well as stack variables and such.
1002+
// This is more-or-less tied to the very large comment in `fiber.rs` about
1003+
// `unsafe impl Send` there.
1004+
#[cfg(feature = "async")]
1005+
unsafe impl Send for AsyncWasmCallState {}
1006+
9981007
#[cfg(feature = "async")]
9991008
impl AsyncWasmCallState {
10001009
/// Creates new state that initially starts as null.

0 commit comments

Comments
 (0)