From b25152cac8c2b3cbf0800d4c41278d85f967a38d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 24 Feb 2022 09:26:36 -0800 Subject: [PATCH 1/2] Initial skeleton of some component model processing This commit is the first of what will likely be many to implement the component model proposal in Wasmtime. This will be structured as a series of incremental commits, most of which haven't been written yet. My hope is to make this incremental and over time to make this easier to review and easier to test each step in isolation. Here much of the skeleton of how components are going to work in Wasmtime is sketched out. This is not a complete implementation of the component model so it's not all that useful yet, but some things you can do are: * Process the type section into a representation amenable for working with in Wasmtime. * Process the module section and register core wasm modules. * Process the instance section for core wasm modules. * Process core wasm module imports. * Process core wasm instance aliasing. * Ability to compile a component with core wasm embedded. * Ability to instantiate a component with no imports. * Ability to get functions from this component. This is already starting to diverge from the previous module linking representation where a `Component` will try to avoid unnecessary metadata about the component and instead internally only have the bare minimum necessary to instantiate the module. My hope is we can avoid constructing most of the index spaces during instantiation only for it to all ge thrown away. Additionally I'm predicting that we'll need to see through processing where possible to know how to generate adapters and where they are fused. At this time you can't actually call a component's functions, and that's the next PR that I would like to make. --- .github/workflows/main.yml | 2 + Cargo.toml | 1 + crates/cranelift/src/compiler.rs | 8 +- crates/cranelift/src/func_environ.rs | 8 +- crates/cranelift/src/lib.rs | 4 +- crates/environ/Cargo.toml | 3 + crates/environ/src/compilation.rs | 8 +- crates/environ/src/component.rs | 34 + crates/environ/src/component/info.rs | 174 +++++ crates/environ/src/component/translate.rs | 709 ++++++++++++++++++ crates/environ/src/component/types.rs | 752 ++++++++++++++++++++ crates/environ/src/lib.rs | 5 + crates/environ/src/module.rs | 28 +- crates/environ/src/module_environ.rs | 130 ++-- crates/environ/src/module_types.rs | 78 ++ crates/types/src/lib.rs | 24 + crates/wasmtime/Cargo.toml | 5 + crates/wasmtime/src/component/component.rs | 122 ++++ crates/wasmtime/src/component/func.rs | 83 +++ crates/wasmtime/src/component/instance.rs | 173 +++++ crates/wasmtime/src/component/mod.rs | 14 + crates/wasmtime/src/component/store.rs | 28 + crates/wasmtime/src/config.rs | 14 + crates/wasmtime/src/func.rs | 18 +- crates/wasmtime/src/func/typed.rs | 42 +- crates/wasmtime/src/instance.rs | 72 +- crates/wasmtime/src/lib.rs | 3 + crates/wasmtime/src/module.rs | 87 ++- crates/wasmtime/src/module/serialization.rs | 12 +- crates/wasmtime/src/signatures.rs | 6 +- crates/wasmtime/src/store/data.rs | 4 + crates/wasmtime/src/types.rs | 12 +- crates/wasmtime/src/types/matching.rs | 4 +- 33 files changed, 2494 insertions(+), 173 deletions(-) create mode 100644 crates/environ/src/component.rs create mode 100644 crates/environ/src/component/info.rs create mode 100644 crates/environ/src/component/translate.rs create mode 100644 crates/environ/src/component/types.rs create mode 100644 crates/environ/src/module_types.rs create mode 100644 crates/wasmtime/src/component/component.rs create mode 100644 crates/wasmtime/src/component/func.rs create mode 100644 crates/wasmtime/src/component/instance.rs create mode 100644 crates/wasmtime/src/component/mod.rs create mode 100644 crates/wasmtime/src/component/store.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 663fac5f2dd5..187688618143 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 diff --git a/Cargo.toml b/Cargo.toml index 5888bde21a51..d81e6076c15c 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"] # Stub feature that does nothing, for Cargo-features compatibility: the new # backend is the default now. 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..5dd153ec0b51 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, + DataKind, ElementItem, ElementKind, Encoding, ExternalKind, FuncValidator, FunctionBody, NameSectionReader, Naming, Operator, Parser, Payload, TypeDef, TypeRef, Validator, - ValidatorResources, WasmFeatures, + 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 @@ -647,7 +640,7 @@ 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); } } @@ -751,16 +744,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, } From 0af5ff647198b8848b245ed1faa8092b15b4a34c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 18 May 2022 08:42:37 -0700 Subject: [PATCH 2/2] Add tests for the component model support This commit uses the recently updated wasm-tools crates to add tests for the component model added in the previous commit. This involved updating the `wasmtime-wast` crate for component-model changes. Currently the component support there is quite primitive, but enough to at least instantiate components and verify the internals of Wasmtime are all working correctly. Additionally some simple tests for the embedding API have also been added. --- .github/workflows/main.yml | 4 + Cargo.toml | 2 +- build.rs | 3 + crates/cli-flags/Cargo.toml | 1 + crates/cli-flags/src/lib.rs | 12 ++ crates/environ/src/module_environ.rs | 12 +- crates/wast/Cargo.toml | 3 + crates/wast/src/wast.rs | 96 ++++++++------ tests/all/component_model.rs | 68 ++++++++++ tests/all/main.rs | 2 + tests/all/wast.rs | 3 + .../component-model/adapter.wast | 54 ++++++++ .../component-model/instance.wast | 121 ++++++++++++++++++ .../component-model/simple.wast | 22 ++++ .../misc_testsuite/component-model/types.wast | 87 +++++++++++++ 15 files changed, 447 insertions(+), 43 deletions(-) create mode 100644 tests/all/component_model.rs create mode 100644 tests/misc_testsuite/component-model/adapter.wast create mode 100644 tests/misc_testsuite/component-model/instance.wast create mode 100644 tests/misc_testsuite/component-model/simple.wast create mode 100644 tests/misc_testsuite/component-model/types.wast diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 187688618143..6bd631c9907c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -299,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 d81e6076c15c..8518ac5da6bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +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"] +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/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index 5dd153ec0b51..0df931c3be6b 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -14,9 +14,9 @@ use std::convert::{TryFrom, TryInto}; use std::path::PathBuf; use std::sync::Arc; use wasmparser::{ - DataKind, ElementItem, ElementKind, Encoding, ExternalKind, FuncValidator, FunctionBody, - NameSectionReader, Naming, Operator, Parser, Payload, TypeDef, TypeRef, Validator, - ValidatorResources, + CustomSectionReader, DataKind, ElementItem, ElementKind, Encoding, ExternalKind, FuncValidator, + FunctionBody, NameSectionReader, Naming, Operator, Parser, Payload, TypeDef, TypeRef, + Validator, ValidatorResources, }; /// Object containing the standalone environment information. @@ -632,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,7 +647,8 @@ and for re-adding support for interface types you can see this issue: 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; } @@ -658,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 { 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)) + )) +)