Skip to content

Commit 21797bb

Browse files
Refactor how concurrency support is enabled in a Store (#12416)
* Document panics from using CM async machinery when CM async is not enabled * Refactor how concurrency support is enabled in a `Store` This commit is an extension/refactor of #12377 and #12379. Notably this decouples the runtime behavior of Wasmtime from enabled/disabled WebAssembly proposals. This enables the `wasmtime serve` subcommand, for example, to continue to disallow component-model-async by default but continue to use `*_concurrent` under the hood. Specifically a new `Config::concurrency_support` knob is added. This is plumbed directly through to `Tunables` and takes over the preexisting `component_model_concurrency` field. This field configures whether tasks/etc are enabled at runtime for component-y things. The default value of this configuration option is the same as `cfg!(feature = "component-model-async")`, and this field is required if component-model-async wasm proposals are enabled. It's intended that eventually this'll affect on-by-default wasm features in Wasmtime depending if the support is compiled in. This results in a subtle shift in behavior where component-model-async concurrency is used by default now because the feature is turned on by default, even though the wasm features are off-by-default. This required adjusting a few indices expected in runtime tests due to tasks/threads being allocated in index spaces. Finally, this additionally denies access at runtime to `Linker::*_concurrent` when concurrent support is disabled as otherwise the various runtime data structures won't be initialized and panics will happen. Closes #12393 * Add a `-Wconcurrency-support` CLI flag Used to update disas tests to show that, when disabled, old codegen quality is preserved * Ungate `Config` flag * Review comments --------- Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>
1 parent 32089d4 commit 21797bb

24 files changed

Lines changed: 250 additions & 123 deletions

File tree

crates/cli-flags/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ wasmtime_option_group! {
412412
/// Component model support for fixed-length lists: this corresponds
413413
/// to the 🔧 emoji in the component model specification
414414
pub component_model_fixed_length_lists: Option<bool>,
415+
/// Whether or not any concurrency infrastructure in Wasmtime is
416+
/// enabled or not.
417+
pub concurrency_support: Option<bool>,
415418
}
416419

417420
enum Wasm {
@@ -1006,6 +1009,10 @@ impl CommonOptions {
10061009
config.gc_support(enable);
10071010
}
10081011

1012+
if let Some(enable) = self.wasm.concurrency_support {
1013+
config.concurrency_support(enable);
1014+
}
1015+
10091016
if let Some(enable) = self.wasm.shared_memory {
10101017
config.shared_memory(enable);
10111018
}

crates/cranelift/src/compiler/component.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1220,7 +1220,7 @@ impl<'a> TrampolineCompiler<'a> {
12201220
.ins()
12211221
.trapz(masked, TRAP_CANNOT_LEAVE_COMPONENT);
12221222

1223-
if self.compiler.tunables.component_model_concurrency {
1223+
if self.compiler.tunables.concurrency_support {
12241224
// Stash the old value of `may_block` and then set it to false.
12251225
let old_may_block = self.builder.ins().load(
12261226
ir::types::I32,

crates/environ/src/fact/trampoline.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) {
180180
compiler.compile_sync_to_sync_adapter(adapter, &lower_sig, &lift_sig)
181181
}
182182
(true, true) => {
183-
assert!(module.tunables.component_model_concurrency);
183+
assert!(module.tunables.concurrency_support);
184184

185185
// In the async->async case, we must compile a couple of helper functions:
186186
//
@@ -209,7 +209,7 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) {
209209
);
210210
}
211211
(false, true) => {
212-
assert!(module.tunables.component_model_concurrency);
212+
assert!(module.tunables.concurrency_support);
213213

214214
// Like the async->async case above, for the sync->async case we
215215
// also need `async-start` and `async-return` helper functions to
@@ -235,7 +235,7 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) {
235235
);
236236
}
237237
(true, false) => {
238-
assert!(module.tunables.component_model_concurrency);
238+
assert!(module.tunables.concurrency_support);
239239

240240
// As with the async->async and sync->async cases above, for the
241241
// async->sync case we use `async-start` and `async-return` helper
@@ -759,7 +759,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
759759
Trap::CannotLeaveComponent,
760760
);
761761

762-
let old_task_may_block = if self.module.tunables.component_model_concurrency {
762+
let old_task_may_block = if self.module.tunables.concurrency_support {
763763
// Save, clear, and later restore the `may_block` field.
764764
let task_may_block = self.module.import_task_may_block();
765765
let old_task_may_block = if self.types[adapter.lift.ty].async_ {
@@ -871,7 +871,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
871871
self.instruction(Call(exit.as_u32()));
872872
}
873873

874-
if self.module.tunables.component_model_concurrency {
874+
if self.module.tunables.concurrency_support {
875875
// Pop the task we pushed earlier off of the current task stack.
876876
//
877877
// FIXME: Apply the optimizations described in #12311.

crates/environ/src/tunables.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ define_tunables! {
143143

144144
/// Whether any component model feature related to concurrency is
145145
/// enabled.
146-
pub component_model_concurrency: bool,
146+
pub concurrency_support: bool,
147147
}
148148

149149
pub struct ConfigTunables {
@@ -219,7 +219,7 @@ impl Tunables {
219219
inlining_small_callee_size: 50,
220220
inlining_sum_size_threshold: 2000,
221221
debug_guest: false,
222-
component_model_concurrency: true,
222+
concurrency_support: true,
223223
}
224224
}
225225

crates/wasmtime/src/config.rs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,10 @@ impl Config {
23582358

23592359
let mut tunables = Tunables::default_for_target(&self.compiler_target())?;
23602360

2361+
// By default this is enabled with the Cargo feature, and if the feature
2362+
// is missing this is disabled.
2363+
tunables.concurrency_support = cfg!(feature = "component-model-async");
2364+
23612365
// If no target is explicitly specified then further refine `tunables`
23622366
// for the configuration of this host depending on what platform
23632367
// features were found available at compile time. This means that anyone
@@ -2430,9 +2434,23 @@ impl Config {
24302434
);
24312435
}
24322436

2433-
#[cfg(feature = "component-model")]
2434-
{
2435-
tunables.component_model_concurrency = self.cm_concurrency_enabled();
2437+
// Concurrency support is required for some component model features.
2438+
let requires_concurrency = WasmFeatures::CM_ASYNC
2439+
| WasmFeatures::CM_ASYNC_BUILTINS
2440+
| WasmFeatures::CM_ASYNC_STACKFUL
2441+
| WasmFeatures::CM_THREADING
2442+
| WasmFeatures::CM_ERROR_CONTEXT;
2443+
if tunables.concurrency_support && !cfg!(feature = "component-model-async") {
2444+
bail!(
2445+
"concurrency support was requested but was not \
2446+
compiled into this build of Wasmtime"
2447+
)
2448+
}
2449+
if !tunables.concurrency_support && features.intersects(requires_concurrency) {
2450+
bail!(
2451+
"concurrency support must be enabled to use the component \
2452+
model async or threading features"
2453+
)
24362454
}
24372455

24382456
Ok((tunables, features))
@@ -2923,17 +2941,41 @@ impl Config {
29232941
self
29242942
}
29252943

2926-
#[cfg(feature = "component-model")]
2927-
#[inline]
2928-
pub(crate) fn cm_concurrency_enabled(&self) -> bool {
2929-
cfg!(feature = "component-model-async")
2930-
&& self.enabled_features.intersects(
2931-
WasmFeatures::CM_ASYNC
2932-
| WasmFeatures::CM_ASYNC_BUILTINS
2933-
| WasmFeatures::CM_ASYNC_STACKFUL
2934-
| WasmFeatures::CM_THREADING
2935-
| WasmFeatures::CM_ERROR_CONTEXT,
2936-
)
2944+
/// Specifies whether support for concurrent execution of WebAssembly is
2945+
/// supported within this store.
2946+
///
2947+
/// This configuration option affects whether runtime data structures are
2948+
/// initialized within a `Store` on creation to support concurrent execution
2949+
/// of WebAssembly guests. This is primarily applicable to the
2950+
/// [`Config::wasm_component_model_async`] configuration which is the first
2951+
/// time Wasmtime has supported concurrent execution of guests. This
2952+
/// configuration option, for example, enables usage of
2953+
/// [`Store::run_concurrent`], [`Func::call_concurrent`], [`StreamReader`],
2954+
/// etc.
2955+
///
2956+
/// This configuration option can be manually disabled to avoid initializing
2957+
/// data structures in the [`Store`] related to concurrent execution. When
2958+
/// this option is disabled then APIs related to concurrency will all fail
2959+
/// with a panic. For example [`Store::run_concurrent`] will panic, creating
2960+
/// a [`StreamReader`] will panic, etc.
2961+
///
2962+
/// The value of this option additionally affects whether a [`Config`] is
2963+
/// valid and the default set of enabled WebAssembly features. If this
2964+
/// option is disabled then component-model features related to concurrency
2965+
/// will all be disabled. If this option is enabled, then the options will
2966+
/// retain their normal defaults. It is not valid to create a [`Config`]
2967+
/// with component-model-async explicitly enabled and this option explicitly
2968+
/// disabled, however.
2969+
///
2970+
/// This option defaults to `true`.
2971+
///
2972+
/// [`Store`]: crate::Store
2973+
/// [`Store::run_concurrent`]: crate::Store::run_concurrent
2974+
/// [`Func::call_concurrent`]: crate::component::Func::call_concurrent
2975+
/// [`StreamReader`]: crate::component::StreamReader
2976+
pub fn concurrency_support(&mut self, enable: bool) -> &mut Self {
2977+
self.tunables.concurrency_support = Some(enable);
2978+
self
29372979
}
29382980
}
29392981

crates/wasmtime/src/engine/serialization.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ impl Metadata<'_> {
285285
inlining_intra_module,
286286
inlining_small_callee_size,
287287
inlining_sum_size_threshold,
288-
component_model_concurrency,
288+
concurrency_support,
289289

290290
// This doesn't affect compilation, it's just a runtime setting.
291291
memory_reservation_for_growth: _,
@@ -367,9 +367,9 @@ impl Metadata<'_> {
367367
"function inlining sum-size threshold",
368368
)?;
369369
Self::check_bool(
370-
component_model_concurrency,
371-
other.component_model_concurrency,
372-
"component model concurrency",
370+
concurrency_support,
371+
other.concurrency_support,
372+
"concurrency support",
373373
)?;
374374
Self::check_intra_module_inlining(inlining_intra_module, other.inlining_intra_module)?;
375375

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

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -886,8 +886,8 @@ impl<T> Store<T> {
886886
T: Send + 'static,
887887
{
888888
ensure!(
889-
self.as_context().0.cm_concurrency_enabled(),
890-
"cannot use `run_concurrent` without enabling component-model async"
889+
self.as_context().0.concurrency_support(),
890+
"cannot use `run_concurrent` when Config::concurrency_support disabled",
891891
);
892892
self.as_context_mut().run_concurrent(fun).await
893893
}
@@ -983,6 +983,11 @@ impl<T> StoreContextMut<'_, T> {
983983
/// This function can be used to invoke [`Func::call_concurrent`] for
984984
/// example within the async closure provided here.
985985
///
986+
/// This function will unconditionally return an error if
987+
/// [`Config::concurrency_support`] is disabled.
988+
///
989+
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
990+
///
986991
/// # Store-blocking behavior
987992
///
988993
/// At this time there are certain situations in which the `Future` returned
@@ -1056,8 +1061,8 @@ impl<T> StoreContextMut<'_, T> {
10561061
T: Send + 'static,
10571062
{
10581063
ensure!(
1059-
self.0.cm_concurrency_enabled(),
1060-
"cannot use `run_concurrent` without enabling component-model async"
1064+
self.0.concurrency_support(),
1065+
"cannot use `run_concurrent` when Config::concurrency_support disabled",
10611066
);
10621067
self.do_run_concurrent(fun, false).await
10631068
}
@@ -1080,7 +1085,7 @@ impl<T> StoreContextMut<'_, T> {
10801085
where
10811086
T: Send + 'static,
10821087
{
1083-
debug_assert!(self.0.cm_concurrency_enabled());
1088+
debug_assert!(self.0.concurrency_support());
10841089
check_recursive_run();
10851090
let token = StoreToken::new(self.as_context_mut());
10861091

@@ -1513,8 +1518,10 @@ impl StoreOpaque {
15131518
/// - The top-level instance is not already on the current task's call stack.
15141519
/// - The instance is not in need of a post-return function call.
15151520
/// - `self` has not been poisoned due to a trap.
1516-
pub(crate) fn may_enter_concurrent(&mut self, instance: RuntimeInstance) -> bool {
1517-
debug_assert!(self.cm_concurrency_enabled());
1521+
pub(crate) fn may_enter(&mut self, instance: RuntimeInstance) -> bool {
1522+
if !self.concurrency_support() {
1523+
return self.may_enter_at_all(instance);
1524+
}
15181525
let state = self.concurrent_state_mut();
15191526
if let Some(caller) = state.guest_thread {
15201527
instance != state.get_mut(caller.task).unwrap().instance
@@ -1524,23 +1531,6 @@ impl StoreOpaque {
15241531
}
15251532
}
15261533

1527-
/// Returns `false` if the specified instance may not be entered, regardless
1528-
/// of what's on a task's call stack.
1529-
///
1530-
/// If this returns `true`, the instance may be entered as long as it isn't
1531-
/// on the task's call stack, if applicable.
1532-
fn may_enter_at_all(&self, instance: RuntimeInstance) -> bool {
1533-
if self.trapped() {
1534-
return false;
1535-
}
1536-
1537-
let flags = self
1538-
.component_instance(instance.instance)
1539-
.instance_flags(instance.index);
1540-
1541-
unsafe { !flags.needs_post_return() }
1542-
}
1543-
15441534
/// Variation of `may_enter` which takes a `TableId<GuestTask>` representing
15451535
/// the callee.
15461536
fn may_enter_task(&mut self, task: TableId<GuestTask>) -> bool {
@@ -1641,8 +1631,10 @@ impl StoreOpaque {
16411631
.set_task_may_block(may_block)
16421632
}
16431633

1644-
pub(crate) fn check_blocking_concurrent(&mut self) -> Result<()> {
1645-
debug_assert!(self.cm_concurrency_enabled());
1634+
pub(crate) fn check_blocking(&mut self) -> Result<()> {
1635+
if !self.concurrency_support() {
1636+
return Ok(());
1637+
}
16461638
let state = self.concurrent_state_mut();
16471639
let task = state.guest_thread.unwrap().task;
16481640
let instance = state.get_mut(task).unwrap().instance.instance;

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,14 +1116,20 @@ pub struct FutureReader<T> {
11161116

11171117
impl<T> FutureReader<T> {
11181118
/// Create a new future with the specified producer.
1119+
///
1120+
/// # Panics
1121+
///
1122+
/// Panics if [`Config::concurrency_support`] is not enabled.
1123+
///
1124+
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
11191125
pub fn new<S: AsContextMut>(
11201126
mut store: S,
11211127
producer: impl FutureProducer<S::Data, Item = T>,
11221128
) -> Self
11231129
where
11241130
T: func::Lower + func::Lift + Send + Sync + 'static,
11251131
{
1126-
assert!(store.as_context().0.cm_concurrency_enabled());
1132+
assert!(store.as_context().0.concurrency_support());
11271133

11281134
struct Producer<P>(P);
11291135

@@ -1451,11 +1457,17 @@ where
14511457
A: AsAccessor,
14521458
{
14531459
/// Create a new `GuardedFutureReader` with the specified `accessor` and `reader`.
1460+
///
1461+
/// # Panics
1462+
///
1463+
/// Panics if [`Config::concurrency_support`] is not enabled.
1464+
///
1465+
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
14541466
pub fn new(accessor: A, reader: FutureReader<T>) -> Self {
14551467
assert!(
14561468
accessor
14571469
.as_accessor()
1458-
.with(|a| a.as_context().0.cm_concurrency_enabled())
1470+
.with(|a| a.as_context().0.concurrency_support())
14591471
);
14601472
Self {
14611473
reader: Some(reader),
@@ -1503,14 +1515,20 @@ pub struct StreamReader<T> {
15031515

15041516
impl<T> StreamReader<T> {
15051517
/// Create a new stream with the specified producer.
1518+
///
1519+
/// # Panics
1520+
///
1521+
/// Panics if [`Config::concurrency_support`] is not enabled.
1522+
///
1523+
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
15061524
pub fn new<S: AsContextMut>(
15071525
mut store: S,
15081526
producer: impl StreamProducer<S::Data, Item = T>,
15091527
) -> Self
15101528
where
15111529
T: func::Lower + func::Lift + Send + Sync + 'static,
15121530
{
1513-
assert!(store.as_context().0.cm_concurrency_enabled());
1531+
assert!(store.as_context().0.concurrency_support());
15141532
Self::new_(
15151533
store
15161534
.as_context_mut()
@@ -1785,11 +1803,17 @@ where
17851803
{
17861804
/// Create a new `GuardedStreamReader` with the specified `accessor` and
17871805
/// `reader`.
1806+
///
1807+
/// # Panics
1808+
///
1809+
/// Panics if [`Config::concurrency_support`] is not enabled.
1810+
///
1811+
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
17881812
pub fn new(accessor: A, reader: StreamReader<T>) -> Self {
17891813
assert!(
17901814
accessor
17911815
.as_accessor()
1792-
.with(|a| a.as_context().0.cm_concurrency_enabled())
1816+
.with(|a| a.as_context().0.concurrency_support())
17931817
);
17941818
Self {
17951819
reader: Some(reader),

0 commit comments

Comments
 (0)