diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index fc583c06827d..6a6c5b8224a7 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -1,6 +1,6 @@ //! Compilation support for the component model. -use crate::{TRAP_INTERNAL_ASSERT, compiler::Compiler}; +use crate::{TRAP_CANNOT_LEAVE_COMPONENT, TRAP_INTERNAL_ASSERT, compiler::Compiler}; use cranelift_codegen::ir::condcodes::IntCC; use cranelift_codegen::ir::{self, InstBuilder, MemFlags, Value}; use cranelift_codegen::isa::{CallConv, TargetIsa}; @@ -722,6 +722,22 @@ impl<'a> TrampolineCompiler<'a> { |_, _| {}, ); } + Trampoline::EnterSyncCall => { + self.translate_libcall( + host::enter_sync_call, + TrapSentinel::Falsy, + WasmArgs::InRegisters, + |_, _| {}, + ); + } + Trampoline::ExitSyncCall => { + self.translate_libcall( + host::exit_sync_call, + TrapSentinel::Falsy, + WasmArgs::InRegisters, + |_, _| {}, + ); + } Trampoline::ContextGet { instance, slot } => { self.translate_libcall( host::context_get, @@ -1112,11 +1128,22 @@ impl<'a> TrampolineCompiler<'a> { // brif should_run_destructor, run_destructor_block, return_block // // run_destructor_block: + // ;; test may_leave, but only if the component instances + // ;; differ + // flags = load.i32 vmctx+$instance_flags_offset + // masked = band flags, $FLAG_MAY_LEAVE + // trapz masked, $TRAP_CANNOT_LEAVE_COMPONENT + // // ;; set may_block to false, saving the old value to restore - // ;; later, but only if the component instances differ + // ;; later, but only if the component instances differ and + // ;; concurrency is enabled // old_may_block = load.i32 vmctx+$may_block_offset // store 0, vmctx+$may_block_offset // + // ;; call enter_sync_call, but only if the component instances + // ;; differ and concurrency is enabled + // ... + // // ;; ============================================================ // ;; this is conditionally emitted based on whether the resource // ;; has a destructor or not, and can be statically omitted @@ -1132,6 +1159,12 @@ impl<'a> TrampolineCompiler<'a> { // ;; restore old value of may_block // store old_may_block, vmctx+$may_block_offset // + // ;; if needed, call exit_sync_call + // ... + // + // ;; if needed, restore the old value of may_block + // store old_may_block, vmctx+$may_block_offset + // // jump return_block // // return_block: @@ -1161,29 +1194,69 @@ impl<'a> TrampolineCompiler<'a> { self.builder.switch_to_block(run_destructor_block); - // If this is a defined resource within the component itself then the - // `may_block` field must be updated. Note though that this update can - // be elided if the resource table resides in the same component - // instance that defined the resource as the component is calling - // itself. + // If this is a component-defined resource, the `may_leave` flag must be + // checked. Additionally, if concurrency is enabled, the `may_block` + // field must be updated and `enter_sync_call` called. Note though that + // all of that may be elided if the resource table resides in the same + // component instance that defined the resource as the component is + // calling itself. let old_may_block = if let Some(def) = resource_def { if self.types[resource].unwrap_concrete_instance() != def.instance { - // Stash the old value of `may_block` and then set it to false. - let old_may_block = self.builder.ins().load( + let flags = self.builder.ins().load( ir::types::I32, trusted, vmctx, - i32::try_from(self.offsets.task_may_block()).unwrap(), - ); - let zero = self.builder.ins().iconst(ir::types::I32, i64::from(0)); - self.builder.ins().store( - ir::MemFlags::trusted(), - zero, - vmctx, - i32::try_from(self.offsets.task_may_block()).unwrap(), + i32::try_from( + self.offsets + .instance_flags(self.types[resource].unwrap_concrete_instance()), + ) + .unwrap(), ); + let masked = self + .builder + .ins() + .band_imm(flags, i64::from(FLAG_MAY_LEAVE)); + self.builder + .ins() + .trapz(masked, TRAP_CANNOT_LEAVE_COMPONENT); + + if self.compiler.tunables.component_model_concurrency { + // Stash the old value of `may_block` and then set it to false. + let old_may_block = self.builder.ins().load( + ir::types::I32, + trusted, + vmctx, + i32::try_from(self.offsets.task_may_block()).unwrap(), + ); + let zero = self.builder.ins().iconst(ir::types::I32, i64::from(0)); + self.builder.ins().store( + ir::MemFlags::trusted(), + zero, + vmctx, + i32::try_from(self.offsets.task_may_block()).unwrap(), + ); - Some(old_may_block) + // Call `enter_sync_call` + // + // FIXME: Apply the optimizations described in #12311. + let host_args = vec![ + vmctx, + self.builder + .ins() + .iconst(ir::types::I32, i64::from(instance.as_u32())), + self.builder.ins().iconst(ir::types::I32, i64::from(0)), + self.builder + .ins() + .iconst(ir::types::I32, i64::from(def.instance.as_u32())), + ]; + let call = self.call_libcall(vmctx, host::enter_sync_call, &host_args); + let result = self.builder.func.dfg.inst_results(call).get(0).copied(); + self.raise_if_host_trapped(result.unwrap()); + + Some(old_may_block) + } else { + None + } } else { None } @@ -1240,6 +1313,14 @@ impl<'a> TrampolineCompiler<'a> { } if let Some(old_may_block) = old_may_block { + // Call `exit_sync_call` + // + // FIXME: Apply the optimizations described in #12311. + let call = self.call_libcall(vmctx, host::exit_sync_call, &[vmctx]); + let result = self.builder.func.dfg.inst_results(call).get(0).copied(); + self.raise_if_host_trapped(result.unwrap()); + + // Restore the old value of `may_block` self.builder.ins().store( ir::MemFlags::trusted(), old_may_block, diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 5ebcf2bcd158..1d1ac61cfcc5 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -48,6 +48,8 @@ use self::compiler::Compiler; const TRAP_INTERNAL_ASSERT: TrapCode = TrapCode::unwrap_user(1); const TRAP_OFFSET: u8 = 2; +pub const TRAP_CANNOT_LEAVE_COMPONENT: TrapCode = + TrapCode::unwrap_user(Trap::CannotLeaveComponent as u8 + TRAP_OFFSET); pub const TRAP_INDIRECT_CALL_TO_NULL: TrapCode = TrapCode::unwrap_user(Trap::IndirectCallToNull as u8 + TRAP_OFFSET); pub const TRAP_BAD_SIGNATURE: TrapCode = diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs index 377a98fe95f1..edc02acac524 100644 --- a/crates/environ/src/component.rs +++ b/crates/environ/src/component.rs @@ -99,6 +99,11 @@ macro_rules! foreach_builtin_component_function { resource_enter_call(vmctx: vmctx); resource_exit_call(vmctx: vmctx) -> bool; + #[cfg(feature = "component-model-async")] + enter_sync_call(vmctx: vmctx, caller_instance: u32, callee_async: u32, callee_instance: u32) -> bool; + #[cfg(feature = "component-model-async")] + exit_sync_call(vmctx: vmctx) -> bool; + #[cfg(feature = "component-model-async")] backpressure_modify(vmctx: vmctx, caller_instance: u32, increment: u8) -> bool; #[cfg(feature = "component-model-async")] diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index 1c7e9910744d..f1c4cef3f40f 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -160,7 +160,7 @@ pub enum SideEffect { /// as traps and the core wasm `start` function which may call component /// imports. Instantiation order from the original component must be done in /// the same order. - Instance(InstanceId), + Instance(InstanceId, RuntimeComponentInstanceIndex), /// A resource was declared in this component. /// @@ -476,6 +476,8 @@ pub enum Trampoline { StreamTransfer, ErrorContextTransfer, Trap, + EnterSyncCall, + ExitSyncCall, ContextGet { instance: RuntimeComponentInstanceIndex, slot: u32, @@ -721,8 +723,8 @@ enum RuntimeInstance { impl LinearizeDfg<'_> { fn side_effect(&mut self, effect: &SideEffect) { match effect { - SideEffect::Instance(i) => { - self.instantiate(*i, &self.dfg.instances[*i]); + SideEffect::Instance(i, ci) => { + self.instantiate(*i, &self.dfg.instances[*i], *ci); } SideEffect::Resource(i) => { self.resource(*i, &self.dfg.resources[*i]); @@ -730,7 +732,12 @@ impl LinearizeDfg<'_> { } } - fn instantiate(&mut self, instance: InstanceId, args: &Instance) { + fn instantiate( + &mut self, + instance: InstanceId, + args: &Instance, + component_instance: RuntimeComponentInstanceIndex, + ) { log::trace!("creating instance {instance:?}"); let instantiation = match args { Instance::Static(index, args) => InstantiateModule::Static( @@ -751,8 +758,10 @@ impl LinearizeDfg<'_> { ), }; let index = RuntimeInstanceIndex::new(self.runtime_instances.len()); - self.initializers - .push(GlobalInitializer::InstantiateModule(instantiation)); + self.initializers.push(GlobalInitializer::InstantiateModule( + instantiation, + Some(component_instance), + )); let prev = self .runtime_instances .insert(RuntimeInstance::Normal(instance), index); @@ -1156,6 +1165,8 @@ impl LinearizeDfg<'_> { Trampoline::StreamTransfer => info::Trampoline::StreamTransfer, Trampoline::ErrorContextTransfer => info::Trampoline::ErrorContextTransfer, Trampoline::Trap => info::Trampoline::Trap, + Trampoline::EnterSyncCall => info::Trampoline::EnterSyncCall, + Trampoline::ExitSyncCall => info::Trampoline::ExitSyncCall, Trampoline::ContextGet { instance, slot } => info::Trampoline::ContextGet { instance: *instance, slot: *slot, @@ -1242,7 +1253,7 @@ impl LinearizeDfg<'_> { let (module_index, args) = &me.dfg.adapter_modules[adapter_module]; let args = args.iter().map(|arg| me.core_def(arg)).collect(); let instantiate = InstantiateModule::Static(*module_index, args); - GlobalInitializer::InstantiateModule(instantiate) + GlobalInitializer::InstantiateModule(instantiate, None) }, |_, init| init, ) diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index ea1cb94f7ea9..2aff54ae68d3 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -243,7 +243,10 @@ pub enum GlobalInitializer { /// involve running the `start` function of the instance as well if it's /// specified. This largely delegates to the same standard instantiation /// process as the rest of the core wasm machinery already uses. - InstantiateModule(InstantiateModule), + /// + /// The second field represents the component instance to which the module + /// belongs, if applicable. This will be `None` for adapter modules. + InstantiateModule(InstantiateModule, Option), /// A host function is being lowered, creating a core wasm function. /// @@ -1109,6 +1112,13 @@ pub enum Trampoline { /// code. Trap, + /// An intrinsic used by FACT-generated modules to push a task onto the + /// stack for a sync-to-sync, guest-to-guest call. + EnterSyncCall, + /// An intrinsic used by FACT-generated modules to pop the task previously + /// pushed by `EnterSyncCall`. + ExitSyncCall, + /// Intrinsic used to implement the `context.get` component model builtin. /// /// The payload here represents that this is accessing the Nth slot of local @@ -1238,6 +1248,8 @@ impl Trampoline { StreamTransfer => format!("stream-transfer"), ErrorContextTransfer => format!("error-context-transfer"), Trap => format!("trap"), + EnterSyncCall => format!("enter-sync-call"), + ExitSyncCall => format!("exit-sync-call"), ContextGet { .. } => format!("context-get"), ContextSet { .. } => format!("context-set"), ThreadIndex => format!("thread-index"), diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 99b8ae3b9e2a..78bdb0307ed6 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -551,7 +551,7 @@ impl<'a, 'data> Translator<'a, 'data> { PrimaryMap::>::new(); for init in &translation.component.initializers { match init { - GlobalInitializer::InstantiateModule(instantiation) => match instantiation { + GlobalInitializer::InstantiateModule(instantiation, _) => match instantiation { InstantiateModule::Static(module, args) => { instantiations[*module].join(AbstractInstantiations::One(&*args)); instance_to_module.push(Some(*module).into()); diff --git a/crates/environ/src/component/translate/adapt.rs b/crates/environ/src/component/translate/adapt.rs index d528099a2069..618934ec707b 100644 --- a/crates/environ/src/component/translate/adapt.rs +++ b/crates/environ/src/component/translate/adapt.rs @@ -208,8 +208,7 @@ impl<'data> Translator<'_, 'data> { // the module using standard core wasm translation, and then fills out // the dfg metadata for each adapter. for (module_id, adapter_module) in state.adapter_modules.iter() { - let mut module = - fact::Module::new(self.types.types(), self.tunables.debug_adapter_modules); + let mut module = fact::Module::new(self.types.types(), self.tunables); let mut names = Vec::with_capacity(adapter_module.adapters.len()); for adapter in adapter_module.adapters.iter() { let name = format!("adapter{}", adapter.as_u32()); @@ -349,6 +348,8 @@ fn fact_import_to_core_def( simple_intrinsic(dfg::Trampoline::ErrorContextTransfer) } fact::Import::Trap => simple_intrinsic(dfg::Trampoline::Trap), + fact::Import::EnterSyncCall => simple_intrinsic(dfg::Trampoline::EnterSyncCall), + fact::Import::ExitSyncCall => simple_intrinsic(dfg::Trampoline::ExitSyncCall), } } diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index b6004c62ce1b..8492b9e04ae3 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -1216,7 +1216,7 @@ impl<'a> Inliner<'a> { self.result .side_effects - .push(dfg::SideEffect::Instance(instance)); + .push(dfg::SideEffect::Instance(instance, frame.instance)); frame .module_instances diff --git a/crates/environ/src/fact.rs b/crates/environ/src/fact.rs index 77011665a017..f3a131ba15f8 100644 --- a/crates/environ/src/fact.rs +++ b/crates/environ/src/fact.rs @@ -24,7 +24,7 @@ use crate::component::{ RuntimeComponentInstanceIndex, StringEncoding, Transcode, TypeFuncIndex, }; use crate::fact::transcode::Transcoder; -use crate::{EntityRef, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap}; +use crate::{EntityRef, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap, Tunables}; use crate::{ModuleInternedTypeIndex, prelude::*}; use std::collections::HashMap; use wasm_encoder::*; @@ -52,8 +52,8 @@ pub static PREPARE_CALL_FIXED_PARAMS: &[ValType] = &[ /// Representation of an adapter module. pub struct Module<'a> { - /// Whether or not debug code is inserted into the adapters themselves. - debug: bool, + /// Compilation configuration + tunables: &'a Tunables, /// Type information from the creator of this `Module` types: &'a ComponentTypesBuilder, @@ -88,6 +88,9 @@ pub struct Module<'a> { imported_stream_transfer: Option, imported_error_context_transfer: Option, + imported_enter_sync_call: Option, + imported_exit_sync_call: Option, + imported_trap: Option, // Current status of index spaces from the imports generated so far. @@ -241,9 +244,9 @@ enum HelperLocation { impl<'a> Module<'a> { /// Creates an empty module. - pub fn new(types: &'a ComponentTypesBuilder, debug: bool) -> Module<'a> { + pub fn new(types: &'a ComponentTypesBuilder, tunables: &'a Tunables) -> Module<'a> { Module { - debug, + tunables, types, core_types: Default::default(), core_imports: Default::default(), @@ -264,6 +267,8 @@ impl<'a> Module<'a> { imported_future_transfer: None, imported_stream_transfer: None, imported_error_context_transfer: None, + imported_enter_sync_call: None, + imported_exit_sync_call: None, imported_trap: None, exports: Vec::new(), task_may_block: None, @@ -737,6 +742,28 @@ impl<'a> Module<'a> { ) } + fn import_enter_sync_call(&mut self) -> FuncIndex { + self.import_simple( + "async", + "enter-sync-call", + &[ValType::I32; 3], + &[], + Import::EnterSyncCall, + |me| &mut me.imported_enter_sync_call, + ) + } + + fn import_exit_sync_call(&mut self) -> FuncIndex { + self.import_simple( + "async", + "exit-sync-call", + &[], + &[], + Import::ExitSyncCall, + |me| &mut me.imported_exit_sync_call, + ) + } + fn import_trap(&mut self) -> FuncIndex { self.import_simple( "runtime", @@ -893,6 +920,13 @@ pub enum Import { ErrorContextTransfer, /// An intrinsic for trapping the instance with a specific trap code. Trap, + /// An intrinsic used by FACT-generated modules to check whether an instance + /// may be entered for a sync-to-sync call and push a task onto the stack if + /// so. + EnterSyncCall, + /// An intrinsic used by FACT-generated modules to pop the task previously + /// pushed by `EnterSyncCall`. + ExitSyncCall, } impl Options { diff --git a/crates/environ/src/fact/trampoline.rs b/crates/environ/src/fact/trampoline.rs index 928fdaa72e71..160949ae06b8 100644 --- a/crates/environ/src/fact/trampoline.rs +++ b/crates/environ/src/fact/trampoline.rs @@ -180,6 +180,8 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) { compiler.compile_sync_to_sync_adapter(adapter, &lower_sig, &lift_sig) } (true, true) => { + assert!(module.tunables.component_model_concurrency); + // In the async->async case, we must compile a couple of helper functions: // // - `async-start`: copies the parameters from the caller to the callee @@ -207,6 +209,8 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) { ); } (false, true) => { + assert!(module.tunables.component_model_concurrency); + // Like the async->async case above, for the sync->async case we // also need `async-start` and `async-return` helper functions to // allow the callee to asynchronously "pull" the parameters and @@ -231,6 +235,8 @@ pub(super) fn compile(module: &mut Module<'_>, adapter: &AdapterData) { ); } (true, false) => { + assert!(module.tunables.component_model_concurrency); + // As with the async->async and sync->async cases above, for the // async->sync case we use `async-start` and `async-return` helper // functions. Here, those functions allow the host to enforce @@ -753,21 +759,46 @@ impl<'a, 'b> Compiler<'a, 'b> { Trap::CannotLeaveComponent, ); - let task_may_block = self.module.import_task_may_block(); - let old_task_may_block = if self.types[adapter.lift.ty].async_ { - self.instruction(GlobalGet(task_may_block.as_u32())); - self.instruction(I32Eqz); - self.instruction(If(BlockType::Empty)); - self.trap(Trap::CannotBlockSyncTask); - self.instruction(End); - None - } else { + let old_task_may_block = if self.module.tunables.component_model_concurrency { + // Save, clear, and later restore the `may_block` field. let task_may_block = self.module.import_task_may_block(); - self.instruction(GlobalGet(task_may_block.as_u32())); - let old_task_may_block = self.local_set_new_tmp(ValType::I32); - self.instruction(I32Const(0)); - self.instruction(GlobalSet(task_may_block.as_u32())); - Some(old_task_may_block) + let old_task_may_block = if self.types[adapter.lift.ty].async_ { + self.instruction(GlobalGet(task_may_block.as_u32())); + self.instruction(I32Eqz); + self.instruction(If(BlockType::Empty)); + self.trap(Trap::CannotBlockSyncTask); + self.instruction(End); + None + } else { + let task_may_block = self.module.import_task_may_block(); + self.instruction(GlobalGet(task_may_block.as_u32())); + let old_task_may_block = self.local_set_new_tmp(ValType::I32); + self.instruction(I32Const(0)); + self.instruction(GlobalSet(task_may_block.as_u32())); + Some(old_task_may_block) + }; + + // Push a task onto the current task stack. + // + // FIXME: Apply the optimizations described in #12311. + + self.instruction(I32Const( + i32::try_from(adapter.lower.instance.as_u32()).unwrap(), + )); + self.instruction(I32Const(if self.types[adapter.lift.ty].async_ { + 1 + } else { + 0 + })); + self.instruction(I32Const( + i32::try_from(adapter.lift.instance.as_u32()).unwrap(), + )); + let enter_sync_call = self.module.import_enter_sync_call(); + self.instruction(Call(enter_sync_call.as_u32())); + + old_task_may_block + } else { + None }; if self.emit_resource_call { @@ -840,11 +871,20 @@ impl<'a, 'b> Compiler<'a, 'b> { self.instruction(Call(exit.as_u32())); } - if let Some(old_task_may_block) = old_task_may_block { - let task_may_block = self.module.import_task_may_block(); - self.instruction(LocalGet(old_task_may_block.idx)); - self.instruction(GlobalSet(task_may_block.as_u32())); - self.free_temp_local(old_task_may_block); + if self.module.tunables.component_model_concurrency { + // Pop the task we pushed earlier off of the current task stack. + // + // FIXME: Apply the optimizations described in #12311. + let exit_sync_call = self.module.import_exit_sync_call(); + self.instruction(Call(exit_sync_call.as_u32())); + + // Restore old `may_block_field` + if let Some(old_task_may_block) = old_task_may_block { + let task_may_block = self.module.import_task_may_block(); + self.instruction(LocalGet(old_task_may_block.idx)); + self.instruction(GlobalSet(task_may_block.as_u32())); + self.free_temp_local(old_task_may_block); + } } self.finish() @@ -1953,7 +1993,7 @@ impl<'a, 'b> Compiler<'a, 'b> { // In debug mode verify the first result consumed the entire string, // otherwise simply discard it. - if self.module.debug { + if self.module.tunables.debug_adapter_modules { self.instruction(LocalGet(src.len.idx)); self.instruction(LocalGet(src_len_tmp.idx)); self.ptr_sub(src_mem_opts); @@ -1980,7 +2020,7 @@ impl<'a, 'b> Compiler<'a, 'b> { // If the first transcode was enough then assert that the returned // amount of destination items written equals the byte size. - if self.module.debug { + if self.module.tunables.debug_adapter_modules { self.instruction(Else); self.instruction(LocalGet(dst.len.idx)); @@ -2151,7 +2191,7 @@ impl<'a, 'b> Compiler<'a, 'b> { // Assert that the untagged code unit length is the same as the // source code unit length. - if self.module.debug { + if self.module.tunables.debug_adapter_modules { self.instruction(LocalGet(dst.len.idx)); self.ptr_uconst(dst_mem_opts, !UTF16_TAG); self.ptr_and(dst_mem_opts); @@ -3424,7 +3464,7 @@ impl<'a, 'b> Compiler<'a, 'b> { fn assert_aligned(&mut self, ty: &InterfaceType, mem: &Memory) { let mem_opts = mem.mem_opts(); - if !self.module.debug { + if !self.module.tunables.debug_adapter_modules { return; } let align = self.types.align(mem_opts, ty); @@ -3614,7 +3654,7 @@ impl<'a, 'b> Compiler<'a, 'b> { } fn assert_i64_upper_bits_not_set(&mut self, local: u32) { - if !self.module.debug { + if !self.module.tunables.debug_adapter_modules { return; } self.instruction(LocalGet(local)); diff --git a/crates/environ/src/tunables.rs b/crates/environ/src/tunables.rs index e68acedb8194..5a958a313a8b 100644 --- a/crates/environ/src/tunables.rs +++ b/crates/environ/src/tunables.rs @@ -140,6 +140,10 @@ define_tunables! { /// The general size threshold for the sum of the caller's and callee's /// sizes, past which we will generally not inline calls anymore. pub inlining_sum_size_threshold: u32, + + /// Whether any component model feature related to concurrency is + /// enabled. + pub component_model_concurrency: bool, } pub struct ConfigTunables { @@ -215,6 +219,7 @@ impl Tunables { inlining_small_callee_size: 50, inlining_sum_size_threshold: 2000, debug_guest: false, + component_model_concurrency: true, } } diff --git a/crates/test-programs/src/bin/async_yield_caller_cancel.rs b/crates/test-programs/src/bin/async_yield_caller_cancel.rs index 807f644ee5f2..01e1ddd6ec77 100644 --- a/crates/test-programs/src/bin/async_yield_caller_cancel.rs +++ b/crates/test-programs/src/bin/async_yield_caller_cancel.rs @@ -9,10 +9,7 @@ mod bindings { } use { - bindings::{ - exports::local::local::run::Guest, - local::local::{continue_, ready}, - }, + bindings::{exports::local::local::run::Guest, local::local::continue_}, test_programs::async_::{STATUS_RETURNED, STATUS_STARTED, subtask_cancel}, }; @@ -31,8 +28,6 @@ struct Component; impl Guest for Component { async fn run() { - let thing = ready::Thing::new(); - thing.set_ready(true); continue_::set_continue(true); unsafe { diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 8998bac2b3c0..d500ed9c372f 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -2430,6 +2430,11 @@ impl Config { ); } + #[cfg(feature = "component-model")] + { + tunables.component_model_concurrency = self.cm_concurrency_enabled(); + } + Ok((tunables, features)) } @@ -2922,17 +2927,13 @@ impl Config { #[inline] pub(crate) fn cm_concurrency_enabled(&self) -> bool { cfg!(feature = "component-model-async") - && (self.enabled_features.contains(WasmFeatures::CM_ASYNC) - || self - .enabled_features - .contains(WasmFeatures::CM_ASYNC_BUILTINS) - || self - .enabled_features - .contains(WasmFeatures::CM_ASYNC_STACKFUL) - || self.enabled_features.contains(WasmFeatures::CM_THREADING) - || self - .enabled_features - .contains(WasmFeatures::CM_ERROR_CONTEXT)) + && self.enabled_features.intersects( + WasmFeatures::CM_ASYNC + | WasmFeatures::CM_ASYNC_BUILTINS + | WasmFeatures::CM_ASYNC_STACKFUL + | WasmFeatures::CM_THREADING + | WasmFeatures::CM_ERROR_CONTEXT, + ) } } diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index 3584e07b06df..190183971248 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -285,6 +285,7 @@ impl Metadata<'_> { inlining_intra_module, inlining_small_callee_size, inlining_sum_size_threshold, + component_model_concurrency, // This doesn't affect compilation, it's just a runtime setting. memory_reservation_for_growth: _, @@ -365,6 +366,11 @@ impl Metadata<'_> { other.inlining_sum_size_threshold, "function inlining sum-size threshold", )?; + Self::check_bool( + component_model_concurrency, + other.component_model_concurrency, + "component model concurrency", + )?; Self::check_intra_module_inlining(inlining_intra_module, other.inlining_intra_module)?; Ok(()) diff --git a/crates/wasmtime/src/runtime/component/component.rs b/crates/wasmtime/src/runtime/component/component.rs index 495f859b4bde..45f1e0382147 100644 --- a/crates/wasmtime/src/runtime/component/component.rs +++ b/crates/wasmtime/src/runtime/component/component.rs @@ -653,7 +653,7 @@ impl Component { }; for init in &self.env_component().initializers { match init { - GlobalInitializer::InstantiateModule(inst) => match inst { + GlobalInitializer::InstantiateModule(inst, _) => match inst { InstantiateModule::Static(index, _) => { let module = self.static_module(*index); resources.add(&module.resources_required()); diff --git a/crates/wasmtime/src/runtime/component/concurrent.rs b/crates/wasmtime/src/runtime/component/concurrent.rs index 52f5227d0503..fcff9d596511 100644 --- a/crates/wasmtime/src/runtime/component/concurrent.rs +++ b/crates/wasmtime/src/runtime/component/concurrent.rs @@ -94,6 +94,7 @@ use wasmtime_environ::component::{ TypeComponentGlobalErrorContextTableIndex, TypeComponentLocalErrorContextTableIndex, TypeFuncIndex, TypeFutureTableIndex, TypeStreamTableIndex, TypeTupleIndex, }; +use wasmtime_environ::packed_option::ReservedValue; pub use abort::JoinHandle; pub use future_stream_any::{FutureAny, StreamAny}; @@ -720,19 +721,11 @@ pub(crate) fn poll_and_block( future: impl Future> + Send + 'static, caller_instance: RuntimeInstance, ) -> Result { + store.check_may_leave(caller_instance)?; + let state = store.concurrent_state_mut(); - // If there is no current guest thread set, that means the host function was - // registered using e.g. `LinkerInstance::func_wrap`, in which case it - // should complete immediately. - let Some(caller) = state.guest_thread else { - return match pin!(future).poll(&mut Context::from_waker(&Waker::noop())) { - Poll::Ready(result) => result, - Poll::Pending => { - unreachable!() - } - }; - }; + let caller = state.guest_thread.unwrap(); // Save any existing result stashed in `GuestTask::result` so we can replace // it with the new result. @@ -1391,6 +1384,128 @@ impl StoreContextMut<'_, T> { } impl StoreOpaque { + fn check_may_leave(&mut self, instance: RuntimeInstance) -> Result<()> { + Instance::from_wasmtime(self, instance.instance) + .id() + .get(self) + .check_may_leave(instance.index)?; + + // While we're here, verify that the caller instance matches the most + // recent task pushed onto the task stack: + let state = self.concurrent_state_mut(); + let caller = state.guest_thread.unwrap(); + assert_eq!(state.get_mut(caller.task)?.instance, instance); + + Ok(()) + } + + /// Push a `GuestTask` onto the task stack for either a sync-to-sync, + /// guest-to-guest call or a sync host-to-guest call. + /// + /// This task will only be used for the purpose of handling calls to + /// intrinsic functions; both parameter lowering and result lifting are + /// assumed to be taken care of elsewhere. + pub(crate) fn enter_sync_call( + &mut self, + guest_caller: Option, + callee_async: bool, + callee: RuntimeInstance, + ) -> Result<()> { + log::trace!("enter sync call {callee:?}"); + + let state = self.concurrent_state_mut(); + let thread = state.guest_thread; + let instance = if let Some(thread) = thread { + Some(state.get_mut(thread.task)?.instance) + } else { + None + }; + let task = GuestTask::new( + state, + Box::new(move |_, _| unreachable!()), + LiftResult { + lift: Box::new(move |_, _| unreachable!()), + ty: TypeTupleIndex::reserved_value(), + memory: None, + string_encoding: StringEncoding::Utf8, + }, + if let Some(caller) = guest_caller { + assert_eq!(caller, instance.unwrap()); + Caller::Guest { + thread: thread.unwrap(), + } + } else { + Caller::Host { + tx: None, + exit_tx: Arc::new(oneshot::channel().0), + host_future_present: false, + call_post_return_automatically: false, + caller: state.guest_thread, + } + }, + None, + callee, + callee_async, + )?; + + let guest_task = state.push(task)?; + let new_thread = GuestThread::new_implicit(guest_task); + let guest_thread = state.push(new_thread)?; + Instance::from_wasmtime(self, callee.instance).add_guest_thread_to_instance_table( + guest_thread, + self, + callee.index, + )?; + + let state = self.concurrent_state_mut(); + state.get_mut(guest_task)?.threads.insert(guest_thread); + if guest_caller.is_some() { + let thread = state.guest_thread.unwrap(); + state.get_mut(thread.task)?.subtasks.insert(guest_task); + } + + self.set_thread(Some(QualifiedThreadId { + task: guest_task, + thread: guest_thread, + })); + + Ok(()) + } + + /// Pop a `GuestTask` previously pushed using `enter_sync_call`. + pub(crate) fn exit_sync_call(&mut self, guest_caller: bool) -> Result<()> { + let thread = self.set_thread(None).unwrap(); + let instance = self.concurrent_state_mut().get_mut(thread.task)?.instance; + log::trace!("exit sync call {instance:?}"); + Instance::from_wasmtime(self, instance.instance).cleanup_thread( + self, + thread, + instance.index, + )?; + + let state = self.concurrent_state_mut(); + let task = state.get_mut(thread.task)?; + let caller = match &task.caller { + &Caller::Guest { thread } => { + assert!(guest_caller); + Some(thread) + } + &Caller::Host { caller, .. } => { + assert!(!guest_caller); + caller + } + }; + self.set_thread(caller); + + let state = self.concurrent_state_mut(); + let task = state.get_mut(thread.task)?; + if task.ready_to_delete() { + state.delete(thread.task)?.dispose(state, thread.task)?; + } + + Ok(()) + } + /// Determine whether the specified instance may be entered from the host. /// /// We return `true` here only if all of the following hold: @@ -1409,6 +1524,11 @@ impl StoreOpaque { } } + /// Returns `false` if the specified instance may not be entered, regardless + /// of what's on a task's call stack. + /// + /// If this returns `true`, the instance may be entered as long as it isn't + /// on the task's call stack, if applicable. fn may_enter_at_all(&self, instance: RuntimeInstance) -> bool { if self.trapped() { return false; @@ -1457,8 +1577,8 @@ impl StoreOpaque { caller } } - &Caller::Guest { thread, instance } => { - if instance.instance == guest_instance { + &Caller::Guest { thread } => { + if state.get_mut(thread.task).unwrap().instance.instance == guest_instance { break false; } else { thread @@ -1753,6 +1873,17 @@ impl StoreOpaque { } impl Instance { + fn check_may_leave( + self, + store: &mut StoreOpaque, + caller: RuntimeComponentInstanceIndex, + ) -> Result<()> { + store.check_may_leave(RuntimeInstance { + instance: self.id().instance(), + index: caller, + }) + } + /// Get the next pending event for the specified task and (optional) /// waitable set, along with the waitable handle if applicable. fn get_event( @@ -2263,7 +2394,7 @@ impl Instance { string_encoding: u8, caller_info: CallerInfo, ) -> Result<()> { - self.id().get(store.0).check_may_leave(caller_instance)?; + self.check_may_leave(store.0, caller_instance)?; if let (CallerInfo::Sync { .. }, true) = (&caller_info, callee_async) { // A task may only call an async-typed function via a sync lower if @@ -2307,7 +2438,16 @@ impl Instance { let return_ = SendSyncPtr::new(NonNull::new(return_).unwrap()); let token = StoreToken::new(store.as_context_mut()); let state = store.0.concurrent_state_mut(); - let old_thread = state.guest_thread.take(); + let old_thread = state.guest_thread.unwrap(); + + assert_eq!( + state.get_mut(old_thread.task)?.instance, + RuntimeInstance { + instance: self.id().instance(), + index: caller_instance, + } + ); + let new_task = GuestTask::new( state, Box::new(move |store, dst| { @@ -2404,13 +2544,7 @@ impl Instance { memory: NonNull::new(memory).map(SendSyncPtr::new), string_encoding: StringEncoding::from_u8(string_encoding).unwrap(), }, - Caller::Guest { - thread: old_thread.unwrap(), - instance: RuntimeInstance { - instance: self.id().instance(), - index: caller_instance, - }, - }, + Caller::Guest { thread: old_thread }, None, RuntimeInstance { instance: self.id().instance(), @@ -2424,10 +2558,12 @@ impl Instance { let guest_thread = state.push(new_thread)?; state.get_mut(guest_task)?.threads.insert(guest_thread); - let state = store.0.concurrent_state_mut(); - if let Some(old_thread) = old_thread { - state.get_mut(old_thread.task)?.subtasks.insert(guest_task); - }; + store + .0 + .concurrent_state_mut() + .get_mut(old_thread.task)? + .subtasks + .insert(guest_task); // Make the new thread the current one so that `Self::start_call` knows // which one to start. @@ -2519,17 +2655,13 @@ impl Instance { })); } - let Caller::Guest { - thread: caller, - instance: runtime_instance, - } = &task.caller - else { + let Caller::Guest { thread: caller } = &task.caller else { // As of this writing, `start_call` is only used for guest->guest // calls. unreachable!() }; let caller = *caller; - let caller_instance = *runtime_instance; + let caller_instance = state.get_mut(caller.task)?.instance; // Queue the call as a "high priority" work item. unsafe { @@ -2808,7 +2940,8 @@ impl Instance { options: OptionsIndex, storage: &[ValRaw], ) -> Result<()> { - self.id().get(store).check_may_leave(caller)?; + self.check_may_leave(store, caller)?; + let state = store.concurrent_state_mut(); let guest_thread = state.guest_thread.unwrap(); let lift = state @@ -2865,7 +2998,8 @@ impl Instance { store: &mut StoreOpaque, caller: RuntimeComponentInstanceIndex, ) -> Result<()> { - self.id().get(store).check_may_leave(caller)?; + self.check_may_leave(store, caller)?; + let state = store.concurrent_state_mut(); let guest_thread = state.guest_thread.unwrap(); let task = state.get_mut(guest_thread.task)?; @@ -2951,7 +3085,8 @@ impl Instance { store: &mut StoreOpaque, caller_instance: RuntimeComponentInstanceIndex, ) -> Result { - self.id().get_mut(store).check_may_leave(caller_instance)?; + self.check_may_leave(store, caller_instance)?; + let set = store.concurrent_state_mut().push(WaitableSet::default())?; let handle = store .handle_table(RuntimeInstance { @@ -2970,7 +3105,8 @@ impl Instance { caller_instance: RuntimeComponentInstanceIndex, set: u32, ) -> Result<()> { - self.id().get_mut(store).check_may_leave(caller_instance)?; + self.check_may_leave(store, caller_instance)?; + let rep = store .handle_table(RuntimeInstance { instance: self.id().instance(), @@ -2999,8 +3135,9 @@ impl Instance { waitable_handle: u32, set_handle: u32, ) -> Result<()> { + self.check_may_leave(store, caller_instance)?; + let mut instance = self.id().get_mut(store); - instance.check_may_leave(caller_instance)?; let waitable = Waitable::from_instance(instance.as_mut(), caller_instance, waitable_handle)?; @@ -3028,7 +3165,8 @@ impl Instance { caller_instance: RuntimeComponentInstanceIndex, task_id: u32, ) -> Result<()> { - self.id().get_mut(store).check_may_leave(caller_instance)?; + self.check_may_leave(store, caller_instance)?; + self.waitable_join(store, caller_instance, task_id, 0)?; let (rep, is_host) = store @@ -3052,8 +3190,12 @@ impl Instance { if task.lift_result.is_some() { bail!("cannot drop a subtask which has not yet resolved"); } - if let Caller::Guest { instance, .. } = &task.caller { - (Waitable::Guest(id), *instance, task.exited) + if let &Caller::Guest { thread } = &task.caller { + ( + Waitable::Guest(id), + concurrent_state.get_mut(thread.task)?.instance, + concurrent_state.get_mut(id)?.exited, + ) } else { unreachable!() } @@ -3092,7 +3234,7 @@ impl Instance { set: u32, payload: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; + self.check_may_leave(store, caller)?; if !self.options(store, options).async_ { // The caller may only call `waitable-set.wait` from an async task @@ -3134,7 +3276,7 @@ impl Instance { set: u32, payload: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; + self.check_may_leave(store, caller)?; let &CanonicalOptions { cancellable, @@ -3162,11 +3304,7 @@ impl Instance { /// Implements the `thread.index` intrinsic. pub(crate) fn thread_index(&self, store: &mut dyn VMStore) -> Result { - let thread_id = store - .concurrent_state_mut() - .guest_thread - .ok_or_else(|| format_err!("no current thread"))? - .thread; + let thread_id = store.concurrent_state_mut().guest_thread.unwrap().thread; // The unwrap is safe because `instance_rep` must be `Some` by this point Ok(store .concurrent_state_mut() @@ -3185,7 +3323,7 @@ impl Instance { start_func_idx: u32, context: i32, ) -> Result { - self.id().get(store.0).check_may_leave(runtime_instance)?; + self.check_may_leave(store.0, runtime_instance)?; log::trace!("creating new thread"); @@ -3321,7 +3459,7 @@ impl Instance { yielding: bool, to_thread: Option, ) -> Result { - self.id().get(store).check_may_leave(caller)?; + self.check_may_leave(store, caller)?; if to_thread.is_none() { let state = store.concurrent_state_mut(); @@ -3472,7 +3610,7 @@ impl Instance { async_: bool, task_id: u32, ) -> Result { - self.id().get(store).check_may_leave(caller_instance)?; + self.check_may_leave(store, caller_instance)?; if !async_ { // The caller may only sync call `subtask.cancel` from an async task @@ -3495,10 +3633,11 @@ impl Instance { ) } else { let id = TableId::::new(rep); - if let Caller::Guest { instance, .. } = - &store.concurrent_state_mut().get_mut(id)?.caller - { - (Waitable::Guest(id), *instance) + if let &Caller::Guest { thread } = &store.concurrent_state_mut().get_mut(id)?.caller { + ( + Waitable::Guest(id), + store.concurrent_state_mut().get_mut(thread.task)?.instance, + ) } else { unreachable!() } @@ -3627,7 +3766,8 @@ impl Instance { caller: RuntimeComponentInstanceIndex, slot: u32, ) -> Result { - self.id().get(store).check_may_leave(caller)?; + self.check_may_leave(store, caller)?; + store.concurrent_state_mut().context_get(slot) } @@ -3638,7 +3778,8 @@ impl Instance { slot: u32, value: u32, ) -> Result<()> { - self.id().get(store).check_may_leave(caller)?; + self.check_may_leave(store, caller)?; + store.concurrent_state_mut().context_set(slot, value) } } @@ -3926,7 +4067,8 @@ impl VMComponentAsyncStore for StoreInner { future: u32, address: u32, ) -> Result { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance .guest_write( StoreContextMut(self), @@ -3950,7 +4092,8 @@ impl VMComponentAsyncStore for StoreInner { future: u32, address: u32, ) -> Result { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance .guest_read( StoreContextMut(self), @@ -3975,7 +4118,8 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance .guest_write( StoreContextMut(self), @@ -4000,7 +4144,8 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance .guest_read( StoreContextMut(self), @@ -4022,7 +4167,8 @@ impl VMComponentAsyncStore for StoreInner { ty: TypeFutureTableIndex, writer: u32, ) -> Result<()> { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance.guest_drop_writable(self, TransmitIndex::Future(ty), writer) } @@ -4038,7 +4184,8 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance .guest_write( StoreContextMut(self), @@ -4068,7 +4215,8 @@ impl VMComponentAsyncStore for StoreInner { address: u32, count: u32, ) -> Result { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance .guest_read( StoreContextMut(self), @@ -4093,7 +4241,8 @@ impl VMComponentAsyncStore for StoreInner { ty: TypeStreamTableIndex, writer: u32, ) -> Result<()> { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance.guest_drop_writable(self, TransmitIndex::Stream(ty), writer) } @@ -4106,7 +4255,8 @@ impl VMComponentAsyncStore for StoreInner { err_ctx_handle: u32, debug_msg_address: u32, ) -> Result<()> { - instance.id().get(self).check_may_leave(caller)?; + instance.check_may_leave(self, caller)?; + instance.error_context_debug_message( StoreContextMut(self), ty, @@ -4191,12 +4341,6 @@ enum Caller { Guest { /// The id of the caller thread: QualifiedThreadId, - /// The instance to use to enforce reentrance rules. - /// - /// Note that this might not be the same as the instance the caller task - /// started executing in given that one or more synchronous guest->guest - /// calls may have occurred involving multiple instances. - instance: RuntimeInstance, }, } @@ -4496,10 +4640,7 @@ impl GuestTask { // Reparent any pending subtasks to the caller. match &self.caller { - Caller::Guest { - thread, - instance: runtime_instance, - } => { + Caller::Guest { thread } => { let task_mut = state.get_mut(thread.task)?; let present = task_mut.subtasks.remove(&me); assert!(present); @@ -4509,10 +4650,7 @@ impl GuestTask { } for subtask in &self.subtasks { - state.get_mut(*subtask)?.caller = Caller::Guest { - thread: *thread, - instance: *runtime_instance, - }; + state.get_mut(*subtask)?.caller = Caller::Guest { thread: *thread }; } } Caller::Host { @@ -4534,7 +4672,8 @@ impl GuestTask { } for subtask in self.subtasks { - if state.get_mut(subtask)?.exited { + let task = state.get_mut(subtask)?; + if task.exited && task.ready_to_delete() { Waitable::Guest(subtask).delete_from(state)?; } } diff --git a/crates/wasmtime/src/runtime/component/concurrent_disabled.rs b/crates/wasmtime/src/runtime/component/concurrent_disabled.rs index bf710393212e..fa8ea16708f1 100644 --- a/crates/wasmtime/src/runtime/component/concurrent_disabled.rs +++ b/crates/wasmtime/src/runtime/component/concurrent_disabled.rs @@ -1,6 +1,7 @@ use crate::component::func::{LiftContext, LowerContext}; use crate::component::matching::InstanceType; -use crate::component::{ComponentType, Lift, Lower, Val}; +use crate::component::{ComponentType, Lift, Lower, RuntimeInstance, Val}; +use crate::store::StoreOpaque; use crate::{Result, bail, error::format_err}; use core::convert::Infallible; use core::mem::MaybeUninit; @@ -149,3 +150,22 @@ unsafe impl Lower for StreamAny { match self.0 {} } } + +impl StoreOpaque { + pub(crate) fn enter_sync_call( + &mut self, + _guest_caller: Option, + _callee_async: bool, + _callee: RuntimeInstance, + ) -> Result<()> { + // If we've reached here, the user somehow managed to enable the + // `component-model-async` runtime config feature without enabling the + // corresponding compile-time feature. + unreachable!() + } + + pub(crate) fn exit_sync_call(&mut self, _guest_caller: bool) -> Result<()> { + // See comment in `enter_sync_call` + unreachable!() + } +} diff --git a/crates/wasmtime/src/runtime/component/func.rs b/crates/wasmtime/src/runtime/component/func.rs index eca12988d995..e56d933ab000 100644 --- a/crates/wasmtime/src/runtime/component/func.rs +++ b/crates/wasmtime/src/runtime/component/func.rs @@ -610,6 +610,11 @@ impl Func { bail!(crate::Trap::CannotEnterComponent); } + if store.engine().config().cm_concurrency_enabled() { + let async_type = self.abi_async(store.0); + store.0.enter_sync_call(None, async_type, instance)?; + } + #[repr(C)] union Union { params: Params, @@ -688,6 +693,7 @@ impl Func { _ => unreachable!(), }, ); + return Ok(val); } @@ -719,7 +725,7 @@ impl Func { pub fn post_return(&self, mut store: impl AsContextMut) -> Result<()> { let store = store.as_context_mut(); store.0.validate_sync_call()?; - self.post_return_impl(store) + self.post_return_impl(store, false) } /// Exactly like [`Self::post_return`] except for invoke WebAssembly @@ -730,10 +736,12 @@ impl Func { // Future optimization opportunity: conditionally use a fiber here since // some func's post_return will not need the async context (i.e. end up // calling async host functionality) - store.on_fiber(|store| self.post_return_impl(store)).await? + store + .on_fiber(|store| self.post_return_impl(store, true)) + .await? } - fn post_return_impl(&self, mut store: impl AsContextMut) -> Result<()> { + fn post_return_impl(&self, mut store: impl AsContextMut, async_: bool) -> Result<()> { let mut store = store.as_context_mut(); let index = self.index; @@ -803,6 +811,10 @@ impl Func { guest: Some(instance.instance_states()), } .exit_call()?; + + if !async_ && store.engine().config().cm_concurrency_enabled() { + store.0.exit_sync_call(false)?; + } } Ok(()) } diff --git a/crates/wasmtime/src/runtime/component/instance.rs b/crates/wasmtime/src/runtime/component/instance.rs index c00870e155d6..887313e78802 100644 --- a/crates/wasmtime/src/runtime/component/instance.rs +++ b/crates/wasmtime/src/runtime/component/instance.rs @@ -1,3 +1,4 @@ +use crate::component::RuntimeInstance; use crate::component::func::HostFunc; use crate::component::matching::InstanceType; use crate::component::store::{ComponentInstanceId, StoreComponentInstanceId}; @@ -837,7 +838,8 @@ impl<'a> Instantiator<'a> { for initializer in env_component.initializers.iter() { match initializer { - GlobalInitializer::InstantiateModule(m) => { + GlobalInitializer::InstantiateModule(m, component_instance) => { + let instance = self.id; let module; let imports = match m { // Since upvars are statically know we know that the @@ -867,6 +869,23 @@ impl<'a> Instantiator<'a> { } }; + let exit = if let (&Some(component_instance), true) = ( + component_instance, + store.engine().config().cm_concurrency_enabled(), + ) { + store.0.enter_sync_call( + None, + false, + RuntimeInstance { + instance, + index: component_instance, + }, + )?; + true + } else { + false + }; + // Note that the unsafety here should be ok because the // validity of the component means that type-checks have // already been performed. This means that the unsafety due @@ -880,6 +899,11 @@ impl<'a> Instantiator<'a> { crate::Instance::new_started(store, module, imports.as_ref(), asyncness) .await? }; + + if exit { + store.0.exit_sync_call(false)?; + } + self.instance_mut(store.0).push_instance_id(i.id()); } @@ -1202,6 +1226,7 @@ impl InstancePre { .decrement_component_instance_count(); e })?; + let instance = Instance::from_wasmtime(store.0, instantiator.id); store.0.push_component_instance(instance); Ok(instance) diff --git a/crates/wasmtime/src/runtime/component/resources/any.rs b/crates/wasmtime/src/runtime/component/resources/any.rs index 642a60295e4a..78c417b6728f 100644 --- a/crates/wasmtime/src/runtime/component/resources/any.rs +++ b/crates/wasmtime/src/runtime/component/resources/any.rs @@ -197,12 +197,30 @@ impl ResourceAny { }; let mut args = [ValRaw::u32(rep)]; + let exit = if let (Some(instance), true) = ( + slot.instance, + store.engine().config().cm_concurrency_enabled(), + ) { + store.0.enter_sync_call(None, false, instance)?; + true + } else { + false + }; + // This should be safe because `dtor` has been checked to belong to the // `store` provided which means it's valid and still alive. Additionally // destructors have al been previously type-checked and are guaranteed // to take one i32 argument and return no results, so the parameters // here should be configured correctly. - unsafe { crate::Func::call_unchecked_raw(store, dtor, NonNull::from(&mut args)) } + unsafe { + crate::Func::call_unchecked_raw(store, dtor, NonNull::from(&mut args))?; + } + + if exit { + store.0.exit_sync_call(false)?; + } + + Ok(()) } fn lower_to_index(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result { diff --git a/crates/wasmtime/src/runtime/vm/component/libcalls.rs b/crates/wasmtime/src/runtime/vm/component/libcalls.rs index bb426e98e11a..e8ee548e192a 100644 --- a/crates/wasmtime/src/runtime/vm/component/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/component/libcalls.rs @@ -673,6 +673,32 @@ fn trap(_store: &mut dyn VMStore, _instance: Instance, code: u32) -> Result<()> .into()) } +#[cfg(feature = "component-model-async")] +fn enter_sync_call( + store: &mut dyn VMStore, + instance: Instance, + caller_instance: u32, + callee_async: u32, + callee_instance: u32, +) -> Result<()> { + store.enter_sync_call( + Some(RuntimeInstance { + instance: instance.id().instance(), + index: RuntimeComponentInstanceIndex::from_u32(caller_instance), + }), + callee_async != 0, + RuntimeInstance { + instance: instance.id().instance(), + index: RuntimeComponentInstanceIndex::from_u32(callee_instance), + }, + ) +} + +#[cfg(feature = "component-model-async")] +fn exit_sync_call(store: &mut dyn VMStore, _instance: Instance) -> Result<()> { + store.exit_sync_call(true) +} + #[cfg(feature = "component-model-async")] fn backpressure_modify( store: &mut dyn VMStore, diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs index 0c78df7aef22..3c40b8fa4700 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs @@ -569,12 +569,12 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator { use wasmtime_environ::component::GlobalInitializer::*; use wasmtime_environ::component::InstantiateModule; match init { - InstantiateModule(InstantiateModule::Import(_, _)) => { + InstantiateModule(InstantiateModule::Import(_, _), _) => { num_core_instances += 1; // Can't statically account for the total vmctx size, number // of memories, and number of tables in this component. } - InstantiateModule(InstantiateModule::Static(static_module_index, _)) => { + InstantiateModule(InstantiateModule::Static(static_module_index, _), _) => { let module = get_module(*static_module_index); let offsets = VMOffsets::new(HostPtr, &module); self.validate_module(module, &offsets)?; diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs index daa5d5534883..4ad94b84eb6e 100644 --- a/tests/all/component_model.rs +++ b/tests/all/component_model.rs @@ -3,7 +3,7 @@ use std::iter; use wasmtime::Result; use wasmtime::component::Component; use wasmtime_component_util::REALLOC_AND_FREE; -use wasmtime_test_util::component::{TypedFuncExt, async_engine, engine}; +use wasmtime_test_util::component::{TypedFuncExt, async_engine, config, engine}; mod aot; mod r#async; diff --git a/tests/all/component_model/func.rs b/tests/all/component_model/func.rs index 331a623c6d00..2bc1d6ad02cc 100644 --- a/tests/all/component_model/func.rs +++ b/tests/all/component_model/func.rs @@ -3455,8 +3455,259 @@ fn test_recurse(kind: RecurseKind) -> Result<()> { } }; - let run = instance.get_typed_func::<(), ()>(&mut store, "export")?; - run.call(&mut store, ())?; + let export = instance.get_typed_func::<(), ()>(&mut store, "export")?; + export.call(&mut store, ())?; + Ok(()) +} + +#[test] +fn thread_index_during_init() -> Result<()> { + let component = r#" +(component + (core module $m + (import "" "thread.index" (func $thread-index (result i32))) + (func $start + (if (i32.eqz (call $thread-index)) (then unreachable)) + ) + (start $start) + ) + (core func $thread-index (canon thread.index)) + (core instance $m (instantiate $m (with "" (instance + (export "thread.index" (func $thread-index)) + )))) +) +"#; + let mut config = super::config(); + config.wasm_component_model_threading(true); + let engine = Engine::new(&config)?; + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + linker.instantiate(&mut store, &component)?; + Ok(()) +} + +#[test] +fn thread_index_via_sync_host_call() -> Result<()> { + let component = r#" +(component + (core module $m + (import "" "thread.index" (func $thread-index (result i32))) + (func (export "run") + (if (i32.eqz (call $thread-index)) (then unreachable)) + ) + ) + (core func $thread-index (canon thread.index)) + (core instance $m (instantiate $m (with "" (instance + (export "thread.index" (func $thread-index)) + )))) + (func (export "run") (canon lift (core func $m "run"))) +) +"#; + let mut config = super::config(); + config.wasm_component_model_threading(true); + let engine = Engine::new(&config)?; + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + let instance = linker.instantiate(&mut store, &component)?; + let run = instance.get_typed_func::<(), ()>(&mut store, "run")?; + run.call_and_post_return(&mut store, ())?; + Ok(()) +} + +#[test] +fn thread_index_via_sync_host_call_post_return() -> Result<()> { + let component = r#" +(component + (core module $m + (import "" "thread.index" (func $thread-index (result i32))) + (global $index (mut i32) (i32.const 0)) + (func (export "run") + (global.set $index (call $thread-index)) + (if (i32.eqz (global.get $index)) (then unreachable)) + ) + (func (export "run-post-return") + (local $index i32) + (local.set $index (call $thread-index)) + (if (i32.eqz (local.get $index)) (then unreachable)) + (if (i32.ne (local.get $index) (global.get $index)) (then unreachable)) + ) + ) + (core func $thread-index (canon thread.index)) + (core instance $m (instantiate $m (with "" (instance + (export "thread.index" (func $thread-index)) + )))) + (func (export "run") (canon lift (core func $m "run") (post-return (func $m "run-post-return")))) +) +"#; + let mut config = super::config(); + config.wasm_component_model_threading(true); + let engine = Engine::new(&config)?; + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + let instance = linker.instantiate(&mut store, &component)?; + let run = instance.get_typed_func::<(), ()>(&mut store, "run")?; + run.call_and_post_return(&mut store, ())?; + Ok(()) +} + +#[test] +fn thread_index_via_sync_host_call_cabi_realloc() -> Result<()> { + let component = r#" +(component + (core module $m + (import "" "thread.index" (func $thread-index (result i32))) + (global $index (mut i32) (i32.const 0)) + (memory (export "memory") 1) + (func (export "realloc") (param i32 i32 i32 i32) (result i32) + (global.set $index (call $thread-index)) + (if (i32.eqz (global.get $index)) (then unreachable)) + (i32.const 100) + ) + (func (export "run") (param i32 i32) + (local $index i32) + (local.set $index (call $thread-index)) + (if (i32.eqz (local.get $index)) (then unreachable)) + (if (i32.ne (local.get $index) (global.get $index)) (then unreachable)) + ) + ) + (core func $thread-index (canon thread.index)) + (core instance $m (instantiate $m (with "" (instance + (export "thread.index" (func $thread-index)) + )))) + (func (export "run") (param "s" string) (canon lift + (core func $m "run") + (memory $m "memory") + (realloc (func $m "realloc")) + )) +) +"#; + let mut config = super::config(); + config.wasm_component_model_threading(true); + let engine = Engine::new(&config)?; + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + let instance = linker.instantiate(&mut store, &component)?; + let run = instance.get_typed_func::<(&str,), ()>(&mut store, "run")?; + run.call_and_post_return(&mut store, ("hola",))?; + Ok(()) +} + +#[test] +fn thread_index_via_resource_drop_from_host() -> Result<()> { + let component = r#" +(component + (core module $m + (import "" "thread.index" (func $thread-index (result i32))) + (func (export "dtor") (param i32) + (if (i32.eqz (call $thread-index)) (then unreachable)) + ) + ) + (core func $thread-index (canon thread.index)) + (core instance $m (instantiate $m (with "" (instance + (export "thread.index" (func $thread-index)) + )))) + (type $r (resource (rep i32) (dtor (func $m "dtor")))) + (core func $new (canon resource.new $r)) + (core module $m2 + (import "" "new" (func $new (param i32) (result i32))) + (func (export "new") (result i32) + (call $new (i32.const 100)) + ) + ) + (core instance $m2 (instantiate $m2 (with "" (instance + (export "new" (func $new)) + )))) + (func $new (result (own $r)) (canon lift (core func $m2 "new"))) + (component $c + (import "r" (type $r (sub resource))) + (import "new" (func $new (result (own $r)))) + (export $r-export "r" (type $r)) + (export "new" (func $new) (func (result (own $r-export)))) + ) + (instance $c (instantiate $c + (with "r" (type $r)) + (with "new" (func $new)) + )) + (export "i" (instance $c)) +) +"#; + let mut config = super::config(); + config.wasm_component_model_threading(true); + let engine = Engine::new(&config)?; + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + let instance = linker.instantiate(&mut store, &component)?; + let instance_index = instance.get_export_index(&mut store, None, "i").unwrap(); + let func_index = instance + .get_export_index(&mut store, Some(&instance_index), "new") + .unwrap(); + let run = instance.get_typed_func::<(), (ResourceAny,)>(&mut store, &func_index)?; + let (resource,) = run.call_and_post_return(&mut store, ())?; + resource.resource_drop(&mut store)?; + Ok(()) +} + +#[test] +fn thread_index_via_sync_host_call_and_sync_guest_call() -> Result<()> { + let component = r#" +(component + (component $c + (core module $m + (import "" "thread.index" (func $thread-index (result i32))) + (func (export "run") (result i32) + (call $thread-index) + ) + ) + (core func $thread-index (canon thread.index)) + (core instance $m (instantiate $m (with "" (instance + (export "thread.index" (func $thread-index)) + )))) + (func (export "run") (result u32) (canon lift (core func $m "run"))) + ) + (instance $c (instantiate $c)) + + (component $d + (import "c" (instance $c + (export "run" (func (result u32))) + )) + (core func $run (canon lower (func $c "run"))) + (core module $m + (import "" "thread.index" (func $thread-index (result i32))) + (import "" "run" (func $run (result i32))) + (func (export "run") + (local $mine i32) + (local $theirs i32) + (local.set $mine (call $thread-index)) + (if (i32.eqz (local.get $mine)) (then unreachable)) + (local.set $theirs (call $run)) + (if (i32.eqz (local.get $theirs)) (then unreachable)) + ) + ) + (core func $thread-index (canon thread.index)) + (core instance $m (instantiate $m (with "" (instance + (export "thread.index" (func $thread-index)) + (export "run" (func $run)) + )))) + (func (export "run") (canon lift (core func $m "run"))) + ) + (instance $d (instantiate $d (with "c" (instance $c)))) + (func (export "run") (alias export $d "run")) +) +"#; + let mut config = super::config(); + config.wasm_component_model_threading(true); + let engine = Engine::new(&config)?; + let component = Component::new(&engine, component)?; + let mut store = Store::new(&engine, ()); + let linker = Linker::new(&engine); + let instance = linker.instantiate(&mut store, &component)?; + let run = instance.get_typed_func::<(), ()>(&mut store, "run")?; + run.call_and_post_return(&mut store, ())?; Ok(()) } diff --git a/tests/component-model b/tests/component-model index ac37fe92a218..85ef74272310 160000 --- a/tests/component-model +++ b/tests/component-model @@ -1 +1 @@ -Subproject commit ac37fe92a21865aa8ca84b5bcbeb2da3746b6dc8 +Subproject commit 85ef7427231093b26eb636eba3d775f9a2f4adc0 diff --git a/tests/disas/component-model/direct-adapter-calls-inlining.wat b/tests/disas/component-model/direct-adapter-calls-inlining.wat index 5ebe6f42abce..82c1aae8b16f 100644 --- a/tests/disas/component-model/direct-adapter-calls-inlining.wat +++ b/tests/disas/component-model/direct-adapter-calls-inlining.wat @@ -64,11 +64,10 @@ ;; gv6 = load.i64 notrap aligned gv5+16 ;; gv7 = vmctx ;; gv8 = load.i64 notrap aligned readonly can_move gv7+120 -;; gv9 = load.i64 notrap aligned readonly can_move gv7+144 -;; gv10 = load.i64 notrap aligned readonly can_move gv7+96 -;; gv11 = vmctx -;; gv12 = load.i64 notrap aligned readonly gv11+8 -;; gv13 = load.i64 notrap aligned gv12+16 +;; gv9 = load.i64 notrap aligned readonly can_move gv7+96 +;; gv10 = vmctx +;; gv11 = load.i64 notrap aligned readonly gv10+8 +;; gv12 = load.i64 notrap aligned gv11+16 ;; sig0 = (i64 vmctx, i64, i32) -> i32 tail ;; sig1 = (i64 vmctx, i64, i32) tail ;; sig2 = (i64 vmctx, i64, i32) -> i32 tail @@ -98,18 +97,14 @@ ;; trap user11 ;; ;; block5: -;; v22 = load.i64 notrap aligned readonly can_move v5+144 +;; v22 = load.i64 notrap aligned readonly can_move v5+96 ;; v23 = load.i32 notrap aligned table v22 -;; v61 = iconst.i32 0 -;; store notrap aligned table v61, v22 ; v61 = 0 -;; v26 = load.i64 notrap aligned readonly can_move v5+96 -;; v27 = load.i32 notrap aligned table v26 -;; v28 = iconst.i32 -2 -;; v29 = band v27, v28 ; v28 = -2 -;; store notrap aligned table v29, v26 -;; v62 = iconst.i32 1 -;; v63 = bor v27, v62 ; v62 = 1 -;; store notrap aligned table v63, v26 +;; v24 = iconst.i32 -2 +;; v25 = band v23, v24 ; v24 = -2 +;; store notrap aligned table v25, v22 +;; v56 = iconst.i32 1 +;; v57 = bor v23, v56 ; v56 = 1 +;; store notrap aligned table v57, v22 ;; jump block6 ;; ;; block6: @@ -119,14 +114,13 @@ ;; jump block8 ;; ;; block8: -;; v40 = load.i32 notrap aligned table v12 -;; v64 = iconst.i32 -2 -;; v65 = band v40, v64 ; v64 = -2 -;; store notrap aligned table v65, v12 -;; v66 = iconst.i32 1 -;; v67 = bor v40, v66 ; v66 = 1 -;; store notrap aligned table v67, v12 -;; store.i32 notrap aligned table v23, v22 +;; v36 = load.i32 notrap aligned table v12 +;; v58 = iconst.i32 -2 +;; v59 = band v36, v58 ; v58 = -2 +;; store notrap aligned table v59, v12 +;; v60 = iconst.i32 1 +;; v61 = bor v36, v60 ; v60 = 1 +;; store notrap aligned table v61, v12 ;; jump block3 ;; ;; block3: @@ -136,6 +130,6 @@ ;; @00f0 jump block1 ;; ;; block1: -;; v52 = iconst.i32 1276 -;; @00f0 return v52 ; v52 = 1276 +;; v47 = iconst.i32 1276 +;; @00f0 return v47 ; v47 = 1276 ;; } diff --git a/tests/disas/component-model/direct-adapter-calls-x64.wat b/tests/disas/component-model/direct-adapter-calls-x64.wat index d6d1684ba1eb..4d5bece74070 100644 --- a/tests/disas/component-model/direct-adapter-calls-x64.wat +++ b/tests/disas/component-model/direct-adapter-calls-x64.wat @@ -85,49 +85,41 @@ ;; movq %rsp, %rbp ;; movq 8(%rdi), %r10 ;; movq 0x10(%r10), %r10 -;; addq $0x30, %r10 +;; addq $0x20, %r10 ;; cmpq %rsp, %r10 -;; ja 0x113 -;; 79: subq $0x20, %rsp -;; movq %rbx, (%rsp) -;; movq %r12, 8(%rsp) -;; movq %r14, 0x10(%rsp) -;; movq 0x78(%rdi), %r14 -;; movl (%r14), %esi -;; testl $1, %esi -;; je 0xff -;; 9e: movq 0x90(%rdi), %rbx -;; movl (%rbx), %r12d -;; movl $0, (%rbx) -;; movq 0x60(%rdi), %rcx -;; movq %rdi, %r9 -;; movl (%rcx), %eax -;; movq %rax, %r8 -;; andl $0xfffffffe, %r8d -;; movl %r8d, (%rcx) -;; orl $1, %eax -;; movl %eax, (%rcx) -;; movq 0x40(%r9), %rdi -;; movq %r9, %rsi +;; ja 0xf2 +;; 79: subq $0x10, %rsp +;; movq %r12, (%rsp) +;; movq 0x78(%rdi), %r12 +;; movl (%r12), %r10d +;; testl $1, %r10d +;; je 0xdd +;; 96: movq 0x60(%rdi), %rsi +;; movl (%rsi), %r10d +;; movq %r10, %rax +;; andl $0xfffffffe, %eax +;; movl %eax, (%rsi) +;; orl $1, %r10d +;; movl %r10d, (%rsi) +;; movq %rdi, %rax +;; movq 0x40(%rax), %rdi +;; movq %rax, %rsi ;; callq 0 -;; movl (%r14), %ecx -;; movq %rcx, %r8 -;; andl $0xfffffffe, %r8d -;; movl %r8d, (%r14) -;; orl $1, %ecx -;; movl %ecx, (%r14) -;; movl %r12d, (%rbx) -;; movq (%rsp), %rbx -;; movq 8(%rsp), %r12 -;; movq 0x10(%rsp), %r14 -;; addq $0x20, %rsp +;; movl (%r12), %esi +;; movq %rsi, %rcx +;; andl $0xfffffffe, %ecx +;; movl %ecx, (%r12) +;; orl $1, %esi +;; movl %esi, (%r12) +;; movq (%rsp), %r12 +;; addq $0x10, %rsp ;; movq %rbp, %rsp ;; popq %rbp ;; retq -;; ff: movq %rdi, %rsi -;; 102: movq 0x48(%rsi), %rax -;; 106: movq 0x58(%rsi), %rdi -;; 10a: movl $0x17, %edx -;; 10f: callq *%rax -;; 111: ud2 -;; 113: ud2 +;; dd: movq %rdi, %rsi +;; e0: movq 0x48(%rsi), %r9 +;; e4: movq 0x58(%rsi), %rdi +;; e8: movl $0x17, %edx +;; ed: callq *%r9 +;; f0: ud2 +;; f2: ud2 diff --git a/tests/disas/component-model/direct-adapter-calls.wat b/tests/disas/component-model/direct-adapter-calls.wat index 26d94affea67..f8e71a7d98aa 100644 --- a/tests/disas/component-model/direct-adapter-calls.wat +++ b/tests/disas/component-model/direct-adapter-calls.wat @@ -97,53 +97,47 @@ ;; gv2 = load.i64 notrap aligned gv1+16 ;; gv3 = vmctx ;; gv4 = load.i64 notrap aligned readonly can_move gv3+120 -;; gv5 = load.i64 notrap aligned readonly can_move gv3+144 -;; gv6 = load.i64 notrap aligned readonly can_move gv3+96 +;; gv5 = load.i64 notrap aligned readonly can_move gv3+96 ;; sig0 = (i64 vmctx, i64, i32) tail ;; sig1 = (i64 vmctx, i64, i32) -> i32 tail ;; fn0 = colocated u0:0 sig1 ;; stack_limit = gv2 ;; ;; block0(v0: i64, v1: i64, v2: i32): -;; @0092 v5 = load.i64 notrap aligned readonly can_move v0+120 -;; @0092 v6 = load.i32 notrap aligned table v5 -;; @0094 v7 = iconst.i32 1 -;; @0096 v8 = band v6, v7 ; v7 = 1 -;; @0090 v4 = iconst.i32 0 -;; @0097 v9 = icmp eq v8, v4 ; v4 = 0 -;; @0097 v10 = uextend.i32 v9 -;; @0098 brif v10, block2, block3 +;; @0077 v5 = load.i64 notrap aligned readonly can_move v0+120 +;; @0077 v6 = load.i32 notrap aligned table v5 +;; @0079 v7 = iconst.i32 1 +;; @007b v8 = band v6, v7 ; v7 = 1 +;; @0075 v4 = iconst.i32 0 +;; @007c v9 = icmp eq v8, v4 ; v4 = 0 +;; @007c v10 = uextend.i32 v9 +;; @007d brif v10, block2, block3 ;; ;; block2: -;; @009c v14 = load.i64 notrap aligned readonly can_move v0+72 -;; @009c v13 = load.i64 notrap aligned readonly can_move v0+88 -;; @009a v11 = iconst.i32 23 -;; @009c call_indirect sig0, v14(v13, v0, v11) ; v11 = 23 -;; @009e trap user11 +;; @0081 v14 = load.i64 notrap aligned readonly can_move v0+72 +;; @0081 v13 = load.i64 notrap aligned readonly can_move v0+88 +;; @007f v11 = iconst.i32 23 +;; @0081 call_indirect sig0, v14(v13, v0, v11) ; v11 = 23 +;; @0083 trap user11 ;; ;; block3: -;; @00a0 v15 = load.i64 notrap aligned readonly can_move v0+144 -;; @00a0 v16 = load.i32 notrap aligned table v15 -;; v60 = iconst.i32 0 -;; @00a6 store notrap aligned table v60, v15 ; v60 = 0 -;; @00a8 v19 = load.i64 notrap aligned readonly can_move v0+96 -;; @00a8 v20 = load.i32 notrap aligned table v19 -;; @00aa v21 = iconst.i32 -2 -;; @00ac v22 = band v20, v21 ; v21 = -2 -;; @00ad store notrap aligned table v22, v19 -;; v61 = iconst.i32 1 -;; v62 = bor v20, v61 ; v61 = 1 -;; @00b6 store notrap aligned table v62, v19 -;; @00b8 v30 = load.i64 notrap aligned readonly can_move v0+64 -;; @00b8 v31 = call fn0(v30, v0, v2) -;; @00bc v33 = load.i32 notrap aligned table v5 -;; @00c0 v35 = band v33, v21 ; v21 = -2 -;; @00c1 store notrap aligned table v35, v5 -;; v63 = bor v33, v61 ; v61 = 1 -;; @00ca store notrap aligned table v63, v5 -;; @00ce store notrap aligned table v16, v15 -;; @00d0 jump block1 +;; @0085 v15 = load.i64 notrap aligned readonly can_move v0+96 +;; @0085 v16 = load.i32 notrap aligned table v15 +;; @0087 v17 = iconst.i32 -2 +;; @0089 v18 = band v16, v17 ; v17 = -2 +;; @008a store notrap aligned table v18, v15 +;; v52 = iconst.i32 1 +;; v53 = bor v16, v52 ; v52 = 1 +;; @0093 store notrap aligned table v53, v15 +;; @0095 v26 = load.i64 notrap aligned readonly can_move v0+64 +;; @0095 v27 = call fn0(v26, v0, v2) +;; @0099 v29 = load.i32 notrap aligned table v5 +;; @009d v31 = band v29, v17 ; v17 = -2 +;; @009e store notrap aligned table v31, v5 +;; v54 = bor v29, v52 ; v52 = 1 +;; @00a7 store notrap aligned table v54, v5 +;; @00a9 jump block1 ;; ;; block1: -;; @00d0 return v31 +;; @00a9 return v27 ;; } diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast index f019c8b87145..5302e7eb904a 100644 --- a/tests/misc_testsuite/component-model/resources.wast +++ b/tests/misc_testsuite/component-model/resources.wast @@ -16,7 +16,7 @@ (local $r i32) (local.set $r (call $new (i32.const 100))) - (if (i32.ne (local.get $r) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r) (i32.const 2)) (then (unreachable))) (if (i32.ne (call $rep (local.get $r)) (i32.const 100)) (then (unreachable))) (call $drop (local.get $r)) @@ -97,13 +97,13 @@ ;; resources assigned sequentially (local.set $r1 (call $new (i32.const 100))) - (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 2)) (then (unreachable))) (local.set $r2 (call $new (i32.const 200))) - (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 3)) (then (unreachable))) (local.set $r3 (call $new (i32.const 300))) - (if (i32.ne (local.get $r3) (i32.const 3)) (then (unreachable))) + (if (i32.ne (local.get $r3) (i32.const 4)) (then (unreachable))) ;; representations all look good (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (then (unreachable))) @@ -114,8 +114,8 @@ (call $drop (local.get $r2)) (local.set $r2 (call $new (i32.const 400))) - ;; should have reused index 1 - (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) + ;; should have reused index 3 + (if (i32.ne (local.get $r2) (i32.const 3)) (then (unreachable))) ;; representations all look good (if (i32.ne (call $rep (local.get $r1)) (i32.const 100)) (then (unreachable))) @@ -137,13 +137,13 @@ (if (i32.ne (call $rep (local.get $r3)) (i32.const 700)) (then (unreachable))) ;; indices should be lifo - (if (i32.ne (local.get $r1) (i32.const 3)) (then (unreachable))) - (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) - (if (i32.ne (local.get $r3) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 4)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 3)) (then (unreachable))) + (if (i32.ne (local.get $r3) (i32.const 2)) (then (unreachable))) ;; bump one more time (local.set $r4 (call $new (i32.const 800))) - (if (i32.ne (local.get $r4) (i32.const 4)) (then (unreachable))) + (if (i32.ne (local.get $r4) (i32.const 5)) (then (unreachable))) ;; deallocate everything (call $drop (local.get $r1)) @@ -243,13 +243,13 @@ (local.set $r2 (call $ctor (i32.const 200))) ;; assert r1/r2 are sequential - (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) - (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 2)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 3)) (then (unreachable))) ;; reallocate r1 and it should be reassigned the same index (call $drop (local.get $r1)) (local.set $r1 (call $ctor (i32.const 300))) - (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 2)) (then (unreachable))) ;; internal values should match (call $assert (local.get $r1) (i32.const 300)) @@ -445,7 +445,7 @@ (import "" "ctor" (func $ctor (param i32) (result i32))) (func $start - (if (i32.ne (call $ctor (i32.const 100)) (i32.const 1)) (then (unreachable))) + (if (i32.ne (call $ctor (i32.const 100)) (i32.const 2)) (then (unreachable))) ) (start $start) ) @@ -619,12 +619,12 @@ (call $drop2 (call $new2 (i32.const 104))) ;; should be referencing the same namespace - (if (i32.ne (call $new1 (i32.const 105)) (i32.const 1)) (then (unreachable))) - (if (i32.ne (call $new2 (i32.const 105)) (i32.const 2)) (then (unreachable))) + (if (i32.ne (call $new1 (i32.const 105)) (i32.const 2)) (then (unreachable))) + (if (i32.ne (call $new2 (i32.const 105)) (i32.const 3)) (then (unreachable))) ;; use different drops out of order - (call $drop2 (i32.const 1)) - (call $drop1 (i32.const 2)) + (call $drop2 (i32.const 2)) + (call $drop1 (i32.const 3)) ) (start $start) @@ -704,8 +704,8 @@ ;; indexes start at 1 and while they have distinct types they should be ;; within the same table. - (if (i32.ne (local.get $r1) (i32.const 1)) (then (unreachable))) - (if (i32.ne (local.get $r2) (i32.const 2)) (then (unreachable))) + (if (i32.ne (local.get $r1) (i32.const 2)) (then (unreachable))) + (if (i32.ne (local.get $r2) (i32.const 3)) (then (unreachable))) ;; nothing should be dropped yet (if (i32.ne (call $drops) (i32.const 0)) (then (unreachable))) @@ -868,8 +868,8 @@ (call $drop (local.get $r2)) ;; table should be empty at this point, so a fresh allocation should get - ;; index 0 - (if (i32.ne (call $ctor (i32.const 600)) (i32.const 1)) (then (unreachable))) + ;; index 2 + (if (i32.ne (call $ctor (i32.const 600)) (i32.const 2)) (then (unreachable))) ) (start $start)