From 22eaa4096c00598c445854f37baf02bdc42fcbcf Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Sep 2024 19:29:15 -0700 Subject: [PATCH] Support the new name mangling scheme for components This commit adds support for WebAssembly/component-model#378 to `wit-component`. Notably a new set of alternative names are registered and recognized during the module-to-component translation process. Support for the previous set of names are all preserved and will continue to be supported for some time. The new names are, for now, recognized in parallel to the old names. This involved some refactoring to the validation part of `wit-component` and further encapsulation of various names to one small location instead of a shared location for everywhere else to use as well. --- crates/wit-component/src/dummy.rs | 2 +- crates/wit-component/src/encoding.rs | 5 +- crates/wit-component/src/encoding/world.rs | 4 +- crates/wit-component/src/validation.rs | 759 ++++++++++++------ .../tests/components/cm32-names/component.wat | 366 +++++++++ .../components/cm32-names/component.wit.print | 25 + .../tests/components/cm32-names/module.wat | 32 + .../tests/components/cm32-names/module.wit | 30 + .../error.txt | 2 +- .../error-export-sig-mismatch/error.txt | 2 +- crates/wit-parser/src/lib.rs | 55 +- crates/wit-parser/src/resolve.rs | 30 + 12 files changed, 1053 insertions(+), 259 deletions(-) create mode 100644 crates/wit-component/tests/components/cm32-names/component.wat create mode 100644 crates/wit-component/tests/components/cm32-names/component.wit.print create mode 100644 crates/wit-component/tests/components/cm32-names/module.wat create mode 100644 crates/wit-component/tests/components/cm32-names/module.wit diff --git a/crates/wit-component/src/dummy.rs b/crates/wit-component/src/dummy.rs index 6a3cbb457d..50c217a8e9 100644 --- a/crates/wit-component/src/dummy.rs +++ b/crates/wit-component/src/dummy.rs @@ -67,7 +67,7 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { WorldItem::Interface { id: export, .. } => { let name = resolve.name_world_key(name); for (_, func) in resolve.interfaces[*export].functions.iter() { - let name = func.core_export_name(Some(&name)); + let name = func.legacy_core_export_name(Some(&name)); push_func(&mut wat, &name, resolve, func); } diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index b5f0b7d938..5843a7e338 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -72,7 +72,7 @@ //! component model. use crate::metadata::{self, Bindgen, ModuleMetadata}; -use crate::validation::{Export, ExportMap, Import, ImportInstance, ImportMap, RESOURCE_DROP}; +use crate::validation::{Export, ExportMap, Import, ImportInstance, ImportMap}; use crate::StringEncoding; use anyhow::{anyhow, bail, Context, Result}; use indexmap::{IndexMap, IndexSet}; @@ -647,6 +647,7 @@ impl<'a> EncodingState<'a> { } let world = &resolve.worlds[self.info.encoder.metadata.world]; + for export_name in exports { let export_string = resolve.name_world_key(export_name); match &world.exports[export_name] { @@ -1457,7 +1458,7 @@ impl<'a> EncodingState<'a> { Import::ImportedResourceDrop(key, iface, id) => { let ty = &resolve.types[*id]; let name = ty.name.as_ref().unwrap(); - name_tmp = format!("{RESOURCE_DROP}{name}"); + name_tmp = format!("{name}_drop"); (key, &name_tmp, iface.map(|_| resolve.name_world_key(key))) } Import::WorldFunc(key, name) => (key, name, None), diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index e6e763a71a..70f1a89415 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -1,6 +1,6 @@ use super::{Adapter, ComponentEncoder, LibraryInfo, RequiredOptions}; use crate::validation::{ - validate_adapter_module, validate_module, Import, ImportMap, ValidatedModule, RESOURCE_DROP, + validate_adapter_module, validate_module, Import, ImportMap, ValidatedModule, }; use anyhow::{Context, Result}; use indexmap::{IndexMap, IndexSet}; @@ -461,7 +461,7 @@ impl ImportedInterface { let name = ty.name.as_deref().expect("resources must be named"); if required.resource_drops.contains(&id) { - let name = format!("{RESOURCE_DROP}{name}"); + let name = format!("{name}_drop"); let prev = self.lowerings.insert(name, Lowering::ResourceDrop(id)); assert!(prev.is_none()); } diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index 489a1ab120..63a1224752 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -33,21 +33,6 @@ fn wasm_sig_to_func_type(signature: WasmSignature) -> FuncType { ) } -pub const MAIN_MODULE_IMPORT_NAME: &str = "__main_module__"; - -/// The module name used when a top-level function in a world is imported into a -/// core wasm module. Note that this is not a valid WIT identifier to avoid -/// clashes with valid WIT interfaces. This is also not empty because LLVM -/// interprets an empty module import string as "not specified" which means it -/// turns into `env`. -pub const BARE_FUNC_MODULE_NAME: &str = "$root"; - -pub const RESOURCE_DROP: &str = "[resource-drop]"; -pub const RESOURCE_REP: &str = "[resource-rep]"; -pub const RESOURCE_NEW: &str = "[resource-new]"; - -pub const POST_RETURN_PREFIX: &str = "cabi_post_"; - /// Metadata about a validated module and what was found internally. /// /// This structure houses information about `imports` and `exports` to the @@ -203,14 +188,10 @@ pub enum Import { impl ImportMap { /// Returns whether the top-level world function `func` is imported. pub fn uses_toplevel_func(&self, func: &str) -> bool { - let item = self - .names - .get(BARE_FUNC_MODULE_NAME) - .and_then(|map| match map { - ImportInstance::Names(names) => names.get(func), - _ => None, - }); - matches!(item, Some(Import::WorldFunc(_, _))) + self.imports().any(|(_, _, item)| match item { + Import::WorldFunc(_, name) => func == name, + _ => false, + }) } /// Returns whether the interface function specified is imported. @@ -302,8 +283,6 @@ impl ImportMap { adapters: &IndexSet<&str>, types: TypesRef<'_>, ) -> Result { - let world = &resolve.worlds[world_id]; - // Special-case the main module's memory imported into adapters which // currently with `wasm-ld` is not easily configurable. if import.module == "env" && import.name == "memory" { @@ -311,7 +290,7 @@ impl ImportMap { } // Special-case imports from the main module into adapters. - if import.module == MAIN_MODULE_IMPORT_NAME { + if import.module == "__main_module__" { return Ok(Import::MainModuleExport { name: import.name.to_string(), kind: match import.ty { @@ -330,10 +309,33 @@ impl ImportMap { }; let ty = types[types.core_type_at(ty_index).unwrap_sub()].unwrap_func(); - // Handle top-level function imports if they're going through the "bare - // name" representing the world root. - if import.module == BARE_FUNC_MODULE_NAME { - let name = import.name; + // Handle main module imports that match known adapters and set it up as + // an import of an adapter export. + if adapters.contains(import.module) { + return Ok(Import::AdapterExport(ty.clone())); + } + + let (module, names) = match import.module.strip_prefix("cm32p2") { + Some(suffix) => (suffix, STANDARD), + None => (import.module, LEGACY), + }; + self.classify_component_model_import(module, import.name, resolve, world_id, ty, names) + } + + /// Attempts to classify the import `{module}::{name}` with the rules + /// specified in WebAssembly/component-model#378 + fn classify_component_model_import( + &self, + module: &str, + name: &str, + resolve: &Resolve, + world_id: WorldId, + ty: &FuncType, + names: &dyn NameMangling, + ) -> Result { + let world = &resolve.worlds[world_id]; + + if module == names.import_root() { let key = WorldKey::Name(name.to_string()); if let Some(WorldItem::Function(func)) = world.imports.get(&key) { validate_func(resolve, ty, func, AbiVariant::GuestImport)?; @@ -341,8 +343,12 @@ impl ImportMap { } let get_resource = resource_test_for_world(resolve, world_id); - if let Some(id) = valid_resource_drop(name, ty, get_resource)? { - return Ok(Import::ImportedResourceDrop(key, None, id)); + if let Some(resource) = names.resource_drop_name(name) { + if let Some(id) = get_resource(resource) { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ImportedResourceDrop(key, None, id)); + } } match world.imports.get(&key) { @@ -351,143 +357,59 @@ impl ImportMap { } } - // Handle main module imports that match known adapters and set it up as - // an import of an adapter export. - if adapters.contains(import.module) { - return Ok(Import::AdapterExport(ty.clone())); - } + let interface = match module.strip_prefix(names.import_non_root_prefix()) { + Some(name) => name, + None => bail!("unknown or invalid component model import syntax"), + }; - // Handle imports which are used to manipulate state for exported - // resources. - if let Some(suffix) = import.module.strip_prefix("[export]") { - let (key, id) = self.module_to_interface(suffix, resolve, &world.exports)?; - let get_resource = resource_test_for_interface(resolve, id); + if let Some(interface) = interface.strip_prefix(names.import_exported_intrinsic_prefix()) { + let (key, id) = names.module_to_interface(interface, resolve, &world.exports)?; - return if let Some(ty) = valid_resource_drop(import.name, ty, &get_resource)? { - Ok(Import::ExportedResourceDrop(key, ty)) - } else if let Some(id) = import - .name - .strip_prefix(RESOURCE_NEW) - .and_then(&get_resource) - { - let expected = FuncType::new([ValType::I32], [ValType::I32]); - validate_func_sig(import.name, &expected, ty)?; - Ok(Import::ExportedResourceNew(key, id)) - } else if let Some(id) = import - .name - .strip_prefix(RESOURCE_REP) - .and_then(&get_resource) - { - let expected = FuncType::new([ValType::I32], [ValType::I32]); - validate_func_sig(import.name, &expected, ty)?; - Ok(Import::ExportedResourceRep(key, id)) - } else { - bail!("unknown function `{}`", import.name) - }; + let get_resource = resource_test_for_interface(resolve, id); + if let Some(name) = names.resource_drop_name(name) { + if let Some(id) = get_resource(name) { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ExportedResourceDrop(key, id)); + } + } + if let Some(name) = names.resource_new_name(name) { + if let Some(id) = get_resource(name) { + let expected = FuncType::new([ValType::I32], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ExportedResourceNew(key, id)); + } + } + if let Some(name) = names.resource_rep_name(name) { + if let Some(id) = get_resource(name) { + let expected = FuncType::new([ValType::I32], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ExportedResourceRep(key, id)); + } + } + bail!("unknown function `{name}`") } - // And finally handle imports of functions from interfaces here. - let (key, id) = self.module_to_interface(import.module, resolve, &world.imports)?; + let (key, id) = names.module_to_interface(interface, resolve, &world.imports)?; let interface = &resolve.interfaces[id]; let get_resource = resource_test_for_interface(resolve, id); - if let Some(f) = interface.functions.get(import.name) { + if let Some(f) = interface.functions.get(name) { validate_func(resolve, ty, f, AbiVariant::GuestImport).with_context(|| { let name = resolve.name_world_key(&key); format!("failed to validate import interface `{name}`") })?; - Ok(Import::InterfaceFunc(key, id, f.name.clone())) - } else if let Some(ty) = valid_resource_drop(import.name, ty, get_resource)? { - Ok(Import::ImportedResourceDrop(key, Some(id), ty)) - } else { - bail!( - "import interface `{}` is missing function \ - `{}` that is required by the module", - import.module, - import.name, - ) - } - } - - fn module_to_interface( - &self, - module: &str, - resolve: &Resolve, - items: &IndexMap, - ) -> Result<(WorldKey, InterfaceId)> { - // First see if this is a bare name - let bare_name = WorldKey::Name(module.to_string()); - if let Some(WorldItem::Interface { id, .. }) = items.get(&bare_name) { - return Ok((bare_name, *id)); - } - - // ... and if this isn't a bare name then it's time to do some parsing - // related to interfaces, versions, and such. First up the `module` name - // is parsed as a normal component name from `wasmparser` to see if it's - // of the "interface kind". If it's not then that means the above match - // should have been a hit but it wasn't, so an error is returned. - let kebab_name = ComponentName::new(module, 0); - let name = match kebab_name.as_ref().map(|k| k.kind()) { - Ok(ComponentNameKind::Interface(name)) => name, - _ => bail!("module requires an import interface named `{module}`"), - }; - - // Prioritize an exact match based on versions, so try that first. - let pkgname = PackageName { - namespace: name.namespace().to_string(), - name: name.package().to_string(), - version: name.version(), - }; - if let Some(pkg) = resolve.package_names.get(&pkgname) { - if let Some(id) = resolve.packages[*pkg] - .interfaces - .get(name.interface().as_str()) - { - let key = WorldKey::Interface(*id); - if items.contains_key(&key) { - return Ok((key, *id)); - } - } - } - - // If an exact match wasn't found then instead search for the first - // match based on versions. This means that a core wasm import for - // "1.2.3" might end up matching an interface at "1.2.4", for example. - // (or "1.2.2", depending on what's available). - for (key, _) in items { - let id = match key { - WorldKey::Interface(id) => *id, - WorldKey::Name(_) => continue, - }; - // Make sure the interface names match - let interface = &resolve.interfaces[id]; - if interface.name.as_ref().unwrap() != name.interface().as_str() { - continue; - } - - // Make sure the package name (without version) matches - let pkg = &resolve.packages[interface.package.unwrap()]; - if pkg.name.namespace != pkgname.namespace || pkg.name.name != pkgname.name { - continue; - } - - let module_version = match name.version() { - Some(version) => version, - None => continue, - }; - let pkg_version = match &pkg.name.version { - Some(version) => version, - None => continue, - }; - - // Test if the two semver versions are compatible - let module_compat = PackageName::version_compat_track(&module_version); - let pkg_compat = PackageName::version_compat_track(pkg_version); - if module_compat == pkg_compat { - return Ok((key.clone(), id)); + return Ok(Import::InterfaceFunc(key, id, f.name.clone())); + } else if let Some(resource) = names.resource_drop_name(name) { + if let Some(resource) = get_resource(resource) { + let expected = FuncType::new([ValType::I32], []); + validate_func_sig(name, &expected, ty)?; + return Ok(Import::ImportedResourceDrop(key, Some(id), resource)); } } - - bail!("module requires an import interface named `{module}`") + bail!( + "import interface `{module}` is missing function \ + `{name}` that is required by the module", + ) } fn classify_import_with_library( @@ -570,6 +492,7 @@ pub struct ExportMap { /// All possible (known) exports from a core wasm module that are recognized and /// handled during the componentization process. +#[derive(Debug)] pub enum Export { /// An export of a top-level function of a world, where the world function /// is named here. @@ -616,6 +539,7 @@ impl ExportMap { types: TypesRef<'_>, ) -> Result<()> { if let Some(item) = self.classify(export, resolve, world, exports, types)? { + log::debug!("classifying export `{}` as {item:?}", export.name); let prev = self.names.insert(export.name.to_string(), item); assert!(prev.is_none()); } @@ -631,16 +555,16 @@ impl ExportMap { types: TypesRef<'_>, ) -> Result> { match export.kind { - ExternalKind::Func => {} + ExternalKind::Func => { + let ty = types[types.core_function_at(export.index)].unwrap_func(); + self.raw_exports.insert(export.name.to_string(), ty.clone()); + } ExternalKind::Memory => return Ok(Some(Export::Memory)), _ => return Ok(None), } - let ty = types[types.core_function_at(export.index)].unwrap_func(); - self.raw_exports.insert(export.name.to_string(), ty.clone()); - // Handle a few special-cased names first. - if export.name == "cabi_realloc" || export.name == "canonical_abi_realloc" { + if export.name == "canonical_abi_realloc" { return Ok(Some(Export::GeneralPurposeRealloc)); } else if export.name == "cabi_import_realloc" { return Ok(Some(Export::GeneralPurposeImportRealloc)); @@ -648,12 +572,57 @@ impl ExportMap { return Ok(Some(Export::GeneralPurposeExportRealloc)); } else if export.name == "cabi_realloc_adapter" { return Ok(Some(Export::ReallocForAdapter)); - } else if export.name == "_initialize" { + } + + let (name, names) = match export.name.strip_prefix("cm32p2") { + Some(name) => (name, STANDARD), + None => (export.name, LEGACY), + }; + if let Some(export) = self + .classify_component_export(names, name, &export, resolve, world, exports, types) + .with_context(|| format!("failed to classify export `{}`", export.name))? + { + return Ok(Some(export)); + } + log::debug!("unknown export `{}`", export.name); + Ok(None) + } + + fn classify_component_export( + &mut self, + names: &dyn NameMangling, + name: &str, + export: &wasmparser::Export<'_>, + resolve: &Resolve, + world: WorldId, + exports: &IndexSet, + types: TypesRef<'_>, + ) -> Result> { + match export.kind { + ExternalKind::Func => {} + ExternalKind::Memory => { + if name == names.export_memory() { + return Ok(Some(Export::Memory)); + } + return Ok(None); + } + _ => return Ok(None), + } + let ty = types[types.core_function_at(export.index)].unwrap_func(); + + // Handle a few special-cased names first. + if name == names.export_realloc() { + let expected = FuncType::new([ValType::I32; 4], [ValType::I32]); + validate_func_sig(name, &expected, ty)?; + return Ok(Some(Export::GeneralPurposeRealloc)); + } else if name == names.export_initialize() { + let expected = FuncType::new([], []); + validate_func_sig(name, &expected, ty)?; return Ok(Some(Export::Initialize)); } // Try to match this to a known WIT export that `exports` allows. - if let Some((key, id, f)) = self.match_wit_export(export.name, resolve, world, exports) { + if let Some((key, id, f)) = names.match_wit_export(name, resolve, world, exports) { validate_func(resolve, ty, f, AbiVariant::GuestExport).with_context(|| { let key = resolve.name_world_key(key); format!("failed to validate export for `{key}`") @@ -669,8 +638,8 @@ impl ExportMap { } // See if this is a post-return for any known WIT export. - if let Some(suffix) = export.name.strip_prefix(POST_RETURN_PREFIX) { - if let Some((key, id, f)) = self.match_wit_export(suffix, resolve, world, exports) { + if let Some(remaining) = names.strip_post_return(name) { + if let Some((key, id, f)) = names.match_wit_export(remaining, resolve, world, exports) { validate_post_return(resolve, ty, f).with_context(|| { let key = resolve.name_world_key(key); format!("failed to validate export for `{key}`") @@ -690,82 +659,15 @@ impl ExportMap { } // And, finally, see if it matches a known destructor. - if let Some(dtor) = self.match_wit_resource_dtor(export.name, resolve, world, exports) { + if let Some(dtor) = names.match_wit_resource_dtor(name, resolve, world, exports) { let expected = FuncType::new([ValType::I32], []); validate_func_sig(export.name, &expected, ty)?; return Ok(Some(Export::ResourceDtor(dtor))); } - log::debug!("unknown export `{}`", export.name); Ok(None) } - fn match_wit_export<'a>( - &self, - export_name: &str, - resolve: &'a Resolve, - world: WorldId, - exports: &'a IndexSet, - ) -> Option<(&'a WorldKey, Option, &'a Function)> { - let world = &resolve.worlds[world]; - for name in exports { - match &world.exports[name] { - WorldItem::Function(f) => { - if f.core_export_name(None) == export_name { - return Some((name, None, f)); - } - } - WorldItem::Interface { id, .. } => { - let string = resolve.name_world_key(name); - for (_, func) in resolve.interfaces[*id].functions.iter() { - if func.core_export_name(Some(&string)) == export_name { - return Some((name, Some(*id), func)); - } - } - } - - WorldItem::Type(_) => unreachable!(), - } - } - - None - } - - fn match_wit_resource_dtor<'a>( - &self, - export_name: &str, - resolve: &'a Resolve, - world: WorldId, - exports: &'a IndexSet, - ) -> Option { - let world = &resolve.worlds[world]; - for name in exports { - let id = match &world.exports[name] { - WorldItem::Interface { id, .. } => *id, - WorldItem::Function(_) => continue, - WorldItem::Type(_) => unreachable!(), - }; - let name = resolve.name_world_key(name); - let resource = match export_name - .strip_prefix(&name) - .and_then(|s| s.strip_prefix("#[dtor]")) - .and_then(|r| resolve.interfaces[id].types.get(r)) - { - Some(id) => *id, - None => continue, - }; - - match resolve.types[resource].kind { - TypeDefKind::Resource => {} - _ => continue, - } - - return Some(resource); - } - - None - } - /// Returns the name of the post-return export, if any, for the `interface` /// and `func` combo. pub fn post_return(&self, key: &WorldKey, func: &Function) -> Option<&str> { @@ -906,6 +808,380 @@ impl ExportMap { } } +/// Trait dispatch and definition for parsing and interpreting "mangled names" +/// which show up in imports and exports of the component model. +/// +/// This trait is used to implement classification of imports and exports in the +/// component model. The methods on `ImportMap` and `ExportMap` will use this to +/// determine what an import is and how it's lifted/lowered in the world being +/// bound. +/// +/// This trait has a bit of history behind it as well. Before +/// WebAssembly/component-model#378 there was no standard naming scheme for core +/// wasm imports or exports when componenitizing. This meant that +/// `wit-component` implemented a particular scheme which mostly worked but was +/// mostly along the lines of "this at least works" rather than "someone sat +/// down and designed this". Since then, however, an standard naming scheme has +/// now been specified which was indeed designed. +/// +/// This trait serves as the bridge between these two. The historical naming +/// scheme is still supported for now through the `Legacy` implementation below +/// and will be for some time. The transition plan at this time is to support +/// the new scheme, eventually get it supported in bindings generators, and once +/// that's all propagated remove support for the legacy scheme. +trait NameMangling { + fn import_root(&self) -> &str; + fn import_non_root_prefix(&self) -> &str; + fn import_exported_intrinsic_prefix(&self) -> &str; + fn export_memory(&self) -> &str; + fn export_initialize(&self) -> &str; + fn export_realloc(&self) -> &str; + fn resource_drop_name<'a>(&self, s: &'a str) -> Option<&'a str>; + fn resource_new_name<'a>(&self, s: &'a str) -> Option<&'a str>; + fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str>; + fn module_to_interface( + &self, + module: &str, + resolve: &Resolve, + items: &IndexMap, + ) -> Result<(WorldKey, InterfaceId)>; + fn strip_post_return<'a>(&self, s: &'a str) -> Option<&'a str>; + fn match_wit_export<'a>( + &self, + export_name: &str, + resolve: &'a Resolve, + world: WorldId, + exports: &'a IndexSet, + ) -> Option<(&'a WorldKey, Option, &'a Function)>; + fn match_wit_resource_dtor<'a>( + &self, + export_name: &str, + resolve: &'a Resolve, + world: WorldId, + exports: &'a IndexSet, + ) -> Option; +} + +/// Definition of the "standard" naming scheme which currently starts with +/// "cm32p2". Note that wasm64 is not supported at this time. +struct Standard; + +const STANDARD: &'static dyn NameMangling = &Standard; + +impl NameMangling for Standard { + fn import_root(&self) -> &str { + "" + } + fn import_non_root_prefix(&self) -> &str { + "|" + } + fn import_exported_intrinsic_prefix(&self) -> &str { + "_ex_" + } + fn export_memory(&self) -> &str { + "_memory" + } + fn export_initialize(&self) -> &str { + "_initialize" + } + fn export_realloc(&self) -> &str { + "_realloc" + } + fn resource_drop_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_suffix("_drop") + } + fn resource_new_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_suffix("_new") + } + fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_suffix("_rep") + } + fn module_to_interface( + &self, + interface: &str, + resolve: &Resolve, + items: &IndexMap, + ) -> Result<(WorldKey, InterfaceId)> { + for (key, item) in items.iter() { + let id = match key { + // Bare keys are matched exactly against `interface` + WorldKey::Name(name) => match item { + WorldItem::Interface { id, .. } if name == interface => *id, + _ => continue, + }, + // ID-identified keys are matched with their "canonical name" + WorldKey::Interface(id) => { + if resolve.canonicalized_id_of(*id).as_deref() != Some(interface) { + continue; + } + *id + } + }; + return Ok((key.clone(), id)); + } + bail!("failed to find world item corresponding to interface `{interface}`") + } + fn strip_post_return<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_suffix("_post") + } + fn match_wit_export<'a>( + &self, + export_name: &str, + resolve: &'a Resolve, + world: WorldId, + exports: &'a IndexSet, + ) -> Option<(&'a WorldKey, Option, &'a Function)> { + if let Some(world_export_name) = export_name.strip_prefix("||") { + let key = exports.get(&WorldKey::Name(world_export_name.to_string()))?; + match &resolve.worlds[world].exports[key] { + WorldItem::Function(f) => return Some((key, None, f)), + _ => return None, + } + } + + let (key, id, func_name) = + self.match_wit_interface(export_name, resolve, world, exports)?; + let func = resolve.interfaces[id].functions.get(func_name)?; + Some((key, Some(id), func)) + } + + fn match_wit_resource_dtor<'a>( + &self, + export_name: &str, + resolve: &'a Resolve, + world: WorldId, + exports: &'a IndexSet, + ) -> Option { + let (_key, id, name) = + self.match_wit_interface(export_name.strip_suffix("_dtor")?, resolve, world, exports)?; + let ty = *resolve.interfaces[id].types.get(name)?; + match resolve.types[ty].kind { + TypeDefKind::Resource => Some(ty), + _ => None, + } + } +} + +impl Standard { + fn match_wit_interface<'a, 'b>( + &self, + export_name: &'b str, + resolve: &'a Resolve, + world: WorldId, + exports: &'a IndexSet, + ) -> Option<(&'a WorldKey, InterfaceId, &'b str)> { + let world = &resolve.worlds[world]; + let export_name = export_name.strip_prefix("|")?; + + for export in exports { + let id = match &world.exports[export] { + WorldItem::Interface { id, .. } => *id, + WorldItem::Function(_) => continue, + WorldItem::Type(_) => unreachable!(), + }; + let remaining = match export { + WorldKey::Name(name) => export_name.strip_prefix(name), + WorldKey::Interface(_) => { + let prefix = resolve.canonicalized_id_of(id).unwrap(); + export_name.strip_prefix(&prefix) + } + }; + let item_name = match remaining.and_then(|s| s.strip_prefix("|")) { + Some(name) => name, + None => continue, + }; + return Some((export, id, item_name)); + } + + None + } +} + +/// Definition of wit-component's "legacy" naming scheme which predates +/// WebAssembly/component-model#378. +struct Legacy; + +const LEGACY: &'static dyn NameMangling = &Legacy; + +impl NameMangling for Legacy { + fn import_root(&self) -> &str { + "$root" + } + fn import_non_root_prefix(&self) -> &str { + "" + } + fn import_exported_intrinsic_prefix(&self) -> &str { + "[export]" + } + fn export_memory(&self) -> &str { + "memory" + } + fn export_initialize(&self) -> &str { + "_initialize" + } + fn export_realloc(&self) -> &str { + "cabi_realloc" + } + fn resource_drop_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("[resource-drop]") + } + fn resource_new_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("[resource-new]") + } + fn resource_rep_name<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("[resource-rep]") + } + fn module_to_interface( + &self, + module: &str, + resolve: &Resolve, + items: &IndexMap, + ) -> Result<(WorldKey, InterfaceId)> { + // First see if this is a bare name + let bare_name = WorldKey::Name(module.to_string()); + if let Some(WorldItem::Interface { id, .. }) = items.get(&bare_name) { + return Ok((bare_name, *id)); + } + + // ... and if this isn't a bare name then it's time to do some parsing + // related to interfaces, versions, and such. First up the `module` name + // is parsed as a normal component name from `wasmparser` to see if it's + // of the "interface kind". If it's not then that means the above match + // should have been a hit but it wasn't, so an error is returned. + let kebab_name = ComponentName::new(module, 0); + let name = match kebab_name.as_ref().map(|k| k.kind()) { + Ok(ComponentNameKind::Interface(name)) => name, + _ => bail!("module requires an import interface named `{module}`"), + }; + + // Prioritize an exact match based on versions, so try that first. + let pkgname = PackageName { + namespace: name.namespace().to_string(), + name: name.package().to_string(), + version: name.version(), + }; + if let Some(pkg) = resolve.package_names.get(&pkgname) { + if let Some(id) = resolve.packages[*pkg] + .interfaces + .get(name.interface().as_str()) + { + let key = WorldKey::Interface(*id); + if items.contains_key(&key) { + return Ok((key, *id)); + } + } + } + + // If an exact match wasn't found then instead search for the first + // match based on versions. This means that a core wasm import for + // "1.2.3" might end up matching an interface at "1.2.4", for example. + // (or "1.2.2", depending on what's available). + for (key, _) in items { + let id = match key { + WorldKey::Interface(id) => *id, + WorldKey::Name(_) => continue, + }; + // Make sure the interface names match + let interface = &resolve.interfaces[id]; + if interface.name.as_ref().unwrap() != name.interface().as_str() { + continue; + } + + // Make sure the package name (without version) matches + let pkg = &resolve.packages[interface.package.unwrap()]; + if pkg.name.namespace != pkgname.namespace || pkg.name.name != pkgname.name { + continue; + } + + let module_version = match name.version() { + Some(version) => version, + None => continue, + }; + let pkg_version = match &pkg.name.version { + Some(version) => version, + None => continue, + }; + + // Test if the two semver versions are compatible + let module_compat = PackageName::version_compat_track(&module_version); + let pkg_compat = PackageName::version_compat_track(pkg_version); + if module_compat == pkg_compat { + return Ok((key.clone(), id)); + } + } + + bail!("module requires an import interface named `{module}`") + } + fn strip_post_return<'a>(&self, s: &'a str) -> Option<&'a str> { + s.strip_prefix("cabi_post_") + } + fn match_wit_export<'a>( + &self, + export_name: &str, + resolve: &'a Resolve, + world: WorldId, + exports: &'a IndexSet, + ) -> Option<(&'a WorldKey, Option, &'a Function)> { + let world = &resolve.worlds[world]; + for name in exports { + match &world.exports[name] { + WorldItem::Function(f) => { + if f.legacy_core_export_name(None) == export_name { + return Some((name, None, f)); + } + } + WorldItem::Interface { id, .. } => { + let string = resolve.name_world_key(name); + for (_, func) in resolve.interfaces[*id].functions.iter() { + if func.legacy_core_export_name(Some(&string)) == export_name { + return Some((name, Some(*id), func)); + } + } + } + + WorldItem::Type(_) => unreachable!(), + } + } + + None + } + + fn match_wit_resource_dtor<'a>( + &self, + export_name: &str, + resolve: &'a Resolve, + world: WorldId, + exports: &'a IndexSet, + ) -> Option { + let world = &resolve.worlds[world]; + for name in exports { + let id = match &world.exports[name] { + WorldItem::Interface { id, .. } => *id, + WorldItem::Function(_) => continue, + WorldItem::Type(_) => unreachable!(), + }; + let name = resolve.name_world_key(name); + let resource = match export_name + .strip_prefix(&name) + .and_then(|s| s.strip_prefix("#[dtor]")) + .and_then(|r| resolve.interfaces[id].types.get(r)) + { + Some(id) => *id, + None => continue, + }; + + match resolve.types[resource].kind { + TypeDefKind::Resource => {} + _ => continue, + } + + return Some(resource); + } + + None + } +} + /// This function validates the following: /// /// * The `bytes` represent a valid core WebAssembly module. @@ -972,21 +1248,6 @@ pub fn validate_adapter_module( Ok(ret) } -fn valid_resource_drop( - func_name: &str, - ty: &FuncType, - get_resource: impl Fn(&str) -> Option, -) -> Result> { - if let Some(resource_name) = func_name.strip_prefix(RESOURCE_DROP) { - if let Some(id) = get_resource(resource_name) { - let expected = FuncType::new([ValType::I32], []); - validate_func_sig(func_name, &expected, ty)?; - return Ok(Some(id)); - } - } - Ok(None) -} - fn resource_test_for_interface<'a>( resolve: &'a Resolve, id: InterfaceId, diff --git a/crates/wit-component/tests/components/cm32-names/component.wat b/crates/wit-component/tests/components/cm32-names/component.wat new file mode 100644 index 0000000000..7b513ab5b7 --- /dev/null +++ b/crates/wit-component/tests/components/cm32-names/component.wat @@ -0,0 +1,366 @@ +(component + (type (;0;) + (instance + (export (;0;) "r" (type (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (param "s" string) (result 1))) + (export (;0;) "[constructor]r" (func (type 2))) + (type (;3;) (borrow 0)) + (type (;4;) (func (param "self" 3) (result string))) + (export (;1;) "[method]r.m" (func (type 4))) + (type (;5;) (func (param "in" 1) (result 1))) + (export (;2;) "frob" (func (type 5))) + ) + ) + (import "ns:pkg/i@0.2.1" (instance (;0;) (type 0))) + (type (;1;) + (instance + (export (;0;) "r" (type (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (param "s" string) (result 1))) + (export (;0;) "[constructor]r" (func (type 2))) + (type (;3;) (borrow 0)) + (type (;4;) (func (param "self" 3) (result string))) + (export (;1;) "[method]r.m" (func (type 4))) + (type (;5;) (func (param "in" 1) (result 1))) + (export (;2;) "frob" (func (type 5))) + ) + ) + (import "j" (instance (;1;) (type 1))) + (type (;2;) (func (result string))) + (import "f" (func (;0;) (type 2))) + (core module (;0;) + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param i32) (result i32))) + (type (;4;) (func (result i32))) + (type (;5;) (func (param i32 i32 i32 i32) (result i32))) + (type (;6;) (func)) + (import "cm32p2" "f" (func (;0;) (type 0))) + (import "cm32p2|ns:pkg/i@0.2" "[constructor]r" (func (;1;) (type 1))) + (import "cm32p2|ns:pkg/i@0.2" "[method]r.m" (func (;2;) (type 2))) + (import "cm32p2|ns:pkg/i@0.2" "frob" (func (;3;) (type 3))) + (import "cm32p2|ns:pkg/i@0.2" "r_drop" (func (;4;) (type 0))) + (import "cm32p2|j" "[constructor]r" (func (;5;) (type 1))) + (import "cm32p2|j" "[method]r.m" (func (;6;) (type 2))) + (import "cm32p2|j" "frob" (func (;7;) (type 3))) + (import "cm32p2|j" "r_drop" (func (;8;) (type 0))) + (import "cm32p2|_ex_ns:pkg/i@0.2" "r_drop" (func (;9;) (type 0))) + (import "cm32p2|_ex_ns:pkg/i@0.2" "r_new" (func (;10;) (type 3))) + (import "cm32p2|_ex_ns:pkg/i@0.2" "r_rep" (func (;11;) (type 3))) + (import "cm32p2|_ex_j" "r_drop" (func (;12;) (type 0))) + (import "cm32p2|_ex_j" "r_new" (func (;13;) (type 3))) + (import "cm32p2|_ex_j" "r_rep" (func (;14;) (type 3))) + (memory (;0;) 0) + (export "cm32p2_memory" (memory 0)) + (export "cm32p2||g" (func 15)) + (export "cm32p2||g_post" (func 16)) + (export "cm32p2|ns:pkg/i@0.2|[constructor]r" (func 17)) + (export "cm32p2|ns:pkg/i@0.2|[method]r.m" (func 18)) + (export "cm32p2|ns:pkg/i@0.2|frob" (func 19)) + (export "cm32p2|ns:pkg/i@0.2|r_dtor" (func 20)) + (export "cm32p2|j|[constructor]r" (func 21)) + (export "cm32p2|j|[method]r.m" (func 22)) + (export "cm32p2|j|frob" (func 23)) + (export "cm32p2|j|r_dtor" (func 24)) + (export "cm32p2_realloc" (func 25)) + (export "cm32p2_initialize" (func 26)) + (func (;15;) (type 4) (result i32) + unreachable + ) + (func (;16;) (type 0) (param i32) + unreachable + ) + (func (;17;) (type 1) (param i32 i32) (result i32) + unreachable + ) + (func (;18;) (type 3) (param i32) (result i32) + unreachable + ) + (func (;19;) (type 3) (param i32) (result i32) + unreachable + ) + (func (;20;) (type 0) (param i32) + unreachable + ) + (func (;21;) (type 1) (param i32 i32) (result i32) + unreachable + ) + (func (;22;) (type 3) (param i32) (result i32) + unreachable + ) + (func (;23;) (type 3) (param i32) (result i32) + unreachable + ) + (func (;24;) (type 0) (param i32) + unreachable + ) + (func (;25;) (type 5) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;26;) (type 6)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + (processed-by "my-fake-bindgen" "123.45") + ) + ) + (core module (;1;) + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param i32))) + (table (;0;) 7 7 funcref) + (export "0" (func $indirect-cm32p2-f)) + (export "1" (func $"indirect-cm32p2|ns:pkg/i@0.2-[constructor]r")) + (export "2" (func $"indirect-cm32p2|ns:pkg/i@0.2-[method]r.m")) + (export "3" (func $"indirect-cm32p2|j-[constructor]r")) + (export "4" (func $"indirect-cm32p2|j-[method]r.m")) + (export "5" (func $dtor-r)) + (export "6" (func $"#func6 dtor-r")) + (export "$imports" (table 0)) + (func $indirect-cm32p2-f (;0;) (type 0) (param i32) + local.get 0 + i32.const 0 + call_indirect (type 0) + ) + (func $"indirect-cm32p2|ns:pkg/i@0.2-[constructor]r" (;1;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 1 + call_indirect (type 1) + ) + (func $"indirect-cm32p2|ns:pkg/i@0.2-[method]r.m" (;2;) (type 2) (param i32 i32) + local.get 0 + local.get 1 + i32.const 2 + call_indirect (type 2) + ) + (func $"indirect-cm32p2|j-[constructor]r" (;3;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 3 + call_indirect (type 1) + ) + (func $"indirect-cm32p2|j-[method]r.m" (;4;) (type 2) (param i32 i32) + local.get 0 + local.get 1 + i32.const 4 + call_indirect (type 2) + ) + (func $dtor-r (;5;) (type 3) (param i32) + local.get 0 + i32.const 5 + call_indirect (type 3) + ) + (func $"#func6 dtor-r" (@name "dtor-r") (;6;) (type 3) (param i32) + local.get 0 + i32.const 6 + call_indirect (type 3) + ) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core module (;2;) + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 1))) + (import "" "2" (func (;2;) (type 2))) + (import "" "3" (func (;3;) (type 1))) + (import "" "4" (func (;4;) (type 2))) + (import "" "5" (func (;5;) (type 3))) + (import "" "6" (func (;6;) (type 3))) + (import "" "$imports" (table (;0;) 7 7 funcref)) + (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core instance (;0;) (instantiate 1)) + (alias core export 0 "5" (core func (;0;))) + (type (;3;) (resource (rep i32) (dtor (func 0)))) + (alias core export 0 "6" (core func (;1;))) + (type (;4;) (resource (rep i32) (dtor (func 1)))) + (alias core export 0 "0" (core func (;2;))) + (core instance (;1;) + (export "f" (func 2)) + ) + (alias core export 0 "1" (core func (;3;))) + (alias core export 0 "2" (core func (;4;))) + (alias export 0 "frob" (func (;1;))) + (core func (;5;) (canon lower (func 1))) + (alias export 0 "r" (type (;5;))) + (core func (;6;) (canon resource.drop 5)) + (core instance (;2;) + (export "[constructor]r" (func 3)) + (export "[method]r.m" (func 4)) + (export "frob" (func 5)) + (export "r_drop" (func 6)) + ) + (alias core export 0 "3" (core func (;7;))) + (alias core export 0 "4" (core func (;8;))) + (alias export 1 "frob" (func (;2;))) + (core func (;9;) (canon lower (func 2))) + (alias export 1 "r" (type (;6;))) + (core func (;10;) (canon resource.drop 6)) + (core instance (;3;) + (export "[constructor]r" (func 7)) + (export "[method]r.m" (func 8)) + (export "frob" (func 9)) + (export "r_drop" (func 10)) + ) + (core func (;11;) (canon resource.drop 3)) + (core func (;12;) (canon resource.new 3)) + (core func (;13;) (canon resource.rep 3)) + (core instance (;4;) + (export "r_drop" (func 11)) + (export "r_new" (func 12)) + (export "r_rep" (func 13)) + ) + (core func (;14;) (canon resource.drop 4)) + (core func (;15;) (canon resource.new 4)) + (core func (;16;) (canon resource.rep 4)) + (core instance (;5;) + (export "r_drop" (func 14)) + (export "r_new" (func 15)) + (export "r_rep" (func 16)) + ) + (core instance (;6;) (instantiate 0 + (with "cm32p2" (instance 1)) + (with "cm32p2|ns:pkg/i@0.2" (instance 2)) + (with "cm32p2|j" (instance 3)) + (with "cm32p2|_ex_ns:pkg/i@0.2" (instance 4)) + (with "cm32p2|_ex_j" (instance 5)) + ) + ) + (alias core export 6 "cm32p2_memory" (core memory (;0;))) + (alias core export 0 "$imports" (core table (;0;))) + (alias core export 6 "cm32p2_realloc" (core func (;17;))) + (core func (;18;) (canon lower (func 0) (memory 0) (realloc 17) string-encoding=utf8)) + (alias export 0 "[constructor]r" (func (;3;))) + (core func (;19;) (canon lower (func 3) (memory 0) string-encoding=utf8)) + (alias export 0 "[method]r.m" (func (;4;))) + (core func (;20;) (canon lower (func 4) (memory 0) (realloc 17) string-encoding=utf8)) + (alias export 1 "[constructor]r" (func (;5;))) + (core func (;21;) (canon lower (func 5) (memory 0) string-encoding=utf8)) + (alias export 1 "[method]r.m" (func (;6;))) + (core func (;22;) (canon lower (func 6) (memory 0) (realloc 17) string-encoding=utf8)) + (alias core export 6 "cm32p2|ns:pkg/i@0.2|r_dtor" (core func (;23;))) + (alias core export 6 "cm32p2|j|r_dtor" (core func (;24;))) + (core instance (;7;) + (export "$imports" (table 0)) + (export "0" (func 18)) + (export "1" (func 19)) + (export "2" (func 20)) + (export "3" (func 21)) + (export "4" (func 22)) + (export "5" (func 23)) + (export "6" (func 24)) + ) + (core instance (;8;) (instantiate 2 + (with "" (instance 7)) + ) + ) + (alias core export 6 "cm32p2_initialize" (core func (;25;))) + (core module (;3;) + (type (;0;) (func)) + (import "" "" (func (;0;) (type 0))) + (start 0) + ) + (core instance (;9;) + (export "" (func 25)) + ) + (core instance (;10;) (instantiate 3 + (with "" (instance 9)) + ) + ) + (alias core export 6 "cm32p2||g" (core func (;26;))) + (alias core export 6 "cm32p2||g_post" (core func (;27;))) + (func (;7;) (type 2) (canon lift (core func 26) (memory 0) string-encoding=utf8 (post-return 27))) + (export (;8;) "g" (func 7)) + (type (;7;) (own 3)) + (type (;8;) (func (param "s" string) (result 7))) + (alias core export 6 "cm32p2|ns:pkg/i@0.2|[constructor]r" (core func (;28;))) + (func (;9;) (type 8) (canon lift (core func 28) (memory 0) (realloc 17) string-encoding=utf8)) + (type (;9;) (borrow 3)) + (type (;10;) (func (param "self" 9) (result string))) + (alias core export 6 "cm32p2|ns:pkg/i@0.2|[method]r.m" (core func (;29;))) + (func (;10;) (type 10) (canon lift (core func 29) (memory 0) string-encoding=utf8)) + (type (;11;) (func (param "in" 7) (result 7))) + (alias core export 6 "cm32p2|ns:pkg/i@0.2|frob" (core func (;30;))) + (func (;11;) (type 11) (canon lift (core func 30))) + (component (;0;) + (import "import-type-r" (type (;0;) (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (param "s" string) (result 1))) + (import "import-constructor-r" (func (;0;) (type 2))) + (type (;3;) (borrow 0)) + (type (;4;) (func (param "self" 3) (result string))) + (import "import-method-r-m" (func (;1;) (type 4))) + (type (;5;) (func (param "in" 1) (result 1))) + (import "import-func-frob" (func (;2;) (type 5))) + (export (;6;) "r" (type 0)) + (type (;7;) (own 6)) + (type (;8;) (func (param "s" string) (result 7))) + (export (;3;) "[constructor]r" (func 0) (func (type 8))) + (type (;9;) (borrow 6)) + (type (;10;) (func (param "self" 9) (result string))) + (export (;4;) "[method]r.m" (func 1) (func (type 10))) + (type (;11;) (func (param "in" 7) (result 7))) + (export (;5;) "frob" (func 2) (func (type 11))) + ) + (instance (;2;) (instantiate 0 + (with "import-constructor-r" (func 9)) + (with "import-method-r-m" (func 10)) + (with "import-func-frob" (func 11)) + (with "import-type-r" (type 3)) + ) + ) + (export (;3;) "ns:pkg/i@0.2.1" (instance 2)) + (type (;12;) (own 4)) + (type (;13;) (func (param "s" string) (result 12))) + (alias core export 6 "cm32p2|j|[constructor]r" (core func (;31;))) + (func (;12;) (type 13) (canon lift (core func 31) (memory 0) (realloc 17) string-encoding=utf8)) + (type (;14;) (borrow 4)) + (type (;15;) (func (param "self" 14) (result string))) + (alias core export 6 "cm32p2|j|[method]r.m" (core func (;32;))) + (func (;13;) (type 15) (canon lift (core func 32) (memory 0) string-encoding=utf8)) + (type (;16;) (func (param "in" 12) (result 12))) + (alias core export 6 "cm32p2|j|frob" (core func (;33;))) + (func (;14;) (type 16) (canon lift (core func 33))) + (component (;1;) + (import "import-type-r" (type (;0;) (sub resource))) + (type (;1;) (own 0)) + (type (;2;) (func (param "s" string) (result 1))) + (import "import-constructor-r" (func (;0;) (type 2))) + (type (;3;) (borrow 0)) + (type (;4;) (func (param "self" 3) (result string))) + (import "import-method-r-m" (func (;1;) (type 4))) + (type (;5;) (func (param "in" 1) (result 1))) + (import "import-func-frob" (func (;2;) (type 5))) + (export (;6;) "r" (type 0)) + (type (;7;) (own 6)) + (type (;8;) (func (param "s" string) (result 7))) + (export (;3;) "[constructor]r" (func 0) (func (type 8))) + (type (;9;) (borrow 6)) + (type (;10;) (func (param "self" 9) (result string))) + (export (;4;) "[method]r.m" (func 1) (func (type 10))) + (type (;11;) (func (param "in" 7) (result 7))) + (export (;5;) "frob" (func 2) (func (type 11))) + ) + (instance (;4;) (instantiate 1 + (with "import-constructor-r" (func 12)) + (with "import-method-r-m" (func 13)) + (with "import-func-frob" (func 14)) + (with "import-type-r" (type 4)) + ) + ) + (export (;5;) "j" (instance 4)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/cm32-names/component.wit.print b/crates/wit-component/tests/components/cm32-names/component.wit.print new file mode 100644 index 0000000000..a01bff8de2 --- /dev/null +++ b/crates/wit-component/tests/components/cm32-names/component.wit.print @@ -0,0 +1,25 @@ +package root:component; + +world root { + import ns:pkg/i@0.2.1; + import j: interface { + resource r { + constructor(s: string); + m: func() -> string; + } + + frob: func(in: r) -> r; + } + import f: func() -> string; + + export g: func() -> string; + export ns:pkg/i@0.2.1; + export j: interface { + resource r { + constructor(s: string); + m: func() -> string; + } + + frob: func(in: r) -> r; + } +} diff --git a/crates/wit-component/tests/components/cm32-names/module.wat b/crates/wit-component/tests/components/cm32-names/module.wat new file mode 100644 index 0000000000..6b1b47b5d4 --- /dev/null +++ b/crates/wit-component/tests/components/cm32-names/module.wat @@ -0,0 +1,32 @@ +(module + (import "cm32p2" "f" (func (param i32))) + (import "cm32p2|ns:pkg/i@0.2" "[constructor]r" (func (param i32 i32) (result i32))) + (import "cm32p2|ns:pkg/i@0.2" "[method]r.m" (func (param i32 i32))) + (import "cm32p2|ns:pkg/i@0.2" "frob" (func (param i32) (result i32))) + (import "cm32p2|ns:pkg/i@0.2" "r_drop" (func (param i32))) + (import "cm32p2|j" "[constructor]r" (func (param i32 i32) (result i32))) + (import "cm32p2|j" "[method]r.m" (func (param i32 i32))) + (import "cm32p2|j" "frob" (func (param i32) (result i32))) + (import "cm32p2|j" "r_drop" (func (param i32))) + (import "cm32p2|_ex_ns:pkg/i@0.2" "r_drop" (func (param i32))) + (import "cm32p2|_ex_ns:pkg/i@0.2" "r_new" (func (param i32) (result i32))) + (import "cm32p2|_ex_ns:pkg/i@0.2" "r_rep" (func (param i32) (result i32))) + (import "cm32p2|_ex_j" "r_drop" (func (param i32))) + (import "cm32p2|_ex_j" "r_new" (func (param i32) (result i32))) + (import "cm32p2|_ex_j" "r_rep" (func (param i32) (result i32))) + + (memory (export "cm32p2_memory") 0) + + (func (export "cm32p2||g") (result i32) unreachable) + (func (export "cm32p2||g_post") (param i32) unreachable) + (func (export "cm32p2|ns:pkg/i@0.2|[constructor]r") (param i32 i32) (result i32) unreachable) + (func (export "cm32p2|ns:pkg/i@0.2|[method]r.m") (param i32) (result i32) unreachable) + (func (export "cm32p2|ns:pkg/i@0.2|frob") (param i32) (result i32) unreachable) + (func (export "cm32p2|ns:pkg/i@0.2|r_dtor") (param i32) unreachable) + (func (export "cm32p2|j|[constructor]r") (param i32 i32) (result i32) unreachable) + (func (export "cm32p2|j|[method]r.m") (param i32) (result i32) unreachable) + (func (export "cm32p2|j|frob") (param i32) (result i32) unreachable) + (func (export "cm32p2|j|r_dtor") (param i32) unreachable) + (func (export "cm32p2_realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "cm32p2_initialize")) +) diff --git a/crates/wit-component/tests/components/cm32-names/module.wit b/crates/wit-component/tests/components/cm32-names/module.wit new file mode 100644 index 0000000000..32d8820e8f --- /dev/null +++ b/crates/wit-component/tests/components/cm32-names/module.wit @@ -0,0 +1,30 @@ +package ns:pkg@0.2.1; + +interface i { + resource r { + constructor(s: string); + m: func() -> string; + } + frob: func(in: r) -> r; +} + +world module { + import f: func() -> string; + import i; + import j: interface { + resource r { + constructor(s: string); + m: func() -> string; + } + frob: func(in: r) -> r; + } + export g: func() -> string; + export i; + export j: interface { + resource r { + constructor(s: string); + m: func() -> string; + } + frob: func(in: r) -> r; + } +} diff --git a/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt b/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt index e223246a10..d8e5eafd76 100644 --- a/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/error-default-export-sig-mismatch/error.txt @@ -1 +1 @@ -failed to decode world from module: module was not valid: failed to validate export for `a`: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file +failed to decode world from module: module was not valid: failed to classify export `a`: failed to validate export for `a`: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt b/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt index 548e42b291..da7e08eaf9 100644 --- a/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt +++ b/crates/wit-component/tests/components/error-export-sig-mismatch/error.txt @@ -1 +1 @@ -failed to decode world from module: module was not valid: failed to validate export for `foo`: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file +failed to decode world from module: module was not valid: failed to classify export `foo#a`: failed to validate export for `foo`: type mismatch for function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index e85cb2101a..638dd6e96b 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -219,6 +219,23 @@ impl PackageName { } version } + + /// Returns the string corresponding to + /// [`PackageName::version_compat_track`]. This is done to match the + /// component model's expected naming scheme of imports and exports. + pub fn version_compat_track_string(version: &Version) -> String { + let version = Self::version_compat_track(version); + if !version.pre.is_empty() { + return version.to_string(); + } + if version.major != 0 { + return format!("{}", version.major); + } + if version.minor != 0 { + return format!("{}.{}", version.major, version.minor); + } + version.to_string() + } } impl fmt::Display for PackageName { @@ -850,6 +867,20 @@ impl FunctionKind { } } +/// Possible forms of name mangling that are supported by this crate. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Mangling { + /// The "standard" component model mangling format for 32-bit linear + /// memories. This is specified in WebAssembly/component-model#378 + Standard32, + + /// The "legacy" name mangling supported in versions 218-and-prior for this + /// crate. This is the original support for how components were created from + /// core wasm modules and this does not correspond to any standard. This is + /// preserved for now while tools transition to the new scheme. + Legacy, +} + impl Function { pub fn item_name(&self) -> &str { match &self.kind { @@ -873,10 +904,28 @@ impl Function { } /// Gets the core export name for this function. - pub fn core_export_name<'a>(&'a self, interface: Option<&str>) -> Cow<'a, str> { + pub fn standard32_core_export_name<'a>(&'a self, interface: Option<&str>) -> Cow<'a, str> { + self.core_export_name(interface, Mangling::Standard32) + } + + pub fn legacy_core_export_name<'a>(&'a self, interface: Option<&str>) -> Cow<'a, str> { + self.core_export_name(interface, Mangling::Legacy) + } + /// Gets the core export name for this function. + pub fn core_export_name<'a>( + &'a self, + interface: Option<&str>, + mangling: Mangling, + ) -> Cow<'a, str> { match interface { - Some(interface) => Cow::Owned(format!("{interface}#{}", self.name)), - None => Cow::Borrowed(&self.name), + Some(interface) => match mangling { + Mangling::Standard32 => Cow::Owned(format!("cm32p2|{interface}|{}", self.name)), + Mangling::Legacy => Cow::Owned(format!("{interface}#{}", self.name)), + }, + None => match mangling { + Mangling::Standard32 => Cow::Owned(format!("cm32p2||{}", self.name)), + Mangling::Legacy => Cow::Borrowed(&self.name), + }, } } } diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 5122d854a1..d5a62739c3 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -1012,6 +1012,15 @@ package {name} is defined in two different locations:\n\ Some(self.id_of_name(interface.package.unwrap(), interface.name.as_ref()?)) } + /// Returns the "canonicalized interface name" of `interface`. + /// + /// Returns `None` for unnamed interfaces. See `BuildTargets.md` in the + /// upstream component model repository for more information about this. + pub fn canonicalized_id_of(&self, interface: InterfaceId) -> Option { + let interface = &self.interfaces[interface]; + Some(self.canonicalized_id_of_name(interface.package.unwrap(), interface.name.as_ref()?)) + } + /// Convert a world to an "importized" version where the world is updated /// in-place to reflect what it would look like to be imported. /// @@ -1090,6 +1099,27 @@ package {name} is defined in two different locations:\n\ base } + /// Returns the "canonicalized interface name" of the specified `name` + /// within the `pkg`. + /// + /// See `BuildTargets.md` in the upstream component model repository for + /// more information about this. + pub fn canonicalized_id_of_name(&self, pkg: PackageId, name: &str) -> String { + let package = &self.packages[pkg]; + let mut base = String::new(); + base.push_str(&package.name.namespace); + base.push_str(":"); + base.push_str(&package.name.name); + base.push_str("/"); + base.push_str(name); + if let Some(version) = &package.name.version { + base.push_str("@"); + let string = PackageName::version_compat_track_string(version); + base.push_str(&string); + } + base + } + /// Attempts to locate a world given the "default" set of `packages` and the /// optional string specifier `world`. ///