From 3d285bd2e2551b7fdd60934d2bef9ba3679ba93a Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 22 Sep 2022 13:58:08 -0700 Subject: [PATCH] wasm-compose: implement a new `CompositionGraph` API. This commit implements a new `CompositionGraph` API that allows for building a composition with more flexibility than what the `ComponentComposer` API supports. The `CompositionGraph` is geared towards live-editing of a composition graph. This commit also reimplements `ComponentComposer` using the composition graph. The previous instance graph and the encoding implementation for it have been removed. --- crates/wasm-compose/CONFIG.md | 6 +- crates/wasm-compose/Cargo.toml | 1 + crates/wasm-compose/example/server/config.yml | 2 +- crates/wasm-compose/src/composer.rs | 936 ++++-------- crates/wasm-compose/src/config.rs | 18 +- crates/wasm-compose/src/encoding.rs | 1163 +++++++++----- crates/wasm-compose/src/graph.rs | 1343 +++++++++++++++++ crates/wasm-compose/src/lib.rs | 1 + .../compositions/complex-import/composed.wat | 156 +- .../compositions/complex-import/config.yml | 9 +- .../tests/compositions/complex/composed.wat | 446 +++--- .../component-not-valid/error.txt | 2 +- .../compositions/conflict-on-import/error.txt | 2 +- .../compositions/export-on-import/config.yml | 2 +- .../tests/compositions/import-conflict/a.wat | 1 - .../compositions/import-conflict/config.yml | 4 - .../compositions/import-conflict/error.txt | 1 - .../compositions/import-conflict/root.wat | 4 - .../incompatible-explicit-export/config.yml | 2 +- .../instantiation-cycle/config.yml | 2 +- .../instantiation-cycle/error.txt | 2 +- .../invalid-instantiation-arg/config.yml | 2 +- .../compositions/merged-import/composed.wat | 26 +- .../missing-explicit-dep/config.yml | 2 +- .../missing-explicit-export/config.yml | 2 +- .../tests/compositions/module/error.txt | 5 +- crates/wasmparser/src/validator/component.rs | 2 +- 27 files changed, 2733 insertions(+), 1409 deletions(-) create mode 100644 crates/wasm-compose/src/graph.rs delete mode 100644 crates/wasm-compose/tests/compositions/import-conflict/a.wat delete mode 100644 crates/wasm-compose/tests/compositions/import-conflict/config.yml delete mode 100644 crates/wasm-compose/tests/compositions/import-conflict/error.txt delete mode 100644 crates/wasm-compose/tests/compositions/import-conflict/root.wat diff --git a/crates/wasm-compose/CONFIG.md b/crates/wasm-compose/CONFIG.md index 475cdb8ae2..c43f71f9d5 100644 --- a/crates/wasm-compose/CONFIG.md +++ b/crates/wasm-compose/CONFIG.md @@ -72,7 +72,7 @@ An instantiation has the following fields: arguments; argument names match the names of the imports of the dependency being instantiated. -Note that the instantiation name `$component` is special and signifies how the input +Note that the instantiation name `$input` is special and signifies how the input component is to be instantiated. ### Instantiation arguments @@ -97,7 +97,7 @@ A slightly complex example of configuring instantiations: ```yaml instantiations: - $component: + $input: arguments: a: b b: @@ -109,7 +109,7 @@ instantiations: dependency: f ``` -In the above example, the `$component` instantiation (i.e. the root instantiation) has explicitly +In the above example, the `$input` instantiation (i.e. the root instantiation) has explicitly specified that the argument named `a` is to be provided instance `b`. It also defines an instantiation named `b` which is to be passed an instance export named `e` diff --git a/crates/wasm-compose/Cargo.toml b/crates/wasm-compose/Cargo.toml index 7a361355c4..9df681b197 100644 --- a/crates/wasm-compose/Cargo.toml +++ b/crates/wasm-compose/Cargo.toml @@ -21,6 +21,7 @@ petgraph = "0.6.2" log = { workspace = true } serde_yaml = "0.8.26" clap = { workspace = true, optional = true } +smallvec = "1.10.0" [features] default = [] diff --git a/crates/wasm-compose/example/server/config.yml b/crates/wasm-compose/example/server/config.yml index 22a80cb67a..e00301175c 100644 --- a/crates/wasm-compose/example/server/config.yml +++ b/crates/wasm-compose/example/server/config.yml @@ -2,6 +2,6 @@ search-paths: - ../service/target/wasm32-unknown-unknown/release instantiations: - $component: + $input: arguments: backend: svc diff --git a/crates/wasm-compose/src/composer.rs b/crates/wasm-compose/src/composer.rs index 8108e05fcd..4ef93d478f 100644 --- a/crates/wasm-compose/src/composer.rs +++ b/crates/wasm-compose/src/composer.rs @@ -2,525 +2,100 @@ use crate::{ config::Config, - encoding::{InstantiationGraphEncoder, TypeEncoder}, -}; -use anyhow::{anyhow, bail, Context, Result}; -use indexmap::{IndexMap, IndexSet}; -use petgraph::{algo::toposort, dot::Dot, graph::NodeIndex, visit::EdgeRef, EdgeDirection, Graph}; -use std::{ - collections::VecDeque, - path::{Path, PathBuf}, + encoding::CompositionGraphEncoder, + graph::{ + Component, ComponentId, CompositionGraph, EncodeOptions, ExportIndex, ImportIndex, + InstanceId, + }, }; -use wasm_encoder::ComponentExportKind; +use anyhow::{anyhow, bail, Result}; +use indexmap::IndexMap; +use std::{collections::VecDeque, path::Path}; use wasmparser::{ - types::{ComponentEntityType, ComponentInstanceType, Types, TypesRef}, - Chunk, ComponentExport, ComponentExternalKind, ComponentImport, ComponentTypeRef, Encoding, - Parser, Payload, ValidPayload, Validator, WasmFeatures, + types::{ComponentInstanceType, TypesRef}, + ComponentExternalKind, ComponentTypeRef, }; /// The root component name used in configuration. -pub const ROOT_COMPONENT_NAME: &str = "$component"; - -pub(crate) struct Component { - /// The index of the component in the instantiation graph. - index: ComponentIndex, - /// The path to the component file. - path: PathBuf, - /// The raw bytes of the component. - bytes: Vec, - /// The type information of the component. - types: Types, - /// The name to use to import this component in the composed component. - /// If this is `None`, the component will be defined in the composed component. - import_name: Option, - /// The import map of the component. - imports: IndexMap, - /// The export map of the component. - exports: IndexMap, -} - -impl Component { - fn new( - index: ComponentIndex, - path: impl Into, - import_name: Option, - ) -> Result { - let path = path.into(); - log::info!("parsing WebAssembly component `{}`", path.display()); - - let bytes = wat::parse_file(&path).with_context(|| { - format!("failed to parse component `{path}`", path = path.display()) - })?; - - let mut parser = Parser::new(0); - let mut parsers = Vec::new(); - let mut validator = Validator::new_with_features(WasmFeatures { - component_model: true, - ..Default::default() - }); - let mut imports = IndexMap::new(); - let mut exports = IndexMap::new(); - - let mut cur = bytes.as_slice(); - loop { - match parser.parse(cur, true).with_context(|| { - format!("failed to parse component `{path}`", path = path.display()) - })? { - Chunk::Parsed { payload, consumed } => { - cur = &cur[consumed..]; - - match validator.payload(&payload).with_context(|| { - format!( - "failed to validate WebAssembly component `{}`", - path.display() - ) - })? { - ValidPayload::Ok => { - // Don't parse any sub-components or sub-modules - if !parsers.is_empty() { - continue; - } - - match payload { - Payload::Version { encoding, .. } => { - if encoding != Encoding::Component { - bail!( - "file `{path}` is not a WebAssembly component", - path = path.display() - ); - } - } - Payload::ComponentImportSection(s) => { - for import in s { - let import = import?; - imports.insert(import.name.to_string(), import.ty); - } - } - Payload::ComponentExportSection(s) => { - for export in s { - let export = export?; - exports.insert( - export.name.to_string(), - (export.kind, export.index), - ); - } - } - _ => {} - } - } - ValidPayload::Func(_, _) => {} - ValidPayload::Parser(next) => { - parsers.push(parser); - parser = next; - } - ValidPayload::End(types) => match parsers.pop() { - Some(parent) => parser = parent, - None => { - let component = Component { - index, - path, - bytes, - types, - import_name, - imports, - exports, - }; - log::debug!( - "WebAssembly component `{path}` parsed:\n{component:#?}", - path = component.path.display() - ); - return Ok(component); - } - }, - } - } - Chunk::NeedMoreData(_) => unreachable!(), - } - } - } +pub const ROOT_COMPONENT_NAME: &str = "$input"; - pub(crate) fn index(&self) -> ComponentIndex { - self.index - } - - pub(crate) fn path(&self) -> &Path { - &self.path - } - - pub(crate) fn bytes(&self) -> &[u8] { - &self.bytes - } - - pub(crate) fn types(&self) -> TypesRef { - self.types.as_ref() - } - - pub(crate) fn import_name(&self) -> Option<&str> { - self.import_name.as_deref() - } - - pub(crate) fn ty(&self) -> wasm_encoder::ComponentType { - let encoder = TypeEncoder::new(self.types.as_ref()); - - encoder.component( - self.imports.iter().map(|(name, ty)| { - ( - name.as_str(), - self.types - .component_entity_type_from_import(&ComponentImport { - name: name.as_str(), - ty: *ty, - }) - .unwrap(), - ) - }), - self.exports.iter().map(|(name, (kind, index))| { - ( - name.as_str(), - self.types - .component_entity_type_from_export(&ComponentExport { - name: name.as_str(), - kind: *kind, - index: *index, - }) - .unwrap(), - ) - }), - ) - } - - /// Gets an export from the component for the given export index. - pub(crate) fn export(&self, index: ExportIndex) -> (&str, ComponentExternalKind, u32) { - let (name, (kind, index)) = self - .exports - .get_index(index.0) - .expect("invalid export index"); - (name.as_str(), *kind, *index) - } - - /// Gets an iterator over the component's exports. - pub(crate) fn exports(&self) -> impl Iterator { - self.exports - .iter() - .map(|(name, (kind, index))| (name.as_str(), *kind, *index)) - } - - /// Gets an exported instance index and type with the given export name. - fn export_instance(&self, name: &str) -> Option<(ExportIndex, &ComponentInstanceType)> { - self.exports - .get_full(name) - .and_then(|(i, _, (kind, index))| match kind { - ComponentExternalKind::Instance => Some(( - ExportIndex(i), - self.types.component_instance_at(*index).unwrap(), - )), - _ => None, - }) - } - - /// Finds a compatible instance export on the component for the given instance type. - fn find_compatible_export( - &self, - ty: &ComponentInstanceType, - types: TypesRef, - ) -> Option { - self.exports - .iter() - .position(|(_, (kind, index))| { - if *kind != ComponentExternalKind::Instance { - return false; - } - ComponentInstanceType::is_subtype_of( - self.types.component_instance_at(*index).unwrap(), - self.types.as_ref(), - ty, - types, - ) - }) - .map(ExportIndex) - } - - /// Checks to see if an instance of this component would be a - /// subtype of the given instance type. - fn is_subtype_of(&self, ty: &ComponentInstanceType, types: TypesRef) -> bool { - let exports = ty.exports(types); - - // This checks if this import's instance type is a subtype of the given instance type. - for (k, b) in exports { - match self.exports.get(k.as_str()) { - Some((ak, ai)) => { - let a = self - .types - .component_entity_type_from_export(&ComponentExport { - name: k.as_str(), - kind: *ak, - index: *ai, - }) - .unwrap(); - if !ComponentEntityType::is_subtype_of(&a, self.types.as_ref(), b, types) { - return false; - } - } - None => return false, - } - } - - true - } -} - -impl std::fmt::Debug for Component { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("Component") - .field("path", &self.path) - .field("imports", &self.imports) - .field("exports", &self.exports) - .finish_non_exhaustive() - } -} - -/// Represents an index into an instantiation graph's `components` collection. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub(crate) struct ComponentIndex(usize); - -/// An instance index into an instantiation graph. -pub(crate) type InstanceIndex = NodeIndex; - -/// Represents an index into a component's imports collection. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub(crate) struct ImportIndex(usize); - -/// Represents an index into a component's exports collection. +/// A reference to an instance import on a component. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub(crate) struct ExportIndex(usize); - -/// A reference to an import on a component -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub(crate) struct ImportRef { - /// The index of the component with the import. - pub(crate) component: ComponentIndex, +pub(crate) struct InstanceImportRef { + /// The id of the component with the instance import. + pub(crate) component: ComponentId, /// The index of the import on the component. pub(crate) import: ImportIndex, } -/// An instance (node) in the instantiation graph. -#[derive(Debug)] -enum Instance { - /// The instance will be imported in the composed component. - /// - /// The value is the set of components that will import the instance. - Import(IndexSet), - /// The instance will be from an instantiation in the composed component. - Instantiation { - /// The index of the component being instantiated. - component: ComponentIndex, - }, -} - -/// An instantiation argument (edge) in the instantiation graph. -#[derive(Debug, Copy, Clone)] -struct InstantiationArg { - /// The import index on the component being instantiated. - import: ImportIndex, - /// The export index of the instantiation argument. - /// - /// A value of `None` indicates that the instance is itself the argument to use. - export: Option, -} - -pub(crate) struct InstantiationGraph { - /// The parsed components in the graph. - components: IndexMap, - /// The actual instantiation graph. - /// - /// Each node is an instance and each edge is an argument to the instance. - instances: Graph, - /// Map from instantiation name to instance index. - names: IndexMap, - /// True if at least one dependency was found and instantiated. - /// - /// This is used to determine if no dependencies were found. - instantiated: bool, -} - -impl InstantiationGraph { - /// Gets the component index for the given instance. - /// - /// Returns `None` for imported instances. - pub(crate) fn component(&self, instance: InstanceIndex) -> Option<&Component> { - match &self.instances[instance] { - Instance::Import(_) => None, - Instance::Instantiation { component } => Some(&self.components[component.0]), - } - } - - /// Gets the set of import references for an imported instance. - /// - /// Returns `None` for instantiated instances. - pub(crate) fn import_refs(&self, instance: InstanceIndex) -> Option<&IndexSet> { - match &self.instances[instance] { - Instance::Import(imports) => Some(imports), - Instance::Instantiation { .. } => None, - } - } - - /// Gets the name of the given instance index. - pub(crate) fn instance_name(&self, instance: InstanceIndex) -> &str { - self.names - .get_index(instance.index()) - .map(|(n, _)| n.as_str()) - .expect("invalid instance index") - } - - /// Gets the topological instantiation order based on the instantiation graph - pub(crate) fn instantiation_order(&self) -> Result> { - toposort(&self.instances, None).map_err(|e| { - anyhow!( - "instantiation `{name}` and its dependencies form a cycle in the instantiation graph", - name = self.names.get_index(e.node_id().index()).unwrap().0 - ) - }) - } - - /// Gets the instantiation arguments for the given instance index. - /// - /// The given function is used to resolve an encoded instance index - /// for a given graph instance index. - pub(crate) fn instantiation_args( - &self, - instance: InstanceIndex, - mut index_of_instance: T, - ) -> Vec<(&str, ComponentExportKind, u32)> - where - T: FnMut(InstanceIndex, Option) -> u32, - { - match self.instances[instance] { - Instance::Import { .. } => Vec::new(), - Instance::Instantiation { component } => { - let imports = &self.components[component.0].imports; - - self.instances - .edges_directed(instance, EdgeDirection::Incoming) - .map(|e| { - let arg = e.weight(); - ( - imports.get_index(arg.import.0).unwrap().0.as_str(), - ComponentExportKind::Instance, - index_of_instance(e.source(), arg.export), - ) - }) - .collect() - } - } - } - - /// Resolves an import reference to its originating component, import name, and instance type. - pub(crate) fn resolve_import( - &self, - r: ImportRef, - ) -> (&Component, &str, &ComponentInstanceType) { - let component = &self.components[r.component.0]; - let (name, ty) = component.imports.get_index(r.import.0).unwrap(); - match ty { - ComponentTypeRef::Instance(index) => ( - component, - name.as_str(), - component - .types - .type_at(*index, false) - .unwrap() - .as_component_instance_type() - .unwrap(), - ), - _ => unreachable!("should not have an import ref to a non-instance import"), - } - } -} - -/// An instance dependency to process in the instantiation graph. +/// An instance dependency to process in the composer. struct Dependency { - /// The index of the dependent instance. - dependent: InstanceIndex, - /// The import reference on the dependent instance. - import: ImportRef, + /// The index into `instances` for the dependent instance. + dependent: usize, + /// The instance import reference on the dependent instance. + import: InstanceImportRef, /// The name of the instance from the instantiation argument. instance: String, /// The name of the export on the instance to use as the instantiation argument. export: Option, } -struct InstantiationGraphBuilder<'a> { +/// A composition graph builder that wires up instances from components +/// resolved from the file system. +struct CompositionGraphBuilder<'a> { /// The associated composition configuration. config: &'a Config, /// The graph being built. - graph: InstantiationGraph, + graph: CompositionGraph<'a>, + /// A map from instance name to graph instance id. + instances: IndexMap, } -impl<'a> InstantiationGraphBuilder<'a> { - fn new(component: &Path, config: &'a Config) -> Result { - // The root component is always first in the map - let mut components = IndexMap::new(); - components.insert( - ROOT_COMPONENT_NAME.to_string(), - Component::new(ComponentIndex(0), component, None)?, - ); +impl<'a> CompositionGraphBuilder<'a> { + fn new(root_path: &Path, config: &'a Config) -> Result { + let mut graph = CompositionGraph::new(); + graph.add_component(Component::from_file(ROOT_COMPONENT_NAME, root_path)?)?; Ok(Self { config, - graph: InstantiationGraph { - components, - instances: Default::default(), - names: Default::default(), - instantiated: false, - }, + graph, + instances: Default::default(), }) } /// Adds a component of the given name to the graph. /// - /// If a component with the given name already exists, its index is returned. - fn add_component(&mut self, name: &str) -> Result> { - if let Some(index) = self.graph.components.get_index_of(name) { - return Ok(Some(ComponentIndex(index))); + /// If a component with the given name already exists, its id is returned. + /// Returns `Ok(None)` if a matching component cannot be found. + fn add_component(&mut self, name: &str) -> Result> { + if let Some((id, _)) = self.graph.get_component_by_name(name) { + return Ok(Some(id)); } - match self.find_component(ComponentIndex(self.graph.components.len()), name)? { - Some(component) => { - let index = component.index; - assert_eq!(index.0, self.graph.components.len()); - self.graph.components.insert(name.to_string(), component); - log::debug!( - "adding component `{name}` (component index {index})", - index = index.0 - ); - Ok(Some(index)) - } + match self.find_component(name)? { + Some(component) => Ok(Some(self.graph.add_component(component)?)), None => Ok(None), } } /// Finds the component with the given name on disk. - /// - /// The given index is the index at which the component would be - /// inserted into the graph. - fn find_component(&self, index: ComponentIndex, name: &str) -> Result> { + fn find_component(&self, name: &str) -> Result>> { // Check the config for an explicit path (must be a valid component) if let Some(dep) = self.config.dependencies.get(name) { log::debug!( "component with name `{name}` has an explicit path of `{path}`", path = dep.path.display() ); - return Ok(Some(Component::new( - index, + return Ok(Some(Component::from_file( + name, self.config.dir.join(&dep.path), - dep.import.clone(), )?)); } // Otherwise, search the paths for a valid component with the same name log::info!("searching for a component with name `{name}`"); for dir in std::iter::once(&self.config.dir).chain(self.config.search_paths.iter()) { - if let Some(component) = Self::parse_component(index, dir, name)? { + if let Some(component) = Self::parse_component(dir, name)? { return Ok(Some(component)); } } @@ -531,7 +106,7 @@ impl<'a> InstantiationGraphBuilder<'a> { /// Parses a component from the given directory, if it exists. /// /// Returns `Ok(None)` if the component does not exist. - fn parse_component(index: ComponentIndex, dir: &Path, name: &str) -> Result> { + fn parse_component(dir: &Path, name: &str) -> Result>> { let mut path = dir.join(name); for ext in ["wasm", "wat"] { @@ -541,7 +116,7 @@ impl<'a> InstantiationGraphBuilder<'a> { continue; } - return Ok(Some(Component::new(index, &path, None)?)); + return Ok(Some(Component::from_file(name, &path)?)); } Ok(None) @@ -549,48 +124,31 @@ impl<'a> InstantiationGraphBuilder<'a> { /// Instantiates an instance with the given name into the graph. /// - /// `import` is expected to be `None` only for the root instantiation. - fn instantiate( - &mut self, - name: &str, - component_name: &str, - import: Option, - ) -> Result<(InstanceIndex, bool)> { - if let Some(index) = self.graph.names.get(name) { - if let Instance::Import(refs) = &mut self.graph.instances[*index] { - refs.insert(import.unwrap()); - } - return Ok((*index, true)); + /// Returns an index into `dependencies` for the instance being instantiated. + /// + /// Returns `Ok(None)` if a component to instantiate cannot be found. + fn instantiate(&mut self, name: &str, component_name: &str) -> Result> { + if let Some(index) = self.instances.get_index_of(name) { + return Ok(Some((index, true))); } - let instance = match self.add_component(component_name)? { - Some(component) => { - // If a dependency component was instantiated, mark it in the graph - if component.0 != 0 { - self.graph.instantiated = true; - } - Instance::Instantiation { component } + match self.add_component(component_name)? { + Some(component_id) => { + let (index, prev) = self + .instances + .insert_full(name.to_string(), self.graph.instantiate(component_id)?); + assert!(prev.is_none()); + Ok(Some((index, false))) } None => { if self.config.disallow_imports { - bail!( - "a dependency named `{component_name}` could not be found and instance imports are not allowed", - ); + bail!("a dependency named `{component_name}` could not be found and instance imports are not allowed"); } log::warn!("instance `{name}` will be imported because a dependency named `{component_name}` could not be found"); - Instance::Import([import.unwrap()].into()) + Ok(None) } - }; - - let index = self.graph.instances.add_node(instance); - log::debug!( - "adding instance `{name}` to the graph (instance index {index})", - index = index.index() - ); - assert_eq!(index.index(), self.graph.names.len()); - self.graph.names.insert(name.to_string(), index); - Ok((index, false)) + } } /// Finds a compatible instance for the given instance type. @@ -600,51 +158,41 @@ impl<'a> InstantiationGraphBuilder<'a> { /// Returns `Err(_)` if no compatible instance was found. fn find_compatible_instance( &self, - instance: InstanceIndex, - dependent: InstanceIndex, + instance: usize, + dependent: usize, arg_name: &str, ty: &ComponentInstanceType, types: TypesRef, ) -> Result> { - match self.graph.component(instance) { - Some(component) => { - let instance_name = self.graph.instance_name(instance); - let dependent_name = self.graph.instance_name(dependent); - - // Check if the instance or one of its exports is compatible with the expected import type - if component.is_subtype_of(ty, types) { - // The instance itself can be used - log::debug!( - "instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`", - ); - return Ok(None); - } + let (instance_name, instance_id) = self.instances.get_index(instance).unwrap(); + let (_, component) = self.graph.get_component_of_instance(*instance_id).unwrap(); - log::debug!( - "searching for compatible export from instance `{instance_name}` for argument `{arg_name}` of instance `{dependent_name}`", - ); - - let export = component.find_compatible_export(ty, types) .ok_or_else(|| { - anyhow!( - "component `{path}` is not compatible with import `{arg_name}` of component `{dependent_path}`", - path = component.path.display(), - dependent_path = self.graph.component(dependent).unwrap().path.display(), - ) - })?; - - log::debug!( - "export `{export_name}` (export index {export}) from instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`", - export = export.0, - export_name = component.exports.get_index(export.0).unwrap().0, - ); - - Ok(Some(export)) - } - None => { - // An imported instance should be directly compatible - Ok(None) - } + let (dependent_name, dependent_instance_id) = self.instances.get_index(dependent).unwrap(); + + // Check if the instance or one of its exports is compatible with the expected import type + if component.is_instance_subtype_of(ty, types) { + // The instance itself can be used + log::debug!("instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`"); + return Ok(None); } + + log::debug!("searching for compatible export from instance `{instance_name}` for argument `{arg_name}` of instance `{dependent_name}`"); + + let export = component.find_compatible_export(ty, types).ok_or_else(|| { + anyhow!( + "component `{path}` is not compatible with import `{arg_name}` of component `{dependent_path}`", + path = component.path().unwrap().display(), + dependent_path = self.graph.get_component_of_instance(*dependent_instance_id).unwrap().1.path().unwrap().display(), + ) + })?; + + log::debug!( + "export `{export_name}` (export index {export}) from instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`", + export = export.0, + export_name = component.exports.get_index(export.0).unwrap().0, + ); + + Ok(Some(export)) } /// Resolves an explicitly specified export to its index. @@ -653,178 +201,197 @@ impl<'a> InstantiationGraphBuilder<'a> { fn resolve_export_index( &self, export: &str, - instance: InstanceIndex, - dependent: InstanceIndex, + instance: usize, + dependent_path: &Path, arg_name: &str, ty: &ComponentInstanceType, types: TypesRef, ) -> Result { - let instance_name = self.graph.instance_name(instance); - - match self.graph.component(instance) { - Some(component) => match component.export_instance(export) { - Some((index, export_ty)) => { - if !ComponentInstanceType::is_subtype_of( - export_ty, - component.types.as_ref(), - ty, - types, - ) { - bail!("component `{path}` exports an instance named `{export}` but it is not compatible with import `{arg_name}` of component `{dependent_path}`", - path = component.path.display(), - dependent_path = self.graph.component(dependent).unwrap().path.display(), + let (instance_name, instance_id) = self.instances.get_index(instance).unwrap(); + let (_, component) = self.graph.get_component_of_instance(*instance_id).unwrap(); + match component.export_by_name(export) { + Some((export_index, kind, index)) if kind == ComponentExternalKind::Instance => { + let export_ty = component.types.component_instance_at(index).unwrap(); + if !ComponentInstanceType::is_subtype_of(export_ty, component.types(), ty, types) { + bail!("component `{path}` exports an instance named `{export}` but it is not compatible with import `{arg_name}` of component `{dependent_path}`", + path = component.path().unwrap().display(), + dependent_path = dependent_path.display(), ) - } - - Ok(index) } - None => bail!("component `{path}` does not export an instance named `{export}`", - path = component.path.display(), - ), - }, - None => bail!("an explicit export `{export}` cannot be specified for imported instance `{instance_name}`"), + + Ok(export_index) + } + _ => bail!( + "component `{path}` does not export an instance named `{export}`", + path = component.path().unwrap().display(), + ), + } + } + + /// Resolves an import instance reference. + fn resolve_import_ref( + &self, + r: InstanceImportRef, + ) -> (&Component, &str, &ComponentInstanceType) { + let component = self.graph.get_component(r.component).unwrap(); + let (name, ty) = component.import(r.import).unwrap(); + match ty { + ComponentTypeRef::Instance(index) => ( + component, + name, + component + .types + .type_at(index, false) + .unwrap() + .as_component_instance_type() + .unwrap(), + ), + _ => unreachable!("should not have an instance import ref to a non-instance import"), } } /// Processes a dependency in the graph. - fn process_dependency(&mut self, dependency: Dependency) -> Result<(InstanceIndex, bool)> { + fn process_dependency(&mut self, dependency: Dependency) -> Result> { let name = self.config.dependency_name(&dependency.instance); log::info!( - "processing dependency `{name}` from instance `{dependent}` to instance `{instance}`", - dependent = self - .graph - .names - .get_index(dependency.dependent.index()) - .unwrap() - .0, + "processing dependency `{name}` from instance `{dependent_name}` to instance `{instance}`", + dependent_name = self.instances.get_index(dependency.dependent).unwrap().0, instance = dependency.instance ); - let (instance, existing) = - self.instantiate(&dependency.instance, name, Some(dependency.import))?; - - let (dependent, import_name, import_type) = self.graph.resolve_import(dependency.import); - - let export = match &dependency.export { - Some(export) => Some(self.resolve_export_index( - export, - instance, - dependency.dependent, - import_name, - import_type, - dependent.types.as_ref(), - )?), - None => self.find_compatible_instance( - instance, - dependency.dependent, - import_name, - import_type, - dependent.types.as_ref(), - )?, - }; - - // Add the edge from this instance to the dependent instance - self.graph.instances.add_edge( - instance, - dependency.dependent, - InstantiationArg { - import: dependency.import.import, - export, - }, - ); - - Ok((instance, existing)) + match self.instantiate(&dependency.instance, name)? { + Some((instance, existing)) => { + let (dependent, import_name, import_type) = + self.resolve_import_ref(dependency.import); + + let export = match &dependency.export { + Some(export) => Some(self.resolve_export_index( + export, + instance, + dependent.path().unwrap(), + import_name, + import_type, + dependent.types.as_ref(), + )?), + None => self.find_compatible_instance( + instance, + dependency.dependent, + import_name, + import_type, + dependent.types.as_ref(), + )?, + }; + + // Connect the new instance to the dependent + self.graph.connect( + self.instances[instance], + export, + self.instances[dependency.dependent], + dependency.import.import, + )?; + + Ok(Some((instance, existing))) + } + None => { + if let Some(export) = &dependency.export { + bail!("an explicit export `{export}` cannot be specified for imported instance `{name}`"); + } + Ok(None) + } + } } /// Push dependencies of the given instance to the dependency queue. - fn push_dependencies( - &self, - instance: InstanceIndex, - queue: &mut VecDeque>, - ) -> Result<()> { - match self.graph.instances[instance] { - Instance::Import { .. } => { - // Imported instances don't have dependencies + fn push_dependencies(&self, instance: usize, queue: &mut VecDeque) -> Result<()> { + let (instance_name, instance_id) = self.instances.get_index(instance).unwrap(); + let config = self.config.instantiations.get(instance_name); + let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap(); + let count = queue.len(); + + // Push a dependency for every instance import + for (import, name, ty) in component.imports() { + match ty { + ComponentTypeRef::Instance(_) => {} + _ => bail!( + "component `{path}` has a non-instance import named `{name}`", + path = component.path().unwrap().display() + ), } - Instance::Instantiation { component } => { - let instance_name = self.graph.instance_name(instance); - let config = self.config.instantiations.get(instance_name); - let comp = &self.graph.components[component.0]; - let count = queue.len(); - - // Push a dependency for every import - for (import, (name, ty)) in comp.imports.iter().enumerate() { - match ty { - ComponentTypeRef::Instance(_) => {} - _ => bail!( - "component `{path}` has a non-instance import named `{name}`", - path = comp.path.display() - ), - } - - log::debug!("adding dependency for argument `{name}` (import index {import}) from instance `{instance_name}` to the queue"); - - let arg = config.and_then(|c| c.arguments.get(name)); - queue.push_back(Some(Dependency { - dependent: instance, - import: ImportRef { - component, - import: ImportIndex(import), - }, - instance: arg.map(|arg| &arg.instance).unwrap_or(name).clone(), - export: arg.and_then(|arg| arg.export.clone()), - })); - } - // Ensure every explicit argument is a valid import name - if let Some(config) = config { - for arg in config.arguments.keys() { - if !comp.imports.contains_key(arg) { - bail!( - "component `{path}` has no import named `{arg}`", - path = comp.path.display() - ); - } - } - } + log::debug!("adding dependency for argument `{name}` (import index {import}) from instance `{instance_name}` to the queue", import = import.0); + + let arg = config.and_then(|c| c.arguments.get(name)); + queue.push_back(Dependency { + dependent: instance, + import: InstanceImportRef { + component: component_id, + import, + }, + instance: arg + .map(|arg| arg.instance.clone()) + .unwrap_or_else(|| name.to_string()), + export: arg.and_then(|arg| arg.export.clone()), + }); + } - // It is an error if the root component has no instance imports - if count == queue.len() && component.0 == 0 { + // Ensure every explicit argument is a valid import name + if let Some(config) = config { + for arg in config.arguments.keys() { + if !component.imports.contains_key(arg) { bail!( - "component `{path}` does not import any instances", - path = comp.path.display() + "component `{path}` has no import named `{arg}`", + path = component.path().unwrap().display() ); } } } + // It is an error if the root component has no instance imports + if count == queue.len() && instance == 0 { + bail!( + "component `{path}` does not import any instances", + path = component.path().unwrap().display() + ); + } + Ok(()) } /// Build the instantiation graph. - fn build(mut self) -> Result { - let mut queue: VecDeque> = VecDeque::new(); - queue.push_back(None); + fn build(mut self) -> Result<(InstanceId, CompositionGraph<'a>)> { + let mut queue: VecDeque = VecDeque::new(); + + // Instantiate the root and push its dependencies to the queue + let (root_instance, existing) = self + .instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME)? + .unwrap(); + assert!(!existing); + + self.push_dependencies(0, &mut queue)?; + + // Process all remaining dependencies in the queue while let Some(dependency) = queue.pop_front() { - let (instance, existing) = dependency - .map(|dep| self.process_dependency(dep)) - .unwrap_or_else(|| { - self.instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME, None) - })?; - - // Add dependencies only for new instances in the graph - if !existing { - self.push_dependencies(instance, &mut queue)?; + if let Some((instance, existing)) = self.process_dependency(dependency)? { + // Add dependencies only for new instances in the graph + if !existing { + self.push_dependencies(instance, &mut queue)?; + } } } - Ok(self.graph) + Ok((self.instances[root_instance], self.graph)) } } /// Used to compose a WebAssembly component from other components. +/// +/// The component composer resolves the dependencies of a root component +/// from components of matching names in the file system. +/// +/// The exports of the root component are then exported from the composed +/// component. pub struct ComponentComposer<'a> { component: &'a Path, config: &'a Config, @@ -845,22 +412,25 @@ impl<'a> ComponentComposer<'a> { /// ## Returns /// Returns the bytes of the composed component. pub fn compose(&self) -> Result> { - let graph = InstantiationGraphBuilder::new(self.component, self.config)?.build()?; + let (root_instance, graph) = + CompositionGraphBuilder::new(self.component, self.config)?.build()?; - log::debug!( - "components:\n{components:#?}\ninstantiation graph:\n{graph:?}", - components = graph.components, - graph = Dot::new(&graph.instances) - ); - - // If not a single dependency was instantiated, error out - if !graph.instantiated { + // If only the root component was instantiated, then there are no resolved dependencies + if graph.instances.len() == 1 { bail!( "no dependencies of component `{path}` were found", path = self.component.display() ); } - InstantiationGraphEncoder::new(self.config, &graph).encode() + CompositionGraphEncoder::new( + EncodeOptions { + define_components: !self.config.import_components, + export: Some(root_instance), + validate: false, + }, + &graph, + ) + .encode() } } diff --git a/crates/wasm-compose/src/config.rs b/crates/wasm-compose/src/config.rs index 35d54cbdef..547dff9109 100644 --- a/crates/wasm-compose/src/config.rs +++ b/crates/wasm-compose/src/config.rs @@ -15,22 +15,13 @@ use std::{ pub struct Dependency { /// The path to the dependency's component file. pub path: PathBuf, - - /// The name to import the component with. - /// - /// By default, components are defined (embedded) in the composed component. - /// By specifying an import name, the component will be imported instead. - pub import: Option, } impl FromStr for Dependency { type Err = (); fn from_str(s: &str) -> Result { - Ok(Self { - path: s.into(), - import: None, - }) + Ok(Self { path: s.into() }) } } @@ -96,6 +87,13 @@ pub struct Config { #[serde(default)] pub skip_validation: bool, + /// Whether or not to import components in the composed component. + /// + /// By default, components are defined rather than imported in + /// the composed component. + #[serde(default)] + pub import_components: bool, + /// Whether or not to disallow instance imports in the output component. /// /// Enabling this option will cause an error if a dependency cannot be diff --git a/crates/wasm-compose/src/encoding.rs b/crates/wasm-compose/src/encoding.rs index a8256fa614..75fe57b667 100644 --- a/crates/wasm-compose/src/encoding.rs +++ b/crates/wasm-compose/src/encoding.rs @@ -1,30 +1,48 @@ -use crate::{ - composer::{ - Component, ComponentIndex, ExportIndex, ImportRef, InstanceIndex, InstantiationGraph, - }, - config::Config, +use crate::graph::{ + type_desc, CompositionGraph, EncodeOptions, ExportIndex, ImportIndex, InstanceId, }; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use indexmap::{IndexMap, IndexSet}; -use std::collections::{hash_map::Entry, HashMap, HashSet}; +use petgraph::EdgeDirection; +use smallvec::SmallVec; +use std::collections::{hash_map::Entry, HashMap}; use wasm_encoder::*; -use wasmparser::types::{ComponentEntityType, Type, TypeId, TypesRef}; +use wasmparser::{ + types::{ComponentEntityType, Type, TypeId, Types}, + ComponentExternalKind, +}; + +fn type_ref_to_export_kind(ty: wasmparser::ComponentTypeRef) -> ComponentExportKind { + match ty { + wasmparser::ComponentTypeRef::Module(_) => ComponentExportKind::Module, + wasmparser::ComponentTypeRef::Func(_) => ComponentExportKind::Func, + wasmparser::ComponentTypeRef::Value(_) => ComponentExportKind::Value, + wasmparser::ComponentTypeRef::Type(_, _) => ComponentExportKind::Type, + wasmparser::ComponentTypeRef::Instance(_) => ComponentExportKind::Instance, + wasmparser::ComponentTypeRef::Component(_) => ComponentExportKind::Component, + } +} // Utility trait implement on component and instance types // to abstract their encoding. trait Encodable { fn type_count(&self) -> u32; + fn core_type_count(&self) -> u32; fn ty(&mut self) -> ComponentTypeEncoder; fn core_type(&mut self) -> CoreTypeEncoder; } impl Encodable for ComponentType { fn type_count(&self) -> u32 { - Self::type_count(self) + self.type_count() + } + + fn core_type_count(&self) -> u32 { + self.core_type_count() } fn ty(&mut self) -> ComponentTypeEncoder { - Self::ty(self) + self.ty() } fn core_type(&mut self) -> CoreTypeEncoder { @@ -37,6 +55,10 @@ impl Encodable for InstanceType { self.type_count() } + fn core_type_count(&self) -> u32 { + self.core_type_count() + } + fn ty(&mut self) -> ComponentTypeEncoder { self.ty() } @@ -46,47 +68,54 @@ impl Encodable for InstanceType { } } -/// Represents a type key for type maps used in encoding. -/// -/// This implementation prevents encoding of duplicate types from the same -/// validator, but not from different validators. -/// -/// TODO: implement this fully in `wasmparser`? -#[derive(Copy, Clone)] -struct TypeKey<'a> { - types: TypesRef<'a>, - id: TypeId, +impl Encodable for (&mut CoreTypeSection, &mut ComponentTypeSection) { + fn type_count(&self) -> u32 { + self.1.len() + } + + fn core_type_count(&self) -> u32 { + self.0.len() + } + + fn ty(&mut self) -> ComponentTypeEncoder { + self.1.ty() + } + + fn core_type(&mut self) -> CoreTypeEncoder { + self.0.ty() + } } -impl<'a> PartialEq for TypeKey<'a> { +#[derive(Copy, Clone)] +pub struct PtrKey<'a, T>(&'a T); + +impl PartialEq for PtrKey<'_, T> { fn eq(&self, other: &Self) -> bool { - std::ptr::eq( - self.types.type_from_id(self.id).unwrap(), - other.types.type_from_id(other.id).unwrap(), - ) + std::ptr::eq(self.0, other.0) } } -impl<'a> Eq for TypeKey<'a> {} +impl Eq for PtrKey<'_, T> {} -impl std::hash::Hash for TypeKey<'_> { +impl std::hash::Hash for PtrKey<'_, T> { fn hash(&self, state: &mut H) { - struct TypeHash<'a>(&'a Type); - - impl std::hash::Hash for TypeHash<'_> { - fn hash(&self, state: &mut H) { - std::ptr::hash(self.0, state) - } - } - - TypeHash(self.types.type_from_id(self.id).unwrap()).hash(state); + std::ptr::hash(self.0, state); } } -pub struct TypeEncoder<'a>(TypesRef<'a>); +/// Represents a type map used in encoding. +/// +/// This implementation prevents encoding of duplicate types from the same +/// validator, but not from different validators. +/// +/// TODO: implement proper equality checks in `wasmparser` as to eliminate +/// all type duplicates? +type TypeMap<'a> = HashMap, u32>; + +pub(crate) struct TypeEncoder<'a>(&'a Types); impl<'a> TypeEncoder<'a> { - pub fn new(types: TypesRef<'a>) -> Self { + pub fn new(types: &'a Types) -> Self { Self(types) } @@ -96,15 +125,15 @@ impl<'a> TypeEncoder<'a> { E: IntoIterator, { let mut encoded = ComponentType::new(); - let mut types: HashMap, u32> = HashMap::new(); + let mut types = TypeMap::new(); for (name, ty) in imports { - let ty = self.component_entity_type(&mut encoded, &mut types, ty); + let ty = self.encodable_component_entity_type(&mut encoded, &mut types, ty); encoded.import(name, ty); } for (name, ty) in exports { - let ty = self.component_entity_type(&mut encoded, &mut types, ty); + let ty = self.encodable_component_entity_type(&mut encoded, &mut types, ty); encoded.export(name, ty); } @@ -116,10 +145,10 @@ impl<'a> TypeEncoder<'a> { E: IntoIterator, { let mut encoded = InstanceType::new(); - let mut types: HashMap, u32> = HashMap::new(); + let mut types = TypeMap::new(); for (name, ty) in exports { - let ty = self.component_entity_type(&mut encoded, &mut types, ty); + let ty = self.encodable_component_entity_type(&mut encoded, &mut types, ty); encoded.export(name, ty); } @@ -132,7 +161,7 @@ impl<'a> TypeEncoder<'a> { E: IntoIterator, { let mut encoded = ModuleType::new(); - let mut types: HashMap, u32> = HashMap::new(); + let mut types = TypeMap::new(); for (module, name, ty) in imports { let ty = self.entity_type(&mut encoded, &mut types, ty); @@ -147,18 +176,30 @@ impl<'a> TypeEncoder<'a> { encoded } + pub fn component_entity_type( + &self, + core_types: &mut CoreTypeSection, + component_types: &mut ComponentTypeSection, + types: &mut TypeMap<'a>, + ty: wasmparser::types::ComponentEntityType, + ) -> ComponentTypeRef { + let mut encodable = (core_types, component_types); + self.encodable_component_entity_type(&mut encodable, types, ty) + } + fn entity_type( &self, encodable: &mut ModuleType, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, ty: wasmparser::types::EntityType, ) -> EntityType { match ty { wasmparser::types::EntityType::Func(id) => { - let idx = match types.entry(TypeKey { types: self.0, id }) { + let ty = self.0.type_from_id(id).unwrap(); + let idx = match types.entry(PtrKey(ty)) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { - let ty = self.0.type_from_id(id).unwrap().as_func_type().unwrap(); + let ty = ty.as_func_type().unwrap(); let index = encodable.type_count(); encodable.ty().function( ty.params().iter().copied().map(Self::val_type), @@ -173,10 +214,11 @@ impl<'a> TypeEncoder<'a> { wasmparser::types::EntityType::Memory(ty) => EntityType::Memory(Self::memory_type(ty)), wasmparser::types::EntityType::Global(ty) => EntityType::Global(Self::global_type(ty)), wasmparser::types::EntityType::Tag(id) => { - let idx = match types.entry(TypeKey { types: self.0, id }) { + let ty = self.0.type_from_id(id).unwrap(); + let idx = match types.entry(PtrKey(ty)) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { - let ty = self.0.type_from_id(id).unwrap().as_func_type().unwrap(); + let ty = ty.as_func_type().unwrap(); let index = encodable.type_count(); encodable.ty().function( ty.params().iter().copied().map(Self::val_type), @@ -193,6 +235,34 @@ impl<'a> TypeEncoder<'a> { } } + fn encodable_component_entity_type( + &self, + encodable: &mut impl Encodable, + types: &mut TypeMap<'a>, + ty: wasmparser::types::ComponentEntityType, + ) -> ComponentTypeRef { + match ty { + wasmparser::types::ComponentEntityType::Module(id) => { + ComponentTypeRef::Module(self.module_type(encodable, types, id)) + } + wasmparser::types::ComponentEntityType::Func(id) => { + ComponentTypeRef::Func(self.component_func_type(encodable, types, id)) + } + wasmparser::types::ComponentEntityType::Value(ty) => { + ComponentTypeRef::Value(self.component_val_type(encodable, types, ty)) + } + wasmparser::types::ComponentEntityType::Type(id) => { + ComponentTypeRef::Type(TypeBounds::Eq, self.ty(encodable, types, id)) + } + wasmparser::types::ComponentEntityType::Instance(id) => { + ComponentTypeRef::Instance(self.component_instance_type(encodable, types, id)) + } + wasmparser::types::ComponentEntityType::Component(id) => { + ComponentTypeRef::Component(self.component_type(encodable, types, id)) + } + } + } + fn val_type(ty: wasmparser::ValType) -> ValType { match ty { wasmparser::ValType::I32 => ValType::I32, @@ -247,44 +317,17 @@ impl<'a> TypeEncoder<'a> { } } - fn component_entity_type( - &self, - encodable: &mut impl Encodable, - types: &mut HashMap, u32>, - ty: wasmparser::types::ComponentEntityType, - ) -> ComponentTypeRef { - match ty { - wasmparser::types::ComponentEntityType::Module(id) => { - ComponentTypeRef::Module(self.module_type(encodable, types, id)) - } - wasmparser::types::ComponentEntityType::Func(id) => { - ComponentTypeRef::Func(self.component_func_type(encodable, types, id)) - } - wasmparser::types::ComponentEntityType::Value(ty) => { - ComponentTypeRef::Value(self.component_val_type(encodable, types, ty)) - } - wasmparser::types::ComponentEntityType::Type(id) => { - ComponentTypeRef::Type(TypeBounds::Eq, self.ty(encodable, types, id)) - } - wasmparser::types::ComponentEntityType::Instance(id) => { - ComponentTypeRef::Instance(self.component_instance_type(encodable, types, id)) - } - wasmparser::types::ComponentEntityType::Component(id) => { - ComponentTypeRef::Component(self.component_type(encodable, types, id)) - } - } - } - fn module_type( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, id: TypeId, ) -> u32 { - match types.entry(TypeKey { types: self.0, id }) { + let ty = self.0.type_from_id(id).unwrap(); + match types.entry(PtrKey(ty)) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { - let ty = self.0.type_from_id(id).unwrap().as_module_type().unwrap(); + let ty = ty.as_module_type().unwrap(); let module = self.module( ty.imports @@ -293,7 +336,7 @@ impl<'a> TypeEncoder<'a> { ty.exports.iter().map(|(n, t)| (n.as_str(), *t)), ); - let index = encodable.type_count(); + let index = encodable.core_type_count(); encodable.core_type().module(&module); *e.insert(index) } @@ -303,21 +346,20 @@ impl<'a> TypeEncoder<'a> { fn component_instance_type( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, id: TypeId, ) -> u32 { - match types.entry(TypeKey { types: self.0, id }) { + let ty = self.0.type_from_id(id).unwrap(); + match types.entry(PtrKey(ty)) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { - let ty = self - .0 - .type_from_id(id) - .unwrap() - .as_component_instance_type() - .unwrap(); + let ty = ty.as_component_instance_type().unwrap(); - let instance = - self.instance(ty.exports(self.0).iter().map(|(n, t)| (n.as_str(), *t))); + let instance = self.instance( + ty.exports(self.0.as_ref()) + .iter() + .map(|(n, t)| (n.as_str(), *t)), + ); let index = encodable.type_count(); encodable.ty().instance(&instance); @@ -329,18 +371,14 @@ impl<'a> TypeEncoder<'a> { fn component_type( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, id: TypeId, ) -> u32 { - match types.entry(TypeKey { types: self.0, id }) { + let ty = self.0.type_from_id(id).unwrap(); + match types.entry(PtrKey(ty)) { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { - let ty = self - .0 - .type_from_id(id) - .unwrap() - .as_component_type() - .unwrap(); + let ty = ty.as_component_type().unwrap(); let component = self.component( ty.imports.iter().map(|(n, t)| (n.as_str(), *t)), @@ -357,21 +395,17 @@ impl<'a> TypeEncoder<'a> { fn component_func_type( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, id: TypeId, ) -> u32 { - if let Some(idx) = types.get(&TypeKey { types: self.0, id }) { + let ty = self.0.type_from_id(id).unwrap(); + if let Some(idx) = types.get(&PtrKey(ty)) { return *idx; } - let ty = self - .0 - .type_from_id(id) - .unwrap() - .as_component_func_type() - .unwrap(); + let func_ty = ty.as_component_func_type().unwrap(); - let params = ty + let params = func_ty .params .iter() .map(|(name, ty)| { @@ -382,7 +416,7 @@ impl<'a> TypeEncoder<'a> { }) .collect::>(); - let results = ty + let results = func_ty .results .iter() .map(|(name, ty)| { @@ -404,16 +438,11 @@ impl<'a> TypeEncoder<'a> { f.results(results.into_iter().map(|(name, ty)| (name.unwrap(), ty))); } - types.insert(TypeKey { types: self.0, id }, index); + types.insert(PtrKey(ty), index); index } - fn ty( - &self, - encodable: &mut impl Encodable, - types: &mut HashMap, u32>, - id: TypeId, - ) -> u32 { + fn ty(&self, encodable: &mut impl Encodable, types: &mut TypeMap<'a>, id: TypeId) -> u32 { let ty = self.0.type_from_id(id).unwrap(); match ty { @@ -435,7 +464,7 @@ impl<'a> TypeEncoder<'a> { fn component_val_type( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, ty: wasmparser::types::ComponentValType, ) -> ComponentValType { match ty { @@ -451,16 +480,17 @@ impl<'a> TypeEncoder<'a> { fn defined_type( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, id: TypeId, ) -> u32 { - if let Some(idx) = types.get(&TypeKey { types: self.0, id }) { + let ty = self.0.type_from_id(id).unwrap(); + if let Some(idx) = types.get(&PtrKey(ty)) { return *idx; } - let ty = self.0.type_from_id(id).unwrap().as_defined_type().unwrap(); + let defined_ty = ty.as_defined_type().unwrap(); - let index = match ty { + let index = match defined_ty { wasmparser::types::ComponentDefinedType::Primitive(ty) => { let index = encodable.type_count(); encodable @@ -488,14 +518,14 @@ impl<'a> TypeEncoder<'a> { } }; - types.insert(TypeKey { types: self.0, id }, index); + types.insert(PtrKey(ty), index); index } fn record( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, record: &wasmparser::types::RecordType, ) -> u32 { let fields = record @@ -512,7 +542,7 @@ impl<'a> TypeEncoder<'a> { fn variant( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, variant: &wasmparser::types::VariantType, ) -> u32 { let cases = variant @@ -536,7 +566,7 @@ impl<'a> TypeEncoder<'a> { fn list( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, ty: wasmparser::types::ComponentValType, ) -> u32 { let ty = self.component_val_type(encodable, types, ty); @@ -548,7 +578,7 @@ impl<'a> TypeEncoder<'a> { fn tuple( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, tuple: &wasmparser::types::TupleType, ) -> u32 { let types = tuple @@ -582,7 +612,7 @@ impl<'a> TypeEncoder<'a> { fn union( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, union: &wasmparser::types::UnionType, ) -> u32 { let types = union @@ -599,7 +629,7 @@ impl<'a> TypeEncoder<'a> { fn option( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, ty: wasmparser::types::ComponentValType, ) -> u32 { let ty = self.component_val_type(encodable, types, ty); @@ -612,7 +642,7 @@ impl<'a> TypeEncoder<'a> { fn result( &self, encodable: &mut impl Encodable, - types: &mut HashMap, u32>, + types: &mut TypeMap<'a>, ok: Option, err: Option, ) -> u32 { @@ -625,43 +655,328 @@ impl<'a> TypeEncoder<'a> { } } -/// Used to encode an instantiation graph. -pub(crate) struct InstantiationGraphEncoder<'a> { - /// The associated composition configuration. - config: &'a Config, +/// Represents an instance index in a composition graph. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +struct InstanceIndex(usize); + +enum ArgumentImportKind<'a> { + /// An item is being imported. + /// + /// The item may be an instance that does not need to be merged. + Item(&'a crate::graph::Component<'a>, ComponentEntityType), + /// A merged instance is being imported. + /// + /// Instances are unioned together to form a single instance to + /// import that will satisfy all instantiation arguments that + /// reference the import. + Instance(IndexMap<&'a str, (&'a crate::graph::Component<'a>, ComponentEntityType)>), +} + +/// Represents an import for an instantiation argument. +struct ArgumentImport<'a> { + // The name of the import. + name: &'a str, + /// The kind of import. + kind: ArgumentImportKind<'a>, + /// The instances that will use the import for an argument. + instances: SmallVec<[(InstanceIndex, ImportIndex); 1]>, +} + +impl ArgumentImport<'_> { + fn merge(&mut self, arg: Self) -> Result<()> { + assert_eq!(self.name, arg.name); + self.instances.extend(arg.instances); + + // If the existing import is an instance, convert this argument import to + // a merged instance import. + if let ArgumentImportKind::Item(component, ComponentEntityType::Instance(id)) = &self.kind { + let exports = component + .types + .type_from_id(*id) + .unwrap() + .as_component_instance_type() + .unwrap() + .exports(component.types.as_ref()); + + let mut map = IndexMap::with_capacity(exports.len()); + for (name, ty) in exports { + map.insert(name.as_ref(), (*component, *ty)); + } + + self.kind = ArgumentImportKind::Instance(map); + } + + match (&mut self.kind, arg.kind) { + // The new item should never be a merged instance + (_, ArgumentImportKind::Instance(..)) => { + unreachable!("expected an item import to merge with") + } + // If the existing import is a merge instance, merge with an instance item import + ( + ArgumentImportKind::Instance(exports), + ArgumentImportKind::Item(new_component, ComponentEntityType::Instance(id)), + ) => { + for (name, new_type) in new_component + .types + .type_from_id(id) + .unwrap() + .as_component_instance_type() + .unwrap() + .exports(new_component.types.as_ref()) + { + match exports.entry(name.as_str()) { + indexmap::map::Entry::Occupied(mut e) => { + let (existing_component, existing_type) = e.get_mut(); + match Self::compatible_type( + *existing_component, + *existing_type, + new_component, + *new_type, + ) { + Some((c, ty)) => { + *existing_component = c; + *existing_type = ty; + } + None => bail!( + "cannot import instance with name `{name}` for an instantiation argument of component `{cname}` because it conflicts with an imported instantiation argument of component `{ecname}`", + name = self.name, + cname = new_component.name, + ecname = existing_component.name, + ), + } + } + indexmap::map::Entry::Vacant(e) => { + e.insert((new_component, *new_type)); + } + } + } + } + // Otherwise, an attempt to merge an instance with a non-instance is an error + (ArgumentImportKind::Instance(_), ArgumentImportKind::Item(component, ty)) => { + bail!( + "cannot import {ty} with name `{name}` for an instantiation argument of component `{cname}` because it conflicts with an instance imported with the same name", + name = self.name, + ty = type_desc(ty), + cname = component.name, + ); + } + // Finally, merge two item imports together by finding the most-compatible type + ( + ArgumentImportKind::Item(existing_component, existing_type), + ArgumentImportKind::Item(new_component, new_type), + ) => { + match Self::compatible_type( + *existing_component, + *existing_type, + new_component, + new_type, + ) { + Some((c, ty)) => { + *existing_component = c; + *existing_type = ty; + } + None => bail!( + "cannot import {ty} with name `{name}` for an instantiation argument of component `{cname}` because it conflicts with an imported instantiation argument of component `{ecname}`", + ty = type_desc(new_type), + name = self.name, + cname = new_component.name, + ecname = existing_component.name, + ), + } + } + } + + Ok(()) + } + + fn compatible_type<'a>( + existing_component: &'a crate::graph::Component<'a>, + existing_type: ComponentEntityType, + new_component: &'a crate::graph::Component<'a>, + new_type: ComponentEntityType, + ) -> Option<(&'a crate::graph::Component<'a>, ComponentEntityType)> { + if ComponentEntityType::is_subtype_of( + &existing_type, + existing_component.types(), + &new_type, + new_component.types(), + ) { + return Some((existing_component, existing_type)); + } + + if ComponentEntityType::is_subtype_of( + &new_type, + new_component.types(), + &existing_type, + existing_component.types(), + ) { + return Some((new_component, new_type)); + } + + None + } +} + +/// Represents an entry in the import map built up during +/// the encoding of a composition graph. +/// +/// A map entry is either an import of a component or +/// an import of an item to satisfy an instantiation argument. +enum ImportMapEntry<'a> { + /// An import of a component. + Component(&'a crate::graph::Component<'a>), + /// An import to satisfy one or more instantiation arguments. + Argument(ArgumentImport<'a>), +} + +/// Represents the import map built during the encoding +/// of a composition graph. +#[derive(Default)] +struct ImportMap<'a>(IndexMap<&'a str, ImportMapEntry<'a>>); + +impl<'a> ImportMap<'a> { + fn new(import_components: bool, graph: &'a CompositionGraph) -> Result { + let mut imports = Self::default(); + + if import_components { + imports.add_component_imports(graph); + } + + imports.add_instantiation_imports(graph)?; + + Ok(imports) + } + + fn add_component_imports(&mut self, graph: &'a CompositionGraph) { + for entry in graph + .components + .values() + .filter(|e| !e.instances.is_empty()) + { + assert!(self + .0 + .insert( + &entry.component.name, + ImportMapEntry::Component(&entry.component), + ) + .is_none()); + } + } + + fn add_instantiation_imports(&mut self, graph: &'a CompositionGraph) -> Result<()> { + let mut imported = HashMap::new(); + + for (instance_index, instance) in graph.instances.values().enumerate() { + let (component_index, _, entry) = + graph.components.get_full(&instance.component).unwrap(); + + let instance_index = InstanceIndex(instance_index); + + // Import any unconnected instantiation arguments for the instance + for (import_index, name, _) in entry.component.imports() { + if instance.connected.contains(&import_index) { + continue; + } + + let (_, ty) = entry.component.import_entity_type(import_index).unwrap(); + + let arg = ArgumentImport { + name, + kind: ArgumentImportKind::Item(&entry.component, ty), + instances: smallvec::smallvec![(instance_index, import_index)], + }; + + // Check to see if we've seen this import before; if so, don't bother with + // type checking, just add the instance to the list of instances + match imported.entry((component_index, import_index)) { + Entry::Occupied(e) => match self.0.get_index_mut(*e.get()).unwrap().1 { + ImportMapEntry::Component(_) => { + unreachable!("import should not be for a component") + } + ImportMapEntry::Argument(arg) => { + arg.instances.push((instance_index, import_index)); + } + }, + Entry::Vacant(e) => { + let index = match self.0.entry(name) { + indexmap::map::Entry::Occupied(mut e) => match e.get_mut() { + ImportMapEntry::Component(_) => { + bail!( + "cannot import {ty} `{name}` for an instantiation argument of component `{cname}` because it conflicts with a component imported with the same name", + ty = type_desc(ty), + cname = entry.component.name, + ); + } + ImportMapEntry::Argument(existing) => { + existing.merge(arg)?; + e.index() + } + }, + indexmap::map::Entry::Vacant(e) => { + let index = e.index(); + e.insert(ImportMapEntry::Argument(arg)); + index + } + }; + e.insert(index); + } + } + } + } + + Ok(()) + } +} + +#[derive(Default)] +struct ImportEncodingContext<'a> { + types: TypeMap<'a>, + import_section: ComponentImportSection, + core_type_section: CoreTypeSection, + type_section: ComponentTypeSection, +} + +/// Used to encode a composition graph as a new WebAssembly component. +pub(crate) struct CompositionGraphEncoder<'a> { + /// The options for the encoding. + options: EncodeOptions, /// The graph being encoded. - graph: &'a InstantiationGraph, + graph: &'a CompositionGraph<'a>, /// Map from graph component index to encoded component index. - component_indexes: HashMap, - /// Map from graph instance index to encoded instantiation index. - instance_indexes: HashMap, - /// The used import names during encoding. - imports: HashSet<&'a str>, - /// Map from an export on a graph instance to the aliased encoded instance index. - aliases: HashMap<(InstanceIndex, ExportIndex), u32>, - /// The number of modules encoded (i.e. current module index). + encoded_components: HashMap>, u32>, + /// Map from graph instance id to encoded instance index. + encoded_instances: HashMap, + /// Map from instance and import index to encoded item index. + /// + /// This is used for instantiation arguments that are imported. + imported_args: HashMap<(InstanceIndex, ImportIndex), u32>, + /// Map from instance id and export index to encoded item index. + /// + /// This is used to track instantiation arguments aliased from + /// other instances. + aliases: HashMap<(InstanceId, ExportIndex), u32>, + /// The number of modules encoded (i.e. next module index). modules: u32, - /// The number of component functions encoded (i.e. current function index). + /// The number of component functions encoded (i.e. next function index). funcs: u32, - /// The number of values encoded (i.e. current value index). + /// The number of values encoded (i.e. next value index). values: u32, - /// The number of types encoded (i.e. current type index). + /// The number of types encoded (i.e. next type index). types: u32, - /// The number of component instances encoded (i.e. current instance index). + /// The number of component instances encoded (i.e. next instance index). instances: u32, - /// The number of components encoded (i.e. current component index). + /// The number of components encoded (i.e. next component index). components: u32, } -impl<'a> InstantiationGraphEncoder<'a> { - /// Create a new encoder for the given graph. - pub(crate) fn new(config: &'a Config, graph: &'a InstantiationGraph) -> Self { +impl<'a> CompositionGraphEncoder<'a> { + pub(crate) fn new(options: EncodeOptions, graph: &'a CompositionGraph) -> Self { Self { - config, + options, graph, - component_indexes: Default::default(), - instance_indexes: Default::default(), - imports: Default::default(), + encoded_components: Default::default(), + encoded_instances: Default::default(), + imported_args: Default::default(), aliases: Default::default(), modules: 0, funcs: 0, @@ -672,284 +987,388 @@ impl<'a> InstantiationGraphEncoder<'a> { } } - /// Encodes the graph into a component. pub(crate) fn encode(mut self) -> Result> { - let mut encoded = wasm_encoder::Component::new(); - - // Encode the instances from the graph - for instance in self.graph.instantiation_order()? { - if let Some(component) = self.graph.component(instance) { - self.encode_instantiation(instance, component, &mut encoded)?; - continue; - } + let mut encoded = Component::new(); - if let Some(refs) = self.graph.import_refs(instance) { - self.encode_instance_import(instance, refs, &mut encoded)?; - continue; - } + self.encode_imports(&mut encoded)?; + self.encode_components(&mut encoded); + self.encode_instantiations(&mut encoded)?; - unreachable!("every instance in the graph should either be instantiated or imported"); + if let Some(id) = self.options.export { + self.encode_exports(&mut encoded, id)?; } - // Encode the exports from the root component - self.encode_exports(&mut encoded)?; - Ok(encoded.finish()) } - /// Encode an instance import in the given component. - fn encode_instance_import( - &mut self, - instance: InstanceIndex, - refs: &IndexSet, - encoded: &mut wasm_encoder::Component, - ) -> Result<()> { - // Build a map of export names to types; this will be used to encode - // the type of the imported instance - let mut exports: IndexMap<&String, (&crate::composer::Component, ComponentEntityType)> = - IndexMap::new(); - - let instance_name = self.graph.instance_name(instance); - if !self.imports.insert(instance_name) { - bail!("cannot import instance `{instance_name}` because it conflicts with an imported component of the same name"); - } - - for r in refs { - let (component, _, ty) = self.graph.resolve_import(*r); - let types = component.types(); - for (export_name, export_type) in ty.exports(types) { - match exports.entry(export_name) { - indexmap::map::Entry::Occupied(mut e) => { - // Export already exists, ensure the types are compatible - let (existing_component, existing_entity_type) = e.get(); - - // If the existing type is still a subtype, do nothing - if ComponentEntityType::is_subtype_of( - existing_entity_type, - existing_component.types(), - export_type, - types, - ) { - continue; - } + fn encode_imports(&mut self, encoded: &mut Component) -> Result<()> { + let imports = ImportMap::new(!self.options.define_components, self.graph)?; + let mut context = ImportEncodingContext::default(); - // If the new type is the subtype, replace the existing type - if ComponentEntityType::is_subtype_of( - export_type, - types, - existing_entity_type, - existing_component.types(), - ) { - *e.get_mut() = (component, *export_type); - continue; + for (name, entry) in imports.0 { + match entry { + ImportMapEntry::Component(component) => { + self.encode_component_import(&mut context, name, component); + } + ImportMapEntry::Argument(arg) => { + let index = match arg.kind { + ArgumentImportKind::Item(component, ty) => { + self.encode_item_import(&mut context, name, component, ty) + } + ArgumentImportKind::Instance(exports) => { + self.encode_instance_import(&mut context, name, exports) } + }; - // Otherwise, it's a conflict error - bail!( - "cannot import instance `{instance_name}` due to conflicting types for export `{export_name}` between components `{a}` and `{b}`", - a = existing_component.path().display(), - b = component.path().display() - ); - } - indexmap::map::Entry::Vacant(e) => { - e.insert((component, *export_type)); - } + self.imported_args + .extend(arg.instances.into_iter().map(|k| (k, index))); } } } - let mut instance_type = InstanceType::new(); - let mut types = HashMap::new(); - for (name, (component, ty)) in exports { - let encoder = TypeEncoder::new(component.types()); - let ty = encoder.component_entity_type(&mut instance_type, &mut types, ty); - instance_type.export(name, ty); + if !context.core_type_section.is_empty() { + encoded.section(&context.core_type_section); } - let mut types = ComponentTypeSection::new(); - let type_index = self.types; - types.instance(&instance_type); - self.types += 1; - encoded.section(&types); + if !context.type_section.is_empty() { + encoded.section(&context.type_section); + } - let mut imports = ComponentImportSection::new(); - let instance_index = self.instances; - imports.import(instance_name, ComponentTypeRef::Instance(type_index)); - self.instances += 1; - encoded.section(&imports); + if !context.import_section.is_empty() { + encoded.section(&context.import_section); + } - log::debug!("importing instance `{instance_name}` (encoded index {instance_index})",); + Ok(()) + } + + fn encode_component_import( + &mut self, + context: &mut ImportEncodingContext<'a>, + name: &str, + component: &'a crate::graph::Component, + ) -> u32 { + let type_index = self.define_component_type(&mut context.type_section, component); + let index = self.import( + &mut context.import_section, + name, + ComponentTypeRef::Component(type_index), + ); - self.instance_indexes.insert(instance, instance_index); + assert!(self + .encoded_components + .insert(PtrKey(component), index) + .is_none()); - Ok(()) + index } - /// Encode a component instantiation in the given component. - fn encode_instantiation( + fn encode_item_import( &mut self, - instance: InstanceIndex, - component: &'a Component, - encoded: &mut wasm_encoder::Component, - ) -> Result<()> { - let instance_name = self.graph.instance_name(instance); - let dependency = self.config.dependency_name(instance_name); + context: &mut ImportEncodingContext<'a>, + name: &str, + component: &'a crate::graph::Component, + ty: ComponentEntityType, + ) -> u32 { + let prev_type_count = context.type_section.len(); + + let encoder = TypeEncoder::new(&component.types); + let ty = encoder.component_entity_type( + &mut context.core_type_section, + &mut context.type_section, + &mut context.types, + ty, + ); - // Encode the instance's component if it hasn't been encoded already - let component_index = match self.component_indexes.entry(component.index()) { - Entry::Occupied(e) => *e.get(), - Entry::Vacant(e) => { - let index = match component.import_name() { - Some(name) => { - if !self.imports.insert(name) { - bail!( - "cannot import dependency `{dependency}` (`{path}`) with name `{name}` because it conflicts with an imported instance or component of the same name", - path = component.path().display(), - ); - } + self.types += context.type_section.len() - prev_type_count; + self.import(&mut context.import_section, name, ty) + } - log::debug!( - "importing component `{dependency}` with name `{name}` (encoded index {component_index}", - component_index = self.components, - ); + fn encode_instance_import( + &mut self, + context: &mut ImportEncodingContext<'a>, + name: &str, + exports: IndexMap<&'a str, (&'a crate::graph::Component, ComponentEntityType)>, + ) -> u32 { + let mut instance_type = InstanceType::new(); + let mut types = TypeMap::new(); + for (name, (component, ty)) in exports { + let encoder = TypeEncoder::new(&component.types); + let ty = encoder.encodable_component_entity_type(&mut instance_type, &mut types, ty); + instance_type.export(name, ty); + } - let mut types = ComponentTypeSection::new(); - let type_index = self.types; - types.component(&component.ty()); - self.types += 1; - encoded.section(&types); + let index = self.types; + context.type_section.instance(&instance_type); + self.types += 1; - let mut imports = ComponentImportSection::new(); - let component_index = self.components; - imports.import(name, ComponentTypeRef::Component(type_index)); - self.components += 1; - encoded.section(&imports); + self.import( + &mut context.import_section, + name, + ComponentTypeRef::Instance(index), + ) + } - component_index - } - None => { - let component_index = self.components; - log::debug!("defining component `{dependency}` in composed component (encoded index {component_index})"); + fn encode_instantiations(&mut self, encoded: &mut Component) -> Result<()> { + let ordering = self.graph.instantiation_order()?; + + // Encode the independent instances first + for id in self + .graph + .instances + .keys() + .filter(|id| !ordering.contains(*id)) + { + self.encode_instantiation(encoded, *id)?; + } - encoded.section(&RawSection { - id: ComponentSectionId::Component.into(), - data: component.bytes(), - }); + // Encode the dependent instances last + for id in ordering { + self.encode_instantiation(encoded, id)?; + } - self.components += 1; - component_index - } - }; + Ok(()) + } - *e.insert(index) - } - }; + fn encode_exports(&mut self, encoded: &mut Component, instance_id: InstanceId) -> Result<()> { + let instance = self.graph.instances.get(&instance_id).ok_or_else(|| { + anyhow!("cannot export specified instance because it does not exist in the graph") + })?; + let entry = self.graph.components.get(&instance.component).unwrap(); + + let encoded_instance_index = self.encoded_instances[&instance_id]; + + let mut alias_section = ComponentAliasSection::new(); + let mut export_section = ComponentExportSection::new(); + for (export_index, export_name, kind, _) in entry.component.exports() { + let kind = match kind { + ComponentExternalKind::Module => ComponentExportKind::Module, + ComponentExternalKind::Func => ComponentExportKind::Func, + ComponentExternalKind::Value => ComponentExportKind::Value, + ComponentExternalKind::Type => ComponentExportKind::Type, + ComponentExternalKind::Instance => ComponentExportKind::Instance, + ComponentExternalKind::Component => ComponentExportKind::Component, + }; + + let index = match self.aliases.get(&(instance_id, export_index)) { + Some(index) => *index, + None => { + let index = self.alias( + &mut alias_section, + encoded_instance_index, + export_name, + kind, + ); + self.aliases.insert((instance_id, export_index), index); + index + } + }; - let args = self.graph.instantiation_args(instance, |instance, export| { - let instance_index = self.instance_indexes[&instance]; + export_section.export(export_name, kind, index); + } - // Check if we're aliasing an export from the instance - match export { - Some(export) => match self.aliases.entry((instance, export)) { - Entry::Occupied(e) => *e.get(), - Entry::Vacant(e) => { - let name = self.graph.component(instance).unwrap().export(export).0; - - let mut aliases = ComponentAliasSection::new(); - let alias_index = self.instances; - aliases.instance_export( - instance_index, - ComponentExportKind::Instance, - name, - ); - self.instances += 1; - encoded.section(&aliases); + if !alias_section.is_empty() { + encoded.section(&alias_section); + } - log::debug!( - "aliasing instance export `{name}` from instance `{instance_name}` (encoded index {alias_index})", - instance_name = self.graph.instance_name(instance), - ); + if !export_section.is_empty() { + encoded.section(&export_section); + } - *e.insert(alias_index) - } - }, - None => instance_index, - } - }); + Ok(()) + } + + fn encode_instantiation( + &mut self, + encoded: &mut Component, + instance_id: InstanceId, + ) -> Result<()> { + let (instance_index, _, instance) = self.graph.instances.get_full(&instance_id).unwrap(); + let entry = &self.graph.components.get(&instance.component).unwrap(); + + let instance_index = InstanceIndex(instance_index); + let encoded_component_index = self.encoded_components[&PtrKey(&entry.component)]; + + let mut alias_section = ComponentAliasSection::new(); + let args = self.instantiation_args(instance_index, &entry.component, &mut alias_section); + + if !alias_section.is_empty() { + encoded.section(&alias_section); + } log::debug!( - "instantiating component `{dependency}` as instance `{instance_name}` (encoded index {instance_index}) with {args:?}", + "instantiating component `{name}` (encoded index {instance_index}) with {args:?}", + name = entry.component.name, instance_index = self.instances, ); - let mut instances = ComponentInstanceSection::new(); - let instance_index = self.instances; - instances.instantiate(component_index, args); + let mut instance_section = ComponentInstanceSection::new(); + let encoded_instance_index = self.instances; + instance_section.instantiate(encoded_component_index, args); self.instances += 1; - encoded.section(&instances); + encoded.section(&instance_section); - self.instance_indexes.insert(instance, instance_index); + self.encoded_instances + .insert(instance_id, encoded_instance_index); Ok(()) } - /// Encode the exports of the composed component. - /// - /// This always exports everything from the root (index 0) component. - fn encode_exports(&mut self, encoded: &mut wasm_encoder::Component) -> Result<()> { - let mut exports = ComponentExportSection::new(); - - // The root instance is always the first node in the graph - let instance = InstanceIndex::new(0); - let component = self.graph.component(instance).unwrap(); - let instance_index = self.instance_indexes[&instance]; + fn encode_components(&mut self, encoded: &mut Component) { + if !self.options.define_components { + return; + } - // Alias all exports from the root instance - let mut aliases = ComponentAliasSection::new(); - for (name, kind, _) in component.exports() { - self.encode_alias_and_export(instance_index, name, kind, &mut aliases, &mut exports); + for entry in self + .graph + .components + .values() + .filter(|e| !e.instances.is_empty()) + { + let index = self.define_component(encoded, &entry.component); + assert!(self + .encoded_components + .insert(PtrKey(&entry.component), index) + .is_none()); } + } - if !aliases.is_empty() { - encoded.section(&aliases); + fn define_component_type( + &mut self, + type_section: &mut ComponentTypeSection, + component: &crate::graph::Component, + ) -> u32 { + let type_index = self.types; + type_section.component(&component.ty()); + self.types += 1; + type_index + } + + fn define_component( + &mut self, + encoded: &mut Component, + component: &crate::graph::Component, + ) -> u32 { + let index = self.components; + + log::debug!( + "defining component `{name}` (encoded index {index}) in composed component", + name = component.name, + ); + + encoded.section(&RawSection { + id: ComponentSectionId::Component.into(), + data: component.bytes(), + }); + + self.components += 1; + index + } + + fn instantiation_args( + &mut self, + instance_index: InstanceIndex, + component: &'a crate::graph::Component, + alias_section: &mut ComponentAliasSection, + ) -> Vec<(&'a str, ComponentExportKind, u32)> { + let (instance_id, instance) = self.graph.instances.get_index(instance_index.0).unwrap(); + let mut args = Vec::with_capacity(component.imports.len()); + + // Add the arguments that are aliased exports from other instances + for (source_id, _, map) in self + .graph + .graph + .edges_directed(*instance_id, EdgeDirection::Incoming) + { + assert!(source_id != *instance_id); + let source_index = self.encoded_instances[&source_id]; + let (_, source_component) = &self.graph.get_component_of_instance(source_id).unwrap(); + + for (import_index, export_index) in map { + // Check to see if we need to alias the item from the source instance + let (name, ty) = component.import(*import_index).unwrap(); + let index = match export_index { + Some(export_index) => { + let (export_name, _, _) = source_component.export(*export_index).unwrap(); + match self.aliases.get(&(source_id, *export_index)) { + Some(index) => *index, + None => { + let index = self.alias( + alias_section, + source_index, + export_name, + type_ref_to_export_kind(ty), + ); + self.aliases.insert((source_id, *export_index), index); + index + } + } + } + None => source_index, + }; + args.push((name, type_ref_to_export_kind(ty), index)); + } } - if !exports.is_empty() { - encoded.section(&exports); + // Finally, add any instantiation arguments that are being imported + for (i, (name, ty)) in component.imports.iter().enumerate() { + let import_index = ImportIndex(i); + if instance.connected.contains(&import_index) { + continue; + } + + let index = self.imported_args[&(instance_index, import_index)]; + args.push((name.as_str(), type_ref_to_export_kind(*ty), index)); } - Ok(()) + args } - /// Encode an alias for an instance export and then export the aliased item. - fn encode_alias_and_export( + fn import( &mut self, - instance_index: u32, + import_section: &mut ComponentImportSection, name: &str, - kind: wasmparser::ComponentExternalKind, + ty: ComponentTypeRef, + ) -> u32 { + let (desc, count) = match ty { + ComponentTypeRef::Module(_) => ("module", &mut self.modules), + ComponentTypeRef::Func(_) => ("function", &mut self.funcs), + ComponentTypeRef::Value(_) => ("value", &mut self.values), + ComponentTypeRef::Type(_, _) => ("type", &mut self.types), + ComponentTypeRef::Instance(_) => ("instance", &mut self.instances), + ComponentTypeRef::Component(_) => ("component", &mut self.components), + }; + + log::debug!("importing {desc} with `{name}` (encoded index {count}) in composed component"); + + import_section.import(name, ty); + + let index = *count; + *count += 1; + index + } + + fn alias( + &mut self, aliases: &mut ComponentAliasSection, - exports: &mut ComponentExportSection, - ) { - let (indexes, kind) = match kind { - wasmparser::ComponentExternalKind::Module => { - (&mut self.modules, ComponentExportKind::Module) - } - wasmparser::ComponentExternalKind::Func => (&mut self.funcs, ComponentExportKind::Func), - wasmparser::ComponentExternalKind::Value => { - (&mut self.values, ComponentExportKind::Value) - } - wasmparser::ComponentExternalKind::Type => (&mut self.types, ComponentExportKind::Type), - wasmparser::ComponentExternalKind::Instance => { - (&mut self.instances, ComponentExportKind::Instance) - } - wasmparser::ComponentExternalKind::Component => { - (&mut self.components, ComponentExportKind::Component) - } + instance: u32, + export: &str, + kind: ComponentExportKind, + ) -> u32 { + let (desc, count) = match kind { + ComponentExportKind::Module => ("module", &mut self.modules), + ComponentExportKind::Func => ("function", &mut self.funcs), + ComponentExportKind::Value => ("value", &mut self.values), + ComponentExportKind::Type => ("type", &mut self.types), + ComponentExportKind::Instance => ("instance", &mut self.instances), + ComponentExportKind::Component => ("component", &mut self.components), }; - let index = *indexes; - aliases.instance_export(instance_index, kind, name); - *indexes += 1; - exports.export(name, kind, index); + log::debug!("aliasing {desc} export `{export}` from encoded index {instance} (encoded index {count}) in composed component"); + + aliases.instance_export(instance, kind, export); + + let index = *count; + *count += 1; + index } } diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs new file mode 100644 index 0000000000..b8be2aec2a --- /dev/null +++ b/crates/wasm-compose/src/graph.rs @@ -0,0 +1,1343 @@ +//! Module for WebAssembly composition graphs. +use crate::encoding::{CompositionGraphEncoder, TypeEncoder}; +use anyhow::{anyhow, bail, Context, Result}; +use indexmap::{IndexMap, IndexSet}; +use petgraph::{algo::toposort, graphmap::DiGraphMap, EdgeDirection}; +use std::{ + borrow::Cow, + collections::{hash_map::Entry, HashMap, HashSet}, + path::{Path, PathBuf}, + sync::atomic::{AtomicUsize, Ordering}, +}; +use wasmparser::{ + types::{ComponentEntityType, ComponentInstanceType, Types, TypesRef}, + Chunk, ComponentExport, ComponentExternalKind, ComponentImport, ComponentTypeRef, Encoding, + Parser, Payload, ValidPayload, Validator, WasmFeatures, +}; + +pub(crate) fn type_desc(item: ComponentEntityType) -> &'static str { + match item { + ComponentEntityType::Instance(_) => "instance", + ComponentEntityType::Module(_) => "module", + ComponentEntityType::Func(_) => "function", + ComponentEntityType::Value(_) => "value", + ComponentEntityType::Type(_) => "type", + ComponentEntityType::Component(_) => "component", + } +} + +/// Represents a component in a composition graph. +pub struct Component<'a> { + /// The name of the component. + pub(crate) name: String, + /// The path to the component file if parsed via `Component::from_file`. + pub(crate) path: Option, + /// The raw bytes of the component. + pub(crate) bytes: Cow<'a, [u8]>, + /// The type information of the component. + pub(crate) types: Types, + /// The import map of the component. + pub(crate) imports: IndexMap, + /// The export map of the component. + pub(crate) exports: IndexMap, +} + +impl<'a> Component<'a> { + /// Constructs a new component from reading the given file. + pub fn from_file(name: impl Into, path: impl AsRef) -> Result { + let path = path.as_ref(); + log::info!("parsing WebAssembly component file `{}`", path.display()); + + let component = Self::parse( + name.into(), + Some(path.to_owned()), + wat::parse_file(&path) + .with_context(|| { + format!("failed to parse component `{path}`", path = path.display()) + })? + .into(), + ) + .with_context(|| format!("failed to parse component `{path}`", path = path.display()))?; + + log::debug!( + "WebAssembly component `{path}` parsed:\n{component:#?}", + path = path.display() + ); + + Ok(component) + } + + /// Constructs a new component from the given bytes. + pub fn from_bytes(name: impl Into, bytes: impl Into>) -> Result { + let mut bytes = bytes.into(); + + match wat::parse_bytes(bytes.as_ref()).context("failed to parse component")? { + Cow::Borrowed(_) => { + // Original bytes were not modified + } + Cow::Owned(v) => bytes = v.into(), + } + + log::info!("parsing WebAssembly component from bytes"); + let component = + Self::parse(name.into(), None, bytes).context("failed to parse component")?; + + log::debug!("WebAssembly component parsed:\n{component:#?}",); + + Ok(component) + } + + fn parse(name: String, path: Option, bytes: Cow<'a, [u8]>) -> Result { + let mut parser = Parser::new(0); + let mut parsers = Vec::new(); + let mut validator = Validator::new_with_features(WasmFeatures { + component_model: true, + ..Default::default() + }); + let mut imports = IndexMap::new(); + let mut exports = IndexMap::new(); + + let mut cur = bytes.as_ref(); + loop { + match parser.parse(cur, true)? { + Chunk::Parsed { payload, consumed } => { + cur = &cur[consumed..]; + + match validator.payload(&payload)? { + ValidPayload::Ok => { + // Don't parse any sub-components or sub-modules + if !parsers.is_empty() { + continue; + } + + match payload { + Payload::Version { encoding, .. } => { + if encoding != Encoding::Component { + bail!( + "the {} is not a WebAssembly component", + if path.is_none() { "given data" } else { "file" } + ); + } + } + Payload::ComponentImportSection(s) => { + for import in s { + let import = import?; + imports.insert(import.name.to_string(), import.ty); + } + } + Payload::ComponentExportSection(s) => { + for export in s { + let export = export?; + exports.insert( + export.name.to_string(), + (export.kind, export.index), + ); + } + } + _ => {} + } + } + ValidPayload::Func(_, _) => {} + ValidPayload::Parser(next) => { + parsers.push(parser); + parser = next; + } + ValidPayload::End(types) => match parsers.pop() { + Some(parent) => parser = parent, + None => { + return Ok(Component { + name, + path, + bytes, + types, + imports, + exports, + }); + } + }, + } + } + Chunk::NeedMoreData(_) => unreachable!(), + } + } + } + + /// Gets the name of the component. + /// + /// Names must be unique within a composition graph. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets the path of the component. + /// + /// Returns `None` if the component was not loaded from a file. + pub fn path(&self) -> Option<&Path> { + self.path.as_deref() + } + + /// Gets the bytes of the component. + pub fn bytes(&self) -> &[u8] { + self.bytes.as_ref() + } + + /// Gets the type information of the component. + pub fn types(&self) -> TypesRef { + self.types.as_ref() + } + + /// Gets an export from the component for the given export index. + pub fn export( + &self, + index: impl Into, + ) -> Option<(&str, ComponentExternalKind, u32)> { + let index = index.into(); + self.exports + .get_index(index.0) + .map(|(name, (kind, index))| (name.as_str(), *kind, *index)) + } + + /// Gets an export from the component for the given export name. + pub fn export_by_name(&self, name: &str) -> Option<(ExportIndex, ComponentExternalKind, u32)> { + self.exports + .get_full(name) + .map(|(i, _, (kind, index))| (ExportIndex(i), *kind, *index)) + } + + /// Gets an iterator over the component's exports. + pub fn exports( + &self, + ) -> impl Iterator + ExactSizeIterator + { + self.exports + .iter() + .enumerate() + .map(|(i, (name, (kind, index)))| (ExportIndex(i), name.as_str(), *kind, *index)) + } + + /// Gets an import from the component for the given import index. + pub fn import(&self, index: impl Into) -> Option<(&str, ComponentTypeRef)> { + let index = index.into(); + self.imports + .get_index(index.0) + .map(|(name, ty)| (name.as_str(), *ty)) + } + + /// Gets an import from the component for the given import name. + pub fn import_by_name(&self, name: &str) -> Option<(ImportIndex, ComponentTypeRef)> { + self.imports + .get_full(name) + .map(|(i, _, ty)| (ImportIndex(i), *ty)) + } + + /// Gets an iterator over the component's imports. + pub fn imports( + &self, + ) -> impl Iterator + ExactSizeIterator { + self.imports + .iter() + .enumerate() + .map(|(i, (name, ty))| (ImportIndex(i), name.as_str(), *ty)) + } + + pub(crate) fn ty(&self) -> wasm_encoder::ComponentType { + let encoder = TypeEncoder::new(&self.types); + + encoder.component( + self.imports() + .map(|(i, ..)| self.import_entity_type(i).unwrap()), + self.exports() + .map(|(i, ..)| self.export_entity_type(i).unwrap()), + ) + } + + pub(crate) fn export_entity_type( + &self, + index: ExportIndex, + ) -> Option<(&str, ComponentEntityType)> { + let (name, kind, index) = self.export(index)?; + Some(( + name, + self.types + .component_entity_type_from_export(&ComponentExport { name, kind, index })?, + )) + } + + pub(crate) fn import_entity_type( + &self, + index: ImportIndex, + ) -> Option<(&str, ComponentEntityType)> { + let (name, ty) = self.import(index)?; + Some(( + name, + self.types + .component_entity_type_from_import(&ComponentImport { name, ty })?, + )) + } + + /// Finds a compatible instance export on the component for the given instance type. + pub(crate) fn find_compatible_export( + &self, + ty: &ComponentInstanceType, + types: TypesRef, + ) -> Option { + self.exports + .iter() + .position(|(_, (kind, index))| { + if *kind != ComponentExternalKind::Instance { + return false; + } + ComponentInstanceType::is_subtype_of( + self.types.component_instance_at(*index).unwrap(), + self.types.as_ref(), + ty, + types, + ) + }) + .map(ExportIndex) + } + + /// Checks to see if an instance of this component would be a + /// subtype of the given instance type. + pub(crate) fn is_instance_subtype_of( + &self, + ty: &ComponentInstanceType, + types: TypesRef, + ) -> bool { + let exports = ty.exports(types); + + for (k, b) in exports { + match self.exports.get_full(k.as_str()) { + Some((ai, _, _)) => { + let (_, a) = self.export_entity_type(ExportIndex(ai)).unwrap(); + if !ComponentEntityType::is_subtype_of(&a, self.types.as_ref(), b, types) { + return false; + } + } + None => return false, + } + } + + true + } +} + +impl std::fmt::Debug for Component<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("Component") + .field("imports", &self.imports) + .field("exports", &self.exports) + .finish_non_exhaustive() + } +} + +static NEXT_COMPONENT_ID: AtomicUsize = AtomicUsize::new(0); + +/// Represents an identifier of a component in a composition graph. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub struct ComponentId(pub usize); + +impl ComponentId { + fn next() -> Result { + let next = NEXT_COMPONENT_ID.fetch_add(1, Ordering::SeqCst); + if next == usize::MAX { + bail!("component limit reached"); + } + Ok(Self(next)) + } +} + +impl std::fmt::Display for ComponentId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for ComponentId { + fn from(id: usize) -> Self { + Self(id) + } +} + +static NEXT_INSTANCE_ID: AtomicUsize = AtomicUsize::new(0); + +/// Represents an identifier of an instance in a composition graph. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub struct InstanceId(pub usize); + +impl std::fmt::Display for InstanceId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl InstanceId { + fn next() -> Result { + let next = NEXT_INSTANCE_ID.fetch_add(1, Ordering::SeqCst); + if next == usize::MAX { + bail!("instance limit reached"); + } + Ok(Self(next)) + } +} + +impl From for InstanceId { + fn from(id: usize) -> Self { + Self(id) + } +} + +/// Represents an index into a component's import list. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ImportIndex(pub usize); + +impl std::fmt::Display for ImportIndex { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for ImportIndex { + fn from(id: usize) -> Self { + Self(id) + } +} + +/// Represents an index into a component's export list. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ExportIndex(pub usize); + +impl std::fmt::Display for ExportIndex { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for ExportIndex { + fn from(id: usize) -> Self { + Self(id) + } +} + +#[derive(Debug)] +pub(crate) struct ComponentEntry<'a> { + pub(crate) component: Component<'a>, + pub(crate) instances: HashSet, +} + +#[derive(Debug)] +pub(crate) struct Instance { + pub(crate) component: ComponentId, + pub(crate) connected: IndexSet, +} + +/// The options for encoding a composition graph. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +pub struct EncodeOptions { + /// Whether or not to define instantiated components. + /// + /// If `false`, components will be imported instead. + pub define_components: bool, + + /// The instance in the graph to export. + /// + /// If `Some`, the instance's exports will be aliased and + /// exported from the resulting component. + pub export: Option, + + /// Whether or not to validate the encoded output. + pub validate: bool, +} + +/// Represents a composition graph used to compose a new component +/// from other components. +#[derive(Debug, Default)] +pub struct CompositionGraph<'a> { + names: HashMap, + pub(crate) components: IndexMap>, + pub(crate) instances: IndexMap, + // Map where each node is an instance in the graph. + // An edge between nodes stores a map of target import index to source export index. + // A source export index of `None` means that the source instance itself is being used. + pub(crate) graph: DiGraphMap>>, +} + +impl<'a> CompositionGraph<'a> { + /// Constructs a new composition graph. + pub fn new() -> Self { + Self::default() + } + + /// Adds a new component to the graph. + /// + /// The component name must be unique. + pub fn add_component(&mut self, component: Component<'a>) -> Result { + let id = match self.names.entry(component.name.clone()) { + Entry::Occupied(e) => { + bail!( + "a component with name `{name}` already exists", + name = e.key() + ) + } + Entry::Vacant(e) => *e.insert(ComponentId::next()?), + }; + + log::info!( + "adding WebAssembly component `{name}` ({id}) to the graph", + name = component.name(), + ); + + let entry = ComponentEntry { + component, + instances: HashSet::new(), + }; + + assert!(self.components.insert(id, entry).is_none()); + + Ok(id) + } + + /// Gets a component from the graph. + pub fn get_component(&self, id: impl Into) -> Option<&Component<'a>> { + self.components.get(&id.into()).map(|e| &e.component) + } + + /// Gets a component from the graph by name. + pub fn get_component_by_name(&self, name: &str) -> Option<(ComponentId, &Component<'a>)> { + let id = self.names.get(name)?; + let entry = &self.components[id]; + Some((*id, &entry.component)) + } + + /// Removes a component from the graph. + /// + /// All instances and connections relating to the component + /// will also be removed. + pub fn remove_component(&mut self, id: impl Into) { + let id = id.into(); + if let Some(entry) = self.components.remove(&id) { + log::info!( + "removing WebAssembly component `{name}` ({id}) from the graph", + name = entry.component.name(), + ); + + assert!(self.names.remove(&entry.component.name).is_some()); + + for instance_id in entry.instances.iter().copied() { + self.instances.remove(&instance_id); + + // Remove any connected indexes from outward edges from the instance being removed + for (_, target_id, map) in self + .graph + .edges_directed(instance_id, EdgeDirection::Outgoing) + { + let target = self.instances.get_mut(&target_id).unwrap(); + for index in map.keys() { + target.connected.remove(index); + } + } + + self.graph.remove_node(instance_id); + } + } + } + + /// Creates a new instance of a component in the composition graph. + pub fn instantiate(&mut self, id: impl Into) -> Result { + let id = id.into(); + let entry = self + .components + .get_mut(&id) + .ok_or_else(|| anyhow!("component does not exist in the graph"))?; + + let instance_id = InstanceId::next()?; + + log::info!( + "instantiating WebAssembly component `{name}` ({id}) with instance identifier {instance_id}", + name = entry.component.name(), + ); + + self.instances.insert( + instance_id, + Instance { + component: id, + connected: Default::default(), + }, + ); + + entry.instances.insert(instance_id); + + Ok(instance_id) + } + + /// Gets the component of the given instance. + pub fn get_component_of_instance( + &self, + id: impl Into, + ) -> Option<(ComponentId, &Component)> { + let id = id.into(); + let instance = self.instances.get(&id)?; + + Some(( + instance.component, + self.get_component(instance.component).unwrap(), + )) + } + + /// Removes an instance from the graph. + /// + /// All connections relating to the instance will also be removed. + pub fn remove_instance(&mut self, id: impl Into) { + let id = id.into(); + if let Some(instance) = self.instances.remove(&id) { + let entry = self.components.get_mut(&instance.component).unwrap(); + + log::info!( + "removing instance ({id}) of component `{name}` ({cid}) from the graph", + name = entry.component.name(), + cid = instance.component.0, + ); + + entry.instances.remove(&id); + + // Remove any connected indexes from outward edges from this instance + for (_, target, map) in self.graph.edges_directed(id, EdgeDirection::Outgoing) { + let target = self.instances.get_mut(&target).unwrap(); + for index in map.keys() { + target.connected.remove(index); + } + } + + self.graph.remove_node(id); + } + } + + /// Creates a connection (edge) between instances in the composition graph. + /// + /// A connection represents an instantiation argument. + /// + /// If `source_export` is `None`, the source instance itself + /// is used as the instantiation argument. + pub fn connect( + &mut self, + source: impl Into + Copy, + source_export: Option + Copy>, + target: impl Into + Copy, + target_import: impl Into + Copy, + ) -> Result<()> { + self.validate_connection(source, source_export, target, target_import)?; + + let source = source.into(); + let source_export = source_export.map(Into::into); + let target = target.into(); + let target_import = target_import.into(); + + match source_export { + Some(export) => log::info!("connecting export {export} of instance {source} to import `{target_import}` of instance {target}"), + None => log::info!("connecting instance {source} to import {target_import} of instance {target}"), + } + + self.instances + .get_mut(&target) + .unwrap() + .connected + .insert(target_import); + + if let Some(map) = self.graph.edge_weight_mut(source, target) { + assert!(map.insert(target_import, source_export).is_none()); + } else { + let mut map = IndexMap::new(); + map.insert(target_import, source_export); + self.graph.add_edge(source, target, map); + } + + Ok(()) + } + + /// Disconnects a previous connection between instances. + /// + /// Requires that the source and target instances are valid. + /// + /// If the source and target are not connected via the target's import, + /// then this is a no-op. + pub fn disconnect( + &mut self, + source: impl Into, + target: impl Into, + target_import: impl Into, + ) -> Result<()> { + let source = source.into(); + let target = target.into(); + let target_import = target_import.into(); + + log::info!("disconnecting import {target_import} of instance {target}"); + + if !self.instances.contains_key(&source) { + bail!("the source instance does not exist in the graph"); + } + + let target_instance = self + .instances + .get_mut(&target) + .ok_or_else(|| anyhow!("the target instance does not exist in the graph"))?; + + target_instance.connected.remove(&target_import); + + let remove_edge = if let Some(set) = self.graph.edge_weight_mut(source, target) { + set.remove(&target_import); + set.is_empty() + } else { + false + }; + + if remove_edge { + self.graph.remove_edge(source, target); + } + + Ok(()) + } + + /// Validates a connection between two instances in the graph. + /// + /// Use `None` for `source_export` to signify that the instance + /// itself should be the source for the connection. + /// + /// Returns `Err(_)` if the connection would not be valid. + pub fn validate_connection( + &self, + source: impl Into, + source_export: Option>, + target: impl Into, + target_import: impl Into, + ) -> Result<()> { + let source = source.into(); + let source_export = source_export.map(Into::into); + let target = target.into(); + let target_import = target_import.into(); + + if source == target { + bail!("an instance cannot be connected to itself"); + } + + let source_instance = self + .instances + .get(&source) + .ok_or_else(|| anyhow!("the source instance does not exist in the graph"))?; + + let source_component = &self.components[&source_instance.component].component; + + let target_instance = self + .instances + .get(&target) + .ok_or_else(|| anyhow!("the target instance does not exist in the graph"))?; + + let target_component = &self.components[&target_instance.component].component; + let (import_name, import_ty) = target_component + .import_entity_type(target_import) + .ok_or_else(|| anyhow!("the target import index is invalid"))?; + + if target_instance.connected.contains(&target_import) { + bail!( + "{import_ty} import `{import_name}` is already connected", + import_ty = type_desc(import_ty) + ); + } + + if let Some(export_index) = source_export { + let (export_name, export_ty) = source_component + .export_entity_type(export_index) + .ok_or_else(|| anyhow!("the source export index is invalid"))?; + + if !ComponentEntityType::is_subtype_of( + &export_ty, + source_component.types.as_ref(), + &import_ty, + target_component.types.as_ref(), + ) { + bail!( + "source {export_ty} export `{export_name}` is not compatible with target {import_ty} import `{import_name}`", + export_ty = type_desc(export_ty), + import_ty = type_desc(import_ty), + ); + } + } else { + let ty = match import_ty { + ComponentEntityType::Instance(id) => target_component + .types + .type_from_id(id) + .unwrap() + .as_component_instance_type() + .unwrap(), + _ => bail!( + "source instance is not compatible with target {import_ty} import `{import_name}`", + import_ty = type_desc(import_ty) + ), + }; + + if !source_component.is_instance_subtype_of(ty, target_component.types.as_ref()) { + bail!( + "source instance is not compatible with target {import_ty} import `{import_name}`", + import_ty = type_desc(import_ty) + ); + } + }; + + Ok(()) + } + + /// Encodes the current composition graph as a WebAssembly component. + pub fn encode(&self, options: EncodeOptions) -> Result> { + let bytes = CompositionGraphEncoder::new(options, self).encode()?; + + if options.validate { + Validator::new_with_features(WasmFeatures { + component_model: true, + ..Default::default() + }) + .validate_all(&bytes) + .context("failed to validate encoded graph bytes")?; + } + + Ok(bytes) + } + + /// Gets the topological instantiation order based on the composition graph. + /// + /// If an instance is not in the returned set, it is considered to be + /// "independent" (i.e it has no dependencies on other instances). + pub(crate) fn instantiation_order(&self) -> Result> { + toposort(&self.graph, None).map_err(|e| { + let id = e.node_id(); + let instance = &self.instances[&id]; + anyhow!( + "an instantiation of component `{name}` and its dependencies form a cycle in the instantiation graph", + name = self.components[&instance.component].component.name, + ) + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn it_rejects_modules() -> Result<()> { + match Component::from_bytes("a", b"(module)".as_ref()) { + Ok(_) => panic!("expected a failure to parse"), + Err(e) => assert_eq!( + format!("{e:#}"), + "failed to parse component: the given data is not a WebAssembly component" + ), + } + + Ok(()) + } + + #[test] + fn it_rejects_invalid_components() -> Result<()> { + match Component::from_bytes("a", b"(component (export \"x\" (func 0)))".as_ref()) { + Ok(_) => panic!("expected a failure to parse"), + Err(e) => assert_eq!(format!("{e:#}"), "failed to parse component: unknown function 0: function index out of bounds (at offset 0xb)"), + } + + Ok(()) + } + + #[test] + fn it_ensures_unique_component_names() -> Result<()> { + let mut graph = CompositionGraph::new(); + graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + + match graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?) { + Ok(_) => panic!("expected a failure to add component"), + Err(e) => assert_eq!(format!("{e:#}"), "a component with name `a` already exists"), + } + + Ok(()) + } + + #[test] + fn it_fails_to_instantiate_a_missing_component() -> Result<()> { + let mut graph = CompositionGraph::new(); + match graph.instantiate(ComponentId(0)) { + Ok(_) => panic!("expected a failure to instantiate"), + Err(e) => assert_eq!(format!("{e:#}"), "component does not exist in the graph"), + } + + Ok(()) + } + + #[test] + fn it_instantiates_a_component() -> Result<()> { + let mut graph = CompositionGraph::new(); + let id = graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + let id = graph.instantiate(id)?; + assert_eq!(graph.get_component_of_instance(id).unwrap().1.name(), "a"); + Ok(()) + } + + #[test] + fn it_cannot_get_a_component_of_missing_instance() -> Result<()> { + let graph = CompositionGraph::new(); + assert!(graph.get_component_of_instance(InstanceId(0)).is_none()); + Ok(()) + } + + #[test] + fn it_gets_a_component() -> Result<()> { + let mut graph = CompositionGraph::new(); + let id = graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + assert_eq!(graph.get_component(id).unwrap().name(), "a"); + assert_eq!(graph.get_component_by_name("a").unwrap().1.name(), "a"); + Ok(()) + } + + #[test] + fn it_removes_a_component() -> Result<()> { + let mut graph = CompositionGraph::new(); + let a = graph.add_component(Component::from_bytes( + "a", + b"(component (import \"x\" (func)))".as_ref(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component (import \"x\" (func)) (export \"x\" (func 0)))".as_ref(), + )?)?; + let ai = graph.instantiate(a)?; + let bi = graph.instantiate(b)?; + graph.connect(bi, Some(0), ai, 0)?; + + assert!(graph.get_component(a).is_some()); + assert!(graph.get_component(b).is_some()); + assert_eq!(graph.components.len(), 2); + assert_eq!(graph.instances.len(), 2); + assert_eq!(graph.graph.node_count(), 2); + assert_eq!(graph.graph.edge_count(), 1); + + graph.remove_component(b); + + assert!(graph.get_component(a).is_some()); + assert!(graph.get_component(b).is_none()); + assert_eq!(graph.components.len(), 1); + assert_eq!(graph.instances.len(), 1); + assert_eq!(graph.graph.node_count(), 1); + assert_eq!(graph.graph.edge_count(), 0); + Ok(()) + } + + #[test] + fn it_removes_a_connection() -> Result<()> { + let mut graph = CompositionGraph::new(); + let a = graph.add_component(Component::from_bytes( + "a", + b"(component (import \"x\" (func)))".as_ref(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component (import \"x\" (func)) (export \"x\" (func 0)))".as_ref(), + )?)?; + let ai = graph.instantiate(a)?; + let bi = graph.instantiate(b)?; + graph.connect(bi, Some(0), ai, 0)?; + + assert_eq!(graph.graph.node_count(), 2); + assert_eq!(graph.graph.edge_count(), 1); + + graph.disconnect(bi, ai, 0)?; + + assert_eq!(graph.graph.node_count(), 2); + assert_eq!(graph.graph.edge_count(), 0); + Ok(()) + } + + #[test] + fn it_requires_source_to_disconnect() -> Result<()> { + let mut graph = CompositionGraph::new(); + let a = graph.add_component(Component::from_bytes( + "a", + b"(component (import \"x\" (func)))".as_ref(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component (import \"x\" (func)) (export \"x\" (func 0)))".as_ref(), + )?)?; + let ai = graph.instantiate(a)?; + let bi = graph.instantiate(b)?; + graph.connect(bi, Some(0), ai, 0)?; + + match graph.disconnect(101, ai, 0) { + Ok(_) => panic!("expected a failure to disconnect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "the source instance does not exist in the graph" + ), + } + + Ok(()) + } + + #[test] + fn it_requires_a_target_to_disconnect() -> Result<()> { + let mut graph = CompositionGraph::new(); + let a = graph.add_component(Component::from_bytes( + "a", + b"(component (import \"x\" (func)))".as_ref(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component (import \"x\" (func)) (export \"x\" (func 0)))".as_ref(), + )?)?; + let ai = graph.instantiate(a)?; + let bi = graph.instantiate(b)?; + graph.connect(bi, Some(0), ai, 0)?; + + match graph.disconnect(bi, 101, 0) { + Ok(_) => panic!("expected a failure to disconnect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "the target instance does not exist in the graph" + ), + } + + Ok(()) + } + + #[test] + fn it_validates_connections() -> Result<()> { + let mut graph = CompositionGraph::new(); + let a = graph.add_component(Component::from_bytes( + "a", + b"(component (import \"i1\" (func)) (import \"i2\" (instance (export \"\" (func)))))" + .as_ref(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component (import \"i1\" (func)) (import \"i2\" (core module)) (export \"e1\" (func 0)) (export \"e2\" (core module 0)))".as_ref(), + )?)?; + let ai = graph.instantiate(a)?; + let bi = graph.instantiate(b)?; + + match graph.connect(ai, None::, ai, 0) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "an instance cannot be connected to itself" + ), + } + + match graph.connect(ai, Some(0), bi, 0) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!(format!("{e:#}"), "the source export index is invalid"), + } + + match graph.connect(101, Some(0), ai, 0) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "the source instance does not exist in the graph" + ), + } + + match graph.connect(bi, Some(0), 101, 0) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "the target instance does not exist in the graph" + ), + } + + match graph.connect(bi, Some(101), ai, 0) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!(format!("{e:#}"), "the source export index is invalid"), + } + + match graph.connect(bi, Some(0), ai, 101) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!(format!("{e:#}"), "the target import index is invalid"), + } + + match graph.connect(bi, Some(1), ai, 0) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "source module export `e2` is not compatible with target function import `i1`" + ), + } + + match graph.connect(bi, None::, ai, 0) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "source instance is not compatible with target function import `i1`" + ), + } + + match graph.connect(bi, None::, ai, 1) { + Ok(_) => panic!("expected a failure to connect"), + Err(e) => assert_eq!( + format!("{e:#}"), + "source instance is not compatible with target instance import `i2`" + ), + } + + Ok(()) + } + + #[test] + fn it_cannot_encode_a_cycle() -> Result<()> { + let mut graph = CompositionGraph::new(); + let a = graph.add_component(Component::from_bytes( + "a", + b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(), + )?)?; + let ai = graph.instantiate(a)?; + let bi = graph.instantiate(b)?; + + graph.connect(ai, Some(0), bi, 0)?; + graph.connect(bi, Some(0), ai, 0)?; + + match graph.encode(EncodeOptions { + define_components: false, + export: None, + validate: true, + }) { + Ok(_) => panic!("graph should not encode"), + Err(e) => assert_eq!(format!("{e:#}"), "an instantiation of component `b` and its dependencies form a cycle in the instantiation graph"), + } + + Ok(()) + } + + #[test] + fn it_encodes_an_empty_component() -> Result<()> { + let mut graph = CompositionGraph::new(); + graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + graph.add_component(Component::from_bytes("b", b"(component)".as_ref())?)?; + + let encoded = graph.encode(EncodeOptions { + define_components: false, + export: None, + validate: true, + })?; + + let wat = wasmprinter::print_bytes(&encoded)?; + assert_eq!(r#"(component)"#, wat); + + Ok(()) + } + + #[test] + fn it_encodes_component_imports() -> Result<()> { + let mut graph = CompositionGraph::new(); + // Add a component that doesn't get instantiated (shouldn't be imported) + graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + let b = graph.add_component(Component::from_bytes("b", b"(component)".as_ref())?)?; + graph.instantiate(b)?; + + let encoded = graph.encode(EncodeOptions { + define_components: false, + export: None, + validate: true, + })?; + + let wat = wasmprinter::print_bytes(&encoded)?.replace("\r\n", "\n"); + assert_eq!( + r#"(component + (type (;0;) + (component) + ) + (import "b" (component (;0;) (type 0))) + (instance (;0;) (instantiate 0)) +)"#, + wat + ); + + Ok(()) + } + + #[test] + fn it_encodes_defined_components() -> Result<()> { + let mut graph = CompositionGraph::new(); + // Add a component that doesn't get instantiated (shouldn't be imported) + graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + let b = graph.add_component(Component::from_bytes("b", b"(component)".as_ref())?)?; + graph.instantiate(b)?; + + let encoded = graph.encode(EncodeOptions { + define_components: true, + export: None, + validate: true, + })?; + + let wat = wasmprinter::print_bytes(&encoded)?.replace("\r\n", "\n"); + assert_eq!( + r#"(component + (component (;0;)) + (instance (;0;) (instantiate 0)) +)"#, + wat + ); + + Ok(()) + } + + #[test] + fn it_encodes_a_simple_composition() -> Result<()> { + let mut graph = CompositionGraph::new(); + let a = graph.add_component(Component::from_bytes( + "a", + b"(component + (import \"i1\" (instance (export \"e1\" (func)) (export \"e3\" (func (param \"a\" u32))))) + (import \"i2\" (func)) + (import \"i3\" (component)) + (import \"i4\" (core module)) + (export \"e1\" (instance 0)) + (export \"e2\" (func 0)) + (export \"e3\" (component 0)) + (export \"e4\" (core module 0)) +)" + .as_ref(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component + (import \"i1\" (instance (export \"e2\" (func)) (export \"e3\" (func (param \"a\" u64))))) + (import \"i2\" (func)) + (import \"i3\" (component)) + (import \"i4\" (core module)) +)" + .as_ref(), + )?)?; + + let ai = graph.instantiate(a)?; + let bi1 = graph.instantiate(b)?; + let bi2 = graph.instantiate(b)?; + let bi3 = graph.instantiate(b)?; + + // Skip the instance arguments so a merged instance is imported + for i in 1..=3 { + graph.connect(ai, Some(i), bi1, i)?; + graph.connect(ai, Some(i), bi2, i)?; + graph.connect(ai, Some(i), bi3, i)?; + } + + let encoded = graph.encode(EncodeOptions { + define_components: true, + export: None, + validate: true, + })?; + + let wat = wasmprinter::print_bytes(&encoded)?.replace("\r\n", "\n"); + assert_eq!( + r#"(component + (core type (;0;) + (module) + ) + (type (;0;) + (instance + (type (;0;) (func)) + (export "e1" (func (type 0))) + (type (;1;) (func (param "a" u64))) + (export "e3" (func (type 1))) + (type (;2;) (func)) + (export "e2" (func (type 2))) + ) + ) + (type (;1;) (func)) + (type (;2;) + (component) + ) + (import "i1" (instance (;0;) (type 0))) + (import "i2" (func (;0;) (type 1))) + (import "i3" (component (;0;) (type 2))) + (import "i4" (core module (;0;) (type 0))) + (component (;1;) + (type (;0;) + (instance + (type (;0;) (func)) + (export "e1" (func (type 0))) + (type (;1;) (func (param "a" u32))) + (export "e3" (func (type 1))) + ) + ) + (import "i1" (instance (;0;) (type 0))) + (type (;1;) (func)) + (import "i2" (func (;0;) (type 1))) + (type (;2;) + (component) + ) + (import "i3" (component (;0;) (type 2))) + (core type (;0;) + (module) + ) + (import "i4" (core module (;0;) (type 0))) + (export "e1" (instance 0)) + (export "e2" (func 0)) + (export "e3" (component 0)) + (export "e4" (core module 0)) + ) + (component (;2;) + (type (;0;) + (instance + (type (;0;) (func)) + (export "e2" (func (type 0))) + (type (;1;) (func (param "a" u64))) + (export "e3" (func (type 1))) + ) + ) + (import "i1" (instance (;0;) (type 0))) + (type (;1;) (func)) + (import "i2" (func (;0;) (type 1))) + (type (;2;) + (component) + ) + (import "i3" (component (;0;) (type 2))) + (core type (;0;) + (module) + ) + (import "i4" (core module (;0;) (type 0))) + ) + (instance (;1;) (instantiate 1 + (with "i1" (instance 0)) + (with "i2" (func 0)) + (with "i3" (component 0)) + (with "i4" (core module 0)) + ) + ) + (alias export 1 "e2" (func (;1;))) + (alias export 1 "e3" (component (;3;))) + (alias export 1 "e4" (core module (;1;))) + (instance (;2;) (instantiate 2 + (with "i2" (func 1)) + (with "i3" (component 3)) + (with "i4" (core module 1)) + (with "i1" (instance 0)) + ) + ) + (instance (;3;) (instantiate 2 + (with "i2" (func 1)) + (with "i3" (component 3)) + (with "i4" (core module 1)) + (with "i1" (instance 0)) + ) + ) + (instance (;4;) (instantiate 2 + (with "i2" (func 1)) + (with "i3" (component 3)) + (with "i4" (core module 1)) + (with "i1" (instance 0)) + ) + ) +)"#, + wat + ); + + Ok(()) + } +} diff --git a/crates/wasm-compose/src/lib.rs b/crates/wasm-compose/src/lib.rs index affea3c340..eeb51e3c91 100644 --- a/crates/wasm-compose/src/lib.rs +++ b/crates/wasm-compose/src/lib.rs @@ -7,3 +7,4 @@ pub mod cli; pub mod composer; pub mod config; pub(crate) mod encoding; +pub mod graph; diff --git a/crates/wasm-compose/tests/compositions/complex-import/composed.wat b/crates/wasm-compose/tests/compositions/complex-import/composed.wat index 06d2fed50b..31a56b616e 100644 --- a/crates/wasm-compose/tests/compositions/complex-import/composed.wat +++ b/crates/wasm-compose/tests/compositions/complex-import/composed.wat @@ -1,66 +1,19 @@ (component (type (;0;) (component - (type (;0;) (record (field "a" s8) (field "b" u8) (field "c" s16) (field "d" u16) (field "e" s32) (field "f" u32) (field "g" s64) (field "h" u64) (field "i" float32) (field "j" float64) (field "k" bool) (field "l" string))) - (export "record1" (type (eq 0))) - (type (;1;) (flags "a" "b" "c")) - (export "flags1" (type (eq 1))) - (type (;2;) (enum "a" "b" "c")) - (export "enum1" (type (eq 2))) - (type (;3;) (union s8 string 0)) - (export "union1" (type (eq 3))) - (type (;4;) (variant (case "a" s8) (case "b" u8) (case "c" s16) (case "d" u16) (case "e" s32) (case "f" u32) (case "g" s64) (case "h" u64) (case "i" float32) (case "j" float64) (case "k" bool) (case "l" string) (case "m" 0))) - (export "variant1" (type (eq 4))) - (type (;5;) (func)) - (export "a" (func (type 5))) - (type (;6;) (func (param "x" s8))) - (export "b" (func (type 6))) - (type (;7;) (func (param "x" u8))) - (export "c" (func (type 7))) - (type (;8;) (func (param "x" s16))) - (export "d" (func (type 8))) - (type (;9;) (func (param "x" u16))) - (export "e" (func (type 9))) - (type (;10;) (func (param "x" s32))) - (export "f" (func (type 10))) - (type (;11;) (func (param "x" u32))) - (export "g" (func (type 11))) - (type (;12;) (func (param "x" s64))) - (export "h" (func (type 12))) - (type (;13;) (func (param "x" u64))) - (export "i" (func (type 13))) - (type (;14;) (func (param "x" float32))) - (export "j" (func (type 14))) - (type (;15;) (func (param "x" float64))) - (export "k" (func (type 15))) - (type (;16;) (func (param "x" bool))) - (export "l" (func (type 16))) - (type (;17;) (func (param "x" string))) - (export "m" (func (type 17))) - (type (;18;) (func (param "x" 0))) - (export "n" (func (type 18))) - (type (;19;) (list 0)) - (type (;20;) (func (param "x" 19))) - (export "o" (func (type 20))) - (type (;21;) (tuple 0 string)) - (type (;22;) (func (param "x" 21))) - (export "p" (func (type 22))) - (type (;23;) (func (param "x" 1))) - (export "q" (func (type 23))) - (type (;24;) (func (param "x" 2))) - (export "r" (func (type 24))) - (type (;25;) (func (param "x" 3))) - (export "s" (func (type 25))) - (type (;26;) (option 4)) - (type (;27;) (func (param "x" 26))) - (export "t" (func (type 27))) - (type (;28;) (result 0 (error string))) - (type (;29;) (func (result 28))) - (export "u" (func (type 29))) + (type (;0;) + (instance + (type (;0;) (func (param "x" string) (result string))) + (export "m" (func (type 0))) + ) + ) + (import "b1" (instance (type 0))) + (import "b2" (instance (type 0))) + (type (;1;) (func (param "x" string) (result string))) + (export "m1" (func (type 1))) + (export "m2" (func (type 1))) ) ) - (import "a" (component (;0;) (type 0))) - (instance (;0;) (instantiate 0)) (type (;1;) (component (type (;0;) @@ -133,7 +86,70 @@ (export "x" (instance (type 1))) ) ) + (type (;2;) + (component + (type (;0;) (record (field "a" s8) (field "b" u8) (field "c" s16) (field "d" u16) (field "e" s32) (field "f" u32) (field "g" s64) (field "h" u64) (field "i" float32) (field "j" float64) (field "k" bool) (field "l" string))) + (export "record1" (type (eq 0))) + (type (;1;) (flags "a" "b" "c")) + (export "flags1" (type (eq 1))) + (type (;2;) (enum "a" "b" "c")) + (export "enum1" (type (eq 2))) + (type (;3;) (union s8 string 0)) + (export "union1" (type (eq 3))) + (type (;4;) (variant (case "a" s8) (case "b" u8) (case "c" s16) (case "d" u16) (case "e" s32) (case "f" u32) (case "g" s64) (case "h" u64) (case "i" float32) (case "j" float64) (case "k" bool) (case "l" string) (case "m" 0))) + (export "variant1" (type (eq 4))) + (type (;5;) (func)) + (export "a" (func (type 5))) + (type (;6;) (func (param "x" s8))) + (export "b" (func (type 6))) + (type (;7;) (func (param "x" u8))) + (export "c" (func (type 7))) + (type (;8;) (func (param "x" s16))) + (export "d" (func (type 8))) + (type (;9;) (func (param "x" u16))) + (export "e" (func (type 9))) + (type (;10;) (func (param "x" s32))) + (export "f" (func (type 10))) + (type (;11;) (func (param "x" u32))) + (export "g" (func (type 11))) + (type (;12;) (func (param "x" s64))) + (export "h" (func (type 12))) + (type (;13;) (func (param "x" u64))) + (export "i" (func (type 13))) + (type (;14;) (func (param "x" float32))) + (export "j" (func (type 14))) + (type (;15;) (func (param "x" float64))) + (export "k" (func (type 15))) + (type (;16;) (func (param "x" bool))) + (export "l" (func (type 16))) + (type (;17;) (func (param "x" string))) + (export "m" (func (type 17))) + (type (;18;) (func (param "x" 0))) + (export "n" (func (type 18))) + (type (;19;) (list 0)) + (type (;20;) (func (param "x" 19))) + (export "o" (func (type 20))) + (type (;21;) (tuple 0 string)) + (type (;22;) (func (param "x" 21))) + (export "p" (func (type 22))) + (type (;23;) (func (param "x" 1))) + (export "q" (func (type 23))) + (type (;24;) (func (param "x" 2))) + (export "r" (func (type 24))) + (type (;25;) (func (param "x" 3))) + (export "s" (func (type 25))) + (type (;26;) (option 4)) + (type (;27;) (func (param "x" 26))) + (export "t" (func (type 27))) + (type (;28;) (result 0 (error string))) + (type (;29;) (func (result 28))) + (export "u" (func (type 29))) + ) + ) + (import "$input" (component (;0;) (type 0))) (import "b" (component (;1;) (type 1))) + (import "a" (component (;2;) (type 2))) + (instance (;0;) (instantiate 2)) (instance (;1;) (instantiate 1 (with "a" (instance 0)) ) @@ -142,25 +158,11 @@ (with "a" (instance 0)) ) ) - (component (;2;) - (type (;0;) - (instance - (type (;0;) (func (param "x" string) (result string))) - (export "m" (func (type 0))) - ) - ) - (import "b1" (instance (;0;) (type 0))) - (import "b2" (instance (;1;) (type 0))) - (alias export 0 "m" (func (;0;))) - (alias export 1 "m" (func (;1;))) - (export "m1" (func 0)) - (export "m2" (func 1)) - ) - (alias export 1 "x" (instance (;3;))) - (alias export 2 "x" (instance (;4;))) - (instance (;5;) (instantiate 2 - (with "b2" (instance 3)) - (with "b1" (instance 4)) + (alias export 2 "x" (instance (;3;))) + (alias export 1 "x" (instance (;4;))) + (instance (;5;) (instantiate 0 + (with "b1" (instance 3)) + (with "b2" (instance 4)) ) ) (alias export 5 "m1" (func (;0;))) diff --git a/crates/wasm-compose/tests/compositions/complex-import/config.yml b/crates/wasm-compose/tests/compositions/complex-import/config.yml index b950b08664..4e474d794a 100644 --- a/crates/wasm-compose/tests/compositions/complex-import/config.yml +++ b/crates/wasm-compose/tests/compositions/complex-import/config.yml @@ -1,10 +1,7 @@ +import-components: true dependencies: - a: - path: ../complex/a.wat - import: a - b: - path: ../complex/b.wat - import: b + a: ../complex/a.wat + b: ../complex/b.wat instantiations: b1: diff --git a/crates/wasm-compose/tests/compositions/complex/composed.wat b/crates/wasm-compose/tests/compositions/complex/composed.wat index 21f1a083d2..1d2976f196 100644 --- a/crates/wasm-compose/tests/compositions/complex/composed.wat +++ b/crates/wasm-compose/tests/compositions/complex/composed.wat @@ -1,212 +1,18 @@ (component (component (;0;) - (type (;0;) (func)) - (type (;1;) (func (param "x" s8))) - (type (;2;) (func (param "x" u8))) - (type (;3;) (func (param "x" s16))) - (type (;4;) (func (param "x" u16))) - (type (;5;) (func (param "x" s32))) - (type (;6;) (func (param "x" u32))) - (type (;7;) (func (param "x" s64))) - (type (;8;) (func (param "x" u64))) - (type (;9;) (func (param "x" float32))) - (type (;10;) (func (param "x" float64))) - (type (;11;) (func (param "x" bool))) - (type (;12;) (func (param "x" string))) - (type (;13;) (record (field "a" s8) (field "b" u8) (field "c" s16) (field "d" u16) (field "e" s32) (field "f" u32) (field "g" s64) (field "h" u64) (field "i" float32) (field "j" float64) (field "k" bool) (field "l" string))) - (type (;14;) (func (param "x" 13))) - (type (;15;) (list 13)) - (type (;16;) (func (param "x" 15))) - (type (;17;) (tuple 13 string)) - (type (;18;) (func (param "x" 17))) - (type (;19;) (flags "a" "b" "c")) - (type (;20;) (func (param "x" 19))) - (type (;21;) (enum "a" "b" "c")) - (type (;22;) (func (param "x" 21))) - (type (;23;) (union s8 string 13)) - (type (;24;) (func (param "x" 23))) - (type (;25;) (variant (case "a" s8) (case "b" u8) (case "c" s16) (case "d" u16) (case "e" s32) (case "f" u32) (case "g" s64) (case "h" u64) (case "i" float32) (case "j" float64) (case "k" bool) (case "l" string) (case "m" 13))) - (type (;26;) (option 25)) - (type (;27;) (func (param "x" 26))) - (type (;28;) (result 13 (error string))) - (type (;29;) (func (result 28))) - (export "record1" (type 13)) - (export "flags1" (type 19)) - (export "enum1" (type 21)) - (export "union1" (type 23)) - (export "variant1" (type 25)) - (core module (;0;) - (type (;0;) (func)) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i64))) - (type (;3;) (func (param f32))) - (type (;4;) (func (param f64))) - (type (;5;) (func (param i32 i32))) - (type (;6;) (func (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32))) - (type (;7;) (func (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32 i32 i32))) - (type (;8;) (func (param i32 i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32))) - (type (;9;) (func (param i32 i32 i64 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32))) - (type (;10;) (func (result i32))) - (type (;11;) (func (param i32 i32 i32 i32) (result i32))) - (func $a (;0;) (type 0) - unreachable - ) - (func $b (;1;) (type 1) (param i32) - unreachable - ) - (func $c (;2;) (type 1) (param i32) - unreachable - ) - (func $d (;3;) (type 1) (param i32) - unreachable - ) - (func $e (;4;) (type 1) (param i32) - unreachable - ) - (func $f (;5;) (type 1) (param i32) - unreachable - ) - (func $g (;6;) (type 1) (param i32) - unreachable - ) - (func $h (;7;) (type 2) (param i64) - unreachable - ) - (func $i (;8;) (type 2) (param i64) - unreachable - ) - (func $j (;9;) (type 3) (param f32) - unreachable - ) - (func $k (;10;) (type 4) (param f64) - unreachable - ) - (func $l (;11;) (type 1) (param i32) - unreachable - ) - (func $m (;12;) (type 5) (param i32 i32) - unreachable - ) - (func $n (;13;) (type 6) (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32) - unreachable - ) - (func $o (;14;) (type 5) (param i32 i32) - unreachable - ) - (func $p (;15;) (type 7) (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32 i32 i32) - unreachable - ) - (func $q (;16;) (type 1) (param i32) - unreachable - ) - (func $r (;17;) (type 1) (param i32) - unreachable - ) - (func $s (;18;) (type 8) (param i32 i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32) - unreachable - ) - (func $t (;19;) (type 9) (param i32 i32 i64 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32) - unreachable - ) - (func $u (;20;) (type 10) (result i32) - unreachable - ) - (func $canonical_abi_realloc (;21;) (type 11) (param i32 i32 i32 i32) (result i32) - unreachable + (type (;0;) + (instance + (type (;0;) (func (param "x" string) (result string))) + (export "m" (func (type 0))) ) - (memory (;0;) 0) - (export "memory" (memory 0)) - (export "a" (func $a)) - (export "b" (func $b)) - (export "c" (func $c)) - (export "d" (func $d)) - (export "e" (func $e)) - (export "f" (func $f)) - (export "g" (func $g)) - (export "h" (func $h)) - (export "i" (func $i)) - (export "j" (func $j)) - (export "k" (func $k)) - (export "l" (func $l)) - (export "m" (func $m)) - (export "n" (func $n)) - (export "o" (func $o)) - (export "p" (func $p)) - (export "q" (func $q)) - (export "r" (func $r)) - (export "s" (func $s)) - (export "t" (func $t)) - (export "u" (func $u)) - (export "canonical_abi_realloc" (func $canonical_abi_realloc)) ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "canonical_abi_realloc" (core func (;0;))) - (alias core export 0 "a" (core func (;1;))) - (alias core export 0 "b" (core func (;2;))) - (alias core export 0 "c" (core func (;3;))) - (alias core export 0 "d" (core func (;4;))) - (alias core export 0 "e" (core func (;5;))) - (alias core export 0 "f" (core func (;6;))) - (alias core export 0 "g" (core func (;7;))) - (alias core export 0 "h" (core func (;8;))) - (alias core export 0 "i" (core func (;9;))) - (alias core export 0 "j" (core func (;10;))) - (alias core export 0 "k" (core func (;11;))) - (alias core export 0 "l" (core func (;12;))) - (alias core export 0 "m" (core func (;13;))) - (alias core export 0 "n" (core func (;14;))) - (alias core export 0 "o" (core func (;15;))) - (alias core export 0 "p" (core func (;16;))) - (alias core export 0 "q" (core func (;17;))) - (alias core export 0 "r" (core func (;18;))) - (alias core export 0 "s" (core func (;19;))) - (alias core export 0 "t" (core func (;20;))) - (alias core export 0 "u" (core func (;21;))) - (func (;0;) (type 0) (canon lift (core func 1))) - (func (;1;) (type 1) (canon lift (core func 2))) - (func (;2;) (type 2) (canon lift (core func 3))) - (func (;3;) (type 3) (canon lift (core func 4))) - (func (;4;) (type 4) (canon lift (core func 5))) - (func (;5;) (type 5) (canon lift (core func 6))) - (func (;6;) (type 6) (canon lift (core func 7))) - (func (;7;) (type 7) (canon lift (core func 8))) - (func (;8;) (type 8) (canon lift (core func 9))) - (func (;9;) (type 9) (canon lift (core func 10))) - (func (;10;) (type 10) (canon lift (core func 11))) - (func (;11;) (type 11) (canon lift (core func 12))) - (func (;12;) (type 12) (canon lift (core func 13) (memory 0) (realloc 0) string-encoding=utf8)) - (func (;13;) (type 14) (canon lift (core func 14) (memory 0) (realloc 0) string-encoding=utf8)) - (func (;14;) (type 16) (canon lift (core func 15) (memory 0) (realloc 0) string-encoding=utf8)) - (func (;15;) (type 18) (canon lift (core func 16) (memory 0) (realloc 0) string-encoding=utf8)) - (func (;16;) (type 20) (canon lift (core func 17))) - (func (;17;) (type 22) (canon lift (core func 18))) - (func (;18;) (type 24) (canon lift (core func 19) (memory 0) (realloc 0) string-encoding=utf8)) - (func (;19;) (type 27) (canon lift (core func 20) (memory 0) (realloc 0) string-encoding=utf8)) - (func (;20;) (type 29) (canon lift (core func 21) (memory 0) (realloc 0) string-encoding=utf8)) - (export "a" (func 0)) - (export "b" (func 1)) - (export "c" (func 2)) - (export "d" (func 3)) - (export "e" (func 4)) - (export "f" (func 5)) - (export "g" (func 6)) - (export "h" (func 7)) - (export "i" (func 8)) - (export "j" (func 9)) - (export "k" (func 10)) - (export "l" (func 11)) - (export "m" (func 12)) - (export "n" (func 13)) - (export "o" (func 14)) - (export "p" (func 15)) - (export "q" (func 16)) - (export "r" (func 17)) - (export "s" (func 18)) - (export "t" (func 19)) - (export "u" (func 20)) + (import "b1" (instance (;0;) (type 0))) + (import "b2" (instance (;1;) (type 0))) + (alias export 0 "m" (func (;0;))) + (alias export 1 "m" (func (;1;))) + (export "m1" (func 0)) + (export "m2" (func 1)) ) - (instance (;0;) (instantiate 0)) (component (;1;) (type (;0;) (func)) (type (;1;) (func (param "x" s8))) @@ -537,6 +343,214 @@ ) (export "x" (instance 1)) ) + (component (;2;) + (type (;0;) (func)) + (type (;1;) (func (param "x" s8))) + (type (;2;) (func (param "x" u8))) + (type (;3;) (func (param "x" s16))) + (type (;4;) (func (param "x" u16))) + (type (;5;) (func (param "x" s32))) + (type (;6;) (func (param "x" u32))) + (type (;7;) (func (param "x" s64))) + (type (;8;) (func (param "x" u64))) + (type (;9;) (func (param "x" float32))) + (type (;10;) (func (param "x" float64))) + (type (;11;) (func (param "x" bool))) + (type (;12;) (func (param "x" string))) + (type (;13;) (record (field "a" s8) (field "b" u8) (field "c" s16) (field "d" u16) (field "e" s32) (field "f" u32) (field "g" s64) (field "h" u64) (field "i" float32) (field "j" float64) (field "k" bool) (field "l" string))) + (type (;14;) (func (param "x" 13))) + (type (;15;) (list 13)) + (type (;16;) (func (param "x" 15))) + (type (;17;) (tuple 13 string)) + (type (;18;) (func (param "x" 17))) + (type (;19;) (flags "a" "b" "c")) + (type (;20;) (func (param "x" 19))) + (type (;21;) (enum "a" "b" "c")) + (type (;22;) (func (param "x" 21))) + (type (;23;) (union s8 string 13)) + (type (;24;) (func (param "x" 23))) + (type (;25;) (variant (case "a" s8) (case "b" u8) (case "c" s16) (case "d" u16) (case "e" s32) (case "f" u32) (case "g" s64) (case "h" u64) (case "i" float32) (case "j" float64) (case "k" bool) (case "l" string) (case "m" 13))) + (type (;26;) (option 25)) + (type (;27;) (func (param "x" 26))) + (type (;28;) (result 13 (error string))) + (type (;29;) (func (result 28))) + (export "record1" (type 13)) + (export "flags1" (type 19)) + (export "enum1" (type 21)) + (export "union1" (type 23)) + (export "variant1" (type 25)) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (type (;2;) (func (param i64))) + (type (;3;) (func (param f32))) + (type (;4;) (func (param f64))) + (type (;5;) (func (param i32 i32))) + (type (;6;) (func (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32))) + (type (;7;) (func (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32 i32 i32))) + (type (;8;) (func (param i32 i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32))) + (type (;9;) (func (param i32 i32 i64 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32))) + (type (;10;) (func (result i32))) + (type (;11;) (func (param i32 i32 i32 i32) (result i32))) + (func $a (;0;) (type 0) + unreachable + ) + (func $b (;1;) (type 1) (param i32) + unreachable + ) + (func $c (;2;) (type 1) (param i32) + unreachable + ) + (func $d (;3;) (type 1) (param i32) + unreachable + ) + (func $e (;4;) (type 1) (param i32) + unreachable + ) + (func $f (;5;) (type 1) (param i32) + unreachable + ) + (func $g (;6;) (type 1) (param i32) + unreachable + ) + (func $h (;7;) (type 2) (param i64) + unreachable + ) + (func $i (;8;) (type 2) (param i64) + unreachable + ) + (func $j (;9;) (type 3) (param f32) + unreachable + ) + (func $k (;10;) (type 4) (param f64) + unreachable + ) + (func $l (;11;) (type 1) (param i32) + unreachable + ) + (func $m (;12;) (type 5) (param i32 i32) + unreachable + ) + (func $n (;13;) (type 6) (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32) + unreachable + ) + (func $o (;14;) (type 5) (param i32 i32) + unreachable + ) + (func $p (;15;) (type 7) (param i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32 i32 i32) + unreachable + ) + (func $q (;16;) (type 1) (param i32) + unreachable + ) + (func $r (;17;) (type 1) (param i32) + unreachable + ) + (func $s (;18;) (type 8) (param i32 i32 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32) + unreachable + ) + (func $t (;19;) (type 9) (param i32 i32 i64 i32 i32 i32 i32 i32 i64 i64 f32 f64 i32 i32 i32) + unreachable + ) + (func $u (;20;) (type 10) (result i32) + unreachable + ) + (func $canonical_abi_realloc (;21;) (type 11) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (memory (;0;) 0) + (export "memory" (memory 0)) + (export "a" (func $a)) + (export "b" (func $b)) + (export "c" (func $c)) + (export "d" (func $d)) + (export "e" (func $e)) + (export "f" (func $f)) + (export "g" (func $g)) + (export "h" (func $h)) + (export "i" (func $i)) + (export "j" (func $j)) + (export "k" (func $k)) + (export "l" (func $l)) + (export "m" (func $m)) + (export "n" (func $n)) + (export "o" (func $o)) + (export "p" (func $p)) + (export "q" (func $q)) + (export "r" (func $r)) + (export "s" (func $s)) + (export "t" (func $t)) + (export "u" (func $u)) + (export "canonical_abi_realloc" (func $canonical_abi_realloc)) + ) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (alias core export 0 "canonical_abi_realloc" (core func (;0;))) + (alias core export 0 "a" (core func (;1;))) + (alias core export 0 "b" (core func (;2;))) + (alias core export 0 "c" (core func (;3;))) + (alias core export 0 "d" (core func (;4;))) + (alias core export 0 "e" (core func (;5;))) + (alias core export 0 "f" (core func (;6;))) + (alias core export 0 "g" (core func (;7;))) + (alias core export 0 "h" (core func (;8;))) + (alias core export 0 "i" (core func (;9;))) + (alias core export 0 "j" (core func (;10;))) + (alias core export 0 "k" (core func (;11;))) + (alias core export 0 "l" (core func (;12;))) + (alias core export 0 "m" (core func (;13;))) + (alias core export 0 "n" (core func (;14;))) + (alias core export 0 "o" (core func (;15;))) + (alias core export 0 "p" (core func (;16;))) + (alias core export 0 "q" (core func (;17;))) + (alias core export 0 "r" (core func (;18;))) + (alias core export 0 "s" (core func (;19;))) + (alias core export 0 "t" (core func (;20;))) + (alias core export 0 "u" (core func (;21;))) + (func (;0;) (type 0) (canon lift (core func 1))) + (func (;1;) (type 1) (canon lift (core func 2))) + (func (;2;) (type 2) (canon lift (core func 3))) + (func (;3;) (type 3) (canon lift (core func 4))) + (func (;4;) (type 4) (canon lift (core func 5))) + (func (;5;) (type 5) (canon lift (core func 6))) + (func (;6;) (type 6) (canon lift (core func 7))) + (func (;7;) (type 7) (canon lift (core func 8))) + (func (;8;) (type 8) (canon lift (core func 9))) + (func (;9;) (type 9) (canon lift (core func 10))) + (func (;10;) (type 10) (canon lift (core func 11))) + (func (;11;) (type 11) (canon lift (core func 12))) + (func (;12;) (type 12) (canon lift (core func 13) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;13;) (type 14) (canon lift (core func 14) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;14;) (type 16) (canon lift (core func 15) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;15;) (type 18) (canon lift (core func 16) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;16;) (type 20) (canon lift (core func 17))) + (func (;17;) (type 22) (canon lift (core func 18))) + (func (;18;) (type 24) (canon lift (core func 19) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;19;) (type 27) (canon lift (core func 20) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;20;) (type 29) (canon lift (core func 21) (memory 0) (realloc 0) string-encoding=utf8)) + (export "a" (func 0)) + (export "b" (func 1)) + (export "c" (func 2)) + (export "d" (func 3)) + (export "e" (func 4)) + (export "f" (func 5)) + (export "g" (func 6)) + (export "h" (func 7)) + (export "i" (func 8)) + (export "j" (func 9)) + (export "k" (func 10)) + (export "l" (func 11)) + (export "m" (func 12)) + (export "n" (func 13)) + (export "o" (func 14)) + (export "p" (func 15)) + (export "q" (func 16)) + (export "r" (func 17)) + (export "s" (func 18)) + (export "t" (func 19)) + (export "u" (func 20)) + ) + (instance (;0;) (instantiate 2)) (instance (;1;) (instantiate 1 (with "a" (instance 0)) ) @@ -545,25 +559,11 @@ (with "a" (instance 0)) ) ) - (component (;2;) - (type (;0;) - (instance - (type (;0;) (func (param "x" string) (result string))) - (export "m" (func (type 0))) - ) - ) - (import "b1" (instance (;0;) (type 0))) - (import "b2" (instance (;1;) (type 0))) - (alias export 0 "m" (func (;0;))) - (alias export 1 "m" (func (;1;))) - (export "m1" (func 0)) - (export "m2" (func 1)) - ) - (alias export 1 "x" (instance (;3;))) - (alias export 2 "x" (instance (;4;))) - (instance (;5;) (instantiate 2 - (with "b2" (instance 3)) - (with "b1" (instance 4)) + (alias export 2 "x" (instance (;3;))) + (alias export 1 "x" (instance (;4;))) + (instance (;5;) (instantiate 0 + (with "b1" (instance 3)) + (with "b2" (instance 4)) ) ) (alias export 5 "m1" (func (;0;))) diff --git a/crates/wasm-compose/tests/compositions/component-not-valid/error.txt b/crates/wasm-compose/tests/compositions/component-not-valid/error.txt index a69a833403..62b3a24fc9 100644 --- a/crates/wasm-compose/tests/compositions/component-not-valid/error.txt +++ b/crates/wasm-compose/tests/compositions/component-not-valid/error.txt @@ -1,4 +1,4 @@ -failed to validate WebAssembly component `tests/compositions/component-not-valid/root.wat` +failed to parse component `tests/compositions/component-not-valid/root.wat` Caused by: unknown core function 0: function index out of bounds (at offset 0x12) diff --git a/crates/wasm-compose/tests/compositions/conflict-on-import/error.txt b/crates/wasm-compose/tests/compositions/conflict-on-import/error.txt index 2fb1d40846..2b5cf6dafd 100644 --- a/crates/wasm-compose/tests/compositions/conflict-on-import/error.txt +++ b/crates/wasm-compose/tests/compositions/conflict-on-import/error.txt @@ -1 +1 @@ -cannot import instance `a` due to conflicting types for export `x` between components `tests/compositions/conflict-on-import/root.wat` and `tests/compositions/conflict-on-import/b.wat` +cannot import instance with name `a` for an instantiation argument of component `b` because it conflicts with an imported instantiation argument of component `$input` diff --git a/crates/wasm-compose/tests/compositions/export-on-import/config.yml b/crates/wasm-compose/tests/compositions/export-on-import/config.yml index 4d21d91d02..0a0d48cca5 100644 --- a/crates/wasm-compose/tests/compositions/export-on-import/config.yml +++ b/crates/wasm-compose/tests/compositions/export-on-import/config.yml @@ -1,5 +1,5 @@ instantiations: - $component: + $input: arguments: a: instance: a diff --git a/crates/wasm-compose/tests/compositions/import-conflict/a.wat b/crates/wasm-compose/tests/compositions/import-conflict/a.wat deleted file mode 100644 index e5627d1d0f..0000000000 --- a/crates/wasm-compose/tests/compositions/import-conflict/a.wat +++ /dev/null @@ -1 +0,0 @@ -(component) diff --git a/crates/wasm-compose/tests/compositions/import-conflict/config.yml b/crates/wasm-compose/tests/compositions/import-conflict/config.yml deleted file mode 100644 index e935abbc8b..0000000000 --- a/crates/wasm-compose/tests/compositions/import-conflict/config.yml +++ /dev/null @@ -1,4 +0,0 @@ -dependencies: - a: - path: a.wat - import: b diff --git a/crates/wasm-compose/tests/compositions/import-conflict/error.txt b/crates/wasm-compose/tests/compositions/import-conflict/error.txt deleted file mode 100644 index 3348ce5c4e..0000000000 --- a/crates/wasm-compose/tests/compositions/import-conflict/error.txt +++ /dev/null @@ -1 +0,0 @@ -cannot import dependency `a` (`tests/compositions/import-conflict/a.wat`) with name `b` because it conflicts with an imported instance or component of the same name diff --git a/crates/wasm-compose/tests/compositions/import-conflict/root.wat b/crates/wasm-compose/tests/compositions/import-conflict/root.wat deleted file mode 100644 index e163155980..0000000000 --- a/crates/wasm-compose/tests/compositions/import-conflict/root.wat +++ /dev/null @@ -1,4 +0,0 @@ -(component - (import "a" (instance)) - (import "b" (instance)) -) diff --git a/crates/wasm-compose/tests/compositions/incompatible-explicit-export/config.yml b/crates/wasm-compose/tests/compositions/incompatible-explicit-export/config.yml index 4d21d91d02..0a0d48cca5 100644 --- a/crates/wasm-compose/tests/compositions/incompatible-explicit-export/config.yml +++ b/crates/wasm-compose/tests/compositions/incompatible-explicit-export/config.yml @@ -1,5 +1,5 @@ instantiations: - $component: + $input: arguments: a: instance: a diff --git a/crates/wasm-compose/tests/compositions/instantiation-cycle/config.yml b/crates/wasm-compose/tests/compositions/instantiation-cycle/config.yml index 01691bd5d4..f9e5b5cee3 100644 --- a/crates/wasm-compose/tests/compositions/instantiation-cycle/config.yml +++ b/crates/wasm-compose/tests/compositions/instantiation-cycle/config.yml @@ -1,5 +1,5 @@ instantiations: - $component: + $input: arguments: a: a1 a1: diff --git a/crates/wasm-compose/tests/compositions/instantiation-cycle/error.txt b/crates/wasm-compose/tests/compositions/instantiation-cycle/error.txt index 7b0a2610f8..363960b3b5 100644 --- a/crates/wasm-compose/tests/compositions/instantiation-cycle/error.txt +++ b/crates/wasm-compose/tests/compositions/instantiation-cycle/error.txt @@ -1 +1 @@ -instantiation `a2` and its dependencies form a cycle in the instantiation graph +an instantiation of component `a` and its dependencies form a cycle in the instantiation graph diff --git a/crates/wasm-compose/tests/compositions/invalid-instantiation-arg/config.yml b/crates/wasm-compose/tests/compositions/invalid-instantiation-arg/config.yml index 176e093107..9117c70abf 100644 --- a/crates/wasm-compose/tests/compositions/invalid-instantiation-arg/config.yml +++ b/crates/wasm-compose/tests/compositions/invalid-instantiation-arg/config.yml @@ -1,4 +1,4 @@ instantiations: - $component: + $input: arguments: invalid: a diff --git a/crates/wasm-compose/tests/compositions/merged-import/composed.wat b/crates/wasm-compose/tests/compositions/merged-import/composed.wat index 4a274b3e32..d6f2bfa738 100644 --- a/crates/wasm-compose/tests/compositions/merged-import/composed.wat +++ b/crates/wasm-compose/tests/compositions/merged-import/composed.wat @@ -13,34 +13,34 @@ (component (;0;) (type (;0;) (instance - (type (;0;) (func (param "x" string))) - (export "a" (func (type 0))) - (type (;1;) (func (param "y" u64))) + (type (;0;) (func)) + (export "b" (func (type 0))) + (type (;1;) (func (param "y" u32))) (export "c" (func (type 1))) ) ) (import "a" (instance (;0;) (type 0))) - ) - (instance (;1;) (instantiate 0 - (with "a" (instance 0)) + (type (;1;) + (instance) ) + (import "b" (instance (;1;) (type 1))) ) (component (;1;) (type (;0;) (instance - (type (;0;) (func)) - (export "b" (func (type 0))) - (type (;1;) (func (param "y" u32))) + (type (;0;) (func (param "x" string))) + (export "a" (func (type 0))) + (type (;1;) (func (param "y" u64))) (export "c" (func (type 1))) ) ) (import "a" (instance (;0;) (type 0))) - (type (;1;) - (instance) + ) + (instance (;1;) (instantiate 1 + (with "a" (instance 0)) ) - (import "b" (instance (;1;) (type 1))) ) - (instance (;2;) (instantiate 1 + (instance (;2;) (instantiate 0 (with "b" (instance 1)) (with "a" (instance 0)) ) diff --git a/crates/wasm-compose/tests/compositions/missing-explicit-dep/config.yml b/crates/wasm-compose/tests/compositions/missing-explicit-dep/config.yml index 9db16d0bd6..50b0286f06 100644 --- a/crates/wasm-compose/tests/compositions/missing-explicit-dep/config.yml +++ b/crates/wasm-compose/tests/compositions/missing-explicit-dep/config.yml @@ -2,6 +2,6 @@ dependencies: a: a.wat instantiations: - $component: + $input: arguments: '': a diff --git a/crates/wasm-compose/tests/compositions/missing-explicit-export/config.yml b/crates/wasm-compose/tests/compositions/missing-explicit-export/config.yml index 4d21d91d02..0a0d48cca5 100644 --- a/crates/wasm-compose/tests/compositions/missing-explicit-export/config.yml +++ b/crates/wasm-compose/tests/compositions/missing-explicit-export/config.yml @@ -1,5 +1,5 @@ instantiations: - $component: + $input: arguments: a: instance: a diff --git a/crates/wasm-compose/tests/compositions/module/error.txt b/crates/wasm-compose/tests/compositions/module/error.txt index 06441813f2..94c1e8c4fa 100644 --- a/crates/wasm-compose/tests/compositions/module/error.txt +++ b/crates/wasm-compose/tests/compositions/module/error.txt @@ -1 +1,4 @@ -file `tests/compositions/module/root.wat` is not a WebAssembly component +failed to parse component `tests/compositions/module/root.wat` + +Caused by: + the file is not a WebAssembly component diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index b2acb467bf..8347e308da 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -1898,7 +1898,7 @@ impl ComponentState { let id = self.type_at(idx, false, offset)?; match &types[id] { Type::Defined(_) => Ok(id), - _ => bail!(offset, "type index {} is not a defined type", id.index), + _ => bail!(offset, "type index {} is not a defined type", idx), } }