diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 663fac5f2dd5..6bd631c9907c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -137,7 +137,9 @@ jobs: - run: cargo check -p wasmtime --no-default-features --features pooling-allocator - run: cargo check -p wasmtime --no-default-features --features cranelift - run: cargo check -p wasmtime --no-default-features --features wasm-backtrace + - run: cargo check -p wasmtime --no-default-features --features component-model - run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache,wasm-backtrace + - run: cargo check --features component-model # Check that benchmarks of the cranelift project build - run: cargo check --benches -p cranelift-codegen @@ -297,6 +299,10 @@ jobs: env: RUST_BACKTRACE: 1 + # Test the component-model related functionality which is gated behind a + # compile-time feature + - run: cargo test --features component-model + # Build and test the wasi-nn module. test_wasi_nn: name: Test wasi-nn module diff --git a/Cargo.toml b/Cargo.toml index 5888bde21a51..8518ac5da6bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-a all-arch = ["wasmtime/all-arch"] posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"] wasm-backtrace = ["wasmtime/wasm-backtrace", "wasmtime-cli-flags/wasm-backtrace"] +component-model = ["wasmtime/component-model", "wasmtime-wast/component-model", "wasmtime-cli-flags/component-model"] # Stub feature that does nothing, for Cargo-features compatibility: the new # backend is the default now. diff --git a/build.rs b/build.rs index 141469d48711..4918c3466e43 100644 --- a/build.rs +++ b/build.rs @@ -28,6 +28,9 @@ fn main() -> anyhow::Result<()> { test_directory_module(out, "tests/misc_testsuite/simd", strategy)?; test_directory_module(out, "tests/misc_testsuite/threads", strategy)?; test_directory_module(out, "tests/misc_testsuite/memory64", strategy)?; + if cfg!(feature = "component-model") { + test_directory_module(out, "tests/misc_testsuite/component-model", strategy)?; + } Ok(()) })?; diff --git a/crates/cli-flags/Cargo.toml b/crates/cli-flags/Cargo.toml index cc89617af51c..a8183e34eaef 100644 --- a/crates/cli-flags/Cargo.toml +++ b/crates/cli-flags/Cargo.toml @@ -26,3 +26,4 @@ default = [ pooling-allocator = [] memory-init-cow = [] wasm-backtrace = [] +component-model = [] diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index bdbe13f16364..13904ccf2269 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -39,6 +39,8 @@ pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ ("simd", "enables support for proposed SIMD instructions"), ("threads", "enables support for WebAssembly threads"), ("memory64", "enables support for 64-bit memories"), + #[cfg(feature = "component-model")] + ("component-model", "enables support for the component model"), ]; pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[ @@ -333,6 +335,8 @@ impl CommonOptions { threads, multi_memory, memory64, + #[cfg(feature = "component-model")] + component_model, } = self.wasm_features.unwrap_or_default(); if let Some(enable) = simd { @@ -358,6 +362,10 @@ impl CommonOptions { if let Some(enable) = memory64 { config.wasm_memory64(enable); } + #[cfg(feature = "component-model")] + if let Some(enable) = component_model { + config.wasm_component_model(enable); + } } pub fn opt_level(&self) -> wasmtime::OptLevel { @@ -391,6 +399,8 @@ pub struct WasmFeatures { pub threads: Option, pub multi_memory: Option, pub memory64: Option, + #[cfg(feature = "component-model")] + pub component_model: Option, } fn parse_wasm_features(features: &str) -> Result { @@ -439,6 +449,8 @@ fn parse_wasm_features(features: &str) -> Result { threads: all.or(values["threads"]), multi_memory: all.or(values["multi-memory"]), memory64: all.or(values["memory64"]), + #[cfg(feature = "component-model")] + component_model: all.or(values["component-model"]), }) } diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index b0739f0d36b1..8dbd8028bdc6 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -28,8 +28,8 @@ use std::mem; use std::sync::Mutex; use wasmtime_environ::{ AddressMapSection, CompileError, FilePos, FlagValue, FunctionBodyData, FunctionInfo, - InstructionAddressMap, Module, ModuleTranslation, StackMapInformation, Trampoline, TrapCode, - TrapEncodingBuilder, TrapInformation, Tunables, TypeTables, VMOffsets, + InstructionAddressMap, Module, ModuleTranslation, ModuleTypes, StackMapInformation, Trampoline, + TrapCode, TrapEncodingBuilder, TrapInformation, Tunables, VMOffsets, }; /// A compiler that compiles a WebAssembly module with Compiler, translating @@ -109,7 +109,7 @@ impl wasmtime_environ::Compiler for Compiler { func_index: DefinedFuncIndex, mut input: FunctionBodyData<'_>, tunables: &Tunables, - types: &TypeTables, + types: &ModuleTypes, ) -> Result, CompileError> { let isa = &*self.isa; let module = &translation.module; @@ -237,7 +237,7 @@ impl wasmtime_environ::Compiler for Compiler { fn emit_obj( &self, translation: &ModuleTranslation, - types: &TypeTables, + types: &ModuleTypes, funcs: PrimaryMap>, tunables: &Tunables, obj: &mut Object<'static>, diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index d30917294dab..f2eb6df6ea02 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -16,8 +16,8 @@ use std::convert::TryFrom; use std::mem; use wasmparser::Operator; use wasmtime_environ::{ - BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, TableStyle, Tunables, - TypeTables, VMOffsets, WASM_PAGE_SIZE, + BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleTypes, + TableStyle, Tunables, VMOffsets, WASM_PAGE_SIZE, }; use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK}; @@ -113,7 +113,7 @@ pub struct FuncEnvironment<'module_environment> { isa: &'module_environment (dyn TargetIsa + 'module_environment), module: &'module_environment Module, translation: &'module_environment ModuleTranslation<'module_environment>, - types: &'module_environment TypeTables, + types: &'module_environment ModuleTypes, /// The Cranelift global holding the vmctx address. vmctx: Option, @@ -158,7 +158,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> { pub fn new( isa: &'module_environment (dyn TargetIsa + 'module_environment), translation: &'module_environment ModuleTranslation<'module_environment>, - types: &'module_environment TypeTables, + types: &'module_environment ModuleTypes, tunables: &'module_environment Tunables, ) -> Self { let builtin_function_signatures = BuiltinFunctionSignatures::new( diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 02b9c3e0ba4b..0bd91a55f9e2 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -51,7 +51,7 @@ use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmFuncType, WasmType}; use target_lexicon::CallingConvention; use wasmtime_environ::{ - FilePos, FunctionInfo, InstructionAddressMap, ModuleTranslation, TrapInformation, TypeTables, + FilePos, FunctionInfo, InstructionAddressMap, ModuleTranslation, ModuleTypes, TrapInformation, }; pub use builder::builder; @@ -208,7 +208,7 @@ fn indirect_signature(isa: &dyn TargetIsa, wasm: &WasmFuncType) -> ir::Signature fn func_signature( isa: &dyn TargetIsa, translation: &ModuleTranslation, - types: &TypeTables, + types: &ModuleTypes, index: FuncIndex, ) -> ir::Signature { let func = &translation.module.functions[index]; diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml index 99d129565192..9088b6b56e6c 100644 --- a/crates/environ/Cargo.toml +++ b/crates/environ/Cargo.toml @@ -26,3 +26,6 @@ target-lexicon = "0.12" [badges] maintenance = { status = "actively-developed" } + +[features] +component-model = [] diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 1dc6891bab62..bd43d15d342f 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -2,8 +2,8 @@ //! module. use crate::{ - DefinedFuncIndex, FilePos, FunctionBodyData, ModuleTranslation, PrimaryMap, SignatureIndex, - StackMap, Tunables, TypeTables, WasmError, WasmFuncType, + DefinedFuncIndex, FilePos, FunctionBodyData, ModuleTranslation, ModuleTypes, PrimaryMap, + SignatureIndex, StackMap, Tunables, WasmError, WasmFuncType, }; use anyhow::Result; use object::write::Object; @@ -148,7 +148,7 @@ pub trait Compiler: Send + Sync { index: DefinedFuncIndex, data: FunctionBodyData<'_>, tunables: &Tunables, - types: &TypeTables, + types: &ModuleTypes, ) -> Result, CompileError>; /// Collects the results of compilation into an in-memory object. @@ -169,7 +169,7 @@ pub trait Compiler: Send + Sync { fn emit_obj( &self, module: &ModuleTranslation, - types: &TypeTables, + types: &ModuleTypes, funcs: PrimaryMap>, tunables: &Tunables, obj: &mut Object<'static>, diff --git a/crates/environ/src/component.rs b/crates/environ/src/component.rs new file mode 100644 index 000000000000..3295d34975d9 --- /dev/null +++ b/crates/environ/src/component.rs @@ -0,0 +1,34 @@ +//! Support for the component model in Wasmtime. +//! +//! This module contains all of the internal type definitions used by Wasmtime +//! to process the component model. Despite everything being `pub` here this is +//! not the public interface of Wasmtime to the component model. Instead this is +//! the internal support to mirror the core wasm support that Wasmtime already +//! implements. +//! +//! Some main items contained within here are: +//! +//! * Type hierarchy information for the component model +//! * Translation of a component into Wasmtime's representation +//! * Type information about a component used at runtime +//! +//! This module also contains a lot of Serialize/Deserialize types which are +//! encoded in the final compiled image for a component. +//! +//! Note that this entire module is gated behind the `component-model` Cargo +//! feature. +//! +//! ## Warning: In-progress +//! +//! As-of the time of this writing this module is incomplete and under +//! development. It will be added to incrementally over time as more features +//! are implemented. Current design decisions are also susceptible to change at +//! any time. Some comments may reflect historical rather than current state as +//! well (sorry). + +mod info; +mod translate; +mod types; +pub use self::info::*; +pub use self::translate::*; +pub use self::types::*; diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs new file mode 100644 index 000000000000..0ab659e65b57 --- /dev/null +++ b/crates/environ/src/component/info.rs @@ -0,0 +1,174 @@ +// General runtime type-information about a component. +// +// ## Optimizing instantiation +// +// One major consideration for the structure of the types in this module is to +// make instantiation as fast as possible. To facilitate this the representation +// here avoids the need to create a `PrimaryMap` during instantiation of a +// component for each index space like the func, global, table, etc, index +// spaces. Instead a component is simply defined by a list of instantiation +// instructions, and arguments to the instantiation of each instance are a list +// of "pointers" into previously created instances. This means that we only need +// to build up one list of instances during instantiation. +// +// Additionally we also try to avoid string lookups wherever possible. In the +// component model instantiation and aliasing theoretically deals with lots of +// string lookups here and there. This is slower than indexing lookup, though, +// and not actually necessary when the structure of a module is statically +// known. This means that `ExportItem` below has two variants where we try to +// use the indexing variant as much as possible, which can be done for +// everything except imported core wasm modules. + +use crate::component::*; +use crate::{EntityIndex, PrimaryMap}; +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; + +/// Run-time-type-information about a `Component`, its structure, and how to +/// instantiate it. +/// +/// This type is intended to mirror the `Module` type in this crate which +/// provides all the runtime information about the structure of a module and +/// how it works. +/// +/// NB: Lots of the component model is not yet implemented in the runtime so +/// this is going to undergo a lot of churn. +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct Component { + /// A list of typed values that this component imports, indexed by either + /// the import's position or the name of the import. + pub imports: IndexMap, + + /// A list of this component's exports, indexed by either position or name. + pub exports: IndexMap, + + /// The list of instances that this component creates during instantiation. + /// + /// Note that this is flattened/resolved from the original component to + /// the point where alias annotations and such are not required. Instead + /// the list of arguments to instantiate each module is provided as exports + /// of prior instantiations. + pub instances: PrimaryMap, +} + +/// Different ways to instantiate a module at runtime. +#[derive(Debug, Serialize, Deserialize)] +pub enum Instantiation { + /// A module "upvar" is being instantiated which is a closed-over module + /// that is known at runtime by index. + ModuleUpvar { + /// The module index which is being instantiated. + module: ModuleUpvarIndex, + /// The flat list of arguments to the module's instantiation. + args: Box<[CoreExport]>, + }, + + /// A module import is being instantiated. + /// + /// NB: this is not implemented in the runtime yet so this is a little less + /// fleshed out than the above case. For example it's not entirely clear how + /// the import will be referred to here (here a `usize` is used but unsure + /// if that will work out). + ModuleImport { + /// Which module import is being instantiated. + import_index: usize, + /// The flat list of arguments to the module's instantiation. + args: Box<[CoreExport]>, + }, +} + +/// Identifier of an exported item from a core WebAssembly module instance. +/// +/// Note that the `T` here is the index type for exports which can be +/// identified by index. The `T` is monomorphized with types like +/// [`EntityIndex`] or [`FuncIndex`]. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CoreExport { + /// The instance that this item is located within. + /// + /// Note that this is intended to index the `instances` map within a + /// component. It's validated ahead of time that all instance pointers + /// refer only to previously-created instances. + pub instance: RuntimeInstanceIndex, + + /// The item that this export is referencing, either by name or by index. + pub item: ExportItem, +} + +/// An index at which to find an item within a runtime instance. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ExportItem { + /// An exact index that the target can be found at. + /// + /// This is used where possible to avoid name lookups at runtime during the + /// instantiation process. This can only be used on instances where the + /// module was statically known at compile time, however. + Index(T), + + /// An item which is identified by a name, so at runtime we need to + /// perform a name lookup to determine the index that the item is located + /// at. + /// + /// This is used for instantiations of imported modules, for example, since + /// the precise shape of the module is not known. + Name(String), +} + +/// Possible exports from a component. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Export { + /// A lifted function being exported which is an adaptation of a core wasm + /// function. + LiftedFunction(LiftedFunction), +} + +/// Description of a lifted function. +/// +/// This represents how a function was lifted, what options were used to lift +/// it, and how it's all processed at runtime. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LiftedFunction { + /// The component function type of the function being created. + pub ty: FuncTypeIndex, + /// Which core WebAssembly export is being lifted. + pub func: CoreExport, + /// Any options, if present, associated with this lifting. + pub options: CanonicalOptions, +} + +/// Canonical ABI options associated with a lifted function. +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct CanonicalOptions { + /// The optionally-specified encoding used for strings. + pub string_encoding: Option, + /// Representation of the `into` option where intrinsics are peeled out and + /// identified from an instance. + pub intrinsics: Option, +} + +/// Possible encodings of strings within the component model. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[allow(missing_docs)] +pub enum StringEncoding { + Utf8, + Utf16, + CompactUtf16, +} + +/// Intrinsics required with the `(into $instance)` option specified in +/// `canon.lift`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Intrinsics { + /// The linear memory that the module exports which we're reading/writing + /// from. + pub memory: CoreExport, + + /// A memory allocation, and reallocation, function. + pub canonical_abi_realloc: CoreExport, + + /// A memory deallocation function. + /// + /// NB: this will probably be replaced with a per-export-destructor rather + /// than a general memory deallocation function. + pub canonical_abi_free: CoreExport, +} diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs new file mode 100644 index 000000000000..2c66aa0c2143 --- /dev/null +++ b/crates/environ/src/component/translate.rs @@ -0,0 +1,709 @@ +use crate::component::*; +use crate::{EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, Tunables}; +use anyhow::{bail, Result}; +use std::collections::HashMap; +use std::mem; +use wasmparser::{Chunk, Encoding, Parser, Payload, Validator}; + +/// Structure used to translate a component and parse it. +pub struct Translator<'a, 'data> { + result: Translation<'data>, + validator: &'a mut Validator, + types: &'a mut ComponentTypesBuilder, + tunables: &'a Tunables, + parsers: Vec, + parser: Parser, +} + +/// Result of translation of a component to contain all type information and +/// metadata about how to run the component. +#[derive(Default)] +pub struct Translation<'data> { + /// Final type of the component, intended to be persisted all the way to + /// runtime. + pub component: Component, + + /// List of "upvars" or closed over modules that `Component` would refer + /// to. This contains the core wasm results of translation and the indices + /// are referred to within types in `Component`. + pub upvars: PrimaryMap>, + + // Index spaces which are built-up during translation but do not persist to + // runtime. These are used to understand the structure of the component and + // where items come from but at this time these index spaces and their + // definitions are not required at runtime as they're effectively "erased" + // at the moment. This will probably change as more of the component model + // is implemented. + // + /// Modules and how they're defined (either closed-over or imported) + modules: PrimaryMap, + + /// Instances and how they're defined, either as instantiations of modules + /// or "synthetically created" as a bag of named items from our other index + /// spaces. + instances: PrimaryMap>, + + /// Both core wasm and component functions, and how they're defined. + funcs: PrimaryMap>, + + /// Core wasm globals, always sourced from a previously module instance. + globals: PrimaryMap>, + + /// Core wasm memories, always sourced from a previously module instance. + memories: PrimaryMap>, + + /// Core wasm tables, always sourced from a previously module instance. + tables: PrimaryMap>, +} + +/// How a module is defined within a component. +#[derive(Debug)] +enum ModuleDef { + /// This module is defined as an "upvar" or a closed over variable + /// implicitly available for the component. + /// + /// This means that the module was either defined within the component or a + /// module was aliased into this component which was known defined in the + /// parent component. + Upvar(ModuleUpvarIndex), + + /// This module is defined as an import to the current component, so + /// nothing is known about it except for its type. The `import_index` + /// provided here indexes into the `Component`'s import list. + Import { + type_idx: ModuleTypeIndex, + import_index: usize, + }, +} + +#[derive(Debug)] +enum InstanceDef<'data> { + Module { + instance: RuntimeInstanceIndex, + module: ModuleIndex, + }, + ModuleSynthetic(HashMap<&'data str, EntityIndex>), +} + +/// Source of truth for where a core wasm item comes from. +#[derive(Clone)] +enum Func<'data> { + Lifted { + ty: FuncTypeIndex, + func: CoreSource<'data>, + options: CanonicalOptions, + }, + Core(CoreSource<'data>), +} + +/// Source of truth for where a core wasm item comes from. +#[derive(Clone)] +enum CoreSource<'data> { + /// This item comes from an indexed entity within an instance. + /// + /// This is only available when the instance is statically known to be + /// defined within the original component itself so we know the exact + /// index. + Index(RuntimeInstanceIndex, EntityIndex), + + /// This item comes from an named entity within an instance. + /// + /// This must be used for instances of imported modules because we + /// otherwise don't know the internal structure of the module and which + /// index is being exported. + Export(RuntimeInstanceIndex, &'data str), +} + +enum Action { + KeepGoing, + Skip(usize), + Done, +} + +impl<'a, 'data> Translator<'a, 'data> { + /// Creates a new translation state ready to translate a component. + pub fn new( + tunables: &'a Tunables, + validator: &'a mut Validator, + types: &'a mut ComponentTypesBuilder, + ) -> Self { + Self { + result: Translation::default(), + tunables, + validator, + types, + parser: Parser::new(0), + parsers: Vec::new(), + } + } + + /// Translates the binary `component`. + /// + /// This is the workhorse of compilation which will parse all of + /// `component` and create type information for Wasmtime and such. The + /// `component` does not have to be valid and it will be validated during + /// compilation. + pub fn translate(mut self, component: &'data [u8]) -> Result> { + let mut remaining = component; + loop { + let payload = match self.parser.parse(remaining, true)? { + Chunk::Parsed { payload, consumed } => { + remaining = &remaining[consumed..]; + payload + } + Chunk::NeedMoreData(_) => unreachable!(), + }; + + match self.translate_payload(payload, component)? { + Action::KeepGoing => {} + Action::Skip(n) => remaining = &remaining[n..], + Action::Done => break, + } + } + Ok(self.result) + } + + fn translate_payload( + &mut self, + payload: Payload<'data>, + component: &'data [u8], + ) -> Result { + match payload { + Payload::Version { + num, + encoding, + range, + } => { + self.validator.version(num, encoding, &range)?; + + match encoding { + Encoding::Component => {} + Encoding::Module => { + bail!("attempted to parse a wasm module with a component parser"); + } + } + + // Push a new scope for component types so outer aliases know + // that the 0th level is this new component. + self.types.push_component_types_scope(); + } + + Payload::End(offset) => { + self.validator.end(offset)?; + + // When leaving a module be sure to pop the types scope to + // ensure that when we go back to the previous module outer + // type alias indices work correctly again. + self.types.pop_component_types_scope(); + + match self.parsers.pop() { + Some(p) => self.parser = p, + None => return Ok(Action::Done), + } + } + + // When we see a type section the types are validated and then + // translated into Wasmtime's representation. Each active type + // definition is recorded in the `ComponentTypesBuilder` tables, or + // this component's active scope. + // + // Note that the push/pop of the component types scope happens above + // in `Version` and `End` since multiple type sections can appear + // within a component. + Payload::ComponentTypeSection(s) => { + self.validator.component_type_section(&s)?; + for ty in s { + let ty = self.types.component_type_def(&ty?)?; + self.types.push_component_typedef(ty); + } + } + + Payload::ComponentImportSection(s) => { + self.validator.component_import_section(&s)?; + for import in s { + let import = import?; + let ty = TypeIndex::from_u32(import.ty); + let ty = self.types.component_outer_type(0, ty); + let (import_index, prev) = self + .result + .component + .imports + .insert_full(import.name.to_string(), ty); + assert!(prev.is_none()); + match ty { + TypeDef::Module(type_idx) => { + self.result.modules.push(ModuleDef::Import { + type_idx, + import_index, + }); + } + TypeDef::Component(_) => { + unimplemented!("component imports"); + } + TypeDef::ComponentInstance(_) => { + unimplemented!("component instance imports"); + } + TypeDef::Func(_) => { + unimplemented!("function imports"); + } + TypeDef::Interface(_) => { + unimplemented!("interface type imports"); + } + } + } + } + + Payload::ComponentFunctionSection(s) => { + self.validator.component_function_section(&s)?; + for func in s { + let func = match func? { + wasmparser::ComponentFunction::Lift { + type_index, + func_index, + options, + } => { + let ty = TypeIndex::from_u32(type_index); + let func = FuncIndex::from_u32(func_index); + self.lift_function(ty, func, &options) + } + wasmparser::ComponentFunction::Lower { + func_index, + options, + } => { + drop((func_index, options)); + unimplemented!("lowered functions"); + } + }; + self.result.funcs.push(func); + } + } + + // Core wasm modules are translated inline directly here with the + // `ModuleEnvironment` from core wasm compilation. This will return + // to the caller the size of the module so it knows how many bytes + // of the input are skipped. + Payload::ModuleSection { parser, range } => { + self.validator.module_section(&range)?; + let translation = ModuleEnvironment::new( + self.tunables, + self.validator, + self.types.module_types_builder(), + ) + .translate(parser, &component[range.start..range.end])?; + let upvar_idx = self.result.upvars.push(translation); + self.result.modules.push(ModuleDef::Upvar(upvar_idx)); + return Ok(Action::Skip(range.end - range.start)); + } + + Payload::ComponentSection { parser, range } => { + self.validator.component_section(&range)?; + let old_parser = mem::replace(&mut self.parser, parser); + self.parsers.push(old_parser); + unimplemented!("component section"); + } + + Payload::InstanceSection(s) => { + self.validator.instance_section(&s)?; + for instance in s { + let instance = match instance? { + wasmparser::Instance::Module { index, args } => { + self.module_instance(ModuleIndex::from_u32(index), &args) + } + wasmparser::Instance::ModuleFromExports(exports) => { + self.module_instance_from_exports(&exports) + } + wasmparser::Instance::Component { index, args } => { + drop((index, args)); + unimplemented!("instantiating a component"); + } + wasmparser::Instance::ComponentFromExports(exports) => { + drop(exports); + unimplemented!("instantiating a component"); + } + }; + self.result.instances.push(instance); + } + } + + Payload::ComponentExportSection(s) => { + self.validator.component_export_section(&s)?; + for export in s { + self.export(&export?); + } + } + + Payload::ComponentStartSection(s) => { + self.validator.component_start_section(&s)?; + unimplemented!("component start section"); + } + + Payload::AliasSection(s) => { + self.validator.alias_section(&s)?; + for alias in s { + self.alias(&alias?); + } + } + + // All custom sections are ignored by Wasmtime at this time. + // + // FIXME(WebAssembly/component-model#14): probably want to specify + // and parse a `name` section here. + Payload::CustomSection { .. } => {} + + // Anything else is either not reachable since we never enable the + // feature in Wasmtime or we do enable it and it's a bug we don't + // implement it, so let validation take care of most errors here and + // if it gets past validation provide a helpful error message to + // debug. + other => { + self.validator.payload(&other)?; + panic!("unimplemented section {other:?}"); + } + } + + Ok(Action::KeepGoing) + } + + fn module_instance( + &mut self, + module: ModuleIndex, + args: &[wasmparser::ModuleArg<'data>], + ) -> InstanceDef<'data> { + // Map the flat list of `args` to instead a name-to-instance index. + let mut instance_by_name = HashMap::new(); + for arg in args { + match arg.kind { + wasmparser::ModuleArgKind::Instance(idx) => { + instance_by_name.insert(arg.name, InstanceIndex::from_u32(idx)); + } + } + } + + let instantiation = match self.result.modules[module] { + // For modules which we are statically aware of we can look at the + // exact order of imports required and build up a list of arguemnts + // in that order. This will be fast at runtime because all we have + // to do is build up the import lists for instantiation, no name + // lookups necessary. + ModuleDef::Upvar(upvar_idx) => { + let trans = &self.result.upvars[upvar_idx]; + Instantiation::ModuleUpvar { + module: upvar_idx, + args: self.module_instance_args( + &instance_by_name, + trans.module.imports().map(|(m, n, _)| (m, n)), + ), + } + } + + // For imported modules the list of arguments is built to match the + // order of the imports listed. Note that this will need to be + // reshuffled at runtime since the actual module being instantiated + // may originally have required imports in a different order. + ModuleDef::Import { + type_idx, + import_index, + } => { + let ty = &self.types[type_idx]; + Instantiation::ModuleImport { + import_index, + args: self.module_instance_args( + &instance_by_name, + ty.imports.keys().map(|(a, b)| (a.as_str(), b.as_str())), + ), + } + } + }; + let instance = self.result.component.instances.push(instantiation); + InstanceDef::Module { instance, module } + } + + /// Translates the named arguments required by a core wasm module specified + /// by `iter` into a list of where the arguments come from at runtime. + /// + /// The `instance_by_name` map is used go go from the module name of an + /// import to the instance that's satisfying the import. The `name` field + /// of the import is then looked up within the instance's own exports. + fn module_instance_args<'b>( + &self, + instance_by_name: &HashMap<&'data str, InstanceIndex>, + iter: impl Iterator, + ) -> Box<[CoreExport]> { + iter.map(|(module, name)| { + self.lookup_core_source(instance_by_name[module], name) + .to_core_export(|i| i) + }) + .collect() + } + + /// Looks up the `CoreSource` corresponding to the export `name` of the + /// `module` specified. + /// + /// This classifies the export of the module as either one which we + /// statically know by index within the module itself (because we know the + /// module), or one that must be referred to by name. + fn lookup_core_source(&self, instance: InstanceIndex, name: &'data str) -> CoreSource<'data> { + match &self.result.instances[instance] { + // The `instance` points to an instantiated module... + InstanceDef::Module { module, instance } => match self.result.modules[*module] { + // ... and the module instantiated is one that we statically + // know the structure of. This means that `name` points to an + // exact index of an item within the module which we lookup here + // and record. + ModuleDef::Upvar(upvar_idx) => { + let trans = &self.result.upvars[upvar_idx]; + CoreSource::Index(*instance, trans.module.exports[name]) + } + + // ... and the module instantiated is imported so we don't + // statically know its structure. This means taht the export + // must be identified by name. + ModuleDef::Import { .. } => CoreSource::Export(*instance, name), + }, + + // The `instance `points to a "synthetic" instance created in the + // component as a collection of named items from other instances. + // This means that we're simply copying over the original source of + // the item in the first place. + InstanceDef::ModuleSynthetic(defs) => match defs[&name] { + EntityIndex::Function(f) => match &self.result.funcs[f] { + Func::Core(c) => c.clone(), + // should not be possible to hit with a valid component + Func::Lifted { .. } => unreachable!(), + }, + EntityIndex::Global(g) => self.result.globals[g].clone(), + EntityIndex::Table(t) => self.result.tables[t].clone(), + EntityIndex::Memory(m) => self.result.memories[m].clone(), + }, + } + } + + /// Creates a synthetic module from the list of items currently in the + /// module and their given names. + fn module_instance_from_exports( + &mut self, + exports: &[wasmparser::Export<'data>], + ) -> InstanceDef<'data> { + let mut map = HashMap::with_capacity(exports.len()); + for export in exports { + let idx = match export.kind { + wasmparser::ExternalKind::Func => { + let index = FuncIndex::from_u32(export.index); + EntityIndex::Function(index) + } + wasmparser::ExternalKind::Table => { + let index = TableIndex::from_u32(export.index); + EntityIndex::Table(index) + } + wasmparser::ExternalKind::Memory => { + let index = MemoryIndex::from_u32(export.index); + EntityIndex::Memory(index) + } + wasmparser::ExternalKind::Global => { + let index = GlobalIndex::from_u32(export.index); + EntityIndex::Global(index) + } + + // doesn't get past validation + wasmparser::ExternalKind::Tag => unimplemented!(), + }; + map.insert(export.name, idx); + } + InstanceDef::ModuleSynthetic(map) + } + + fn export(&mut self, export: &wasmparser::ComponentExport<'data>) { + let name = export.name; + let export = match export.kind { + wasmparser::ComponentExportKind::Module(i) => { + let idx = ModuleIndex::from_u32(i); + drop(idx); + unimplemented!("unimplemented module export"); + } + wasmparser::ComponentExportKind::Component(i) => { + let idx = ComponentIndex::from_u32(i); + drop(idx); + unimplemented!("unimplemented component export"); + } + wasmparser::ComponentExportKind::Instance(i) => { + let idx = InstanceIndex::from_u32(i); + drop(idx); + unimplemented!("unimplemented instance export"); + } + wasmparser::ComponentExportKind::Function(i) => { + let idx = FuncIndex::from_u32(i); + match &self.result.funcs[idx] { + Func::Lifted { ty, func, options } => Export::LiftedFunction(LiftedFunction { + ty: *ty, + func: func.to_core_export(|i| match i { + EntityIndex::Function(i) => i, + _ => unreachable!(), + }), + options: options.clone(), + }), + // should not be possible to hit with a valid module. + Func::Core(_) => unreachable!(), + } + } + wasmparser::ComponentExportKind::Value(_) => { + unimplemented!("unimplemented value export"); + } + wasmparser::ComponentExportKind::Type(i) => { + let idx = TypeIndex::from_u32(i); + drop(idx); + unimplemented!("unimplemented value export"); + } + }; + self.result + .component + .exports + .insert(name.to_string(), export); + } + + fn alias(&mut self, alias: &wasmparser::Alias<'data>) { + match alias { + wasmparser::Alias::InstanceExport { + kind, + instance, + name, + } => { + let instance = InstanceIndex::from_u32(*instance); + match &self.result.instances[instance] { + InstanceDef::Module { .. } | InstanceDef::ModuleSynthetic(_) => { + self.alias_module_instance_export(*kind, instance, name); + } + } + } + wasmparser::Alias::OuterModule { .. } => { + unimplemented!("alias outer module"); + } + wasmparser::Alias::OuterComponent { .. } => { + unimplemented!("alias outer component"); + } + + // When aliasing a type the `ComponentTypesBuilder` is used to + // resolve the outer `count` plus the index, and then once it's + // resolved we push the type information into our local index + // space. + // + // Note that this is just copying indices around as all type + // information is basically a pointer back into the `TypesBuilder` + // structure (and the eventual `TypeTables` that it produces). + wasmparser::Alias::OuterType { count, index } => { + let index = TypeIndex::from_u32(*index); + let ty = self.types.component_outer_type(*count, index); + self.types.push_component_typedef(ty); + } + } + } + + /// Inserts an item in to the relevant namespace aliasing the `name`'d + /// export of the `instance` provided. + fn alias_module_instance_export( + &mut self, + kind: wasmparser::AliasKind, + instance: InstanceIndex, + name: &'data str, + ) { + let src = self.lookup_core_source(instance, name); + match kind { + wasmparser::AliasKind::Func => { + self.result.funcs.push(Func::Core(src)); + } + wasmparser::AliasKind::Global => { + self.result.globals.push(src); + } + wasmparser::AliasKind::Memory => { + self.result.memories.push(src); + } + wasmparser::AliasKind::Table => { + self.result.tables.push(src); + } + other => { + panic!("unknown/unimplemented alias kind {other:?}"); + } + } + } + + fn lift_function( + &self, + ty: TypeIndex, + func: FuncIndex, + options: &[wasmparser::CanonicalOption], + ) -> Func<'data> { + let ty = match self.types.component_outer_type(0, ty) { + TypeDef::Func(ty) => ty, + // should not be possible after validation + _ => unreachable!(), + }; + let func = match &self.result.funcs[func] { + Func::Core(core) => core.clone(), + // should not be possible after validation + Func::Lifted { .. } => unreachable!(), + }; + let options = self.canonical_options(options); + Func::Lifted { ty, func, options } + } + + fn canonical_options(&self, opts: &[wasmparser::CanonicalOption]) -> CanonicalOptions { + let mut ret = CanonicalOptions::default(); + for opt in opts { + match opt { + wasmparser::CanonicalOption::UTF8 => { + ret.string_encoding = Some(StringEncoding::Utf8); + } + wasmparser::CanonicalOption::UTF16 => { + ret.string_encoding = Some(StringEncoding::Utf16); + } + wasmparser::CanonicalOption::CompactUTF16 => { + ret.string_encoding = Some(StringEncoding::CompactUtf16); + } + wasmparser::CanonicalOption::Into(instance) => { + let instance = InstanceIndex::from_u32(*instance); + + // Note that the `unreachable!()` should not happen for + // components which have passed validation. + let memory = self + .lookup_core_source(instance, "memory") + .to_core_export(|i| match i { + EntityIndex::Memory(i) => i, + _ => unreachable!(), + }); + let canonical_abi_free = self + .lookup_core_source(instance, "canonical_abi_free") + .to_core_export(|i| match i { + EntityIndex::Function(i) => i, + _ => unreachable!(), + }); + let canonical_abi_realloc = self + .lookup_core_source(instance, "canonical_abi_realloc") + .to_core_export(|i| match i { + EntityIndex::Function(i) => i, + _ => unreachable!(), + }); + ret.intrinsics = Some(Intrinsics { + memory, + canonical_abi_free, + canonical_abi_realloc, + }) + } + } + } + return ret; + } +} + +impl CoreSource<'_> { + fn to_core_export(&self, get_index: impl FnOnce(EntityIndex) -> T) -> CoreExport { + match self { + CoreSource::Index(instance, index) => CoreExport { + instance: *instance, + item: ExportItem::Index(get_index(*index)), + }, + CoreSource::Export(instance, name) => CoreExport { + instance: *instance, + item: ExportItem::Name(name.to_string()), + }, + } + } +} diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs new file mode 100644 index 000000000000..1eeddb2cf494 --- /dev/null +++ b/crates/environ/src/component/types.rs @@ -0,0 +1,752 @@ +use crate::{ + EntityType, Global, GlobalInit, ModuleTypes, ModuleTypesBuilder, PrimaryMap, SignatureIndex, +}; +use anyhow::{bail, Result}; +use cranelift_entity::EntityRef; +use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::hash::Hash; +use std::ops::Index; + +macro_rules! indices { + ($( + $(#[$a:meta])* + pub struct $name:ident(u32); + )*) => ($( + $(#[$a])* + #[derive( + Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, + Serialize, Deserialize, + )] + pub struct $name(u32); + cranelift_entity::entity_impl!($name); + )*); +} + +indices! { + // ======================================================================== + // These indices are used during compile time only when we're translating a + // component at this time. The actual indices are not persisted beyond the + // compile phase to when we're actually working with the component at + // runtime. + + /// Index within a component's module index space. + pub struct ModuleIndex(u32); + + /// Index within a component's component index space. + pub struct ComponentIndex(u32); + + /// Index within a component's instance index space. + pub struct InstanceIndex(u32); + + // ======================================================================== + // These indices are used to lookup type information within a `TypeTables` + // structure. These represent generally deduplicated type information across + // an entire component and are a form of RTTI in a sense. + + /// Index pointing to a component's type (exports/imports with + /// component-model types) + pub struct ComponentTypeIndex(u32); + + /// Index pointing to a component instance's type (exports with + /// component-model types, no imports) + pub struct ComponentInstanceTypeIndex(u32); + + /// Index pointing to a core wasm module's type (exports/imports with + /// core wasm types) + pub struct ModuleTypeIndex(u32); + + /// Index pointing to a component model function type with arguments/result + /// as interface types. + pub struct FuncTypeIndex(u32); + + /// Index pointing to an interface type, used for recursive types such as + /// `List`. + pub struct InterfaceTypeIndex(u32); + + /// Index pointing to a record type in the component model (aka a struct). + pub struct RecordTypeIndex(u32); + /// Index pointing to a variant type in the component model (aka an enum). + pub struct VariantTypeIndex(u32); + /// Index pointing to a tuple type in the component model. + pub struct TupleTypeIndex(u32); + /// Index pointing to a flags type in the component model. + pub struct FlagsTypeIndex(u32); + /// Index pointing to an enum type in the component model. + pub struct EnumTypeIndex(u32); + /// Index pointing to a union type in the component model. + pub struct UnionTypeIndex(u32); + /// Index pointing to an expected type in the component model (aka a + /// `Result`) + pub struct ExpectedTypeIndex(u32); + + // ======================================================================== + // These indices are actually used at runtime when managing a component at + // this time. + + /// Index that represents a core wasm instance created at runtime. + /// + /// This is used to keep track of when instances are created and is able to + /// refer back to previously created instances for exports and such. + pub struct RuntimeInstanceIndex(u32); + + /// Index that represents a closed-over-module for a component. + /// + /// Components which embed modules or otherwise refer to module (such as + /// through `alias` annotations) pull in items in to the list of closed over + /// modules, and this index indexes, at runtime, which of the upvars is + /// referenced. + pub struct ModuleUpvarIndex(u32); +} + +// Reexport for convenience some core-wasm indices which are also used in the +// component model, typically for when aliasing exports of core wasm modules. +pub use crate::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeIndex}; + +/// Runtime information about the type information contained within a component. +/// +/// One of these is created per top-level component which describes all of the +/// types contained within the top-level component itself. Each sub-component +/// will have a pointer to this value as well. +#[derive(Default, Serialize, Deserialize)] +pub struct ComponentTypes { + modules: PrimaryMap, + components: PrimaryMap, + component_instances: PrimaryMap, + functions: PrimaryMap, + interface_types: PrimaryMap, + records: PrimaryMap, + variants: PrimaryMap, + tuples: PrimaryMap, + enums: PrimaryMap, + flags: PrimaryMap, + unions: PrimaryMap, + expecteds: PrimaryMap, + + module_types: ModuleTypes, +} + +impl ComponentTypes { + /// Returns the core wasm module types known within this component. + pub fn module_types(&self) -> &ModuleTypes { + &self.module_types + } +} + +macro_rules! impl_index { + ($(impl Index<$ty:ident> for ComponentTypes { $output:ident => $field:ident })*) => ($( + impl std::ops::Index<$ty> for ComponentTypes { + type Output = $output; + fn index(&self, idx: $ty) -> &$output { + &self.$field[idx] + } + } + )*) +} + +impl_index! { + impl Index for ComponentTypes { ModuleType => modules } + impl Index for ComponentTypes { ComponentType => components } + impl Index for ComponentTypes { ComponentInstanceType => component_instances } + impl Index for ComponentTypes { FuncType => functions } + impl Index for ComponentTypes { InterfaceType => interface_types } + impl Index for ComponentTypes { RecordType => records } + impl Index for ComponentTypes { VariantType => variants } + impl Index for ComponentTypes { TupleType => tuples } + impl Index for ComponentTypes { EnumType => enums } + impl Index for ComponentTypes { FlagsType => flags } + impl Index for ComponentTypes { UnionType => unions } + impl Index for ComponentTypes { ExpectedType => expecteds } +} + +/// Structured used to build a [`ComponentTypes`] during translation. +/// +/// This contains tables to intern any component types found as well as +/// managing building up core wasm [`ModuleTypes`] as well. +#[derive(Default)] +pub struct ComponentTypesBuilder { + type_scopes: Vec>, + functions: HashMap, + interface_types: HashMap, + records: HashMap, + variants: HashMap, + tuples: HashMap, + enums: HashMap, + flags: HashMap, + unions: HashMap, + expecteds: HashMap, + + component_types: ComponentTypes, + module_types: ModuleTypesBuilder, +} + +impl ComponentTypesBuilder { + /// Finishes this list of component types and returns the finished + /// structure. + pub fn finish(mut self) -> ComponentTypes { + self.component_types.module_types = self.module_types.finish(); + self.component_types + } + + /// Returns the underlying builder used to build up core wasm module types. + /// + /// Note that this is shared across all modules found within a component to + /// improve the wins from deduplicating function signatures. + pub fn module_types_builder(&mut self) -> &mut ModuleTypesBuilder { + &mut self.module_types + } + + /// Pushes a new scope when entering a new index space for types in the + /// component model. + /// + /// This happens when a component is recursed into or a module/instance + /// type is recursed into. + pub fn push_component_types_scope(&mut self) { + self.type_scopes.push(PrimaryMap::new()); + } + + /// Adds a new `TypeDef` definition within the current component types + /// scope. + /// + /// Returns the `TypeIndex` associated with the type being pushed.. + /// + /// # Panics + /// + /// Requires that `push_component_types_scope` was called previously. + pub fn push_component_typedef(&mut self, ty: TypeDef) -> TypeIndex { + self.type_scopes.last_mut().unwrap().push(ty) + } + + /// Looks up an "outer" type in this builder to handle outer aliases. + /// + /// The `count` parameter and `ty` are taken from the binary format itself, + /// and the `TypeDef` returned is what the outer type refers to. + /// + /// # Panics + /// + /// Assumes that `count` and `ty` are valid. + pub fn component_outer_type(&self, count: u32, ty: TypeIndex) -> TypeDef { + // Reverse the index and 0 means the "current scope" + let idx = self.type_scopes.len() - (count as usize) - 1; + self.type_scopes[idx][ty] + } + + /// Pops a scope pushed by `push_component_types_scope`. + pub fn pop_component_types_scope(&mut self) { + self.type_scopes.pop().unwrap(); + } + + /// Translates a wasmparser `ComponentTypeDef` into a Wasmtime `TypeDef`, + /// interning types along the way. + pub fn component_type_def(&mut self, ty: &wasmparser::ComponentTypeDef<'_>) -> Result { + Ok(match ty { + wasmparser::ComponentTypeDef::Module(ty) => TypeDef::Module(self.module_type(ty)?), + wasmparser::ComponentTypeDef::Component(ty) => { + TypeDef::Component(self.component_type(ty)?) + } + wasmparser::ComponentTypeDef::Instance(ty) => { + TypeDef::ComponentInstance(self.component_instance_type(ty)?) + } + wasmparser::ComponentTypeDef::Function(ty) => TypeDef::Func(self.func_type(ty)), + wasmparser::ComponentTypeDef::Value(_ty) => unimplemented!("value types"), + wasmparser::ComponentTypeDef::Interface(ty) => { + TypeDef::Interface(self.interface_type(ty)) + } + }) + } + + fn module_type(&mut self, ty: &[wasmparser::ModuleType<'_>]) -> Result { + let mut result = ModuleType::default(); + let mut functypes: PrimaryMap = PrimaryMap::default(); + + for item in ty { + match item { + wasmparser::ModuleType::Type(wasmparser::TypeDef::Func(f)) => { + functypes.push(self.module_types.wasm_func_type(f.clone().try_into()?)); + } + wasmparser::ModuleType::Export { name, ty } => { + let prev = result + .exports + .insert(name.to_string(), type_ref(ty, &functypes)?); + assert!(prev.is_none()); + } + wasmparser::ModuleType::Import(import) => { + let prev = result.imports.insert( + (import.module.to_string(), import.name.to_string()), + type_ref(&import.ty, &functypes)?, + ); + assert!(prev.is_none()); + } + } + } + + return Ok(self.component_types.modules.push(result)); + + fn type_ref( + ty: &wasmparser::TypeRef, + functypes: &PrimaryMap, + ) -> Result { + Ok(match ty { + wasmparser::TypeRef::Func(idx) => { + EntityType::Function(functypes[TypeIndex::from_u32(*idx)]) + } + wasmparser::TypeRef::Table(ty) => EntityType::Table(ty.clone().try_into()?), + wasmparser::TypeRef::Memory(ty) => EntityType::Memory(ty.clone().into()), + wasmparser::TypeRef::Global(ty) => { + EntityType::Global(Global::new(ty.clone(), GlobalInit::Import)?) + } + wasmparser::TypeRef::Tag(_) => bail!("exceptions proposal not implemented"), + }) + } + } + + fn component_type( + &mut self, + ty: &[wasmparser::ComponentType<'_>], + ) -> Result { + let mut result = ComponentType::default(); + self.push_component_types_scope(); + + for item in ty { + match item { + wasmparser::ComponentType::Type(ty) => { + let ty = self.component_type_def(ty)?; + self.push_component_typedef(ty); + } + wasmparser::ComponentType::OuterType { count, index } => { + let ty = self.component_outer_type(*count, TypeIndex::from_u32(*index)); + self.push_component_typedef(ty); + } + wasmparser::ComponentType::Export { name, ty } => { + result.exports.insert( + name.to_string(), + self.component_outer_type(0, TypeIndex::from_u32(*ty)), + ); + } + wasmparser::ComponentType::Import(import) => { + result.imports.insert( + import.name.to_string(), + self.component_outer_type(0, TypeIndex::from_u32(import.ty)), + ); + } + } + } + + self.pop_component_types_scope(); + + Ok(self.component_types.components.push(result)) + } + + fn component_instance_type( + &mut self, + ty: &[wasmparser::InstanceType<'_>], + ) -> Result { + let mut result = ComponentInstanceType::default(); + self.push_component_types_scope(); + + for item in ty { + match item { + wasmparser::InstanceType::Type(ty) => { + let ty = self.component_type_def(ty)?; + self.push_component_typedef(ty); + } + wasmparser::InstanceType::OuterType { count, index } => { + let ty = self.component_outer_type(*count, TypeIndex::from_u32(*index)); + self.push_component_typedef(ty); + } + wasmparser::InstanceType::Export { name, ty } => { + result.exports.insert( + name.to_string(), + self.component_outer_type(0, TypeIndex::from_u32(*ty)), + ); + } + } + } + + self.pop_component_types_scope(); + + Ok(self.component_types.component_instances.push(result)) + } + + fn func_type(&mut self, ty: &wasmparser::ComponentFuncType<'_>) -> FuncTypeIndex { + let ty = FuncType { + params: ty + .params + .iter() + .map(|(name, ty)| (name.map(|s| s.to_string()), self.interface_type_ref(ty))) + .collect(), + result: self.interface_type_ref(&ty.result), + }; + intern(&mut self.functions, &mut self.component_types.functions, ty) + } + + fn interface_type(&mut self, ty: &wasmparser::InterfaceType<'_>) -> InterfaceType { + match ty { + wasmparser::InterfaceType::Primitive(ty) => ty.into(), + wasmparser::InterfaceType::Record(e) => InterfaceType::Record(self.record_type(e)), + wasmparser::InterfaceType::Variant(e) => InterfaceType::Variant(self.variant_type(e)), + wasmparser::InterfaceType::List(e) => { + let ty = self.interface_type_ref(e); + InterfaceType::List(self.intern_interface_type(ty)) + } + wasmparser::InterfaceType::Tuple(e) => InterfaceType::Tuple(self.tuple_type(e)), + wasmparser::InterfaceType::Flags(e) => InterfaceType::Flags(self.flags_type(e)), + wasmparser::InterfaceType::Enum(e) => InterfaceType::Enum(self.enum_type(e)), + wasmparser::InterfaceType::Union(e) => InterfaceType::Union(self.union_type(e)), + wasmparser::InterfaceType::Option(e) => { + let ty = self.interface_type_ref(e); + InterfaceType::Option(self.intern_interface_type(ty)) + } + wasmparser::InterfaceType::Expected { ok, error } => { + InterfaceType::Expected(self.expected_type(ok, error)) + } + } + } + + fn interface_type_ref(&mut self, ty: &wasmparser::InterfaceTypeRef) -> InterfaceType { + match ty { + wasmparser::InterfaceTypeRef::Primitive(p) => p.into(), + wasmparser::InterfaceTypeRef::Type(idx) => { + let idx = TypeIndex::from_u32(*idx); + match self.component_outer_type(0, idx) { + TypeDef::Interface(ty) => ty, + // this should not be possible if the module validated + _ => unreachable!(), + } + } + } + } + + fn intern_interface_type(&mut self, ty: InterfaceType) -> InterfaceTypeIndex { + intern( + &mut self.interface_types, + &mut self.component_types.interface_types, + ty, + ) + } + + fn record_type(&mut self, record: &[(&str, wasmparser::InterfaceTypeRef)]) -> RecordTypeIndex { + let record = RecordType { + fields: record + .iter() + .map(|(name, ty)| RecordField { + name: name.to_string(), + ty: self.interface_type_ref(ty), + }) + .collect(), + }; + intern(&mut self.records, &mut self.component_types.records, record) + } + + fn variant_type(&mut self, cases: &[wasmparser::VariantCase<'_>]) -> VariantTypeIndex { + let variant = VariantType { + cases: cases + .iter() + .map(|case| { + // FIXME: need to implement `default_to`, not sure what that + // is at this time. + assert!(case.default_to.is_none()); + VariantCase { + name: case.name.to_string(), + ty: self.interface_type_ref(&case.ty), + } + }) + .collect(), + }; + intern( + &mut self.variants, + &mut self.component_types.variants, + variant, + ) + } + + fn tuple_type(&mut self, types: &[wasmparser::InterfaceTypeRef]) -> TupleTypeIndex { + let tuple = TupleType { + types: types.iter().map(|ty| self.interface_type_ref(ty)).collect(), + }; + intern(&mut self.tuples, &mut self.component_types.tuples, tuple) + } + + fn flags_type(&mut self, flags: &[&str]) -> FlagsTypeIndex { + let flags = FlagsType { + names: flags.iter().map(|s| s.to_string()).collect(), + }; + intern(&mut self.flags, &mut self.component_types.flags, flags) + } + + fn enum_type(&mut self, variants: &[&str]) -> EnumTypeIndex { + let e = EnumType { + names: variants.iter().map(|s| s.to_string()).collect(), + }; + intern(&mut self.enums, &mut self.component_types.enums, e) + } + + fn union_type(&mut self, types: &[wasmparser::InterfaceTypeRef]) -> UnionTypeIndex { + let union = UnionType { + types: types.iter().map(|ty| self.interface_type_ref(ty)).collect(), + }; + intern(&mut self.unions, &mut self.component_types.unions, union) + } + + fn expected_type( + &mut self, + ok: &wasmparser::InterfaceTypeRef, + err: &wasmparser::InterfaceTypeRef, + ) -> ExpectedTypeIndex { + let expected = ExpectedType { + ok: self.interface_type_ref(ok), + err: self.interface_type_ref(err), + }; + intern( + &mut self.expecteds, + &mut self.component_types.expecteds, + expected, + ) + } +} + +// Forward the indexing impl to the internal `TypeTables` +impl Index for ComponentTypesBuilder +where + ComponentTypes: Index, +{ + type Output = >::Output; + + fn index(&self, sig: T) -> &Self::Output { + &self.component_types[sig] + } +} + +fn intern(map: &mut HashMap, list: &mut PrimaryMap, item: T) -> U +where + T: Hash + Clone + Eq, + U: Copy + EntityRef, +{ + if let Some(idx) = map.get(&item) { + return *idx; + } + let idx = list.push(item.clone()); + map.insert(item, idx); + return idx; +} + +/// Types of imports and exports in the component model. +/// +/// These types are what's available for import and export in components. Note +/// that all indirect indices contained here are intended to be looked up +/// through a sibling `ComponentTypes` structure. +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum TypeDef { + /// A core wasm module and its type. + Module(ModuleTypeIndex), + /// A component and its type. + Component(ComponentTypeIndex), + /// An instance of a component. + ComponentInstance(ComponentInstanceTypeIndex), + /// A component function, not to be confused with a core wasm function. + Func(FuncTypeIndex), + /// An interface type. + Interface(InterfaceType), +} + +// NB: Note that maps below are stored as an `IndexMap` now but the order +// typically does not matter. As a minor implementation detail we want the +// serialization of this type to always be deterministic and using `IndexMap` +// gets us that over using a `HashMap` for example. + +/// The type of a module in the component model. +/// +/// Note that this is not to be confused with `ComponentType` below. This is +/// intended only for core wasm modules, not for components. +#[derive(Serialize, Deserialize, Default)] +pub struct ModuleType { + /// The values that this module imports. + /// + /// Note that the value of this map is a core wasm `EntityType`, not a + /// component model `TypeRef`. Additionally note that this reflects the + /// two-level namespace of core WebAssembly, but unlike core wasm all import + /// names are required to be unique to describe a module in the component + /// model. + pub imports: IndexMap<(String, String), EntityType>, + + /// The values that this module exports. + /// + /// Note that the value of this map is the core wasm `EntityType` to + /// represent that core wasm items are being exported. + pub exports: IndexMap, +} + +/// The type of a component in the component model. +#[derive(Serialize, Deserialize, Default)] +pub struct ComponentType { + /// The named values that this component imports. + pub imports: IndexMap, + /// The named values that this component exports. + pub exports: IndexMap, +} + +/// The type of a component instance in the component model, or an instantiated +/// component. +/// +/// Component instances only have exports of types in the component model. +#[derive(Serialize, Deserialize, Default)] +pub struct ComponentInstanceType { + /// The list of exports that this component has along with their types. + pub exports: IndexMap, +} + +/// A component function type in the component model. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct FuncType { + /// The list of optionally named parameters for this function, and their + /// types. + pub params: Box<[(Option, InterfaceType)]>, + /// The return value of this function. + pub result: InterfaceType, +} + +/// All possible interface types that values can have. +/// +/// This list represents an exhaustive listing of interface types and the +/// shapes that they can take. Note that this enum is considered an "index" of +/// forms where for non-primitive types a `ComponentTypes` structure is used to +/// lookup further information based on the index found here. +#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum InterfaceType { + Unit, + Bool, + S8, + U8, + S16, + U16, + S32, + U32, + S64, + U64, + Float32, + Float64, + Char, + String, + Record(RecordTypeIndex), + Variant(VariantTypeIndex), + List(InterfaceTypeIndex), + Tuple(TupleTypeIndex), + Flags(FlagsTypeIndex), + Enum(EnumTypeIndex), + Union(UnionTypeIndex), + Option(InterfaceTypeIndex), + Expected(ExpectedTypeIndex), +} + +impl From<&wasmparser::PrimitiveInterfaceType> for InterfaceType { + fn from(ty: &wasmparser::PrimitiveInterfaceType) -> InterfaceType { + match ty { + wasmparser::PrimitiveInterfaceType::Unit => InterfaceType::Unit, + wasmparser::PrimitiveInterfaceType::Bool => InterfaceType::Bool, + wasmparser::PrimitiveInterfaceType::S8 => InterfaceType::S8, + wasmparser::PrimitiveInterfaceType::U8 => InterfaceType::U8, + wasmparser::PrimitiveInterfaceType::S16 => InterfaceType::S16, + wasmparser::PrimitiveInterfaceType::U16 => InterfaceType::U16, + wasmparser::PrimitiveInterfaceType::S32 => InterfaceType::S32, + wasmparser::PrimitiveInterfaceType::U32 => InterfaceType::U32, + wasmparser::PrimitiveInterfaceType::S64 => InterfaceType::S64, + wasmparser::PrimitiveInterfaceType::U64 => InterfaceType::U64, + wasmparser::PrimitiveInterfaceType::Float32 => InterfaceType::Float32, + wasmparser::PrimitiveInterfaceType::Float64 => InterfaceType::Float64, + wasmparser::PrimitiveInterfaceType::Char => InterfaceType::Char, + wasmparser::PrimitiveInterfaceType::String => InterfaceType::String, + } + } +} + +/// Shape of a "record" type in interface types. +/// +/// This is equivalent to a `struct` in Rust. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct RecordType { + /// The fields that are contained within this struct type. + pub fields: Box<[RecordField]>, +} + +/// One field within a record. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct RecordField { + /// The name of the field, unique amongst all fields in a record. + pub name: String, + /// The type that this field contains. + pub ty: InterfaceType, +} + +/// Shape of a "variant" type in interface types. +/// +/// Variants are close to Rust `enum` declarations where a value is one of many +/// cases and each case has a unique name and an optional payload associated +/// with it. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct VariantType { + /// The list of cases that this variant can take. + pub cases: Box<[VariantCase]>, +} + +/// One case of a `variant` type which contains the name of the variant as well +/// as the payload. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct VariantCase { + /// Name of the variant, unique amongst all cases in a variant. + pub name: String, + /// Type associated with this payload, maybe `Unit`. + pub ty: InterfaceType, +} + +/// Shape of a "tuple" type in interface types. +/// +/// This is largely the same as a tuple in Rust, basically a record with +/// unnamed fields. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct TupleType { + /// The types that are contained within this tuple. + pub types: Box<[InterfaceType]>, +} + +/// Shape of a "flags" type in interface types. +/// +/// This can be thought of as a record-of-bools, although the representation is +/// more efficient as bitflags. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct FlagsType { + /// The names of all flags, all of which are unique. + pub names: Box<[String]>, +} + +/// Shape of an "enum" type in interface types, not to be confused with a Rust +/// `enum` type. +/// +/// In interface types enums are simply a bag of names, and can be seen as a +/// variant where all payloads are `Unit`. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct EnumType { + /// The names of this enum, all of which are unique. + pub names: Box<[String]>, +} + +/// Shape of a "union" type in interface types. +/// +/// Note that this can be viewed as a specialization of the `variant` interface +/// type where each type here has a name that's numbered. This is still a +/// tagged union. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct UnionType { + /// The list of types this is a union over. + pub types: Box<[InterfaceType]>, +} + +/// Shape of an "expected" interface type. +#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)] +pub struct ExpectedType { + /// The `T` in `Result` + pub ok: InterfaceType, + /// The `E` in `Result` + pub err: InterfaceType, +} diff --git a/crates/environ/src/lib.rs b/crates/environ/src/lib.rs index d20b46a20ae3..8102f5e26edc 100644 --- a/crates/environ/src/lib.rs +++ b/crates/environ/src/lib.rs @@ -28,6 +28,7 @@ mod builtin; mod compilation; mod module; mod module_environ; +mod module_types; pub mod obj; mod ref_bits; mod stack_map; @@ -40,6 +41,7 @@ pub use crate::builtin::*; pub use crate::compilation::*; pub use crate::module::*; pub use crate::module_environ::*; +pub use crate::module_types::*; pub use crate::ref_bits::*; pub use crate::stack_map::StackMap; pub use crate::trap_encoding::*; @@ -47,6 +49,9 @@ pub use crate::tunables::Tunables; pub use crate::vmoffsets::*; pub use object; +#[cfg(feature = "component-model")] +pub mod component; + // Reexport all of these type-level since they're quite commonly used and it's // much easier to refer to everything through one crate rather than importing // one of three and making sure you're using the right one. diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 36dd99b9e3bc..6b5b7ba91c3a 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::convert::TryFrom; use std::mem; -use std::ops::{Index, Range}; +use std::ops::Range; use wasmtime_types::*; /// Implemenation styles for WebAssembly linear memory. @@ -990,32 +990,6 @@ impl Module { } } -/// All types which are recorded for the entirety of a translation. -/// -/// Note that this is shared amongst all modules coming out of a translation -/// in the case of nested modules and the module linking proposal. -#[derive(Default, Debug, Serialize, Deserialize)] -#[allow(missing_docs)] -pub struct TypeTables { - pub(crate) wasm_signatures: PrimaryMap, -} - -impl TypeTables { - /// Returns an iterator of all of the core wasm function signatures - /// registered in this instance. - pub fn wasm_signatures(&self) -> impl Iterator { - self.wasm_signatures.iter() - } -} - -impl Index for TypeTables { - type Output = WasmFuncType; - - fn index(&self, idx: SignatureIndex) -> &WasmFuncType { - &self.wasm_signatures[idx] - } -} - /// Type information about functions in a wasm module. #[derive(Debug, Serialize, Deserialize)] pub struct FunctionType { diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index 4928568ca26e..0df931c3be6b 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -1,11 +1,11 @@ use crate::module::{ AnyfuncIndex, Initializer, MemoryInitialization, MemoryInitializer, MemoryPlan, Module, - ModuleType, TableInitializer, TablePlan, TypeTables, + ModuleType, TableInitializer, TablePlan, }; use crate::{ DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType, FuncIndex, Global, - GlobalIndex, GlobalInit, MemoryIndex, PrimaryMap, SignatureIndex, TableIndex, - TableInitialization, Tunables, TypeIndex, WasmError, WasmFuncType, WasmResult, + GlobalIndex, GlobalInit, MemoryIndex, ModuleTypesBuilder, PrimaryMap, SignatureIndex, + TableIndex, TableInitialization, Tunables, TypeIndex, WasmError, WasmFuncType, WasmResult, }; use cranelift_entity::packed_option::ReservedValue; use std::borrow::Cow; @@ -13,26 +13,23 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::path::PathBuf; use std::sync::Arc; -use wasmparser::Type as WasmType; use wasmparser::{ - DataKind, ElementItem, ElementKind, ExternalKind, FuncValidator, FunctionBody, - NameSectionReader, Naming, Operator, Parser, Payload, TypeDef, TypeRef, Validator, - ValidatorResources, WasmFeatures, + CustomSectionReader, DataKind, ElementItem, ElementKind, Encoding, ExternalKind, FuncValidator, + FunctionBody, NameSectionReader, Naming, Operator, Parser, Payload, TypeDef, TypeRef, + Validator, ValidatorResources, }; /// Object containing the standalone environment information. -pub struct ModuleEnvironment<'data> { +pub struct ModuleEnvironment<'a, 'data> { /// The current module being translated result: ModuleTranslation<'data>, /// Intern'd types for this entire translation, shared by all modules. - types: TypeTables, - - interned_func_types: HashMap, + types: &'a mut ModuleTypesBuilder, // Various bits and pieces of configuration - features: WasmFeatures, - tunables: Tunables, + validator: &'a mut Validator, + tunables: &'a Tunables, } /// The result of translating via `ModuleEnvironment`. Function bodies are not @@ -133,67 +130,63 @@ pub struct WasmFileInfo { #[derive(Debug)] #[allow(missing_docs)] pub struct FunctionMetadata { - pub params: Box<[WasmType]>, - pub locals: Box<[(u32, WasmType)]>, + pub params: Box<[wasmparser::Type]>, + pub locals: Box<[(u32, wasmparser::Type)]>, } -impl<'data> ModuleEnvironment<'data> { +impl<'a, 'data> ModuleEnvironment<'a, 'data> { /// Allocates the environment data structures. - pub fn new(tunables: &Tunables, features: &WasmFeatures) -> Self { + pub fn new( + tunables: &'a Tunables, + validator: &'a mut Validator, + types: &'a mut ModuleTypesBuilder, + ) -> Self { Self { result: ModuleTranslation::default(), - types: Default::default(), - tunables: tunables.clone(), - features: *features, - interned_func_types: Default::default(), + types, + tunables, + validator, } } /// Translate a wasm module using this environment. /// - /// This consumes the `ModuleEnvironment` and produces a list of - /// `ModuleTranslation`s as well as a `TypeTables`. The list of module - /// translations corresponds to all wasm modules found in the input `data`. - /// Note that for MVP modules this will always be a list with one element, - /// but with the module linking proposal this may have many elements. - /// - /// For the module linking proposal the top-level module is returned as the - /// first return value. + /// This function will translate the `data` provided with `parser`, + /// validating everything along the way with this environment's validator. /// - /// The `TypeTables` structure returned contains intern'd versions of types - /// referenced from each module translation. This primarily serves as the - /// source of truth for module-linking use cases where modules can refer to - /// other module's types. All `SignatureIndex`, `ModuleTypeIndex`, and - /// `InstanceTypeIndex` values are resolved through the returned tables. + /// The result of translation, [`ModuleTranslation`], contains everything + /// necessary to compile functions afterwards as well as learn type + /// information about the module at runtime. pub fn translate( mut self, + parser: Parser, data: &'data [u8], - ) -> WasmResult<(ModuleTranslation<'data>, TypeTables)> { - let mut validator = Validator::new_with_features(self.features); - - for payload in Parser::new(0).parse_all(data) { - self.translate_payload(&mut validator, payload?)?; + ) -> WasmResult> { + for payload in parser.parse_all(data) { + self.translate_payload(payload?)?; } - Ok((self.result, self.types)) + Ok(self.result) } - fn translate_payload( - &mut self, - validator: &mut Validator, - payload: Payload<'data>, - ) -> WasmResult<()> { + fn translate_payload(&mut self, payload: Payload<'data>) -> WasmResult<()> { match payload { Payload::Version { num, encoding, range, } => { - validator.version(num, encoding, &range)?; + self.validator.version(num, encoding, &range)?; + match encoding { + Encoding::Module => {} + Encoding::Component => { + return Err(WasmError::Unsupported(format!("component model"))); + } + } } Payload::End(offset) => { - validator.end(offset)?; + self.validator.end(offset)?; // With the `escaped_funcs` set of functions finished // we can calculate the set of signatures that are exported as @@ -216,10 +209,10 @@ impl<'data> ModuleEnvironment<'data> { } Payload::TypeSection(types) => { - validator.type_section(&types)?; + self.validator.type_section(&types)?; let num = usize::try_from(types.get_count()).unwrap(); self.result.module.types.reserve(num); - self.types.wasm_signatures.reserve(num); + self.types.reserve_wasm_signatures(num); for ty in types { match ty? { @@ -231,7 +224,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::ImportSection(imports) => { - validator.import_section(&imports)?; + self.validator.import_section(&imports)?; let cnt = usize::try_from(imports.get_count()).unwrap(); self.result.module.initializers.reserve(cnt); @@ -270,7 +263,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::FunctionSection(functions) => { - validator.function_section(&functions)?; + self.validator.function_section(&functions)?; let cnt = usize::try_from(functions.get_count()).unwrap(); self.result.module.functions.reserve_exact(cnt); @@ -284,7 +277,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::TableSection(tables) => { - validator.table_section(&tables)?; + self.validator.table_section(&tables)?; let cnt = usize::try_from(tables.get_count()).unwrap(); self.result.module.table_plans.reserve_exact(cnt); @@ -296,7 +289,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::MemorySection(memories) => { - validator.memory_section(&memories)?; + self.validator.memory_section(&memories)?; let cnt = usize::try_from(memories.get_count()).unwrap(); self.result.module.memory_plans.reserve_exact(cnt); @@ -312,7 +305,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::TagSection(tags) => { - validator.tag_section(&tags)?; + self.validator.tag_section(&tags)?; // This feature isn't enabled at this time, so we should // never get here. @@ -320,7 +313,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::GlobalSection(globals) => { - validator.global_section(&globals)?; + self.validator.global_section(&globals)?; let cnt = usize::try_from(globals.get_count()).unwrap(); self.result.module.globals.reserve_exact(cnt); @@ -358,7 +351,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::ExportSection(exports) => { - validator.export_section(&exports)?; + self.validator.export_section(&exports)?; let cnt = usize::try_from(exports.get_count()).unwrap(); self.result.module.exports.reserve(cnt); @@ -386,7 +379,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::StartSection { func, range } => { - validator.start_section(func, &range)?; + self.validator.start_section(func, &range)?; let func_index = FuncIndex::from_u32(func); self.flag_func_escaped(func_index); @@ -395,7 +388,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::ElementSection(elements) => { - validator.element_section(&elements)?; + self.validator.element_section(&elements)?; for (index, entry) in elements.into_iter().enumerate() { let wasmparser::Element { @@ -488,14 +481,14 @@ impl<'data> ModuleEnvironment<'data> { } Payload::CodeSectionStart { count, range, .. } => { - validator.code_section_start(count, &range)?; + self.validator.code_section_start(count, &range)?; let cnt = usize::try_from(count).unwrap(); self.result.function_body_inputs.reserve_exact(cnt); self.result.debuginfo.wasm_file.code_section_offset = range.start as u64; } Payload::CodeSectionEntry(mut body) => { - let validator = validator.code_section_entry(&body)?; + let validator = self.validator.code_section_entry(&body)?; let func_index = self.result.code_index + self.result.module.num_imported_funcs as u32; let func_index = FuncIndex::from_u32(func_index); @@ -516,7 +509,7 @@ impl<'data> ModuleEnvironment<'data> { params: sig.params().iter().cloned().map(|i| i.into()).collect(), }); } - body.allow_memarg64(self.features.memory64); + body.allow_memarg64(self.validator.features().memory64); self.result .function_body_inputs .push(FunctionBodyData { validator, body }); @@ -524,7 +517,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::DataSection(data) => { - validator.data_section(&data)?; + self.validator.data_section(&data)?; let initializers = match &mut self.result.module.memory_initialization { MemoryInitialization::Segmented(i) => i, @@ -601,7 +594,7 @@ impl<'data> ModuleEnvironment<'data> { } Payload::DataCountSection { count, range } => { - validator.data_count_section(count, &range)?; + self.validator.data_count_section(count, &range)?; // Note: the count passed in here is the *total* segment count // There is no way to reserve for just the passive segments as @@ -639,7 +632,7 @@ and for re-adding support for interface types you can see this issue: } Payload::CustomSection(s) => { - self.register_dwarf_section(s.name(), s.data()); + self.register_dwarf_section(&s); } // It's expected that validation will probably reject other @@ -647,14 +640,15 @@ and for re-adding support for interface types you can see this issue: // component model. If, however, something gets past validation then // that's a bug in Wasmtime as we forgot to implement something. other => { - validator.payload(&other)?; + self.validator.payload(&other)?; panic!("unimplemented section in wasm file {:?}", other); } } Ok(()) } - fn register_dwarf_section(&mut self, name: &str, data: &'data [u8]) { + fn register_dwarf_section(&mut self, section: &CustomSectionReader<'data>) { + let name = section.name(); if !name.starts_with(".debug_") { return; } @@ -665,6 +659,7 @@ and for re-adding support for interface types you can see this issue: let info = &mut self.result.debuginfo; let dwarf = &mut info.dwarf; let endian = gimli::LittleEndian; + let data = section.data(); let slice = gimli::EndianSlice::new(data, endian); match name { @@ -751,16 +746,7 @@ and for re-adding support for interface types you can see this issue: } fn declare_type_func(&mut self, wasm: WasmFuncType) -> WasmResult<()> { - // Deduplicate wasm function signatures through `interned_func_types`, - // which also deduplicates across wasm modules with module linking. - let sig_index = match self.interned_func_types.get(&wasm) { - Some(idx) => *idx, - None => { - let sig_index = self.types.wasm_signatures.push(wasm.clone()); - self.interned_func_types.insert(wasm, sig_index); - sig_index - } - }; + let sig_index = self.types.wasm_func_type(wasm); self.result .module .types diff --git a/crates/environ/src/module_types.rs b/crates/environ/src/module_types.rs new file mode 100644 index 000000000000..026f6b36ec68 --- /dev/null +++ b/crates/environ/src/module_types.rs @@ -0,0 +1,78 @@ +use crate::{PrimaryMap, SignatureIndex, WasmFuncType}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::ops::Index; + +/// All types used in a core wasm module. +/// +/// At this time this only contains function types. Note, though, that function +/// types are deduplicated within this [`ModuleTypes`]. +/// +/// Note that accesing this type is primarily done through the `Index` +/// implementations for this type. +#[derive(Default, Serialize, Deserialize)] +#[allow(missing_docs)] +pub struct ModuleTypes { + wasm_signatures: PrimaryMap, +} + +impl ModuleTypes { + /// Returns an iterator over all the wasm function signatures found within + /// this module. + pub fn wasm_signatures(&self) -> impl Iterator { + self.wasm_signatures.iter() + } +} + +impl Index for ModuleTypes { + type Output = WasmFuncType; + + fn index(&self, sig: SignatureIndex) -> &WasmFuncType { + &self.wasm_signatures[sig] + } +} + +/// A builder for [`ModuleTypes`]. +#[derive(Default)] +#[allow(missing_docs)] +pub struct ModuleTypesBuilder { + types: ModuleTypes, + interned_func_types: HashMap, +} + +impl ModuleTypesBuilder { + /// Reserves space for `amt` more type signatures. + pub fn reserve_wasm_signatures(&mut self, amt: usize) { + self.types.wasm_signatures.reserve(amt); + } + + /// Interns the `sig` specified and returns a unique `SignatureIndex` that + /// can be looked up within [`ModuleTypes`] to recover the [`WasmFuncType`] + /// at runtime. + pub fn wasm_func_type(&mut self, sig: WasmFuncType) -> SignatureIndex { + if let Some(idx) = self.interned_func_types.get(&sig) { + return *idx; + } + + let idx = self.types.wasm_signatures.push(sig.clone()); + self.interned_func_types.insert(sig, idx); + return idx; + } + + /// Returns the result [`ModuleTypes`] of this builder. + pub fn finish(self) -> ModuleTypes { + self.types + } +} + +// Forward the indexing impl to the internal `ModuleTypes` +impl Index for ModuleTypesBuilder +where + ModuleTypes: Index, +{ + type Output = >::Output; + + fn index(&self, sig: T) -> &Self::Output { + &self.types[sig] + } +} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index da380e520677..b3bac9ec9a7f 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -222,6 +222,30 @@ pub enum EntityIndex { Global(GlobalIndex), } +impl From for EntityIndex { + fn from(idx: FuncIndex) -> EntityIndex { + EntityIndex::Function(idx) + } +} + +impl From for EntityIndex { + fn from(idx: TableIndex) -> EntityIndex { + EntityIndex::Table(idx) + } +} + +impl From for EntityIndex { + fn from(idx: MemoryIndex) -> EntityIndex { + EntityIndex::Memory(idx) + } +} + +impl From for EntityIndex { + fn from(idx: GlobalIndex) -> EntityIndex { + EntityIndex::Global(idx) + } +} + /// A type of an item in a wasm module where an item is typically something that /// can be exported. #[allow(missing_docs)] diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 3ae1fe3f36a0..8517fbf99129 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -111,3 +111,8 @@ memory-init-cow = ["wasmtime-runtime/memory-init-cow"] # # This is enabled by default. wasm-backtrace = ["wasmtime-runtime/wasm-backtrace", "backtrace"] + +# Enables in-progress support for the component model. Note that this feature is +# in-progress, buggy, and incomplete. This is primarily here for internal +# testing purposes. +component-model = ["wasmtime-environ/component-model"] diff --git a/crates/wasmtime/src/component/component.rs b/crates/wasmtime/src/component/component.rs new file mode 100644 index 000000000000..f0d7941d4b94 --- /dev/null +++ b/crates/wasmtime/src/component/component.rs @@ -0,0 +1,122 @@ +use crate::{Engine, Module}; +use anyhow::{bail, Context, Result}; +use std::fs; +use std::path::Path; +use std::sync::Arc; +use wasmtime_environ::component::{ComponentTypes, ModuleUpvarIndex, Translation, Translator}; +use wasmtime_environ::PrimaryMap; + +/// A compiled WebAssembly Component. +// +// FIXME: need to write more docs here. +#[derive(Clone)] +pub struct Component { + inner: Arc, +} + +struct ComponentInner { + component: wasmtime_environ::component::Component, + upvars: PrimaryMap, + types: Arc, +} + +impl Component { + /// Compiles a new WebAssembly component from the in-memory wasm image + /// provided. + // + // FIXME: need to write more docs here. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result { + let bytes = bytes.as_ref(); + #[cfg(feature = "wat")] + let bytes = wat::parse_bytes(bytes)?; + Component::from_binary(engine, &bytes) + } + + /// Compiles a new WebAssembly component from a wasm file on disk pointed to + /// by `file`. + // + // FIXME: need to write more docs here. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + pub fn from_file(engine: &Engine, file: impl AsRef) -> Result { + match Self::new( + engine, + &fs::read(&file).with_context(|| "failed to read input file")?, + ) { + Ok(m) => Ok(m), + Err(e) => { + cfg_if::cfg_if! { + if #[cfg(feature = "wat")] { + let mut e = e.downcast::()?; + e.set_path(file); + bail!(e) + } else { + Err(e) + } + } + } + } + } + + /// Compiles a new WebAssembly component from the in-memory wasm image + /// provided. + // + // FIXME: need to write more docs here. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result { + engine + .check_compatible_with_native_host() + .context("compilation settings are not compatible with the native host")?; + + let tunables = &engine.config().tunables; + + let mut validator = + wasmparser::Validator::new_with_features(engine.config().features.clone()); + let mut types = Default::default(); + let translation = Translator::new(tunables, &mut validator, &mut types) + .translate(binary) + .context("failed to parse WebAssembly module")?; + let types = Arc::new(types.finish()); + + let Translation { + component, upvars, .. + } = translation; + let upvars = upvars.into_iter().map(|(_, t)| t).collect::>(); + let upvars = engine + .run_maybe_parallel(upvars, |module| { + let (mmap, info) = Module::compile_functions(engine, module, types.module_types())?; + // FIXME: the `SignatureCollection` here is re-registering the + // entire list of wasm types within `types` on each invocation. + // That's ok semantically but is quite slow to do so. This + // should build up a mapping from `SignatureIndex` to + // `VMSharedSignatureIndex` once and then reuse that for each + // module somehow. + Module::from_parts(engine, mmap, info, types.clone()) + })? + .into_iter() + .collect(); + + Ok(Component { + inner: Arc::new(ComponentInner { + component, + upvars, + types, + }), + }) + } + + pub(crate) fn env_component(&self) -> &wasmtime_environ::component::Component { + &self.inner.component + } + + pub(crate) fn upvar(&self, idx: ModuleUpvarIndex) -> &Module { + &self.inner.upvars[idx] + } + + pub(crate) fn types(&self) -> &Arc { + &self.inner.types + } +} diff --git a/crates/wasmtime/src/component/func.rs b/crates/wasmtime/src/component/func.rs new file mode 100644 index 000000000000..3767eb5d369e --- /dev/null +++ b/crates/wasmtime/src/component/func.rs @@ -0,0 +1,83 @@ +use crate::component::instance::lookup; +use crate::store::{StoreOpaque, Stored}; +use std::sync::Arc; +use wasmtime_environ::component::{ + ComponentTypes, FuncTypeIndex, LiftedFunction, RuntimeInstanceIndex, StringEncoding, +}; +use wasmtime_environ::PrimaryMap; +use wasmtime_runtime::{Export, ExportFunction, ExportMemory, VMTrampoline}; + +/// A WebAssembly component function. +// +// FIXME: write more docs here +#[derive(Copy, Clone, Debug)] +pub struct Func(Stored); + +#[doc(hidden)] +#[allow(dead_code)] // FIXME: remove this when fields are actually used +pub struct FuncData { + trampoline: VMTrampoline, + export: ExportFunction, + ty: FuncTypeIndex, + types: Arc, + options: Options, +} + +#[derive(Clone)] +#[allow(dead_code)] // FIXME: remove this when fields are actually used +pub(crate) struct Options { + string_encoding: Option, + intrinsics: Option, +} + +#[derive(Clone)] +#[allow(dead_code)] // FIXME: remove this when fields are actually used +struct Intrinsics { + memory: ExportMemory, + realloc: ExportFunction, + free: ExportFunction, +} + +impl Func { + pub(crate) fn from_lifted_func( + store: &mut StoreOpaque, + types: &Arc, + instances: &PrimaryMap, + func: &LiftedFunction, + ) -> Func { + let export = match lookup(store, instances, &func.func) { + Export::Function(f) => f, + _ => unreachable!(), + }; + let trampoline = store.lookup_trampoline(unsafe { export.anyfunc.as_ref() }); + let intrinsics = func.options.intrinsics.as_ref().map(|i| { + let memory = match lookup(store, instances, &i.memory) { + Export::Memory(m) => m, + _ => unreachable!(), + }; + let realloc = match lookup(store, instances, &i.canonical_abi_realloc) { + Export::Function(f) => f, + _ => unreachable!(), + }; + let free = match lookup(store, instances, &i.canonical_abi_free) { + Export::Function(f) => f, + _ => unreachable!(), + }; + Intrinsics { + memory, + realloc, + free, + } + }); + Func(store.store_data_mut().insert(FuncData { + trampoline, + export, + options: Options { + intrinsics, + string_encoding: func.options.string_encoding, + }, + ty: func.ty, + types: types.clone(), + })) + } +} diff --git a/crates/wasmtime/src/component/instance.rs b/crates/wasmtime/src/component/instance.rs new file mode 100644 index 000000000000..8822e1fbb8d8 --- /dev/null +++ b/crates/wasmtime/src/component/instance.rs @@ -0,0 +1,173 @@ +use crate::component::{Component, Func}; +use crate::instance::OwnedImports; +use crate::store::{StoreOpaque, Stored}; +use crate::{AsContextMut, Module, StoreContextMut}; +use anyhow::Result; +use wasmtime_environ::component::{ + CoreExport, Export, ExportItem, Instantiation, RuntimeInstanceIndex, +}; +use wasmtime_environ::{EntityIndex, PrimaryMap}; + +/// An instantiated component. +/// +/// This is similar to [`crate::Instance`] except that it represents an +/// instantiated component instead of an instantiated module. Otherwise though +/// the two behave similarly. +// +// FIXME: need to write more docs here. +#[derive(Copy, Clone)] +pub struct Instance(Stored>>); + +pub(crate) struct InstanceData { + instances: PrimaryMap, + // FIXME: shouldn't store the entire component here which keeps upvars + // alive and things like that, instead only the bare minimum necessary + // should be kept alive here (mostly just `wasmtime_environ::Component`. + component: Component, +} + +impl Instance { + /// Instantiates the `component` provided within the given `store`. + /// + /// Does not support components which have imports at this time. + // + // FIXME: need to write more docs here. + pub fn new(mut store: impl AsContextMut, component: &Component) -> Result { + let mut store = store.as_context_mut(); + + let mut instantiator = Instantiator::new(component); + instantiator.run(&mut store)?; + + let data = Box::new(InstanceData { + instances: instantiator.instances, + component: component.clone(), + }); + Ok(Instance(store.0.store_data_mut().insert(Some(data)))) + } + + /// Looks up a function by name within this [`Instance`]. + /// + /// The `store` specified must be the store that this instance lives within + /// and `name` is the name of the function to lookup. If the function is + /// found `Some` is returned otherwise `None` is returned. + /// + /// # Panics + /// + /// Panics if `store` does not own this instance. + pub fn get_func(&self, mut store: impl AsContextMut, name: &str) -> Option { + self._get_func(store.as_context_mut().0, name) + } + + fn _get_func(&self, store: &mut StoreOpaque, name: &str) -> Option { + // FIXME: this movement in ownership is unfortunate and feels like there + // should be a better solution. The reason for this is that we need to + // simultaneously look at lots of pieces of `InstanceData` while also + // inserting into `store`, but `InstanceData` is stored within `store`. + // By moving it out we appease the borrow-checker but take a runtime + // hit. + let data = store[self.0].take().unwrap(); + let result = data.get_func(store, name); + store[self.0] = Some(data); + return result; + } +} + +impl InstanceData { + fn get_func(&self, store: &mut StoreOpaque, name: &str) -> Option { + match self.component.env_component().exports.get(name)? { + Export::LiftedFunction(func) => Some(Func::from_lifted_func( + store, + self.component.types(), + &self.instances, + func, + )), + } + } +} + +struct Instantiator<'a> { + component: &'a Component, + instances: PrimaryMap, + imports: OwnedImports, +} + +impl<'a> Instantiator<'a> { + fn new(component: &'a Component) -> Instantiator<'a> { + let env_component = component.env_component(); + if env_component.imports.len() > 0 { + unimplemented!("component imports"); + } + Instantiator { + component, + instances: PrimaryMap::with_capacity(env_component.instances.len()), + imports: OwnedImports::empty(), + } + } + + fn run(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> { + let env_component = self.component.env_component(); + for (index, instantiation) in env_component.instances.iter() { + let (module, args) = match instantiation { + Instantiation::ModuleUpvar { module, args } => { + (self.component.upvar(*module), args) + } + Instantiation::ModuleImport { import_index, args } => { + drop((import_index, args)); + unimplemented!("component module imports"); + } + }; + + // Note that the unsafety here should be ok because the + // validity of the component means that type-checks have + // already been performed. This maens that the unsafety due + // to imports having the wrong type should not happen here. + let imports = self.build_imports(store.0, module, args); + let instance = + unsafe { crate::Instance::new_started(store, module, imports.as_ref())? }; + let idx = self.instances.push(instance); + assert_eq!(idx, index); + } + Ok(()) + } + + fn build_imports( + &mut self, + store: &mut StoreOpaque, + module: &Module, + args: &[CoreExport], + ) -> &OwnedImports { + self.imports.clear(); + self.imports.reserve(module); + + for arg in args { + let export = lookup(store, &self.instances, arg); + + // The unsafety here should be ok since the `export` is loaded + // directly from an instance which should only give us valid export + // items. + unsafe { + self.imports.push_export(&export); + } + } + + &self.imports + } +} + +pub(crate) fn lookup( + store: &mut StoreOpaque, + instances: &PrimaryMap, + item: &CoreExport, +) -> wasmtime_runtime::Export +where + T: Copy + Into, +{ + let instance = &instances[item.instance]; + let id = instance.id(store); + let instance = store.instance_mut(id); + let idx = match &item.item { + ExportItem::Index(idx) => (*idx).into(), + ExportItem::Name(name) => instance.module().exports[name], + }; + instance.get_export_by_index(idx) +} diff --git a/crates/wasmtime/src/component/mod.rs b/crates/wasmtime/src/component/mod.rs new file mode 100644 index 000000000000..8e7aad148629 --- /dev/null +++ b/crates/wasmtime/src/component/mod.rs @@ -0,0 +1,14 @@ +//! In-progress implementation of the WebAssembly component model +//! +//! This module is a work-in-progress and currently represents an incomplete and +//! probably buggy implementation of the component model. + +mod component; +mod func; +mod instance; +mod store; +pub use self::component::Component; +pub use self::func::Func; +pub use self::instance::Instance; + +pub(crate) use self::store::ComponentStoreData; diff --git a/crates/wasmtime/src/component/store.rs b/crates/wasmtime/src/component/store.rs new file mode 100644 index 000000000000..9144761391c0 --- /dev/null +++ b/crates/wasmtime/src/component/store.rs @@ -0,0 +1,28 @@ +use crate::store::{StoreData, StoredData}; + +macro_rules! component_store_data { + ($($field:ident => $t:ty,)*) => ( + #[derive(Default)] + pub struct ComponentStoreData { + $($field: Vec<$t>,)* + } + + $( + impl StoredData for $t { + #[inline] + fn list(data: &StoreData) -> &Vec { + &data.components.$field + } + #[inline] + fn list_mut(data: &mut StoreData) -> &mut Vec { + &mut data.components.$field + } + } + )* + ) +} + +component_store_data! { + funcs => crate::component::func::FuncData, + instances => Option>, +} diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 9c1ed87cab9c..16e3272bba56 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -619,6 +619,20 @@ impl Config { self } + /// Configures whether the WebAssembly component-model [proposal] will + /// be enabled for compilation. + /// + /// Note that this feature is a work-in-progress and is incomplete. + /// + /// This is `false` by default. + /// + /// [proposal]: https://github.com/webassembly/component-model + #[cfg(feature = "component-model")] + pub fn wasm_component_model(&mut self, enable: bool) -> &mut Self { + self.features.component_model = enable; + self + } + /// Configures which compilation strategy will be used for wasm modules. /// /// This method can be used to configure which compiler is used for wasm diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 0974aff430a3..3c8463f6a7c1 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -813,13 +813,22 @@ impl Func { ) -> Result<(), Trap> { let mut store = store.as_context_mut(); let data = &store.0.store_data()[self.0]; - let trampoline = data.trampoline(); let anyfunc = data.export().anyfunc; - invoke_wasm_and_catch_traps(&mut store, |callee| { + let trampoline = data.trampoline(); + Self::call_unchecked_raw(&mut store, anyfunc, trampoline, params_and_returns) + } + + pub(crate) unsafe fn call_unchecked_raw( + store: &mut StoreContextMut<'_, T>, + anyfunc: NonNull, + trampoline: VMTrampoline, + params_and_returns: *mut ValRaw, + ) -> Result<(), Trap> { + invoke_wasm_and_catch_traps(store, |callee| { trampoline( - (*anyfunc.as_ptr()).vmctx, + anyfunc.as_ref().vmctx, callee, - (*anyfunc.as_ptr()).func_ptr.as_ptr(), + anyfunc.as_ref().func_ptr.as_ptr(), params_and_returns, ) }) @@ -2063,7 +2072,6 @@ impl HostFunc { /// `Engine`. This happens automatically during the above two constructors. fn _new(engine: &Engine, mut instance: InstanceHandle, trampoline: VMTrampoline) -> Self { let export = instance.get_exported_func(FuncIndex::from_u32(0)); - HostFunc { instance, trampoline, diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs index f503c2e57dc0..9cdd3309b2cc 100644 --- a/crates/wasmtime/src/func/typed.rs +++ b/crates/wasmtime/src/func/typed.rs @@ -1,11 +1,11 @@ use super::{invoke_wasm_and_catch_traps, HostAbi}; use crate::store::{AutoAssertNoGc, StoreOpaque}; -use crate::{AsContextMut, ExternRef, Func, StoreContextMut, Trap, ValRaw, ValType}; +use crate::{AsContextMut, ExternRef, Func, FuncType, StoreContextMut, Trap, ValRaw, ValType}; use anyhow::{bail, Result}; use std::marker; use std::mem::{self, MaybeUninit}; use std::ptr; -use wasmtime_runtime::{VMContext, VMFunctionBody}; +use wasmtime_runtime::{VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMSharedSignatureIndex}; /// A statically typed WebAssembly function. /// @@ -76,7 +76,8 @@ where !store.0.async_support(), "must use `call_async` with async stores" ); - unsafe { self._call(&mut store, params) } + let func = self.func.caller_checked_anyfunc(store.0); + unsafe { Self::call_raw(&mut store, func, params) } } /// Invokes this WebAssembly function with the specified parameters. @@ -106,15 +107,24 @@ where "must use `call` with non-async stores" ); store - .on_fiber(|store| unsafe { self._call(store, params) }) + .on_fiber(|store| { + let func = self.func.caller_checked_anyfunc(store.0); + unsafe { Self::call_raw(store, func, params) } + }) .await? } - unsafe fn _call( - &self, + pub(crate) unsafe fn call_raw( store: &mut StoreContextMut<'_, T>, + func: ptr::NonNull, params: Params, ) -> Result { + // double-check that params/results match for this function's type in + // debug mode. + if cfg!(debug_assertions) { + Self::debug_typecheck(store.0, func.as_ref().type_index); + } + // See the comment in `Func::call_impl`'s `write_params` function. if params.externrefs_count() > store @@ -150,12 +160,7 @@ where // efficient to move in memory. This closure is actually invoked on the // other side of a C++ shim, so it can never be inlined enough to make // the memory go away, so the size matters here for performance. - let mut captures = ( - self.func.caller_checked_anyfunc(store.0), - MaybeUninit::uninit(), - params, - false, - ); + let mut captures = (func, MaybeUninit::uninit(), params, false); let result = invoke_wasm_and_catch_traps(store, |callee| { let (anyfunc, ret, params, returned) = &mut captures; @@ -174,6 +179,19 @@ where result?; Ok(Results::from_abi(store.0, ret.assume_init())) } + + /// Purely a debug-mode assertion, not actually used in release builds. + fn debug_typecheck(store: &StoreOpaque, func: VMSharedSignatureIndex) { + let ty = FuncType::from_wasm_func_type( + store + .engine() + .signatures() + .lookup_type(func) + .expect("signature should be registered"), + ); + Params::typecheck(ty.params()).expect("params should match"); + Results::typecheck(ty.results()).expect("results should match"); + } } /// A trait implemented for types which can be arguments and results for diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index 3701d2ed5dd7..e373950f5d89 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -165,7 +165,7 @@ impl Instance { /// Internal function to create an instance and run the start function. /// /// This function's unsafety is the same as `Instance::new_raw`. - unsafe fn new_started( + pub(crate) unsafe fn new_started( store: &mut StoreContextMut<'_, T>, module: &Module, imports: Imports<'_>, @@ -506,9 +506,14 @@ impl Instance { pub fn get_global(&self, store: impl AsContextMut, name: &str) -> Option { self.get_export(store, name)?.into_global() } + + #[cfg(feature = "component-model")] + pub(crate) fn id(&self, store: &StoreOpaque) -> InstanceId { + store[self.0].id + } } -struct OwnedImports { +pub(crate) struct OwnedImports { functions: PrimaryMap, tables: PrimaryMap, memories: PrimaryMap, @@ -517,15 +522,36 @@ struct OwnedImports { impl OwnedImports { fn new(module: &Module) -> OwnedImports { - let raw = module.compiled_module().module(); + let mut ret = OwnedImports::empty(); + ret.reserve(module); + return ret; + } + + pub(crate) fn empty() -> OwnedImports { OwnedImports { - functions: PrimaryMap::with_capacity(raw.num_imported_funcs), - tables: PrimaryMap::with_capacity(raw.num_imported_tables), - memories: PrimaryMap::with_capacity(raw.num_imported_memories), - globals: PrimaryMap::with_capacity(raw.num_imported_globals), + functions: PrimaryMap::new(), + tables: PrimaryMap::new(), + memories: PrimaryMap::new(), + globals: PrimaryMap::new(), } } + pub(crate) fn reserve(&mut self, module: &Module) { + let raw = module.compiled_module().module(); + self.functions.reserve(raw.num_imported_funcs); + self.tables.reserve(raw.num_imported_tables); + self.memories.reserve(raw.num_imported_memories); + self.globals.reserve(raw.num_imported_globals); + } + + #[cfg(feature = "component-model")] + pub(crate) fn clear(&mut self) { + self.functions.clear(); + self.tables.clear(); + self.memories.clear(); + self.globals.clear(); + } + fn push(&mut self, item: &Extern, store: &mut StoreOpaque) { match item { Extern::Func(i) => { @@ -543,7 +569,37 @@ impl OwnedImports { } } - fn as_ref(&self) -> Imports<'_> { + /// Note that this is unsafe as the validity of `item` is not verified and + /// it contains a bunch of raw pointers. + #[cfg(feature = "component-model")] + pub(crate) unsafe fn push_export(&mut self, item: &wasmtime_runtime::Export) { + match item { + wasmtime_runtime::Export::Function(f) => { + let f = f.anyfunc.as_ref(); + self.functions.push(VMFunctionImport { + body: f.func_ptr, + vmctx: f.vmctx, + }); + } + wasmtime_runtime::Export::Global(g) => { + self.globals.push(VMGlobalImport { from: g.definition }); + } + wasmtime_runtime::Export::Table(t) => { + self.tables.push(VMTableImport { + from: t.definition, + vmctx: t.vmctx, + }); + } + wasmtime_runtime::Export::Memory(m) => { + self.memories.push(VMMemoryImport { + from: m.definition, + vmctx: m.vmctx, + }); + } + } + } + + pub(crate) fn as_ref(&self) -> Imports<'_> { Imports { tables: self.tables.values().as_slice(), globals: self.globals.values().as_slice(), diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 1c5ee4795064..ffc46c355c4d 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -424,6 +424,9 @@ pub use crate::trap::*; pub use crate::types::*; pub use crate::values::*; +#[cfg(feature = "component-model")] +pub mod component; + cfg_if::cfg_if! { if #[cfg(all(target_os = "macos", not(feature = "posix-signals-on-macos")))] { // no extensions for macOS at this time diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index e96b8f5ea35d..547e66bd7e1c 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -11,9 +11,11 @@ use std::ops::Range; use std::path::Path; use std::sync::Arc; use wasmparser::{Parser, ValidPayload, Validator}; +#[cfg(feature = "component-model")] +use wasmtime_environ::component::ComponentTypes; use wasmtime_environ::{ - DefinedFuncIndex, DefinedMemoryIndex, FunctionInfo, ModuleEnvironment, PrimaryMap, - SignatureIndex, TypeTables, + DefinedFuncIndex, DefinedMemoryIndex, FunctionInfo, ModuleEnvironment, ModuleTranslation, + ModuleTypes, PrimaryMap, SignatureIndex, }; use wasmtime_jit::{CompiledModule, CompiledModuleInfo}; use wasmtime_runtime::{ @@ -105,7 +107,7 @@ struct ModuleInner { /// executed. module: Arc, /// Type information of this module. - types: TypeTables, + types: Types, /// Registered shared signature for the module. signatures: SignatureCollection, /// A set of initialization images for memories, if any. @@ -340,26 +342,41 @@ impl Module { pub(crate) fn build_artifacts( engine: &Engine, wasm: &[u8], - ) -> Result<(MmapVec, Option, TypeTables)> { + ) -> Result<(MmapVec, Option, ModuleTypes)> { let tunables = &engine.config().tunables; // First a `ModuleEnvironment` is created which records type information // about the wasm module. This is where the WebAssembly is parsed and // validated. Afterwards `types` will have all the type information for // this module. - let (mut translation, types) = ModuleEnvironment::new(tunables, &engine.config().features) - .translate(wasm) + let mut validator = + wasmparser::Validator::new_with_features(engine.config().features.clone()); + let parser = wasmparser::Parser::new(0); + let mut types = Default::default(); + let translation = ModuleEnvironment::new(tunables, &mut validator, &mut types) + .translate(parser, wasm) .context("failed to parse WebAssembly module")?; + let types = types.finish(); + let (mmap, info) = Module::compile_functions(engine, translation, &types)?; + Ok((mmap, info, types)) + } - // Next compile all functions in parallel using rayon. This will perform - // the actual validation of all the function bodies. + #[cfg(compiler)] + pub(crate) fn compile_functions( + engine: &Engine, + mut translation: ModuleTranslation<'_>, + types: &ModuleTypes, + ) -> Result<(MmapVec, Option)> { + // Compile all functions in parallel using rayon. This will also perform + // validation of function bodies. + let tunables = &engine.config().tunables; let functions = mem::take(&mut translation.function_body_inputs); let functions = functions.into_iter().collect::>(); let funcs = engine .run_maybe_parallel(functions, |(index, func)| { engine .compiler() - .compile_function(&translation, index, func, tunables, &types) + .compile_function(&translation, index, func, tunables, types) })? .into_iter() .collect(); @@ -369,7 +386,7 @@ impl Module { let (funcs, trampolines) = engine .compiler() - .emit_obj(&translation, &types, funcs, tunables, &mut obj)?; + .emit_obj(&translation, types, funcs, tunables, &mut obj)?; // If configured attempt to use static memory initialization which // can either at runtime be implemented as a single memcpy to @@ -389,7 +406,7 @@ impl Module { let (mmap, info) = wasmtime_jit::finish_compile(translation, obj, funcs, trampolines, tunables)?; - Ok((mmap, Some(info), types)) + Ok((mmap, Some(info))) } /// Deserializes an in-memory compiled module previously created with @@ -467,11 +484,11 @@ impl Module { module.into_module(engine) } - fn from_parts( + pub(crate) fn from_parts( engine: &Engine, mmap: MmapVec, info: Option, - types: TypeTables, + types: impl Into, ) -> Result { let module = Arc::new(CompiledModule::from_artifacts( mmap, @@ -483,9 +500,10 @@ impl Module { // Validate the module can be used with the current allocator engine.allocator().validate(module.module())?; + let types = types.into(); let signatures = SignatureCollection::new_for_module( engine.signatures(), - &types, + types.module_types(), module.trampolines().map(|(idx, f, _)| (idx, f)), ); @@ -530,9 +548,15 @@ impl Module { let mut functions = Vec::new(); for payload in Parser::new(0).parse_all(binary) { - if let ValidPayload::Func(a, b) = validator.payload(&payload?)? { + let payload = payload?; + if let ValidPayload::Func(a, b) = validator.payload(&payload)? { functions.push((a, b)); } + if let wasmparser::Payload::Version { encoding, .. } = &payload { + if let wasmparser::Encoding::Component = encoding { + bail!("component passed to module validation"); + } + } } engine.run_maybe_parallel(functions, |(mut validator, body)| validator.validate(&body))?; @@ -562,8 +586,8 @@ impl Module { self.compiled_module().module() } - pub(crate) fn types(&self) -> &TypeTables { - &self.inner.types + pub(crate) fn types(&self) -> &ModuleTypes { + self.inner.types.module_types() } pub(crate) fn signatures(&self) -> &SignatureCollection { @@ -1037,6 +1061,35 @@ impl wasmtime_runtime::ModuleRuntimeInfo for BareModuleInfo { } } +pub(crate) enum Types { + Module(ModuleTypes), + #[cfg(feature = "component-model")] + Component(Arc), +} + +impl Types { + fn module_types(&self) -> &ModuleTypes { + match self { + Types::Module(m) => m, + #[cfg(feature = "component-model")] + Types::Component(c) => c.module_types(), + } + } +} + +impl From for Types { + fn from(types: ModuleTypes) -> Types { + Types::Module(types) + } +} + +#[cfg(feature = "component-model")] +impl From> for Types { + fn from(types: Arc) -> Types { + Types::Component(types) + } +} + /// Helper method to construct a `ModuleMemoryImages` for an associated /// `CompiledModule`. fn memory_images(engine: &Engine, module: &CompiledModule) -> Result> { diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index e3bb09d7862e..cb45b6ed79e7 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -7,7 +7,7 @@ //! There are two main pieces of data associated with a binary artifact: //! //! 1. The compiled module image, currently an ELF file. -//! 2. Compilation metadata for the module, including the `TypeTables` +//! 2. Compilation metadata for the module, including the `ModuleTypes` //! information. This metadata is validated for compilation settings. //! //! Compiled modules are, at this time, represented as an ELF file. This ELF @@ -47,7 +47,7 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::path::Path; use std::str::FromStr; -use wasmtime_environ::{FlagValue, Tunables, TypeTables}; +use wasmtime_environ::{FlagValue, ModuleTypes, Tunables}; use wasmtime_jit::{subslice_range, CompiledModuleInfo}; use wasmtime_runtime::MmapVec; @@ -166,7 +166,7 @@ struct Metadata<'a> { isa_flags: BTreeMap, tunables: Tunables, features: WasmFeatures, - types: MyCow<'a, TypeTables>, + types: MyCow<'a, ModuleTypes>, } impl<'a> SerializedModule<'a> { @@ -180,7 +180,7 @@ impl<'a> SerializedModule<'a> { } #[cfg(compiler)] - pub fn from_artifacts(engine: &Engine, artifacts: &'a MmapVec, types: &'a TypeTables) -> Self { + pub fn from_artifacts(engine: &Engine, artifacts: &'a MmapVec, types: &'a ModuleTypes) -> Self { Self::with_data(engine, MyCow::Borrowed(artifacts), MyCow::Borrowed(types)) } @@ -188,7 +188,7 @@ impl<'a> SerializedModule<'a> { fn with_data( engine: &Engine, artifacts: MyCow<'a, MmapVec>, - types: MyCow<'a, TypeTables>, + types: MyCow<'a, ModuleTypes>, ) -> Self { Self { artifacts, @@ -211,7 +211,7 @@ impl<'a> SerializedModule<'a> { pub fn into_parts( mut self, engine: &Engine, - ) -> Result<(MmapVec, Option, TypeTables)> { + ) -> Result<(MmapVec, Option, ModuleTypes)> { // Verify that the compilation settings in the engine match the // compilation settings of the module that's being loaded. self.check_triple(engine)?; diff --git a/crates/wasmtime/src/signatures.rs b/crates/wasmtime/src/signatures.rs index 8820c2c5420c..1a9dabd299ab 100644 --- a/crates/wasmtime/src/signatures.rs +++ b/crates/wasmtime/src/signatures.rs @@ -6,7 +6,7 @@ use std::{ sync::RwLock, }; use std::{convert::TryFrom, sync::Arc}; -use wasmtime_environ::{PrimaryMap, SignatureIndex, TypeTables, WasmFuncType}; +use wasmtime_environ::{ModuleTypes, PrimaryMap, SignatureIndex, WasmFuncType}; use wasmtime_runtime::{VMSharedSignatureIndex, VMTrampoline}; /// Represents a collection of shared signatures. @@ -27,7 +27,7 @@ impl SignatureCollection { /// and trampolines. pub fn new_for_module( registry: &SignatureRegistry, - types: &TypeTables, + types: &ModuleTypes, trampolines: impl Iterator, ) -> Self { let (signatures, trampolines) = registry @@ -89,7 +89,7 @@ struct SignatureRegistryInner { impl SignatureRegistryInner { fn register_for_module( &mut self, - types: &TypeTables, + types: &ModuleTypes, trampolines: impl Iterator, ) -> ( PrimaryMap, diff --git a/crates/wasmtime/src/store/data.rs b/crates/wasmtime/src/store/data.rs index 0d2b0227add4..896b67edec55 100644 --- a/crates/wasmtime/src/store/data.rs +++ b/crates/wasmtime/src/store/data.rs @@ -20,6 +20,8 @@ pub struct StoreData { globals: Vec, instances: Vec, memories: Vec, + #[cfg(feature = "component-model")] + pub(crate) components: crate::component::ComponentStoreData, } pub trait StoredData: Sized { @@ -65,6 +67,8 @@ impl StoreData { globals: Vec::new(), instances: Vec::new(), memories: Vec::new(), + #[cfg(feature = "component-model")] + components: Default::default(), } } diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs index 00cf939ed37f..b7dc9a0a7927 100644 --- a/crates/wasmtime/src/types.rs +++ b/crates/wasmtime/src/types.rs @@ -1,5 +1,5 @@ use std::fmt; -use wasmtime_environ::{EntityType, Global, Memory, Table, TypeTables, WasmFuncType, WasmType}; +use wasmtime_environ::{EntityType, Global, Memory, ModuleTypes, Table, WasmFuncType, WasmType}; pub(crate) mod matching; @@ -147,7 +147,7 @@ impl ExternType { (Memory(MemoryType) memory unwrap_memory) } - pub(crate) fn from_wasmtime(types: &TypeTables, ty: &EntityType) -> ExternType { + pub(crate) fn from_wasmtime(types: &ModuleTypes, ty: &EntityType) -> ExternType { match ty { EntityType::Function(idx) => FuncType::from_wasm_func_type(types[*idx].clone()).into(), EntityType::Global(ty) => GlobalType::from_wasmtime_global(ty).into(), @@ -427,7 +427,7 @@ pub struct ImportType<'module> { /// The type of the import. ty: EntityType, - types: &'module TypeTables, + types: &'module ModuleTypes, } impl<'module> ImportType<'module> { @@ -437,7 +437,7 @@ impl<'module> ImportType<'module> { module: &'module str, name: &'module str, ty: EntityType, - types: &'module TypeTables, + types: &'module ModuleTypes, ) -> ImportType<'module> { ImportType { module, @@ -489,7 +489,7 @@ pub struct ExportType<'module> { /// The type of the export. ty: EntityType, - types: &'module TypeTables, + types: &'module ModuleTypes, } impl<'module> ExportType<'module> { @@ -498,7 +498,7 @@ impl<'module> ExportType<'module> { pub(crate) fn new( name: &'module str, ty: EntityType, - types: &'module TypeTables, + types: &'module ModuleTypes, ) -> ExportType<'module> { ExportType { name, ty, types } } diff --git a/crates/wasmtime/src/types/matching.rs b/crates/wasmtime/src/types/matching.rs index 7f8503ab73a0..076c6d9eebf4 100644 --- a/crates/wasmtime/src/types/matching.rs +++ b/crates/wasmtime/src/types/matching.rs @@ -3,13 +3,13 @@ use crate::store::StoreOpaque; use crate::{signatures::SignatureCollection, Engine, Extern}; use anyhow::{bail, Result}; use wasmtime_environ::{ - EntityType, Global, Memory, SignatureIndex, Table, TypeTables, WasmFuncType, WasmType, + EntityType, Global, Memory, ModuleTypes, SignatureIndex, Table, WasmFuncType, WasmType, }; use wasmtime_runtime::VMSharedSignatureIndex; pub struct MatchCx<'a> { pub signatures: &'a SignatureCollection, - pub types: &'a TypeTables, + pub types: &'a ModuleTypes, pub store: &'a StoreOpaque, pub engine: &'a Engine, } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index 37ddc403eb98..bb6449de738e 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -16,3 +16,6 @@ wast = "41.0.0" [badges] maintenance = { status = "actively-developed" } + +[features] +component-model = ['wasmtime/component-model'] diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 75a3fcca5110..1c8f79b90938 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -10,7 +10,7 @@ use wast::parser::{self, ParseBuffer}; use wast::token::{Float32, Float64}; use wast::{ AssertExpression, NanPattern, QuoteWat, V128Pattern, Wast, WastDirective, WastExecute, - WastInvoke, + WastInvoke, Wat, }; /// Translate from a `script::Value` to a `RuntimeValue`. @@ -49,6 +49,13 @@ enum Outcome> { } impl Outcome { + fn map(self, map: impl FnOnce(T) -> U) -> Outcome { + match self { + Outcome::Ok(t) => Outcome::Ok(map(t)), + Outcome::Trap(t) => Outcome::Trap(t), + } + } + fn into_result(self) -> Result { match self { Outcome::Ok(t) => Ok(t), @@ -87,7 +94,7 @@ impl WastContext { } } - fn instantiate(&mut self, module: &[u8]) -> Result> { + fn instantiate_module(&mut self, module: &[u8]) -> Result> { let module = Module::new(self.store.engine(), module)?; let instance = match self.linker.instantiate(&mut self.store, &module) { Ok(i) => i, @@ -96,6 +103,16 @@ impl WastContext { Ok(Outcome::Ok(instance)) } + #[cfg(feature = "component-model")] + fn instantiate_component(&mut self, module: &[u8]) -> Result> { + let module = component::Component::new(self.store.engine(), module)?; + let instance = match component::Instance::new(&mut self.store, &module) { + Ok(i) => i, + Err(e) => return e.downcast::().map(Outcome::Trap), + }; + Ok(Outcome::Ok(instance)) + } + /// Register "spectest" which is used by the spec testsuite. pub fn register_spectest(&mut self) -> Result<()> { link_spectest(&mut self.linker, &mut self.store)?; @@ -107,8 +124,13 @@ impl WastContext { match exec { WastExecute::Invoke(invoke) => self.perform_invoke(invoke), WastExecute::Wat(mut module) => { - let binary = module.encode()?; - let result = self.instantiate(&binary)?; + let result = match &mut module { + Wat::Module(m) => self.instantiate_module(&m.encode()?)?.map(|_| ()), + #[cfg(feature = "component-model")] + Wat::Component(m) => self.instantiate_component(&m.encode()?)?.map(|_| ()), + #[cfg(not(feature = "component-model"))] + Wat::Component(_) => bail!("component-model support not enabled"), + }; Ok(match result { Outcome::Ok(_) => Outcome::Ok(Vec::new()), Outcome::Trap(e) => Outcome::Trap(e), @@ -128,15 +150,33 @@ impl WastContext { } /// Define a module and register it. - fn module(&mut self, instance_name: Option<&str>, module: &[u8]) -> Result<()> { - let instance = match self.instantiate(module)? { - Outcome::Ok(i) => i, - Outcome::Trap(e) => return Err(e).context("instantiation failed"), + fn wat(&mut self, mut wat: QuoteWat<'_>) -> Result<()> { + let (is_module, name) = match &wat { + QuoteWat::Wat(Wat::Module(m)) => (true, m.id), + QuoteWat::QuoteModule(..) => (true, None), + QuoteWat::Wat(Wat::Component(m)) => (false, m.id), + QuoteWat::QuoteComponent(..) => (false, None), }; - if let Some(name) = instance_name { - self.linker.instance(&mut self.store, name, instance)?; + let bytes = wat.encode()?; + if is_module { + let instance = match self.instantiate_module(&bytes)? { + Outcome::Ok(i) => i, + Outcome::Trap(e) => return Err(e).context("instantiation failed"), + }; + if let Some(name) = name { + self.linker + .instance(&mut self.store, name.name(), instance)?; + } + self.current = Some(instance); + } else { + #[cfg(feature = "component-model")] + match self.instantiate_component(&bytes)? { + Outcome::Ok(_) => {} + Outcome::Trap(e) => return Err(e).context("instantiation failed"), + } + #[cfg(not(feature = "component-model"))] + bail!("component-model support not enabled"); } - self.current = Some(instance); Ok(()) } @@ -239,19 +279,10 @@ impl WastContext { } fn run_directive(&mut self, directive: WastDirective) -> Result<()> { - use WastDirective::*; + use wast::WastDirective::*; match directive { - Wat(mut module) => { - let binary = module.encode()?; - let name = match &module { - QuoteWat::Wat(wast::Wat::Module(m)) => m.id, - QuoteWat::Wat(wast::Wat::Component(c)) => c.id, - QuoteWat::QuoteModule(..) => None, - QuoteWat::QuoteComponent(..) => None, - }; - self.module(name.map(|s| s.name()), &binary)?; - } + Wat(module) => self.wat(module)?, Register { span: _, name, @@ -288,14 +319,10 @@ impl WastContext { } AssertInvalid { span: _, - mut module, + module, message, } => { - let err = match module - .encode() - .map_err(|e| e.into()) - .and_then(|bytes| self.module(None, &bytes)) - { + let err = match self.wat(module) { Ok(()) => bail!("expected module to fail to build"), Err(e) => e, }; @@ -309,25 +336,20 @@ impl WastContext { } } AssertMalformed { - mut module, + module, span: _, message: _, } => { - let result = module - .encode() - .map_err(|e| e.into()) - .and_then(|bytes| self.module(None, &bytes)); - if result.is_ok() { + if let Ok(_) = self.wat(module) { bail!("expected malformed module to fail to instantiate"); } } AssertUnlinkable { span: _, - mut module, + module, message, } => { - let bytes = module.encode()?; - let err = match self.module(None, &bytes) { + let err = match self.wat(QuoteWat::Wat(module)) { Ok(()) => bail!("expected module to fail to link"), Err(e) => e, }; diff --git a/tests/all/component_model.rs b/tests/all/component_model.rs new file mode 100644 index 000000000000..2840aae6d3ae --- /dev/null +++ b/tests/all/component_model.rs @@ -0,0 +1,68 @@ +use anyhow::Result; +use wasmtime::component::Component; +use wasmtime::{Config, Engine}; + +fn engine() -> Engine { + let mut config = Config::new(); + config.wasm_component_model(true); + Engine::new(&config).unwrap() +} + +#[test] +fn components_importing_modules() -> Result<()> { + let engine = engine(); + + // FIXME: these components should actually get instantiated in `*.wast` + // tests once supplying imports has actually been implemented. + + Component::new( + &engine, + r#" + (component + (import "" (module)) + ) + "#, + )?; + + Component::new( + &engine, + r#" + (component + (import "" (module $m1 + (import "" "" (func)) + (import "" "x" (global i32)) + + (export "a" (table 1 funcref)) + (export "b" (memory 1)) + (export "c" (func (result f32))) + (export "d" (global i64)) + )) + + (module $m2 + (func (export "")) + (global (export "x") i32 i32.const 0) + ) + (instance $i2 (instantiate (module $m2))) + (instance $i1 (instantiate (module $m1) (with "" (instance $i2)))) + + (module $m3 + (import "mod" "1" (memory 1)) + (import "mod" "2" (table 1 funcref)) + (import "mod" "3" (global i64)) + (import "mod" "4" (func (result f32))) + ) + + (instance $i3 (instantiate (module $m3) + (with "mod" (instance + (export "1" (memory $i1 "b")) + (export "2" (table $i1 "a")) + (export "3" (global $i1 "d")) + (export "4" (func $i1 "c")) + )) + )) + ) + "#, + )?; + + Ok(()) +} diff --git a/tests/all/main.rs b/tests/all/main.rs index a04ced711e50..2db9e611d8d7 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -1,6 +1,8 @@ mod async_functions; mod call_hook; mod cli_tests; +#[cfg(feature = "component-model")] +mod component_model; mod custom_signal_handler; mod debug; mod epoch_interruption; diff --git a/tests/all/wast.rs b/tests/all/wast.rs index 5d96f21fa5c5..ecc5c34312d6 100644 --- a/tests/all/wast.rs +++ b/tests/all/wast.rs @@ -30,6 +30,9 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()> .wasm_memory64(memory64) .cranelift_debug_verifier(true); + #[cfg(feature = "component-model")] + cfg.wasm_component_model(feature_found(wast, "component-model")); + if feature_found(wast, "canonicalize-nan") { cfg.cranelift_nan_canonicalization(true); } diff --git a/tests/misc_testsuite/component-model/adapter.wast b/tests/misc_testsuite/component-model/adapter.wast new file mode 100644 index 000000000000..a7bb7ceddd55 --- /dev/null +++ b/tests/misc_testsuite/component-model/adapter.wast @@ -0,0 +1,54 @@ +;; basic function lifting +(component + (module $m + (func (export "")) + ) + (instance $i (instantiate (module $m))) + + (func (export "thunk") + (canon.lift (func) (func $i "")) + ) +) + +;; use an aliased type +(component $c + (module $m + (func (export "")) + ) + (instance $i (instantiate (module $m))) + + (type $to_alias (func)) + (alias outer $c $to_alias (type $alias)) + + (func (export "thunk") + (canon.lift (type $alias) (func $i "")) + ) +) + +;; test out some various canonical abi +(component $c + (module $m + (func (export "") (param i32 i32)) + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) + unreachable) + (func (export "canonical_abi_free") (param i32 i32 i32)) + ) + (instance $i (instantiate (module $m))) + + (func (export "thunk") + (canon.lift (func (param string)) (into $i) (func $i "")) + ) + + (func (export "thunk8") + (canon.lift (func (param string)) string=utf8 (into $i) (func $i "")) + ) + + (func (export "thunk16") + (canon.lift (func (param string)) string=utf16 (into $i) (func $i "")) + ) + + (func (export "thunklatin16") + (canon.lift (func (param string)) string=latin1+utf16 (into $i) (func $i "")) + ) +) diff --git a/tests/misc_testsuite/component-model/instance.wast b/tests/misc_testsuite/component-model/instance.wast new file mode 100644 index 000000000000..d10c1ee9b51e --- /dev/null +++ b/tests/misc_testsuite/component-model/instance.wast @@ -0,0 +1,121 @@ +(component + (module $m) + (instance (instantiate (module $m))) +) + +(component + (module $m + (func (export "")) + ) + (instance $i (instantiate (module $m))) + + (module $m2 + (func (import "" "")) + ) + (instance (instantiate (module $m2) (with "" (instance $i)))) +) + +(component + (module $m + (func (export "a")) + ) + (instance $i (instantiate (module $m))) + + (module $m2 + (func (import "" "b")) + ) + (instance (instantiate (module $m2) + (with "" (instance (export "b" (func $i "a")))) + )) +) + +;; all kinds of imports for core wasm modules, and register a start function on +;; one module to ensure that everything is correct +(component + (module $m + (func (export "a")) + (table (export "b") 1 funcref) + (memory (export "c") 1) + (global (export "d") i32 i32.const 1) + ) + (instance $i (instantiate (module $m))) + + (module $m2 + (import "" "a" (func $f)) + (import "" "b" (table 1 funcref)) + (import "" "c" (memory 1)) + (import "" "d" (global $g i32)) + + (func $start + global.get $g + i32.const 1 + i32.ne + if + unreachable + end + + call $f + ) + + (start $start) + + (data (i32.const 0) "hello") + (elem (i32.const 0) $start) + ) + (instance (instantiate (module $m2) + (with "" (instance $i)) + )) +) + +;; double-check the start function runs by ensuring that a trap shows up and it +;; sees the wrong value for the global import +(assert_trap + (component + (module $m + (global (export "g") i32 i32.const 1) + ) + (instance $i (instantiate (module $m))) + + (module $m2 + (import "" "g" (global $g i32)) + + (func $start + global.get $g + i32.const 0 + i32.ne + if + unreachable + end + ) + + (start $start) + ) + (instance (instantiate (module $m2) (with "" (instance $i)))) + ) + "unreachable") + +;; shuffle around imports to get to what the target core wasm module needs +(component + (module $m + (func (export "1")) + (table (export "2") 1 funcref) + (memory (export "3") 1) + (global (export "4") i32 i32.const 1) + ) + (instance $i (instantiate (module $m))) + + (module $m2 + (import "" "a" (func $f)) + (import "" "b" (table 1 funcref)) + (import "" "c" (memory 1)) + (import "" "d" (global $g i32)) + ) + (instance (instantiate (module $m2) + (with "" (instance + (export "a" (func $i "1")) + (export "b" (table $i "2")) + (export "c" (memory $i "3")) + (export "d" (global $i "4")) + )) + )) +) diff --git a/tests/misc_testsuite/component-model/simple.wast b/tests/misc_testsuite/component-model/simple.wast new file mode 100644 index 000000000000..00c537916368 --- /dev/null +++ b/tests/misc_testsuite/component-model/simple.wast @@ -0,0 +1,22 @@ +(component) + +(component + (module) +) + +(component + (module) + (module) + (module) +) + +(component + (module + (func (export "a") (result i32) i32.const 0) + (func (export "b") (result i64) i64.const 0) + ) + (module + (func (export "c") (result f32) f32.const 0) + (func (export "d") (result f64) f64.const 0) + ) +) diff --git a/tests/misc_testsuite/component-model/types.wast b/tests/misc_testsuite/component-model/types.wast new file mode 100644 index 000000000000..9a20b1d75117 --- /dev/null +++ b/tests/misc_testsuite/component-model/types.wast @@ -0,0 +1,87 @@ +(component + (type string) + (type (func (param string))) + (type $r (record (field "x" unit) (field "y" string))) + (type $u (union $r string)) + (type $e (expected $u u32)) + + (type (func (param $e) (result (option $r)))) + + (type (variant + (case "a" string) + (case "b" u32) + (case "c" float32) + (case "d" float64) + )) + + (type $errno (enum "a" "b" "e")) + (type (list $errno)) + (type $oflags (flags "read" "write" "exclusive")) + (type (tuple $oflags $errno $r)) + + ;; primitives in functions + (type (func + (param bool) + (param u8) + (param s8) + (param u16) + (param s16) + (param u32) + (param s32) + (param u64) + (param s64) + (param char) + (param string) + )) + + ;; primitives in types + (type bool) + (type u8) + (type s8) + (type u16) + (type s16) + (type u32) + (type s32) + (type u64) + (type s64) + (type char) + (type string) +) + +(component + (type $empty (func)) + (type (func (param string) (result u32))) + (type (component)) + (type (module)) + (type (instance)) + + (type (component + (import "" (func (type $empty))) + (import "y" (func)) + (import "z" (component)) + + (type $t (instance)) + + (export "a" (module)) + (export "b" (instance (type $t))) + )) + + (type (instance + (export "" (func (type $empty))) + (export "y" (func)) + (export "z" (component)) + + (type $t (instance)) + + (export "a" (module)) + (export "b" (instance (type $t))) + )) + + (type (module + (import "" "" (func (param i32))) + (import "" "1" (func (result i32))) + (export "1" (global i32)) + (export "2" (memory 1)) + (export "3" (table 1 funcref)) + )) +)