From c6aef4719840276ce55413aea17df39d79fb993e Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 5 Nov 2024 16:27:39 -0700 Subject: [PATCH] add wasmparser::WasmFeatures support to wasm-compose Signed-off-by: Joel Dice --- crates/wasm-compose/src/composer.rs | 66 +++++++++++------ crates/wasm-compose/src/graph.rs | 102 +++++++++++++++++++++------ crates/wasm-compose/tests/compose.rs | 2 +- src/bin/wasm-tools/compose.rs | 5 +- 4 files changed, 131 insertions(+), 44 deletions(-) diff --git a/crates/wasm-compose/src/composer.rs b/crates/wasm-compose/src/composer.rs index 84ce8339df..3db217814e 100644 --- a/crates/wasm-compose/src/composer.rs +++ b/crates/wasm-compose/src/composer.rs @@ -14,7 +14,7 @@ use std::{collections::VecDeque, ffi::OsStr, path::Path}; use wasmparser::{ component_types::{ComponentEntityType, ComponentInstanceTypeId}, types::TypesRef, - ComponentExternalKind, ComponentTypeRef, + ComponentExternalKind, ComponentTypeRef, WasmFeatures, }; /// The root component name used in configuration. @@ -72,9 +72,13 @@ struct CompositionGraphBuilder<'a> { } impl<'a> CompositionGraphBuilder<'a> { - fn new(root_path: &Path, config: &'a Config) -> Result { + fn new(root_path: &Path, config: &'a Config, features: WasmFeatures) -> Result { let mut graph = CompositionGraph::new(); - graph.add_component(Component::from_file(ROOT_COMPONENT_NAME, root_path)?)?; + graph.add_component(Component::from_file( + ROOT_COMPONENT_NAME, + root_path, + features, + )?)?; let definitions = config .definitions @@ -87,7 +91,7 @@ impl<'a> CompositionGraphBuilder<'a> { ) })?; - let component = Component::from_file(name, config.dir.join(path))?; + let component = Component::from_file(name, config.dir.join(path), features)?; Ok((graph.add_component(component)?, None)) }) @@ -105,19 +109,19 @@ impl<'a> CompositionGraphBuilder<'a> { /// /// 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> { + fn add_component(&mut self, name: &str, features: WasmFeatures) -> Result> { if let Some((id, _)) = self.graph.get_component_by_name(name) { return Ok(Some(id)); } - match self.find_component(name)? { + match self.find_component(name, features)? { Some(component) => Ok(Some(self.graph.add_component(component)?)), None => Ok(None), } } /// Finds the component with the given name on disk. - fn find_component(&self, name: &str) -> Result>> { + fn find_component(&self, name: &str, features: WasmFeatures) -> Result>> { // Check the config for an explicit path (must be a valid component) if let Some(dep) = self.config.dependencies.get(name) { log::debug!( @@ -127,13 +131,14 @@ impl<'a> CompositionGraphBuilder<'a> { return Ok(Some(Component::from_file( name, self.config.dir.join(&dep.path), + features, )?)); } // 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(dir, name)? { + if let Some(component) = Self::parse_component(dir, name, features)? { return Ok(Some(component)); } } @@ -144,7 +149,11 @@ impl<'a> CompositionGraphBuilder<'a> { /// Parses a component from the given directory, if it exists. /// /// Returns `Ok(None)` if the component does not exist. - fn parse_component(dir: &Path, name: &str) -> Result>> { + fn parse_component( + dir: &Path, + name: &str, + features: WasmFeatures, + ) -> Result>> { let mut path = dir.join(name); for ext in ["wasm", "wat"] { @@ -154,7 +163,7 @@ impl<'a> CompositionGraphBuilder<'a> { continue; } - return Ok(Some(Component::from_file(name, &path)?)); + return Ok(Some(Component::from_file(name, &path, features)?)); } Ok(None) @@ -165,12 +174,17 @@ impl<'a> CompositionGraphBuilder<'a> { /// Returns an index into `instances` 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> { + fn instantiate( + &mut self, + name: &str, + component_name: &str, + features: WasmFeatures, + ) -> Result> { if let Some(index) = self.instances.get_index_of(name) { return Ok(Some((index, true))); } - match self.add_component(component_name)? { + match self.add_component(component_name, features)? { Some(component_id) => { let (index, prev) = self .instances @@ -301,13 +315,18 @@ impl<'a> CompositionGraphBuilder<'a> { /// Processes a dependency in the graph. /// /// Returns `Ok(Some(index))` if the dependency resulted in a new dependency instance being created. - fn process_dependency(&mut self, dependency: Dependency) -> Result> { + fn process_dependency( + &mut self, + dependency: Dependency, + features: WasmFeatures, + ) -> Result> { match dependency.kind { DependencyKind::Instance { instance, export } => self.process_instance_dependency( dependency.dependent, dependency.import, &instance, export.as_deref(), + features, ), DependencyKind::Definition { index, export } => { // The dependency is on a definition component, so we simply connect the dependent to the definition's export @@ -348,6 +367,7 @@ impl<'a> CompositionGraphBuilder<'a> { import: InstanceImportRef, instance: &str, export: Option<&str>, + features: WasmFeatures, ) -> Result> { let name = self.config.dependency_name(instance); @@ -356,7 +376,7 @@ impl<'a> CompositionGraphBuilder<'a> { dependent_name = self.instances.get_index(dependent_index).unwrap().0, ); - match self.instantiate(instance, name)? { + match self.instantiate(instance, name, features)? { Some((instance, existing)) => { let (dependent, import_name, import_type) = self.resolve_import_ref(import); @@ -478,12 +498,12 @@ impl<'a> CompositionGraphBuilder<'a> { } /// Build the instantiation graph. - fn build(mut self) -> Result<(InstanceId, CompositionGraph<'a>)> { + fn build(mut self, features: WasmFeatures) -> 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)? + .instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME, features)? .unwrap(); assert!(!existing); @@ -492,7 +512,7 @@ impl<'a> CompositionGraphBuilder<'a> { // Process all remaining dependencies in the queue while let Some(dependency) = queue.pop_front() { - if let Some(instance) = self.process_dependency(dependency)? { + if let Some(instance) = self.process_dependency(dependency, features)? { self.push_dependencies(instance, &mut queue)?; } } @@ -511,6 +531,7 @@ impl<'a> CompositionGraphBuilder<'a> { pub struct ComponentComposer<'a> { component: &'a Path, config: &'a Config, + features: WasmFeatures, } impl<'a> ComponentComposer<'a> { @@ -519,8 +540,12 @@ impl<'a> ComponentComposer<'a> { /// ## Arguments /// * `component` - The path to the component to compose. /// * `config` - The configuration to use for the composition. - pub fn new(component: &'a Path, config: &'a Config) -> Self { - Self { component, config } + pub fn new(component: &'a Path, config: &'a Config, features: WasmFeatures) -> Self { + Self { + component, + config, + features, + } } /// Composes a WebAssembly component based on the composer's configuration. @@ -529,7 +554,8 @@ impl<'a> ComponentComposer<'a> { /// Returns the bytes of the composed component. pub fn compose(&self) -> Result> { let (root_instance, graph) = - CompositionGraphBuilder::new(self.component, self.config)?.build()?; + CompositionGraphBuilder::new(self.component, self.config, self.features)? + .build(self.features)?; // If only the root component was instantiated, then there are no resolved dependencies if graph.instances.len() == 1 { diff --git a/crates/wasm-compose/src/graph.rs b/crates/wasm-compose/src/graph.rs index 8bb1dcdbb8..10166d6b51 100644 --- a/crates/wasm-compose/src/graph.rs +++ b/crates/wasm-compose/src/graph.rs @@ -50,7 +50,7 @@ pub struct Component<'a> { impl<'a> Component<'a> { /// Constructs a new component from reading the given file. - pub fn from_file(name: &str, path: impl AsRef) -> Result { + pub fn from_file(name: &str, path: impl AsRef, features: WasmFeatures) -> Result { let path = path.as_ref(); log::info!("parsing WebAssembly component file `{}`", path.display()); let component = Self::parse( @@ -61,6 +61,7 @@ impl<'a> Component<'a> { format!("failed to parse component `{path}`", path = path.display()) })? .into(), + features, ) .with_context(|| format!("failed to parse component `{path}`", path = path.display()))?; @@ -73,7 +74,11 @@ impl<'a> Component<'a> { } /// Constructs a new component from the given bytes. - pub fn from_bytes(name: impl Into, bytes: impl Into>) -> Result { + pub fn from_bytes( + name: impl Into, + bytes: impl Into>, + features: WasmFeatures, + ) -> Result { let mut bytes = bytes.into(); match wat::parse_bytes(bytes.as_ref()).context("failed to parse component")? { @@ -88,6 +93,7 @@ impl<'a> Component<'a> { ComponentName::new(&name.into(), 0)?.to_string(), None, bytes, + features, ) .context("failed to parse component")?; @@ -96,14 +102,15 @@ impl<'a> Component<'a> { Ok(component) } - fn parse(name: String, path: Option, bytes: Cow<'a, [u8]>) -> Result { + fn parse( + name: String, + path: Option, + bytes: Cow<'a, [u8]>, + features: WasmFeatures, + ) -> Result { let mut parser = Parser::new(0); let mut parsers = Vec::new(); - let mut validator = Validator::new_with_features( - WasmFeatures::WASM2 - | WasmFeatures::COMPONENT_MODEL - | WasmFeatures::COMPONENT_MODEL_ASYNC, - ); + let mut validator = Validator::new_with_features(features); let mut imports = IndexMap::new(); let mut exports = IndexMap::new(); @@ -1040,7 +1047,7 @@ mod test { #[test] fn it_rejects_modules() -> Result<()> { - match Component::from_bytes("a", b"(module)".as_ref()) { + match Component::from_bytes("a", b"(module)".as_ref(), WasmFeatures::default()) { Ok(_) => panic!("expected a failure to parse"), Err(e) => assert_eq!( format!("{e:#}"), @@ -1053,7 +1060,7 @@ mod test { #[test] fn it_rejects_invalid_components() -> Result<()> { - match Component::from_bytes("a", b"(component (export \"x\" (func 0)))".as_ref()) { + match Component::from_bytes("a", b"(component (export \"x\" (func 0)))".as_ref(), WasmFeatures::default()) { 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)"), } @@ -1064,9 +1071,17 @@ mod test { #[test] fn it_ensures_unique_component_names() -> Result<()> { let mut graph = CompositionGraph::new(); - graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?)?; + graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; - match graph.add_component(Component::from_bytes("a", b"(component)".as_ref())?) { + match graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?) { Ok(_) => panic!("expected a failure to add component"), Err(e) => assert_eq!(format!("{e:#}"), "a component with name `a` already exists"), } @@ -1088,7 +1103,11 @@ mod test { #[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.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; let id = graph.instantiate(id)?; assert_eq!(graph.get_component_of_instance(id).unwrap().1.name(), "a"); Ok(()) @@ -1104,7 +1123,11 @@ mod test { #[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())?)?; + let id = graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; assert_eq!(graph.get_component(id).unwrap().name(), "a"); assert_eq!(graph.get_component_by_name("a").unwrap().1.name(), "a"); Ok(()) @@ -1116,10 +1139,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1149,10 +1174,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1174,10 +1201,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1200,10 +1229,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"x\" (func)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1227,10 +1258,11 @@ mod test { "a", b"(component (import \"i1\" (func)) (import \"i2\" (instance (export \"no\" (func)))))" .as_ref(), + WasmFeatures::default(), )?)?; 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(), + b"(component (import \"i1\" (func)) (import \"i2\" (core module)) (export \"e1\" (func 0)) (export \"e2\" (core module 0)))".as_ref(), WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1307,10 +1339,12 @@ mod test { let a = graph.add_component(Component::from_bytes( "a", b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; let bi = graph.instantiate(b)?; @@ -1333,8 +1367,16 @@ mod test { #[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())?)?; + graph.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; + graph.add_component(Component::from_bytes( + "b", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; let encoded = graph.encode(EncodeOptions { define_components: false, @@ -1356,8 +1398,16 @@ mod 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.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; graph.instantiate(b)?; let encoded = graph.encode(EncodeOptions { @@ -1386,8 +1436,16 @@ mod 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.add_component(Component::from_bytes( + "a", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; + let b = graph.add_component(Component::from_bytes( + "b", + b"(component)".as_ref(), + WasmFeatures::default(), + )?)?; graph.instantiate(b)?; let encoded = graph.encode(EncodeOptions { @@ -1428,6 +1486,7 @@ mod test { (export \"e5\" (type 1)) )" .as_ref(), + WasmFeatures::default(), )?)?; let b = graph.add_component(Component::from_bytes( "b", @@ -1440,6 +1499,7 @@ mod test { (import \"i5\" (type (eq 0))) )" .as_ref(), + WasmFeatures::default(), )?)?; let ai = graph.instantiate(a)?; diff --git a/crates/wasm-compose/tests/compose.rs b/crates/wasm-compose/tests/compose.rs index e9e28f231c..f5d9930f9d 100644 --- a/crates/wasm-compose/tests/compose.rs +++ b/crates/wasm-compose/tests/compose.rs @@ -47,7 +47,7 @@ fn component_composing() -> Result<()> { ..Default::default() } }; - let composer = ComponentComposer::new(&root_path, &config); + let composer = ComponentComposer::new(&root_path, &config, WasmFeatures::default()); let r = composer.compose(); let (output, baseline_path) = if error_path.is_file() { diff --git a/src/bin/wasm-tools/compose.rs b/src/bin/wasm-tools/compose.rs index 60f8b212b3..3da0633a97 100644 --- a/src/bin/wasm-tools/compose.rs +++ b/src/bin/wasm-tools/compose.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result}; use clap::Parser; use std::path::{Path, PathBuf}; use wasm_compose::{composer::ComponentComposer, config::Config}; -use wasmparser::Validator; +use wasmparser::{Validator, WasmFeatures}; /// WebAssembly component composer. /// @@ -59,7 +59,8 @@ impl Opts { let config = self.create_config()?; log::debug!("configuration:\n{:#?}", config); - let bytes = ComponentComposer::new(&self.component, &config).compose()?; + let bytes = + ComponentComposer::new(&self.component, &config, WasmFeatures::default()).compose()?; self.output.output_wasm(&self.general, &bytes, self.wat)?;