diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index 5d36f9d0a4..11916350e4 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -342,6 +342,7 @@ impl wasmtime_environ::Compiler for Compiler { .iter() .zip(&compiled_trampolines) { + assert!(func.traps.is_empty()); trampolines.push(builder.trampoline(*i, &func)); } @@ -683,19 +684,21 @@ impl Compiler { context .compile_and_emit(isa, &mut code_buf) .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; + let result = context.mach_compile_result.as_ref().unwrap(); // Processing relocations isn't the hardest thing in the world here but // no trampoline should currently generate a relocation, so assert that // they're all empty and if this ever trips in the future then handling // will need to be added here to ensure they make their way into the // `CompiledFunction` below. - assert!(context - .mach_compile_result - .as_ref() - .unwrap() + assert!(result.buffer.relocs().is_empty()); + + let traps = result .buffer - .relocs() - .is_empty()); + .traps() + .into_iter() + .map(mach_trap_to_trap) + .collect::>(); let unwind_info = if isa.flags().unwind_info() { context @@ -713,7 +716,7 @@ impl Compiler { value_labels_ranges: Default::default(), info: Default::default(), address_map: Default::default(), - traps: Vec::new(), + traps, }) } @@ -889,6 +892,8 @@ fn mach_reloc_to_reloc(reloc: &MachReloc) -> Relocation { } } +const ALWAYS_TRAP_CODE: u16 = 100; + fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation { let &MachTrap { offset, code } = trap; TrapInformation { @@ -905,6 +910,7 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation { ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger, ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached, ir::TrapCode::Interrupt => TrapCode::Interrupt, + ir::TrapCode::User(ALWAYS_TRAP_CODE) => TrapCode::AlwaysTrapAdapter, // these should never be emitted by wasmtime-cranelift ir::TrapCode::User(_) => unreachable!(), diff --git a/crates/cranelift/src/compiler/component.rs b/crates/cranelift/src/compiler/component.rs index 11aa64b709..a55867e766 100644 --- a/crates/cranelift/src/compiler/component.rs +++ b/crates/cranelift/src/compiler/component.rs @@ -9,10 +9,10 @@ use cranelift_frontend::FunctionBuilder; use object::write::Object; use std::any::Any; use wasmtime_environ::component::{ - CanonicalOptions, Component, ComponentCompiler, ComponentTypes, LowerImport, LoweredIndex, - LoweringInfo, VMComponentOffsets, + AlwaysTrapInfo, CanonicalOptions, Component, ComponentCompiler, ComponentTypes, LowerImport, + LoweredIndex, LoweringInfo, RuntimeAlwaysTrapIndex, VMComponentOffsets, }; -use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline}; +use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline, TrapCode, WasmFuncType}; impl ComponentCompiler for Compiler { fn compile_lowered_trampoline( @@ -152,25 +152,51 @@ impl ComponentCompiler for Compiler { Ok(Box::new(func)) } + fn compile_always_trap(&self, ty: &WasmFuncType) -> Result> { + let isa = &*self.isa; + let CompilerContext { + mut func_translator, + codegen_context: mut context, + } = self.take_context(); + context.func = ir::Function::with_name_signature( + ir::ExternalName::user(0, 0), + crate::indirect_signature(isa, ty), + ); + let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context()); + let block0 = builder.create_block(); + builder.append_block_params_for_function_params(block0); + builder.switch_to_block(block0); + builder.seal_block(block0); + builder + .ins() + .trap(ir::TrapCode::User(super::ALWAYS_TRAP_CODE)); + builder.finalize(); + + let func: CompiledFunction = self.finish_trampoline(&mut context, isa)?; + self.save_context(CompilerContext { + func_translator, + codegen_context: context, + }); + Ok(Box::new(func)) + } + fn emit_obj( &self, lowerings: PrimaryMap>, + always_trap: PrimaryMap>, trampolines: Vec<(SignatureIndex, Box)>, obj: &mut Object<'static>, - ) -> Result<(PrimaryMap, Vec)> { - let lowerings: PrimaryMap = lowerings - .into_iter() - .map(|(_, f)| *f.downcast().unwrap()) - .collect(); - let trampolines: Vec<(SignatureIndex, CompiledFunction)> = trampolines - .into_iter() - .map(|(i, f)| (i, *f.downcast().unwrap())) - .collect(); - + ) -> Result<( + PrimaryMap, + PrimaryMap, + Vec, + )> { let module = Default::default(); let mut text = ModuleTextBuilder::new(obj, &module, &*self.isa); let mut ret = PrimaryMap::new(); for (idx, lowering) in lowerings.iter() { + let lowering = lowering.downcast_ref::().unwrap(); + assert!(lowering.traps.is_empty()); let (_symbol, range) = text.append_func( false, format!("_wasm_component_lowering_trampoline{}", idx.as_u32()).into_bytes(), @@ -183,13 +209,35 @@ impl ComponentCompiler for Compiler { }); assert_eq!(i, idx); } + let ret_always_trap = always_trap + .iter() + .map(|(i, func)| { + let func = func.downcast_ref::().unwrap(); + assert_eq!(func.traps.len(), 1); + assert_eq!(func.traps[0].trap_code, TrapCode::AlwaysTrapAdapter); + let name = format!("_wasmtime_always_trap{}", i.as_u32()); + let range = text.named_func(&name, func); + let start = u32::try_from(range.start).unwrap(); + let end = u32::try_from(range.end).unwrap(); + AlwaysTrapInfo { + start: start, + length: end - start, + trap_offset: func.traps[0].code_offset, + } + }) + .collect(); + let ret_trampolines = trampolines .iter() - .map(|(i, func)| text.trampoline(*i, func)) + .map(|(i, func)| { + let func = func.downcast_ref::().unwrap(); + assert!(func.traps.is_empty()); + text.trampoline(*i, func) + }) .collect(); text.finish()?; - Ok((ret, ret_trampolines)) + Ok((ret, ret_always_trap, ret_trampolines)) } } diff --git a/crates/cranelift/src/obj.rs b/crates/cranelift/src/obj.rs index a10a53844e..58bb4c6eca 100644 --- a/crates/cranelift/src/obj.rs +++ b/crates/cranelift/src/obj.rs @@ -168,7 +168,7 @@ impl<'a> ModuleTextBuilder<'a> { pub fn trampoline(&mut self, sig: SignatureIndex, func: &'a CompiledFunction) -> Trampoline { let name = obj::trampoline_symbol_name(sig); - let (_, range) = self.append_func(false, name.into_bytes(), func); + let range = self.named_func(&name, func); Trampoline { signature: sig, start: range.start, @@ -176,6 +176,11 @@ impl<'a> ModuleTextBuilder<'a> { } } + pub fn named_func(&mut self, name: &str, func: &'a CompiledFunction) -> Range { + let (_, range) = self.append_func(false, name.as_bytes().to_vec(), func); + range + } + /// Forces "veneers" to be used for inter-function calls in the text /// section which means that in-bounds optimized addresses are never used. /// diff --git a/crates/environ/src/component/compiler.rs b/crates/environ/src/component/compiler.rs index 37735ea91b..c93000bc76 100644 --- a/crates/environ/src/component/compiler.rs +++ b/crates/environ/src/component/compiler.rs @@ -1,5 +1,7 @@ -use crate::component::{Component, ComponentTypes, LowerImport, LoweredIndex}; -use crate::{PrimaryMap, SignatureIndex, Trampoline}; +use crate::component::{ + Component, ComponentTypes, LowerImport, LoweredIndex, RuntimeAlwaysTrapIndex, +}; +use crate::{PrimaryMap, SignatureIndex, Trampoline, WasmFuncType}; use anyhow::Result; use object::write::Object; use serde::{Deserialize, Serialize}; @@ -16,6 +18,19 @@ pub struct LoweringInfo { pub length: u32, } +/// Description of an "always trap" function generated by +/// `ComponentCompiler::compile_always_trap`. +#[derive(Serialize, Deserialize)] +pub struct AlwaysTrapInfo { + /// The byte offset from the start of the text section where this trampoline + /// starts. + pub start: u32, + /// The byte length of this trampoline's function body. + pub length: u32, + /// The offset from `start` of where the trapping instruction is located. + pub trap_offset: u32, +} + /// Compilation support necessary for components. pub trait ComponentCompiler: Send + Sync { /// Creates a trampoline for a `canon.lower`'d host function. @@ -42,6 +57,13 @@ pub trait ComponentCompiler: Send + Sync { types: &ComponentTypes, ) -> Result>; + /// Creates a function which will always trap that has the `ty` specified. + /// + /// This will create a small trampoline whose only purpose is to generate a + /// trap at runtime. This is used to implement the degenerate case of a + /// `canon lift`'d function immediately being `canon lower`'d. + fn compile_always_trap(&self, ty: &WasmFuncType) -> Result>; + /// Emits the `lowerings` and `trampolines` specified into the in-progress /// ELF object specified by `obj`. /// @@ -53,7 +75,12 @@ pub trait ComponentCompiler: Send + Sync { fn emit_obj( &self, lowerings: PrimaryMap>, + always_trap: PrimaryMap>, tramplines: Vec<(SignatureIndex, Box)>, obj: &mut Object<'static>, - ) -> Result<(PrimaryMap, Vec)>; + ) -> Result<( + PrimaryMap, + PrimaryMap, + Vec, + )>; } diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index b28814ed5a..d75a03857f 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -143,6 +143,10 @@ pub struct Component { /// The number of modules that are required to be saved within an instance /// at runtime, or effectively the number of exported modules. pub num_runtime_modules: u32, + + /// The number of functions which "always trap" used to implement + /// `canon.lower` of `canon.lift`'d functions within the same component. + pub num_always_trap: u32, } /// GlobalInitializer instructions to get processed when instantiating a component @@ -173,6 +177,12 @@ pub enum GlobalInitializer { /// pointer the trampoline calls, and the canonical ABI options. LowerImport(LowerImport), + /// A core wasm function was "generated" via `canon lower` of a function + /// that was `canon lift`'d in the same component, meaning that the function + /// always traps. This is recorded within the `VMComponentContext` as a new + /// `VMCallerCheckedAnyfunc` that's available for use. + AlwaysTrap(AlwaysTrap), + /// A core wasm linear memory is going to be saved into the /// `VMComponentContext`. /// @@ -272,6 +282,17 @@ pub struct LowerImport { pub options: CanonicalOptions, } +/// Description of what to initialize when a `GlobalInitializer::AlwaysTrap` is +/// encountered. +#[derive(Debug, Serialize, Deserialize)] +pub struct AlwaysTrap { + /// The index of the function that is being initialized in the + /// `VMComponentContext`. + pub index: RuntimeAlwaysTrapIndex, + /// The core wasm signature of the function that's inserted. + pub canonical_abi: SignatureIndex, +} + /// Definition of a core wasm item and where it can come from within a /// component. /// @@ -288,6 +309,10 @@ pub enum CoreDef { /// that this `LoweredIndex` corresponds to the nth /// `GlobalInitializer::LowerImport` instruction. Lowered(LoweredIndex), + /// This is used to represent a degenerate case of where a `canon lift`'d + /// function is immediately `canon lower`'d in the same instance. Such a + /// function always traps at runtime. + AlwaysTrap(RuntimeAlwaysTrapIndex), } impl From> for CoreDef diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index e5dbf786cd..75f2334bfe 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -46,7 +46,7 @@ //! final `Component`. use crate::component::translate::*; -use crate::{ModuleTranslation, PrimaryMap}; +use crate::{ModuleTranslation, PrimaryMap, SignatureIndex}; use indexmap::IndexMap; pub(super) fn run( @@ -64,6 +64,7 @@ pub(super) fn run( runtime_realloc_interner: Default::default(), runtime_post_return_interner: Default::default(), runtime_memory_interner: Default::default(), + runtime_always_trap_interner: Default::default(), }; // The initial arguments to the root component are all host imports. This @@ -194,6 +195,7 @@ struct Inliner<'a> { runtime_realloc_interner: HashMap, runtime_post_return_interner: HashMap, runtime_memory_interner: HashMap, RuntimeMemoryIndex>, + runtime_always_trap_interner: HashMap, } /// A "stack frame" as part of the inlining process, or the progress through @@ -443,12 +445,6 @@ impl<'a> Inliner<'a> { // // NB: at this time only lowered imported functions are supported. Lower(func, options) => { - // Assign this lowering a unique index and determine the core - // wasm function index we're defining. - let index = LoweredIndex::from_u32(self.result.num_lowerings); - self.result.num_lowerings += 1; - let func_index = frame.funcs.push(CoreDef::Lowered(index)); - // Use the type information from `wasmparser` to lookup the core // wasm function signature of the lowered function. This avoids // us having to reimplement the @@ -461,20 +457,22 @@ impl<'a> Inliner<'a> { .types .as_ref() .unwrap() - .function_at(func_index.as_u32()) + .function_at(frame.funcs.next_key().as_u32()) .expect("should be in-bounds"); let canonical_abi = self .types .module_types_builder() .wasm_func_type(lowered_function_type.clone().try_into()?); - let options = self.canonical_options(frame, options); - match &frame.component_funcs[*func] { + let options_lower = self.canonical_options(frame, options); + let func = match &frame.component_funcs[*func] { // If this component function was originally a host import // then this is a lowered host function which needs a // trampoline to enter WebAssembly. That's recorded here // with all relevant information. ComponentFuncDef::Import(path) => { + let index = LoweredIndex::from_u32(self.result.num_lowerings); + self.result.num_lowerings += 1; let import = self.runtime_import(path); self.result .initializers @@ -482,35 +480,91 @@ impl<'a> Inliner<'a> { canonical_abi, import, index, - options, + options: options_lower, })); + CoreDef::Lowered(index) } - // TODO: Lowering a lift function could mean one of two - // things: + // This case handles when a lifted function is later + // lowered, and both the lowering and the lifting are + // happening within the same component instance. // - // * This could mean that a "fused adapter" was just - // identified. If the lifted function here comes from a - // different component than we're lowering into then we - // have identified the fusion location of two components - // talking to each other. Metadata needs to be recorded - // here about the fusion to get something generated by - // Cranelift later on. + // In this situation if the `canon.lower`'d function is + // called then it immediately sets `may_enter` to `false`. + // When calling the callee, however, that's `canon.lift` + // which immediately traps if `may_enter` is `false`. That + // means that this pairing of functions creates a function + // that always traps. // - // * Otherwise if the lifted function is in the same - // component that we're lowering into then that means - // something "funky" is happening. This needs to be - // carefully implemented with respect to the - // may_{enter,leave} flags as specified with the canonical - // ABI. The careful consideration for how to do this has - // not yet happened. + // When closely reading the spec though the precise trap + // that comes out can be somewhat variable. Technically the + // function yielded here is one that should validate the + // arguments by lifting them, and then trap. This means that + // the trap could be different depending on whether all + // arguments are valid for now. This was discussed in + // WebAssembly/component-model#51 somewhat and the + // conclusion was that we can probably get away with "always + // trap" here. // - // In general this is almost certainly going to require some - // new variant of `GlobalInitializer` in one form or another. - ComponentFuncDef::Lifted { .. } => { + // The `CoreDef::AlwaysTrap` variant here is used to + // indicate that this function is valid but if something + // actually calls it then it just generates a trap + // immediately. + ComponentFuncDef::Lifted { + options: options_lift, + .. + } if options_lift.instance == options_lower.instance => { + let index = *self + .runtime_always_trap_interner + .entry(canonical_abi) + .or_insert_with(|| { + let index = + RuntimeAlwaysTrapIndex::from_u32(self.result.num_always_trap); + self.result.num_always_trap += 1; + self.result.initializers.push(GlobalInitializer::AlwaysTrap( + AlwaysTrap { + canonical_abi, + index, + }, + )); + index + }); + CoreDef::AlwaysTrap(index) + } + + // Lowering a lifted function where the destination + // component is different than the source component means + // that a "fused adapter" was just identified. + // + // This is the location where, when this is actually + // implemented, we'll record metadata about this fused + // adapter to get compiled later during the compilation + // process. The fused adapter here will be generated by + // cranelift and will perfom argument validation when + // called, copy the arguments from `options_lower` to + // `options_lift` and then call the `func` specified for the + // lifted options. + // + // When the `func` returns the canonical adapter will verify + // the return values, copy them from `options_lift` to + // `options_lower`, and then return. + ComponentFuncDef::Lifted { + ty, + func, + options: options_lift, + } => { + // These are the various compilation options for lifting + // and lowering. + drop(ty); // component-model function type + drop(func); // original core wasm function that was lifted + drop(options_lift); // options during `canon lift` + drop(options_lower); // options during `canon lower` + drop(canonical_abi); // type signature of created core wasm function + unimplemented!("lowering a lifted function") } - } + }; + frame.funcs.push(func); } // Lifting a core wasm function is relatively easy for now in that @@ -656,7 +710,7 @@ impl<'a> Inliner<'a> { frame.tables.push( match self.core_def_of_module_instance_export(frame, *instance, *name) { CoreDef::Export(e) => e, - CoreDef::Lowered(_) => unreachable!(), + CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(), }, ); } @@ -665,7 +719,7 @@ impl<'a> Inliner<'a> { frame.globals.push( match self.core_def_of_module_instance_export(frame, *instance, *name) { CoreDef::Export(e) => e, - CoreDef::Lowered(_) => unreachable!(), + CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(), }, ); } @@ -674,7 +728,7 @@ impl<'a> Inliner<'a> { frame.memories.push( match self.core_def_of_module_instance_export(frame, *instance, *name) { CoreDef::Export(e) => e, - CoreDef::Lowered(_) => unreachable!(), + CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(), }, ); } diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index fac05c0bc8..5882bfc1e7 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -145,6 +145,9 @@ indices! { /// component model. pub struct LoweredIndex(u32); + /// Same as `LoweredIndex` but for the `CoreDef::AlwaysTrap` variant. + pub struct RuntimeAlwaysTrapIndex(u32); + /// Index representing a linear memory extracted from a wasm instance /// which is stored in a `VMComponentContext`. This is used to deduplicate /// references to the same linear memory where it's only stored once in a diff --git a/crates/environ/src/component/vmcomponent_offsets.rs b/crates/environ/src/component/vmcomponent_offsets.rs index 4922db4d9a..700d2a16df 100644 --- a/crates/environ/src/component/vmcomponent_offsets.rs +++ b/crates/environ/src/component/vmcomponent_offsets.rs @@ -5,6 +5,7 @@ // store: *mut dyn Store, // flags: [VMComponentFlags; component.num_runtime_component_instances], // lowering_anyfuncs: [VMCallerCheckedAnyfunc; component.num_lowerings], +// always_trap_anyfuncs: [VMCallerCheckedAnyfunc; component.num_always_trap], // lowerings: [VMLowering; component.num_lowerings], // memories: [*mut VMMemoryDefinition; component.num_memories], // reallocs: [*mut VMCallerCheckedAnyfunc; component.num_reallocs], @@ -12,8 +13,8 @@ // } use crate::component::{ - Component, LoweredIndex, RuntimeComponentInstanceIndex, RuntimeMemoryIndex, - RuntimePostReturnIndex, RuntimeReallocIndex, + Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex, + RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex, }; use crate::PtrSize; @@ -52,12 +53,16 @@ pub struct VMComponentOffsets

{ /// Number of component instances internally in the component (always at /// least 1). pub num_runtime_component_instances: u32, + /// Number of "always trap" functions which have their + /// `VMCallerCheckedAnyfunc` stored inline in the `VMComponentContext`. + pub num_always_trap: u32, // precalculated offsets of various member fields magic: u32, store: u32, flags: u32, lowering_anyfuncs: u32, + always_trap_anyfuncs: u32, lowerings: u32, memories: u32, reallocs: u32, @@ -85,10 +90,12 @@ impl VMComponentOffsets

{ .num_runtime_component_instances .try_into() .unwrap(), + num_always_trap: component.num_always_trap, magic: 0, store: 0, flags: 0, lowering_anyfuncs: 0, + always_trap_anyfuncs: 0, lowerings: 0, memories: 0, reallocs: 0, @@ -127,6 +134,7 @@ impl VMComponentOffsets

{ size(flags) = cmul(ret.num_runtime_component_instances, ret.size_of_vmcomponent_flags()), align(u32::from(ret.ptr.size())), size(lowering_anyfuncs) = cmul(ret.num_lowerings, ret.ptr.size_of_vmcaller_checked_anyfunc()), + size(always_trap_anyfuncs) = cmul(ret.num_always_trap, ret.ptr.size_of_vmcaller_checked_anyfunc()), size(lowerings) = cmul(ret.num_lowerings, ret.ptr.size() * 2), size(memories) = cmul(ret.num_runtime_memories, ret.ptr.size()), size(reallocs) = cmul(ret.num_runtime_reallocs, ret.ptr.size()), @@ -188,6 +196,20 @@ impl VMComponentOffsets

{ + index.as_u32() * u32::from(self.ptr.size_of_vmcaller_checked_anyfunc()) } + /// The offset of the `always_trap_anyfuncs` field. + #[inline] + pub fn always_trap_anyfuncs(&self) -> u32 { + self.always_trap_anyfuncs + } + + /// The offset of `VMCallerCheckedAnyfunc` for the `index` specified. + #[inline] + pub fn always_trap_anyfunc(&self, index: RuntimeAlwaysTrapIndex) -> u32 { + assert!(index.as_u32() < self.num_always_trap); + self.always_trap_anyfuncs() + + index.as_u32() * u32::from(self.ptr.size_of_vmcaller_checked_anyfunc()) + } + /// The offset of the `lowerings` field. #[inline] pub fn lowerings(&self) -> u32 { diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs index b0969afcad..1a56bb2618 100644 --- a/crates/environ/src/trap_encoding.rs +++ b/crates/environ/src/trap_encoding.rs @@ -98,6 +98,10 @@ pub enum TrapCode { /// Execution has potentially run too long and may be interrupted. /// This trap is resumable. Interrupt, + + /// Used for the component model when functions are lifted/lowered in a way + /// that generates a function that always traps. + AlwaysTrapAdapter, // if adding a variant here be sure to update the `check!` macro below } @@ -205,6 +209,7 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { BadConversionToInteger UnreachableCodeReached Interrupt + AlwaysTrapAdapter } if cfg!(debug_assertions) { diff --git a/crates/runtime/src/component.rs b/crates/runtime/src/component.rs index b6ed665273..ddc939d0c8 100644 --- a/crates/runtime/src/component.rs +++ b/crates/runtime/src/component.rs @@ -17,10 +17,10 @@ use std::mem; use std::ops::Deref; use std::ptr::{self, NonNull}; use wasmtime_environ::component::{ - Component, LoweredIndex, RuntimeComponentInstanceIndex, RuntimeMemoryIndex, - RuntimePostReturnIndex, RuntimeReallocIndex, StringEncoding, VMComponentOffsets, - VMCOMPONENT_FLAG_MAY_ENTER, VMCOMPONENT_FLAG_MAY_LEAVE, VMCOMPONENT_FLAG_NEEDS_POST_RETURN, - VMCOMPONENT_MAGIC, + Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex, + RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex, StringEncoding, + VMComponentOffsets, VMCOMPONENT_FLAG_MAY_ENTER, VMCOMPONENT_FLAG_MAY_LEAVE, + VMCOMPONENT_FLAG_NEEDS_POST_RETURN, VMCOMPONENT_MAGIC, }; use wasmtime_environ::HostPtr; @@ -259,6 +259,20 @@ impl ComponentInstance { } } + /// Same as `lowering_anyfunc` except for the functions that always trap. + pub fn always_trap_anyfunc( + &self, + idx: RuntimeAlwaysTrapIndex, + ) -> NonNull { + unsafe { + let ret = self + .vmctx_plus_offset::(self.offsets.always_trap_anyfunc(idx)); + debug_assert!((*ret).func_ptr.as_ptr() as usize != INVALID_PTR); + debug_assert!((*ret).vmctx as usize != INVALID_PTR); + NonNull::new(ret).unwrap() + } + } + /// Stores the runtime memory pointer at the index specified. /// /// This is intended to be called during the instantiation process of a @@ -340,6 +354,28 @@ impl ComponentInstance { } } + /// Same as `set_lowering` but for the "always trap" functions. + pub fn set_always_trap( + &mut self, + idx: RuntimeAlwaysTrapIndex, + func_ptr: NonNull, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + debug_assert!( + *self.vmctx_plus_offset::(self.offsets.always_trap_anyfunc(idx)) + == INVALID_PTR + ); + let vmctx = self.vmctx(); + *self.vmctx_plus_offset(self.offsets.always_trap_anyfunc(idx)) = + VMCallerCheckedAnyfunc { + func_ptr, + type_index, + vmctx: VMOpaqueContext::from_vmcomponent(vmctx), + }; + } + } + unsafe fn initialize_vmctx(&mut self, store: *mut dyn Store) { *self.vmctx_plus_offset(self.offsets.magic()) = VMCOMPONENT_MAGIC; *self.vmctx_plus_offset(self.offsets.store()) = store; @@ -362,6 +398,11 @@ impl ComponentInstance { let offset = self.offsets.lowering_anyfunc(i); *self.vmctx_plus_offset(offset) = INVALID_PTR; } + for i in 0..self.offsets.num_always_trap { + let i = RuntimeAlwaysTrapIndex::from_u32(i); + let offset = self.offsets.always_trap_anyfunc(i); + *self.vmctx_plus_offset(offset) = INVALID_PTR; + } for i in 0..self.offsets.num_runtime_memories { let i = RuntimeMemoryIndex::from_u32(i); let offset = self.offsets.runtime_memory(i); @@ -476,6 +517,19 @@ impl OwnedComponentInstance { .set_lowering(idx, lowering, anyfunc_func_ptr, anyfunc_type_index) } } + + /// See `ComponentInstance::set_always_trap` + pub fn set_always_trap( + &mut self, + idx: RuntimeAlwaysTrapIndex, + func_ptr: NonNull, + type_index: VMSharedSignatureIndex, + ) { + unsafe { + self.instance_mut() + .set_always_trap(idx, func_ptr, type_index) + } + } } impl Deref for OwnedComponentInstance { diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs index f3cd6c749b..49071de636 100644 --- a/crates/wasmtime/src/component/component.rs +++ b/crates/wasmtime/src/component/component.rs @@ -1,6 +1,7 @@ use crate::signatures::SignatureCollection; use crate::{Engine, Module}; use anyhow::{bail, Context, Result}; +use std::any::Any; use std::collections::HashMap; use std::collections::HashSet; use std::fs; @@ -9,9 +10,10 @@ use std::path::Path; use std::ptr::NonNull; use std::sync::Arc; use wasmtime_environ::component::{ - ComponentTypes, GlobalInitializer, LoweredIndex, LoweringInfo, StaticModuleIndex, Translator, + AlwaysTrapInfo, ComponentTypes, GlobalInitializer, LoweredIndex, LoweringInfo, + RuntimeAlwaysTrapIndex, StaticModuleIndex, Translator, }; -use wasmtime_environ::PrimaryMap; +use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline, TrapCode}; use wasmtime_jit::CodeMemory; use wasmtime_runtime::VMFunctionBody; @@ -41,19 +43,27 @@ struct ComponentInner { /// this field. types: Arc, - /// The in-memory ELF image of the compiled trampolines for this component. - /// - /// This is currently only used for wasm-to-host trampolines when - /// `canon.lower` is encountered. + /// The in-memory ELF image of the compiled functions for this component. trampoline_obj: CodeMemory, /// The index ranges within `trampoline_obj`'s mmap memory for the entire /// text section. text: Range, - /// Where trampolines are located within the `text` section of - /// `trampoline_obj`. - trampolines: PrimaryMap, + /// Where lowered function trampolines are located within the `text` + /// section of `trampoline_obj`. + /// + /// These trampolines are the function pointer within the + /// `VMCallerCheckedAnyfunc` and will delegate indirectly to a host function + /// pointer when called. + lowerings: PrimaryMap, + + /// Where the "always trap" functions are located within the `text` section + /// of `trampoline_obj`. + /// + /// These functions are "degenerate functions" here solely to implement + /// functions that are `canon lift`'d then immediately `canon lower`'d. + always_trap: PrimaryMap, } impl Component { @@ -117,39 +127,10 @@ impl Component { .context("failed to parse WebAssembly module")?; let types = Arc::new(types.finish()); - // All lowered functions will require a trampoline to be available in - // case they're used when entering wasm. For example a lowered function - // could be immediately lifted in which case we'll need a trampoline to - // call that lowered function. - // - // Most of the time trampolines can come from the core wasm modules - // since lifted functions come from core wasm. For these esoteric cases - // though we may have to compile trampolines specifically into the - // component object as well in case core wasm doesn't provide the - // necessary trampoline. - let lowerings = component - .initializers - .iter() - .filter_map(|init| match init { - GlobalInitializer::LowerImport(i) => Some(i), - _ => None, - }) - .collect::>(); - let required_trampolines = lowerings - .iter() - .map(|l| l.canonical_abi) - .collect::>(); let provided_trampolines = modules .iter() .flat_map(|(_, m)| m.exported_signatures.iter().copied()) .collect::>(); - let mut trampolines_to_compile = required_trampolines - .difference(&provided_trampolines) - .collect::>(); - // Ensure a deterministically compiled artifact by sorting this list - // which was otherwise created with nondeterministically ordered hash - // tables. - trampolines_to_compile.sort(); let (static_modules, trampolines) = engine.join_maybe_parallel( // In one (possibly) parallel task all the modules found within this @@ -173,41 +154,10 @@ impl Component { }, // In another (possibly) parallel task we compile lowering // trampolines necessary found in the component. - || -> Result<_> { - let compiler = engine.compiler(); - let (lowered_trampolines, core_trampolines) = engine.join_maybe_parallel( - // Compile all the lowered trampolines here which implement - // `canon lower` and are used to exit wasm into the host. - || -> Result<_> { - Ok(engine - .run_maybe_parallel(lowerings, |lowering| { - compiler - .component_compiler() - .compile_lowered_trampoline(&component, lowering, &types) - })? - .into_iter() - .collect()) - }, - // Compile all entry host-to-wasm trampolines here that - // aren't otherwise provided by core wasm modules. - || -> Result<_> { - engine.run_maybe_parallel(trampolines_to_compile.clone(), |i| { - let ty = &types[*i]; - Ok((*i, compiler.compile_host_to_wasm_trampoline(ty)?)) - }) - }, - ); - let mut obj = engine.compiler().object()?; - let trampolines = compiler.component_compiler().emit_obj( - lowered_trampolines?, - core_trampolines?, - &mut obj, - )?; - Ok((trampolines, wasmtime_jit::mmap_vec_from_obj(obj)?)) - }, + || Component::compile_component(engine, &component, &types, &provided_trampolines), ); let static_modules = static_modules?; - let ((lowering_trampolines, core_trampolines), trampoline_obj) = trampolines?; + let (lowerings, always_trap, trampolines, trampoline_obj) = trampolines?; let mut trampoline_obj = CodeMemory::new(trampoline_obj); let code = trampoline_obj.publish()?; let text = wasmtime_jit::subslice_range(code.text, code.mmap); @@ -231,8 +181,8 @@ impl Component { vmtrampolines.insert(idx, trampoline); } } - for (signature, trampoline) in trampolines_to_compile.iter().zip(core_trampolines) { - vmtrampolines.insert(**signature, unsafe { + for trampoline in trampolines { + vmtrampolines.insert(trampoline.signature, unsafe { let ptr = code.text[trampoline.start as usize..][..trampoline.length as usize].as_ptr(); std::mem::transmute::<*const u8, wasmtime_runtime::VMTrampoline>(ptr) @@ -248,6 +198,15 @@ impl Component { vmtrampolines.into_iter(), ); + // Assert that this `always_trap` list is sorted which is relied on in + // `register_component` as well as `Component::lookup_trap_code` below. + assert!(always_trap + .values() + .as_slice() + .windows(2) + .all(|window| { window[0].start < window[1].start })); + + crate::module::register_component(code.text, &always_trap); Ok(Component { inner: Arc::new(ComponentInner { component, @@ -256,11 +215,136 @@ impl Component { signatures, trampoline_obj, text, - trampolines: lowering_trampolines, + lowerings, + always_trap, }), }) } + #[cfg(compiler)] + fn compile_component( + engine: &Engine, + component: &wasmtime_environ::component::Component, + types: &ComponentTypes, + provided_trampolines: &HashSet, + ) -> Result<( + PrimaryMap, + PrimaryMap, + Vec, + wasmtime_runtime::MmapVec, + )> { + let results = engine.join_maybe_parallel( + || compile_lowerings(engine, component, types), + || -> Result<_> { + Ok(engine.join_maybe_parallel( + || compile_always_trap(engine, component, types), + || compile_trampolines(engine, component, types, provided_trampolines), + )) + }, + ); + let (lowerings, other) = results; + let (always_trap, trampolines) = other?; + let mut obj = engine.compiler().object()?; + let (lower, traps, trampolines) = engine.compiler().component_compiler().emit_obj( + lowerings?, + always_trap?, + trampolines?, + &mut obj, + )?; + return Ok(( + lower, + traps, + trampolines, + wasmtime_jit::mmap_vec_from_obj(obj)?, + )); + + fn compile_lowerings( + engine: &Engine, + component: &wasmtime_environ::component::Component, + types: &ComponentTypes, + ) -> Result>> { + let lowerings = component + .initializers + .iter() + .filter_map(|init| match init { + GlobalInitializer::LowerImport(i) => Some(i), + _ => None, + }) + .collect::>(); + Ok(engine + .run_maybe_parallel(lowerings, |lowering| { + engine + .compiler() + .component_compiler() + .compile_lowered_trampoline(&component, lowering, &types) + })? + .into_iter() + .collect()) + } + + fn compile_always_trap( + engine: &Engine, + component: &wasmtime_environ::component::Component, + types: &ComponentTypes, + ) -> Result>> { + let always_trap = component + .initializers + .iter() + .filter_map(|init| match init { + GlobalInitializer::AlwaysTrap(i) => Some(i), + _ => None, + }) + .collect::>(); + Ok(engine + .run_maybe_parallel(always_trap, |info| { + engine + .compiler() + .component_compiler() + .compile_always_trap(&types[info.canonical_abi]) + })? + .into_iter() + .collect()) + } + + fn compile_trampolines( + engine: &Engine, + component: &wasmtime_environ::component::Component, + types: &ComponentTypes, + provided_trampolines: &HashSet, + ) -> Result)>> { + // All lowered functions will require a trampoline to be available in + // case they're used when entering wasm. For example a lowered function + // could be immediately lifted in which case we'll need a trampoline to + // call that lowered function. + // + // Most of the time trampolines can come from the core wasm modules + // since lifted functions come from core wasm. For these esoteric cases + // though we may have to compile trampolines specifically into the + // component object as well in case core wasm doesn't provide the + // necessary trampoline. + let required_trampolines = component + .initializers + .iter() + .filter_map(|init| match init { + GlobalInitializer::LowerImport(i) => Some(i.canonical_abi), + GlobalInitializer::AlwaysTrap(i) => Some(i.canonical_abi), + _ => None, + }) + .collect::>(); + let mut trampolines_to_compile = required_trampolines + .difference(&provided_trampolines) + .collect::>(); + // Ensure a deterministically compiled artifact by sorting this list + // which was otherwise created with nondeterministically ordered hash + // tables. + trampolines_to_compile.sort(); + engine.run_maybe_parallel(trampolines_to_compile.clone(), |i| { + let ty = &types[*i]; + Ok((*i, engine.compiler().compile_host_to_wasm_trampoline(ty)?)) + }) + } + } + pub(crate) fn env_component(&self) -> &wasmtime_environ::component::Component { &self.inner.component } @@ -278,13 +362,52 @@ impl Component { } pub(crate) fn text(&self) -> &[u8] { - &self.inner.trampoline_obj.mmap()[self.inner.text.clone()] + self.inner.text() } - pub(crate) fn trampoline_ptr(&self, index: LoweredIndex) -> NonNull { - let info = &self.inner.trampolines[index]; + pub(crate) fn lowering_ptr(&self, index: LoweredIndex) -> NonNull { + let info = &self.inner.lowerings[index]; + self.func(info.start, info.length) + } + + pub(crate) fn always_trap_ptr(&self, index: RuntimeAlwaysTrapIndex) -> NonNull { + let info = &self.inner.always_trap[index]; + self.func(info.start, info.length) + } + + fn func(&self, start: u32, len: u32) -> NonNull { let text = self.text(); - let trampoline = &text[info.start as usize..][..info.length as usize]; + let trampoline = &text[start as usize..][..len as usize]; NonNull::new(trampoline.as_ptr() as *mut VMFunctionBody).unwrap() } + + /// Looks up a trap code for the instruction at `offset` where the offset + /// specified is relative to the start of this component's text section. + pub(crate) fn lookup_trap_code(&self, offset: usize) -> Option { + let offset = u32::try_from(offset).ok()?; + // Currently traps only come from "always trap" adapters so that map is + // the only map that's searched. + match self + .inner + .always_trap + .values() + .as_slice() + .binary_search_by_key(&offset, |info| info.start + info.trap_offset) + { + Ok(_) => Some(TrapCode::AlwaysTrapAdapter), + Err(_) => None, + } + } +} + +impl ComponentInner { + fn text(&self) -> &[u8] { + &self.trampoline_obj.mmap()[self.text.clone()] + } +} + +impl Drop for ComponentInner { + fn drop(&mut self) { + crate::module::unregister_component(self.text()); + } } diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs index 80c65f2f10..6ceb04bc4c 100644 --- a/crates/wasmtime/src/component/instance.rs +++ b/crates/wasmtime/src/component/instance.rs @@ -7,9 +7,9 @@ use anyhow::{anyhow, Context, Result}; use std::marker; use std::sync::Arc; use wasmtime_environ::component::{ - ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory, ExtractPostReturn, - ExtractRealloc, GlobalInitializer, InstantiateModule, LowerImport, RuntimeImportIndex, - RuntimeInstanceIndex, RuntimeModuleIndex, + AlwaysTrap, ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory, + ExtractPostReturn, ExtractRealloc, GlobalInitializer, InstantiateModule, LowerImport, + RuntimeImportIndex, RuntimeInstanceIndex, RuntimeModuleIndex, }; use wasmtime_environ::{EntityIndex, PrimaryMap}; use wasmtime_runtime::component::{ComponentInstance, OwnedComponentInstance}; @@ -145,12 +145,17 @@ impl InstanceData { pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> wasmtime_runtime::Export { match def { + CoreDef::Export(e) => self.lookup_export(store, e), CoreDef::Lowered(idx) => { wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction { anyfunc: self.state.lowering_anyfunc(*idx), }) } - CoreDef::Export(e) => self.lookup_export(store, e), + CoreDef::AlwaysTrap(idx) => { + wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction { + anyfunc: self.state.always_trap_anyfunc(*idx), + }) + } } } @@ -272,6 +277,8 @@ impl<'a> Instantiator<'a> { GlobalInitializer::LowerImport(import) => self.lower_import(import), + GlobalInitializer::AlwaysTrap(trap) => self.always_trap(trap), + GlobalInitializer::ExtractMemory(mem) => self.extract_memory(store.0, mem), GlobalInitializer::ExtractRealloc(realloc) => { @@ -307,7 +314,7 @@ impl<'a> Instantiator<'a> { self.data.state.set_lowering( import.index, func.lowering(), - self.component.trampoline_ptr(import.index), + self.component.lowering_ptr(import.index), self.component .signatures() .shared_signature(import.canonical_abi) @@ -324,6 +331,17 @@ impl<'a> Instantiator<'a> { self.data.funcs.push(func.clone()); } + fn always_trap(&mut self, trap: &AlwaysTrap) { + self.data.state.set_always_trap( + trap.index, + self.component.always_trap_ptr(trap.index), + self.component + .signatures() + .shared_signature(trap.canonical_abi) + .expect("found unregistered signature"), + ); + } + fn extract_memory(&mut self, store: &mut StoreOpaque, memory: &ExtractMemory) { let mem = match self.data.lookup_export(store, &memory.export) { wasmtime_runtime::Export::Memory(m) => m, diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index c48e878884..1f96daaaca 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -26,6 +26,8 @@ mod registry; mod serialization; pub use registry::{is_wasm_trap_pc, ModuleRegistry}; +#[cfg(feature = "component-model")] +pub use registry::{register_component, unregister_component}; pub use serialization::SerializedModule; /// A compiled WebAssembly module, ready to be instantiated. @@ -537,7 +539,7 @@ impl Module { // into the global registry of modules so we can resolve traps // appropriately. Note that the corresponding `unregister` happens below // in `Drop for ModuleInner`. - registry::register(&module); + registry::register_module(&module); Ok(Self { inner: Arc::new(ModuleInner { @@ -987,7 +989,7 @@ impl wasmtime_runtime::ModuleInfo for ModuleInner { impl Drop for ModuleInner { fn drop(&mut self) { - registry::unregister(&self.module); + registry::unregister_module(&self.module); } } diff --git a/crates/wasmtime/src/module/registry.rs b/crates/wasmtime/src/module/registry.rs index de59026b80..7062ba98ee 100644 --- a/crates/wasmtime/src/module/registry.rs +++ b/crates/wasmtime/src/module/registry.rs @@ -8,6 +8,11 @@ use std::{ sync::{Arc, RwLock}, }; use wasmtime_environ::TrapCode; +#[cfg(feature = "component-model")] +use wasmtime_environ::{ + component::{AlwaysTrapInfo, RuntimeAlwaysTrapIndex}, + PrimaryMap, +}; use wasmtime_jit::CompiledModule; use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline}; @@ -15,7 +20,7 @@ use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline}; /// /// Note that the primary reason for this registry is to ensure that everything /// in `Module` is kept alive for the duration of a `Store`. At this time we -/// need "basically everything" within a `Moudle` to stay alive once it's +/// need "basically everything" within a `Module` to stay alive once it's /// instantiated within a store. While there's some smaller portions that could /// theoretically be omitted as they're not needed by the store they're /// currently small enough to not worry much about. @@ -147,8 +152,13 @@ impl ModuleRegistry { /// Fetches trap information about a program counter in a backtrace. pub fn lookup_trap_code(&self, pc: usize) -> Option { - let (module, offset) = self.module(pc)?; - wasmtime_environ::lookup_trap_code(module.compiled_module().trap_data(), offset) + match self.module_or_component(pc)? { + (ModuleOrComponent::Module(module), offset) => { + wasmtime_environ::lookup_trap_code(module.compiled_module().trap_data(), offset) + } + #[cfg(feature = "component-model")] + (ModuleOrComponent::Component(component), offset) => component.lookup_trap_code(offset), + } } /// Fetches frame information about a program counter in a backtrace. @@ -160,9 +170,21 @@ impl ModuleRegistry { /// boolean indicates whether the engine used to compile this module is /// using environment variables to control debuginfo parsing. pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> { - let (module, offset) = self.module(pc)?; - let info = FrameInfo::new(module, offset)?; - Some((info, module)) + match self.module_or_component(pc)? { + (ModuleOrComponent::Module(module), offset) => { + let info = FrameInfo::new(module, offset)?; + Some((info, module)) + } + #[cfg(feature = "component-model")] + (ModuleOrComponent::Component(_), _) => { + // FIXME: should investigate whether it's worth preserving + // frame information on a `Component` to resolve a frame here. + // Note that this can be traced back to either a lowered + // function via a trampoline or an "always trap" function at + // this time which may be useful debugging information to have. + None + } + } } } @@ -183,12 +205,19 @@ lazy_static::lazy_static! { static ref GLOBAL_MODULES: RwLock = Default::default(); } -type GlobalModuleRegistry = BTreeMap)>; +type GlobalModuleRegistry = BTreeMap; + +#[derive(Clone)] +enum TrapInfo { + Module(Arc), + #[cfg(feature = "component-model")] + Component(Arc>), +} /// Returns whether the `pc`, according to globally registered information, /// is a wasm trap or not. pub fn is_wasm_trap_pc(pc: usize) -> bool { - let (module, text_offset) = { + let (trap_info, text_offset) = { let all_modules = GLOBAL_MODULES.read().unwrap(); let (end, (start, module)) = match all_modules.range(pc..).next() { @@ -201,7 +230,16 @@ pub fn is_wasm_trap_pc(pc: usize) -> bool { (module.clone(), pc - *start) }; - wasmtime_environ::lookup_trap_code(module.trap_data(), text_offset).is_some() + match trap_info { + TrapInfo::Module(module) => { + wasmtime_environ::lookup_trap_code(module.trap_data(), text_offset).is_some() + } + #[cfg(feature = "component-model")] + TrapInfo::Component(traps) => { + let offset = u32::try_from(text_offset).unwrap(); + traps.binary_search(&offset).is_ok() + } + } } /// Registers a new region of code. @@ -212,7 +250,7 @@ pub fn is_wasm_trap_pc(pc: usize) -> bool { /// This is required to enable traps to work correctly since the signal handler /// will lookup in the `GLOBAL_MODULES` list to determine which a particular pc /// is a trap or not. -pub fn register(module: &Arc) { +pub fn register_module(module: &Arc) { let code = module.code(); if code.is_empty() { return; @@ -222,14 +260,14 @@ pub fn register(module: &Arc) { let prev = GLOBAL_MODULES .write() .unwrap() - .insert(end, (start, module.clone())); + .insert(end, (start, TrapInfo::Module(module.clone()))); assert!(prev.is_none()); } /// Unregisters a module from the global map. /// -/// Must hae been previously registered with `register`. -pub fn unregister(module: &Arc) { +/// Must have been previously registered with `register`. +pub fn unregister_module(module: &Arc) { let code = module.code(); if code.is_empty() { return; @@ -239,6 +277,39 @@ pub fn unregister(module: &Arc) { assert!(module.is_some()); } +/// Same as `register_module`, but for components +#[cfg(feature = "component-model")] +pub fn register_component(text: &[u8], traps: &PrimaryMap) { + if text.is_empty() { + return; + } + let start = text.as_ptr() as usize; + let end = start + text.len(); + let info = Arc::new( + traps + .iter() + .map(|(_, info)| info.start + info.trap_offset) + .collect::>(), + ); + let prev = GLOBAL_MODULES + .write() + .unwrap() + .insert(end, (start, TrapInfo::Component(info))); + assert!(prev.is_none()); +} + +/// Same as `unregister_module`, but for components +#[cfg(feature = "component-model")] +pub fn unregister_component(text: &[u8]) { + if text.is_empty() { + return; + } + let start = text.as_ptr() as usize; + let end = start + text.len(); + let info = GLOBAL_MODULES.write().unwrap().remove(&end); + assert!(info.is_some()); +} + #[test] fn test_frame_info() -> Result<(), anyhow::Error> { use crate::*; diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 432f14f407..b07f2d7dea 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -87,6 +87,13 @@ pub enum TrapCode { /// Execution has potentially run too long and may be interrupted. Interrupt, + + /// When the `component-model` feature is enabled this trap represents a + /// function that was `canon lift`'d, then `canon lower`'d, then called. + /// This combination of creation of a function in the component model + /// generates a function that always traps and, when called, produces this + /// flavor of trap. + AlwaysTrapAdapter, } impl TrapCode { @@ -104,6 +111,7 @@ impl TrapCode { EnvTrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger, EnvTrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached, EnvTrapCode::Interrupt => TrapCode::Interrupt, + EnvTrapCode::AlwaysTrapAdapter => TrapCode::AlwaysTrapAdapter, } } } @@ -123,6 +131,7 @@ impl fmt::Display for TrapCode { BadConversionToInteger => "invalid conversion to integer", UnreachableCodeReached => "wasm `unreachable` instruction executed", Interrupt => "interrupt", + AlwaysTrapAdapter => "degenerate component adapter called", }; write!(f, "{}", desc) } diff --git a/tests/misc_testsuite/component-model/adapter.wast b/tests/misc_testsuite/component-model/adapter.wast index df314cd8d1..0f2936951b 100644 --- a/tests/misc_testsuite/component-model/adapter.wast +++ b/tests/misc_testsuite/component-model/adapter.wast @@ -83,3 +83,31 @@ ) (export "f" (func $f2)) ) + +;; valid, but odd +(component + (core module $m (func (export ""))) + (core instance $m (instantiate $m)) + + (func $f1 (canon lift (core func $m ""))) + (core func $f2 (canon lower (func $f1))) +) +(assert_trap + (component + (core module $m (func (export ""))) + (core instance $m (instantiate $m)) + + (func $f1 (canon lift (core func $m ""))) + (core func $f2 (canon lower (func $f1))) + + (core module $m2 + (import "" "" (func $f)) + (func $start + call $f) + (start $start) + ) + (core instance (instantiate $m2 + (with "" (instance (export "" (func $f2)))) + )) + ) + "degenerate component adapter called")