Skip to content

Commit 92f238b

Browse files
dicejalexcrichton
andauthored
align multithreading and trap behavior with CM spec (bytecodealliance#14146)
* align multithreading and trap behavior with CM spec This updates Wasmtime's Component Model async and cooperative multithreading support to match the current specification, including: - Refined rules for trapping when a sync-typed function blocks. We now enforce this "lazily" rather than "eagerly", mwaning a sync-typed function is allowed to call an async-typed function or blocking intrinsic, and if it doesn't actually block, we won't trap. And if the call _does_ block, we will look for any eligible threads to run and run them until no such threads remain, only trapping if and when we still need to block and have no more threads to run. - Allow reentrance in all cases except when the instance has trapped. - Remove the previous "may block" bookkeeping at the task and root instance level, replacing it with (sub-)instance level tracking of whether any sync-typed function is running in that instance. - Run the event loop during start function calls since they are now allowed to call async-typed functions, create and resume threads, etc. I've also added some code to assert that the event loop is running when it is required. - Add `ConcurrentState::switch_item` for use when we need to run a specific work item at the next turn of the event loop, regardless of what's already in the `high_priority` queue. This is necessary because the spec is particular about which thread to switch to e.g. when calling a function or promoting a thread, and it won't allow us to run any other threads first. Note that this includes `test/component-model` submodule updates which haven't yet been merged to the main branch of the upstream repo, but should be merged soon. See WebAssembly/component-model#705 Fixes bytecodealliance#14117 Co-authored-by: Alex Crichton <alex@alexcrichton.com> * Avoid `Send`-related changes (#7) Accept an `unsafe` block which I believe is correct and still otherwise safe at the invocation site. The main hidden constraint now is that we can't transfer fibers to other non-store-bound-locations but that's effectively already true so shouldn't be too onerous to uphold. * align more scheduler details with the spec - Ensure that subtask status updates are delivered promptly and deterministically according to the spec by using `ConcurrentState::switch_item` instead of `ConcurrentState::high_priority` - Fix reentrance scenarious involving `subtask.cancel` where the subtask tries to add itself to a waitable set before or after being canceled - Refine rules for switching-or-trapping on thread exit when the current instance has a sync-typed call in progress - Misc. bug fixes * fix test regressions Notably, this makes Wasmtime more aggressive about poisoning the store if an error happens when e.g. lifting a result (e.g. due to a misaligned pointer), and the tests have been updated accordingly. * address review feedback * update tests/component-model submodule * address review feedback * add comments to `any_may_not_suspend` * avoid triggering rustc's recursion limit in `serve.rs` (again) * another rustc overflow workaround --------- Co-authored-by: Alex Crichton <alex@alexcrichton.com>
1 parent 30cdd8d commit 92f238b

55 files changed

Lines changed: 1590 additions & 1362 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/cranelift/src/compiler/component.rs

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,12 +1129,6 @@ impl<'a> TrampolineCompiler<'a> {
11291129
// may_leave = load.i32 vmctx+$instance_flags_offset
11301130
// trapz may_leave, $TRAP_CANNOT_LEAVE_COMPONENT
11311131
//
1132-
// ;; set may_block to false, saving the old value to restore
1133-
// ;; later, but only if the component instances differ and
1134-
// ;; concurrency is enabled
1135-
// old_may_block = load.i32 vmctx+$may_block_offset
1136-
// store 0, vmctx+$may_block_offset
1137-
//
11381132
// ;; enter a sync call, but only if the component instances
11391133
// ;; differ and concurrency is enabled. This pushes an on-stack
11401134
// ;; `VMDeferredThread` and zeroes the live context slots; see
@@ -1158,11 +1152,6 @@ impl<'a> TrampolineCompiler<'a> {
11581152
// ...
11591153
// ;; ============================================================
11601154
//
1161-
// ;; if needed, exit the sync call entered above and restore the
1162-
// ;; old value of may_block
1163-
// ...
1164-
// store old_may_block, vmctx+$may_block_offset
1165-
//
11661155
// jump return_block
11671156
//
11681157
// return_block:
@@ -1191,32 +1180,16 @@ impl<'a> TrampolineCompiler<'a> {
11911180
self.builder.switch_to_block(run_destructor_block);
11921181

11931182
// If this is a component-defined resource, the `may_leave` flag must be
1194-
// checked. Additionally, if concurrency is enabled, the `may_block`
1195-
// field must be updated and a sync call entered.
1183+
// checked. Additionally, if concurrency is enabled, the sync call will
1184+
// be entered.
11961185
let entered_sync_call = if has_destructor && let Some(def) = resource_def {
11971186
// Skip the may-leave check for self-owned resources.
11981187
if self.types[resource].unwrap_concrete_instance() != def.instance {
11991188
self.check_may_leave_instance(self.types[resource].unwrap_concrete_instance());
12001189
}
12011190

12021191
if self.compiler.tunables.concurrency_support {
1203-
// Stash the old value of `may_block` and then set it to false.
1204-
let old_may_block = self
1205-
.alias_regions
1206-
.vmcomponent()
1207-
.task_may_block()
1208-
.readonly()
1209-
.load(&mut self.builder.cursor(), vmctx);
1210-
let zero = self.builder.ins().iconst(ir::types::I32, i64::from(0));
1211-
self.alias_regions.vmcomponent().task_may_block().store(
1212-
&mut self.builder.cursor(),
1213-
vmctx,
1214-
zero,
1215-
);
1216-
1217-
let slot = self.enter_sync_call_inline(instance, def.instance);
1218-
1219-
Some((old_may_block, slot))
1192+
Some(self.enter_sync_call_inline(instance, def.instance))
12201193
} else {
12211194
None
12221195
}
@@ -1296,15 +1269,8 @@ impl<'a> TrampolineCompiler<'a> {
12961269
self.builder.seal_block(continuation);
12971270
}
12981271

1299-
if let Some((old_may_block, slot)) = entered_sync_call {
1272+
if let Some(slot) = entered_sync_call {
13001273
self.exit_sync_call_inline(vmctx, slot);
1301-
1302-
// Restore the old value of `may_block`
1303-
self.alias_regions.vmcomponent().task_may_block().store(
1304-
&mut self.builder.cursor(),
1305-
vmctx,
1306-
old_may_block,
1307-
);
13081274
}
13091275

13101276
self.builder.ins().jump(return_block, &[]);

crates/cranelift/src/func_environ.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -455,11 +455,6 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
455455
.vmcomponent()
456456
.may_leave(instance)
457457
.region(func),
458-
Some(KnownGlobal::TaskMayBlock) => self
459-
.alias_regions
460-
.vmcomponent()
461-
.task_may_block()
462-
.region(func),
463458
None => self.alias_regions.public_global_region(func),
464459
},
465460
}

crates/environ/src/compile/module_environ.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,6 @@ pub enum KnownGlobal {
9292
/// flag.
9393
#[cfg(feature = "component-model")]
9494
ComponentInstanceFlags(crate::component::RuntimeComponentInstanceIndex),
95-
96-
/// The runtime-managed flag recording whether the currently-executing task
97-
/// may perform blocking operations.
98-
#[cfg(feature = "component-model")]
99-
TaskMayBlock,
10095
}
10196

10297
/// The result of translating via `ModuleEnvironment`.

crates/environ/src/component/dfg.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ pub enum CoreDef {
267267
InstanceFlags(RuntimeComponentInstanceIndex),
268268
Trampoline(TrampolineIndex),
269269
UnsafeIntrinsic(ModuleInternedTypeIndex, UnsafeIntrinsic),
270-
TaskMayBlock,
271270

272271
/// This is a special variant not present in `info::CoreDef` which
273272
/// represents that this definition refers to a fused adapter function. This
@@ -913,7 +912,6 @@ impl LinearizeDfg<'_> {
913912
}
914913
info::CoreDef::UnsafeIntrinsic(*i)
915914
}
916-
CoreDef::TaskMayBlock => info::CoreDef::TaskMayBlock,
917915
}
918916
}
919917

crates/environ/src/component/info.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,6 @@ pub enum CoreDef {
392392
Trampoline(TrampolineIndex),
393393
/// An intrinsic for compile-time builtins.
394394
UnsafeIntrinsic(UnsafeIntrinsic),
395-
/// Reference to a wasm global which represents a runtime-managed boolean
396-
/// indicating whether the currently-running task may perform a blocking
397-
/// operation.
398-
TaskMayBlock,
399395
}
400396

401397
impl<T> From<CoreExport<T>> for CoreDef

crates/environ/src/component/translate.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -691,9 +691,6 @@ impl<'a, 'data> Translator<'a, 'data> {
691691
CoreDef::InstanceFlags(_) => {
692692
unreachable!("instance flags are not a function")
693693
}
694-
CoreDef::TaskMayBlock => {
695-
unreachable!("task_may_block is not a function")
696-
}
697694

698695
// We could in theory inline these trampolines, so it
699696
// could potentially make sense to record that we
@@ -1976,7 +1973,6 @@ struct Ambiguous {
19761973
fn component_flags(def: &CoreDef) -> Option<KnownGlobal> {
19771974
match def {
19781975
CoreDef::InstanceFlags(instance) => Some(KnownGlobal::ComponentInstanceFlags(*instance)),
1979-
CoreDef::TaskMayBlock => Some(KnownGlobal::TaskMayBlock),
19801976
CoreDef::Export(_) | CoreDef::Trampoline(_) | CoreDef::UnsafeIntrinsic(_) => None,
19811977
}
19821978
}
@@ -2043,10 +2039,9 @@ fn resolve_core_export(
20432039
// The chain bottoms out in something that is not an export of
20442040
// another instance in this component, so there is no defining module
20452041
// for us to name.
2046-
CoreDef::InstanceFlags(_)
2047-
| CoreDef::Trampoline(_)
2048-
| CoreDef::UnsafeIntrinsic(_)
2049-
| CoreDef::TaskMayBlock => return None,
2042+
CoreDef::InstanceFlags(_) | CoreDef::Trampoline(_) | CoreDef::UnsafeIntrinsic(_) => {
2043+
return None;
2044+
}
20502045
}
20512046
}
20522047
}
@@ -2111,7 +2106,7 @@ fn ambiguous_entities(
21112106
}
21122107
}
21132108

