Skip to content

Commit b271e45

Browse files
authored
consistently create thread and task when entering component instance (#12379)
* consistently create thread and task when entering component instance Previously, we weren't creating a new thread or task in all cases when entering a component instance, even when component model async features were enabled. In particular, entering an instance via a sync-to-sync, guest-to-guest adapter, via `Linker::instantiate[_async]`, or via `[Typed]Func::call` all skipped creating a thread or task, creating panics and/or instance mismatches in certain cases. This commit addresses all those cases and also adds assertions to all CM async intrinsics to verify that the caller instance matches the most-recently-pushed task. Note that we still skip pushing and popping threads and tasks if no CM async features are enabled in the `Config`. In order to populate the `GuestTask::instance` field for tasks created as part of `Linker::instantiate[_async]` calls, I had to add a `RuntimeComponentInstanceIndex` field to `GlobalInitializer::InstantiateModule` and friends so it would be available when needed. While testing this, I uncovered and fixed a couple of related issues: - We weren't checking the `may_leave` flag when guest-to-guest calling a resource destructor - We weren't checking whether a subtask was ready to delete (e.g. that no threads were still running) before attempting to delete it while deleting its supertask * fix warnings when component-model-async feature disabled * address review feedback * push a task when calling a resource dtor host-to-guest * add tests which call `thread.index` from realloc and post-return functions ...and assert that the indexes match as expected. Getting the post-return test to pass required moving the call to `StoreOpaque::exit_sync_call` to after the post-return function is called.
1 parent a6f4bd2 commit b271e45

File tree

29 files changed

+959
-295
lines changed

29 files changed

+959
-295
lines changed

crates/cranelift/src/compiler/component.rs

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Compilation support for the component model.
22
3-
use crate::{TRAP_INTERNAL_ASSERT, compiler::Compiler};
3+
use crate::{TRAP_CANNOT_LEAVE_COMPONENT, TRAP_INTERNAL_ASSERT, compiler::Compiler};
44
use cranelift_codegen::ir::condcodes::IntCC;
55
use cranelift_codegen::ir::{self, InstBuilder, MemFlags, Value};
66
use cranelift_codegen::isa::{CallConv, TargetIsa};
@@ -722,6 +722,22 @@ impl<'a> TrampolineCompiler<'a> {
722722
|_, _| {},
723723
);
724724
}
725+
Trampoline::EnterSyncCall => {
726+
self.translate_libcall(
727+
host::enter_sync_call,
728+
TrapSentinel::Falsy,
729+
WasmArgs::InRegisters,
730+
|_, _| {},
731+
);
732+
}
733+
Trampoline::ExitSyncCall => {
734+
self.translate_libcall(
735+
host::exit_sync_call,
736+
TrapSentinel::Falsy,
737+
WasmArgs::InRegisters,
738+
|_, _| {},
739+
);
740+
}
725741
Trampoline::ContextGet { instance, slot } => {
726742
self.translate_libcall(
727743
host::context_get,
@@ -1112,11 +1128,22 @@ impl<'a> TrampolineCompiler<'a> {
11121128
// brif should_run_destructor, run_destructor_block, return_block
11131129
//
11141130
// run_destructor_block:
1131+
// ;; test may_leave, but only if the component instances
1132+
// ;; differ
1133+
// flags = load.i32 vmctx+$instance_flags_offset
1134+
// masked = band flags, $FLAG_MAY_LEAVE
1135+
// trapz masked, $TRAP_CANNOT_LEAVE_COMPONENT
1136+
//
11151137
// ;; set may_block to false, saving the old value to restore
1116-
// ;; later, but only if the component instances differ
1138+
// ;; later, but only if the component instances differ and
1139+
// ;; concurrency is enabled
11171140
// old_may_block = load.i32 vmctx+$may_block_offset
11181141
// store 0, vmctx+$may_block_offset
11191142
//
1143+
// ;; call enter_sync_call, but only if the component instances
1144+
// ;; differ and concurrency is enabled
1145+
// ...
1146+
//
11201147
// ;; ============================================================
11211148
// ;; this is conditionally emitted based on whether the resource
11221149
// ;; has a destructor or not, and can be statically omitted
@@ -1132,6 +1159,12 @@ impl<'a> TrampolineCompiler<'a> {
11321159
// ;; restore old value of may_block
11331160
// store old_may_block, vmctx+$may_block_offset
11341161
//
1162+
// ;; if needed, call exit_sync_call
1163+
// ...
1164+
//
1165+
// ;; if needed, restore the old value of may_block
1166+
// store old_may_block, vmctx+$may_block_offset
1167+
//
11351168
// jump return_block
11361169
//
11371170
// return_block:
@@ -1161,29 +1194,69 @@ impl<'a> TrampolineCompiler<'a> {
11611194

11621195
self.builder.switch_to_block(run_destructor_block);
11631196

1164-
// If this is a defined resource within the component itself then the
1165-
// `may_block` field must be updated. Note though that this update can
1166-
// be elided if the resource table resides in the same component
1167-
// instance that defined the resource as the component is calling
1168-
// itself.
1197+
// If this is a component-defined resource, the `may_leave` flag must be
1198+
// checked. Additionally, if concurrency is enabled, the `may_block`
1199+
// field must be updated and `enter_sync_call` called. Note though that
1200+
// all of that may be elided if the resource table resides in the same
1201+
// component instance that defined the resource as the component is
1202+
// calling itself.
11691203
let old_may_block = if let Some(def) = resource_def {
11701204
if self.types[resource].unwrap_concrete_instance() != def.instance {
1171-
// Stash the old value of `may_block` and then set it to false.
1172-
let old_may_block = self.builder.ins().load(
1205+
let flags = self.builder.ins().load(
11731206
ir::types::I32,
11741207
trusted,
11751208
vmctx,
1176-
i32::try_from(self.offsets.task_may_block()).unwrap(),
1177-
);
1178-
let zero = self.builder.ins().iconst(ir::types::I32, i64::from(0));
1179-
self.builder.ins().store(
1180-
ir::MemFlags::trusted(),
1181-
zero,
1182-
vmctx,
1183-
i32::try_from(self.offsets.task_may_block()).unwrap(),
1209+
i32::try_from(
1210+
self.offsets
1211+
.instance_flags(self.types[resource].unwrap_concrete_instance()),
1212+
)
1213+
.unwrap(),
11841214
);
1215+
let masked = self
1216+
.builder
1217+
.ins()
1218+
.band_imm(flags, i64::from(FLAG_MAY_LEAVE));
1219+
self.builder
1220+
.ins()
1221+
.trapz(masked, TRAP_CANNOT_LEAVE_COMPONENT);
1222+
1223+
if self.compiler.tunables.component_model_concurrency {
1224+
// Stash the old value of `may_block` and then set it to false.
1225+
let old_may_block = self.builder.ins().load(
1226+
ir::types::I32,
1227+
trusted,
1228+
vmctx,
1229+
i32::try_from(self.offsets.task_may_block()).unwrap(),
1230+
);
1231+
let zero = self.builder.ins().iconst(ir::types::I32, i64::from(0));
1232+
self.builder.ins().store(
1233+
ir::MemFlags::trusted(),
1234+
zero,
1235+
vmctx,
1236+
i32::try_from(self.offsets.task_may_block()).unwrap(),
1237+
);
11851238

1186-
Some(old_may_block)
1239+
// Call `enter_sync_call`
1240+
//
1241+
// FIXME: Apply the optimizations described in #12311.
1242+
let host_args = vec![
1243+
vmctx,
1244+
self.builder
1245+
.ins()
1246+
.iconst(ir::types::I32, i64::from(instance.as_u32())),
1247+
self.builder.ins().iconst(ir::types::I32, i64::from(0)),
1248+
self.builder
1249+
.ins()
1250+
.iconst(ir::types::I32, i64::from(def.instance.as_u32())),
1251+
];
1252+
let call = self.call_libcall(vmctx, host::enter_sync_call, &host_args);
1253+
let result = self.builder.func.dfg.inst_results(call).get(0).copied();
1254+
self.raise_if_host_trapped(result.unwrap());
1255+
1256+
Some(old_may_block)
1257+
} else {
1258+
None
1259+
}
11871260
} else {
11881261
None
11891262
}
@@ -1240,6 +1313,14 @@ impl<'a> TrampolineCompiler<'a> {
12401313
}
12411314

12421315
if let Some(old_may_block) = old_may_block {
1316+
// Call `exit_sync_call`
1317+
//
1318+
// FIXME: Apply the optimizations described in #12311.
1319+
let call = self.call_libcall(vmctx, host::exit_sync_call, &[vmctx]);
1320+
let result = self.builder.func.dfg.inst_results(call).get(0).copied();
1321+
self.raise_if_host_trapped(result.unwrap());
1322+
1323+
// Restore the old value of `may_block`
12431324
self.builder.ins().store(
12441325
ir::MemFlags::trusted(),
12451326
old_may_block,

crates/cranelift/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ use self::compiler::Compiler;
4848

4949
const TRAP_INTERNAL_ASSERT: TrapCode = TrapCode::unwrap_user(1);
5050
const TRAP_OFFSET: u8 = 2;
51+
pub const TRAP_CANNOT_LEAVE_COMPONENT: TrapCode =
52+
TrapCode::unwrap_user(Trap::CannotLeaveComponent as u8 + TRAP_OFFSET);
5153
pub const TRAP_INDIRECT_CALL_TO_NULL: TrapCode =
5254
TrapCode::unwrap_user(Trap::IndirectCallToNull as u8 + TRAP_OFFSET);
5355
pub const TRAP_BAD_SIGNATURE: TrapCode =

crates/environ/src/component.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ macro_rules! foreach_builtin_component_function {
9999
resource_enter_call(vmctx: vmctx);
100100
resource_exit_call(vmctx: vmctx) -> bool;
101101

102+
#[cfg(feature = "component-model-async")]
103+
enter_sync_call(vmctx: vmctx, caller_instance: u32, callee_async: u32, callee_instance: u32) -> bool;
104+
#[cfg(feature = "component-model-async")]
105+
exit_sync_call(vmctx: vmctx) -> bool;
106+
102107
#[cfg(feature = "component-model-async")]
103108
backpressure_modify(vmctx: vmctx, caller_instance: u32, increment: u8) -> bool;
104109
#[cfg(feature = "component-model-async")]

crates/environ/src/component/dfg.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ pub enum SideEffect {
160160
/// as traps and the core wasm `start` function which may call component
161161
/// imports. Instantiation order from the original component must be done in
162162
/// the same order.
163-
Instance(InstanceId),
163+
Instance(InstanceId, RuntimeComponentInstanceIndex),
164164

165165
/// A resource was declared in this component.
166166
///
@@ -476,6 +476,8 @@ pub enum Trampoline {
476476
StreamTransfer,
477477
ErrorContextTransfer,
478478
Trap,
479+
EnterSyncCall,
480+
ExitSyncCall,
479481
ContextGet {
480482
instance: RuntimeComponentInstanceIndex,
481483
slot: u32,
@@ -721,16 +723,21 @@ enum RuntimeInstance {
721723
impl LinearizeDfg<'_> {
722724
fn side_effect(&mut self, effect: &SideEffect) {
723725
match effect {
724-
SideEffect::Instance(i) => {
725-
self.instantiate(*i, &self.dfg.instances[*i]);
726+
SideEffect::Instance(i, ci) => {
727+
self.instantiate(*i, &self.dfg.instances[*i], *ci);
726728
}
727729
SideEffect::Resource(i) => {
728730
self.resource(*i, &self.dfg.resources[*i]);
729731
}
730732
}
731733
}
732734

733-
fn instantiate(&mut self, instance: InstanceId, args: &Instance) {
735+
fn instantiate(
736+
&mut self,
737+
instance: InstanceId,
738+
args: &Instance,
739+
component_instance: RuntimeComponentInstanceIndex,
740+
) {
734741
log::trace!("creating instance {instance:?}");
735742
let instantiation = match args {
736743
Instance::Static(index, args) => InstantiateModule::Static(
@@ -751,8 +758,10 @@ impl LinearizeDfg<'_> {
751758
),
752759
};
753760
let index = RuntimeInstanceIndex::new(self.runtime_instances.len());
754-
self.initializers
755-
.push(GlobalInitializer::InstantiateModule(instantiation));
761+
self.initializers.push(GlobalInitializer::InstantiateModule(
762+
instantiation,
763+
Some(component_instance),
764+
));
756765
let prev = self
757766
.runtime_instances
758767
.insert(RuntimeInstance::Normal(instance), index);
@@ -1156,6 +1165,8 @@ impl LinearizeDfg<'_> {
11561165
Trampoline::StreamTransfer => info::Trampoline::StreamTransfer,
11571166
Trampoline::ErrorContextTransfer => info::Trampoline::ErrorContextTransfer,
11581167
Trampoline::Trap => info::Trampoline::Trap,
1168+
Trampoline::EnterSyncCall => info::Trampoline::EnterSyncCall,
1169+
Trampoline::ExitSyncCall => info::Trampoline::ExitSyncCall,
11591170
Trampoline::ContextGet { instance, slot } => info::Trampoline::ContextGet {
11601171
instance: *instance,
11611172
slot: *slot,
@@ -1242,7 +1253,7 @@ impl LinearizeDfg<'_> {
12421253
let (module_index, args) = &me.dfg.adapter_modules[adapter_module];
12431254
let args = args.iter().map(|arg| me.core_def(arg)).collect();
12441255
let instantiate = InstantiateModule::Static(*module_index, args);
1245-
GlobalInitializer::InstantiateModule(instantiate)
1256+
GlobalInitializer::InstantiateModule(instantiate, None)
12461257
},
12471258
|_, init| init,
12481259
)

crates/environ/src/component/info.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,10 @@ pub enum GlobalInitializer {
243243
/// involve running the `start` function of the instance as well if it's
244244
/// specified. This largely delegates to the same standard instantiation
245245
/// process as the rest of the core wasm machinery already uses.
246-
InstantiateModule(InstantiateModule),
246+
///
247+
/// The second field represents the component instance to which the module
248+
/// belongs, if applicable. This will be `None` for adapter modules.
249+
InstantiateModule(InstantiateModule, Option<RuntimeComponentInstanceIndex>),
247250

248251
/// A host function is being lowered, creating a core wasm function.
249252
///
@@ -1109,6 +1112,13 @@ pub enum Trampoline {
11091112
/// code.
11101113
Trap,
11111114

1115+
/// An intrinsic used by FACT-generated modules to push a task onto the
1116+
/// stack for a sync-to-sync, guest-to-guest call.
1117+
EnterSyncCall,
1118+
/// An intrinsic used by FACT-generated modules to pop the task previously
1119+
/// pushed by `EnterSyncCall`.
1120+
ExitSyncCall,
1121+
11121122
/// Intrinsic used to implement the `context.get` component model builtin.
11131123
///
11141124
/// The payload here represents that this is accessing the Nth slot of local
@@ -1238,6 +1248,8 @@ impl Trampoline {
12381248
StreamTransfer => format!("stream-transfer"),
12391249
ErrorContextTransfer => format!("error-context-transfer"),
12401250
Trap => format!("trap"),
1251+
EnterSyncCall => format!("enter-sync-call"),
1252+
ExitSyncCall => format!("exit-sync-call"),
12411253
ContextGet { .. } => format!("context-get"),
12421254
ContextSet { .. } => format!("context-set"),
12431255
ThreadIndex => format!("thread-index"),

crates/environ/src/component/translate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ impl<'a, 'data> Translator<'a, 'data> {
551551
PrimaryMap::<RuntimeInstanceIndex, PackedOption<StaticModuleIndex>>::new();
552552
for init in &translation.component.initializers {
553553
match init {
554-
GlobalInitializer::InstantiateModule(instantiation) => match instantiation {
554+
GlobalInitializer::InstantiateModule(instantiation, _) => match instantiation {
555555
InstantiateModule::Static(module, args) => {
556556
instantiations[*module].join(AbstractInstantiations::One(&*args));
557557
instance_to_module.push(Some(*module).into());

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,7 @@ impl<'data> Translator<'_, 'data> {
208208
// the module using standard core wasm translation, and then fills out
209209
// the dfg metadata for each adapter.
210210
for (module_id, adapter_module) in state.adapter_modules.iter() {
211-
let mut module =
212-
fact::Module::new(self.types.types(), self.tunables.debug_adapter_modules);
211+
let mut module = fact::Module::new(self.types.types(), self.tunables);
213212
let mut names = Vec::with_capacity(adapter_module.adapters.len());
214213
for adapter in adapter_module.adapters.iter() {
215214
let name = format!("adapter{}", adapter.as_u32());
@@ -349,6 +348,8 @@ fn fact_import_to_core_def(
349348
simple_intrinsic(dfg::Trampoline::ErrorContextTransfer)
350349
}
351350
fact::Import::Trap => simple_intrinsic(dfg::Trampoline::Trap),
351+
fact::Import::EnterSyncCall => simple_intrinsic(dfg::Trampoline::EnterSyncCall),
352+
fact::Import::ExitSyncCall => simple_intrinsic(dfg::Trampoline::ExitSyncCall),
352353
}
353354
}
354355

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,7 @@ impl<'a> Inliner<'a> {
12161216

12171217
self.result
12181218
.side_effects
1219-
.push(dfg::SideEffect::Instance(instance));
1219+
.push(dfg::SideEffect::Instance(instance, frame.instance));
12201220

12211221
frame
12221222
.module_instances

0 commit comments

Comments
 (0)