2114-
CoreDef::InstanceFlags(_) | CoreDef::TaskMayBlock => {
2109+
CoreDef::InstanceFlags(_) => {
21152110
ambiguous.flags.insert(component_flags(def).unwrap());
21162111
}
21172112

crates/environ/src/component/translate/adapt.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,6 @@ pub struct AdapterOptions {
164164
/// The Wasmtime-assigned component instance index where the options were
165165
/// originally specified.
166166
pub instance: RuntimeComponentInstanceIndex,
167-
/// The ancestors (i.e. chain of instantiating instances) of the instance
168-
/// specified in the `instance` field.
169-
pub ancestors: Vec<RuntimeComponentInstanceIndex>,
170167
/// How strings are encoded.
171168
pub string_encoding: StringEncoding,
172169
/// The async callback function used by these options, if specified.
@@ -455,8 +452,7 @@ impl PartitionAdapterModules {
455452
// These items can't transitively depend on an adapter
456453
dfg::CoreDef::Trampoline(_)
457454
| dfg::CoreDef::InstanceFlags(_)
458-
| dfg::CoreDef::UnsafeIntrinsic(..)
459-
| dfg::CoreDef::TaskMayBlock => {}
455+
| dfg::CoreDef::UnsafeIntrinsic(..) => {}
460456
}
461457
}
462458

crates/environ/src/component/translate/inline.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,12 +1583,6 @@ impl<'a> Inliner<'a> {
15831583
let post_return = options.post_return.map(|i| frame.funcs[i].1.clone());
15841584
AdapterOptions {
15851585
instance: frame.instance,
1586-
ancestors: frames
1587-
.iter()
1588-
.rev()
1589-
.skip(1)
1590-
.map(|(frame, _)| frame.instance)
1591-
.collect(),
15921586
string_encoding: options.string_encoding,
15931587
callback,
15941588
post_return,

crates/environ/src/component/vmcomponent_offsets.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ pub struct VMComponentOffsets<P> {
4949
// plus this `VMComponentContext`'s total size. These are all computed by the
5050
// generated `compute_field_offsets` and read by the generated accessors of
5151
// the same names.
52-
task_may_block: u32,
5352
may_leave: u32,
5453
trampoline_func_refs: u32,
5554
intrinsic_func_refs: u32,
@@ -165,7 +164,6 @@ impl<P: PtrSize> VMComponentOffsets<P> {
165164
0
166165
},
167166
num_resources: component.num_resources,
168-
task_may_block: 0,
169167
may_leave: 0,
170168
trampoline_func_refs: 0,
171169
intrinsic_func_refs: 0,
@@ -183,7 +181,6 @@ impl<P: PtrSize> VMComponentOffsets<P> {
183181

184182
// The component-model flags must land where a compiler that only knows
185183
// the pointer size can find them.
186-
debug_assert_eq!(ret.task_may_block(), ret.ptr.vmcomponent().task_may_block());
187184
debug_assert!(
188185
(0..ret.num_runtime_component_instances)
189186
.map(RuntimeComponentInstanceIndex::from_u32)
@@ -248,8 +245,6 @@ mod tests {
248245
};
249246
let offsets = VMComponentOffsets::new(ptr, &component);
250247

251-
assert_eq!(offsets.task_may_block(), ptr.vmcomponent().task_may_block());
252-
253248
for i in 0..num_runtime_component_instances {
254249
let index = RuntimeComponentInstanceIndex::from_u32(i);
255250
assert_eq!(

crates/environ/src/fact.rs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ pub struct Module<'a> {
113113
helper_worklist: Vec<(FunctionId, Helper)>,
114114

115115
exports: Vec<(u32, String)>,
116-
117-
task_may_block: Option<GlobalIndex>,
118116
}
119117

120118
struct AdapterData {
@@ -137,9 +135,6 @@ struct AdapterOptions {
137135
/// The Wasmtime-assigned component instance index where the options were
138136
/// originally specified.
139137
instance: RuntimeComponentInstanceIndex,
140-
/// The ancestors (i.e. chain of instantiating instances) of the instance
141-
/// specified in the `instance` field.
142-
ancestors: Vec<RuntimeComponentInstanceIndex>,
143138
/// The ascribed type of this adapter.
144139
ty: TypeFuncIndex,
145140
/// The global that represents the instance flags for where this adapter
@@ -298,7 +293,6 @@ impl<'a> Module<'a> {
298293
imported_unsafe_intrinsics: HashMap::new(),
299294
imported_traps: HashMap::new(),
300295
exports: Vec::new(),
301-
task_may_block: None,
302296
}
303297
}
304298

@@ -352,7 +346,6 @@ impl<'a> Module<'a> {
352346
fn import_options(&mut self, ty: TypeFuncIndex, options: &AdapterOptionsDfg) -> AdapterOptions {
353347
let AdapterOptionsDfg {
354348
instance,
355-
ancestors,
356349
string_encoding,
357350
post_return: _, // handled above
358351
callback,
@@ -429,7 +422,6 @@ impl<'a> Module<'a> {
429422

430423
AdapterOptions {
431424
instance: *instance,
432-
ancestors: ancestors.clone(),
433425
ty,
434426
flags,
435427
post_return: None,
@@ -491,25 +483,6 @@ impl<'a> Module<'a> {
491483
idx
492484
}
493485

494-
fn import_task_may_block(&mut self) -> GlobalIndex {
495-
if let Some(task_may_block) = self.task_may_block {
496-
task_may_block
497-
} else {
498-
let task_may_block = self.import_global(
499-
"instance",
500-
"task_may_block",
501-
GlobalType {
502-
val_type: ValType::I32,
503-
mutable: true,
504-
shared: false,
505-
},
506-
CoreDef::TaskMayBlock,
507-
);
508-
self.task_may_block = Some(task_may_block);
509-
task_may_block
510-
}
511-
}
512-
513486
fn import_transcoder(&mut self, transcoder: transcode::Transcoder) -> FuncIndex {
514487
*self
515488
.imported_transcoders

0 commit comments

Comments
 (0)