diff --git a/crates/wit-component/src/dummy.rs b/crates/wit-component/src/dummy.rs index 6a3cbb457d..78980e73d1 100644 --- a/crates/wit-component/src/dummy.rs +++ b/crates/wit-component/src/dummy.rs @@ -1,8 +1,11 @@ use wit_parser::abi::{AbiVariant, WasmType}; -use wit_parser::{Function, Resolve, TypeDefKind, TypeId, WorldId, WorldItem}; +use wit_parser::{ + Function, Mangling, Resolve, ResourceIntrinsic, TypeDefKind, TypeId, WasmExport, WasmImport, + WorldId, WorldItem, WorldKey, +}; /// Generate a dummy implementation core Wasm module for a given WIT document -pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { +pub fn dummy_module(resolve: &Resolve, world: WorldId, mangling: Mangling) -> Vec { let world = &resolve.worlds[world]; let mut wat = String::new(); wat.push_str("(module\n"); @@ -11,27 +14,41 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { WorldItem::Function(func) => { let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - wat.push_str(&format!("(import \"$root\" \"{}\" (func", func.name)); + let (module, name) = resolve.wasm_import_name( + mangling, + WasmImport::Func { + interface: None, + func, + }, + ); + + wat.push_str(&format!("(import {module:?} {name:?} (func")); push_tys(&mut wat, "param", &sig.params); push_tys(&mut wat, "result", &sig.results); wat.push_str("))\n"); } WorldItem::Interface { id: import, .. } => { - let name = resolve.name_world_key(name); for (_, func) in resolve.interfaces[*import].functions.iter() { let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); - wat.push_str(&format!("(import \"{name}\" \"{}\" (func", func.name)); + let (module, name) = resolve.wasm_import_name( + mangling, + WasmImport::Func { + interface: Some(name), + func, + }, + ); + wat.push_str(&format!("(import {module:?} {name:?} (func")); push_tys(&mut wat, "param", &sig.params); push_tys(&mut wat, "result", &sig.results); wat.push_str("))\n"); } for (_, ty) in resolve.interfaces[*import].types.iter() { - push_resource_func_imports(&mut wat, resolve, &name, *ty); + push_resource_func_imports(&mut wat, resolve, Some(name), *ty, mangling); } } WorldItem::Type(id) => { - push_resource_func_imports(&mut wat, resolve, "$root", *id); + push_resource_func_imports(&mut wat, resolve, None, *id, mangling); } } } @@ -42,77 +59,136 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec { WorldItem::Interface { id, .. } => *id, _ => continue, }; - let module = format!("[export]{}", resolve.name_world_key(name)); - for (name, ty) in resolve.interfaces[export].types.iter() { - let ty = &resolve.types[*ty]; + for resource in resolve.interfaces[export].types.values().copied() { + let ty = &resolve.types[resource]; match ty.kind { TypeDefKind::Resource => {} _ => continue, } - wat.push_str(&format!( - "\ -(import \"{module}\" \"[resource-drop]{name}\" (func (param i32))) -(import \"{module}\" \"[resource-new]{name}\" (func (param i32) (result i32))) -(import \"{module}\" \"[resource-rep]{name}\" (func (param i32) (result i32))) - " - )); + let intrinsics = [ + (ResourceIntrinsic::ExportedDrop, "(func (param i32))"), + ( + ResourceIntrinsic::ExportedNew, + "(func (param i32) (result i32))", + ), + ( + ResourceIntrinsic::ExportedRep, + "(func (param i32) (result i32))", + ), + ]; + for (intrinsic, sig) in intrinsics { + let (module, name) = resolve.wasm_import_name( + mangling, + WasmImport::ResourceIntrinsic { + interface: Some(name), + resource, + intrinsic, + }, + ); + wat.push_str(&format!("(import {module:?} {name:?} {sig})\n")); + } } } for (name, export) in world.exports.iter() { match export { WorldItem::Function(func) => { - push_func(&mut wat, &func.name, resolve, func); + push_func_export(&mut wat, resolve, None, func, mangling); } 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)); - push_func(&mut wat, &name, resolve, func); + push_func_export(&mut wat, resolve, Some(name), func, mangling); } // Feign destructors for any resource that this interface // exports - for (resource_name, ty) in resolve.interfaces[*export].types.iter() { - let ty = &resolve.types[*ty]; + for resource in resolve.interfaces[*export].types.values().copied() { + let ty = &resolve.types[resource]; match ty.kind { TypeDefKind::Resource => {} _ => continue, } - wat.push_str(&format!( - "(func (export \"{name}#[dtor]{resource_name}\") (param i32))" - )); + let name = resolve.wasm_export_name( + mangling, + WasmExport::ResourceDtor { + interface: name, + resource, + }, + ); + wat.push_str(&format!("(func (export {name:?}) (param i32))")); } } WorldItem::Type(_) => {} } } - wat.push_str("(memory (export \"memory\") 0)\n"); - wat.push_str( - "(func (export \"cabi_realloc\") (param i32 i32 i32 i32) (result i32) unreachable)\n", - ); + let memory = resolve.wasm_export_name(mangling, WasmExport::Memory); + wat.push_str(&format!("(memory (export {memory:?}) 0)\n")); + let realloc = resolve.wasm_export_name(mangling, WasmExport::Realloc); + wat.push_str(&format!( + "(func (export {realloc:?}) (param i32 i32 i32 i32) (result i32) unreachable)\n" + )); + let initialize = resolve.wasm_export_name(mangling, WasmExport::Initialize); + wat.push_str(&format!("(func (export {initialize:?}))")); wat.push_str(")\n"); return wat::parse_str(&wat).unwrap(); - fn push_resource_func_imports(wat: &mut String, resolve: &Resolve, module: &str, ty: TypeId) { - let ty = &resolve.types[ty]; + fn push_resource_func_imports( + wat: &mut String, + resolve: &Resolve, + interface: Option<&WorldKey>, + resource: TypeId, + mangling: Mangling, + ) { + let ty = &resolve.types[resource]; match ty.kind { TypeDefKind::Resource => {} _ => return, } - let name = ty.name.as_ref().unwrap(); - wat.push_str(&format!("(import \"{module}\" \"[resource-drop]{name}\"")); - wat.push_str(" (func (param i32)))\n"); + let (module, name) = resolve.wasm_import_name( + mangling, + WasmImport::ResourceIntrinsic { + interface, + resource, + intrinsic: ResourceIntrinsic::ImportedDrop, + }, + ); + wat.push_str(&format!("(import {module:?} {name:?} (func (param i32)))")); } - fn push_func(wat: &mut String, name: &str, resolve: &Resolve, func: &Function) { + fn push_func_export( + wat: &mut String, + resolve: &Resolve, + interface: Option<&WorldKey>, + func: &Function, + mangling: Mangling, + ) { let sig = resolve.wasm_signature(AbiVariant::GuestExport, func); + let name = resolve.wasm_export_name( + mangling, + WasmExport::Func { + interface, + func, + post_return: false, + }, + ); wat.push_str(&format!("(func (export \"{name}\")")); push_tys(wat, "param", &sig.params); push_tys(wat, "result", &sig.results); wat.push_str(" unreachable)\n"); + + let name = resolve.wasm_export_name( + mangling, + WasmExport::Func { + interface, + func, + post_return: true, + }, + ); + wat.push_str(&format!("(func (export \"{name}\")")); + push_tys(wat, "param", &sig.results); + wat.push_str(")\n"); } fn push_tys(dst: &mut String, desc: &str, params: &[WasmType]) { diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 19dd760583..51a45cffb1 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}; @@ -615,7 +615,39 @@ impl<'a> EncodingState<'a> { CustomModule::Main => &self.info.encoder.main_module_exports, CustomModule::Adapter(name) => &self.info.encoder.adapters[name].required_exports, }; + if exports.is_empty() { + return Ok(()); + } + + let mut interface_func_core_names = IndexMap::new(); + let mut world_func_core_names = IndexMap::new(); + for (core_name, export) in self.info.exports_for(module).iter() { + match export { + Export::WorldFunc(name) => { + let prev = world_func_core_names.insert(name, core_name); + assert!(prev.is_none()); + } + Export::InterfaceFunc(id, name) => { + let prev = interface_func_core_names + .entry(id) + .or_insert(IndexMap::new()) + .insert(name.as_str(), core_name); + assert!(prev.is_none()); + } + Export::WorldFuncPostReturn(..) + | Export::InterfaceFuncPostReturn(..) + | Export::ResourceDtor(..) + | Export::Memory + | Export::GeneralPurposeRealloc + | Export::GeneralPurposeExportRealloc + | Export::GeneralPurposeImportRealloc + | Export::Initialize + | Export::ReallocForAdapter => continue, + } + } + 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] { @@ -623,13 +655,20 @@ impl<'a> EncodingState<'a> { let ty = self .root_import_type_encoder(None) .encode_func_type(resolve, func)?; - let core_name = func.core_export_name(None); - let idx = self.encode_lift(module, &core_name, None, func, ty)?; + let core_name = world_func_core_names[&func.name]; + let idx = self.encode_lift(module, &core_name, export_name, func, ty)?; self.component .export(&export_string, ComponentExportKind::Func, idx, None); } WorldItem::Interface { id, .. } => { - self.encode_interface_export(&export_string, module, *id)?; + let core_names = interface_func_core_names.get(id); + self.encode_interface_export( + &export_string, + module, + export_name, + *id, + core_names, + )?; } WorldItem::Type(_) => unreachable!(), } @@ -642,7 +681,9 @@ impl<'a> EncodingState<'a> { &mut self, export_name: &str, module: CustomModule<'_>, + key: &WorldKey, export: InterfaceId, + interface_func_core_names: Option<&IndexMap<&str, &str>>, ) -> Result<()> { log::trace!("encode interface export `{export_name}`"); let resolve = &self.info.encoder.metadata.resolve; @@ -656,11 +697,9 @@ impl<'a> EncodingState<'a> { let mut imports = Vec::new(); let mut root = self.root_export_type_encoder(Some(export)); for (_, func) in &resolve.interfaces[export].functions { - let core_name = func.core_export_name(Some(export_name)); + let core_name = interface_func_core_names.unwrap()[func.name.as_str()]; let ty = root.encode_func_type(resolve, func)?; - let func_index = root - .state - .encode_lift(module, &core_name, Some(export), func, ty)?; + let func_index = root.state.encode_lift(module, &core_name, key, func, ty)?; imports.push(( import_func_name(func), ComponentExportKind::Func, @@ -953,7 +992,7 @@ impl<'a> EncodingState<'a> { &mut self, module: CustomModule<'_>, core_name: &str, - interface: Option, + key: &WorldKey, func: &Function, ty: u32, ) -> Result { @@ -964,16 +1003,19 @@ impl<'a> EncodingState<'a> { let options = RequiredOptions::for_export(resolve, func); - let encoding = metadata.export_encodings[core_name]; + let encoding = metadata + .export_encodings + .get(resolve, key, &func.name) + .unwrap(); let exports = self.info.exports_for(module); let realloc_index = exports - .export_realloc_for(interface, func) + .export_realloc_for(key, func) .map(|name| self.core_alias_export(instance_index, name, ExportKind::Func)); let mut options = options .into_iter(encoding, self.memory_index, realloc_index)? .collect::>(); - if let Some(post_return) = exports.post_return(interface, func) { + if let Some(post_return) = exports.post_return(key, func) { let post_return = self.core_alias_export(instance_index, post_return, ExportKind::Func); options.push(CanonicalOption::PostReturn(post_return)); } @@ -1346,7 +1388,7 @@ impl<'a> EncodingState<'a> { log::trace!("attempting to materialize import of `{module}::{field}` for {for_module:?}"); let resolve = &self.info.encoder.metadata.resolve; let name_tmp; - let (key, name) = match import { + let (key, name, interface_key) = match import { // Main module dependencies on an adapter in use are done with an // indirection here, so load the shim function and use that. Import::AdapterExport(_) => { @@ -1413,18 +1455,17 @@ impl<'a> EncodingState<'a> { // through to the code below. This is where these are connected to a // WIT `ImportedInterface` one way or another with the name that was // detected during validation. - Import::ImportedResourceDrop(key, id) => { + Import::ImportedResourceDrop(key, iface, id) => { let ty = &resolve.types[*id]; let name = ty.name.as_ref().unwrap(); - name_tmp = format!("{RESOURCE_DROP}{name}"); - (key.as_ref(), &name_tmp) + name_tmp = format!("{name}_drop"); + (key, &name_tmp, iface.map(|_| resolve.name_world_key(key))) } - Import::WorldFunc(name) => (None, name), - Import::InterfaceFunc(key, _, name) => (Some(key), name), + Import::WorldFunc(key, name) => (key, name, None), + Import::InterfaceFunc(key, _, name) => (key, name, Some(resolve.name_world_key(key))), }; - let interface = key.map(|key| resolve.name_world_key(key)); - let import = &self.info.import_map[&interface]; + let import = &self.info.import_map[&interface_key]; let (index, _, lowering) = import.lowerings.get_full(name).unwrap(); let metadata = self.info.module_metadata_for(for_module); @@ -1447,12 +1488,12 @@ impl<'a> EncodingState<'a> { // created, so the specific export is loaded here and used as an // import. Lowering::Indirect { .. } => { - let encoding = metadata.import_encodings[&(module.to_string(), field.to_string())]; + let encoding = metadata.import_encodings.get(resolve, key, name).unwrap(); self.core_alias_export( self.shim_instance_index .expect("shim should be instantiated"), &shims.shims[&ShimKind::IndirectLowering { - interface: interface.clone(), + interface: interface_key, index, realloc: for_module, encoding, @@ -1663,7 +1704,7 @@ impl<'a> Shims<'a> { let resolve = &world.encoder.metadata.resolve; for (module, field, import) in module_imports.imports() { - let (key, name) = match import { + let (key, name, interface_key) = match import { // These imports don't require shims, they can be satisfied // as-needed when required. Import::ImportedResourceDrop(..) @@ -1713,11 +1754,12 @@ impl<'a> Shims<'a> { // WIT-level functions may require an indirection, so yield some // metadata out of this `match` to the loop below to figure that // out. - Import::InterfaceFunc(key, _, name) => (Some(key), name), - Import::WorldFunc(name) => (None, name), + Import::InterfaceFunc(key, _, name) => { + (key, name, Some(resolve.name_world_key(key))) + } + Import::WorldFunc(key, name) => (key, name, None), }; - let key = key.map(|key| resolve.name_world_key(key)); - let interface = &world.import_map[&key]; + let interface = &world.import_map[&interface_key]; let (index, _, lowering) = interface.lowerings.get_full(name).unwrap(); let shim_name = self.shims.len().to_string(); match lowering { @@ -1727,9 +1769,9 @@ impl<'a> Shims<'a> { log::debug!( "shim {shim_name} is import `{module}::{field}` lowering {index} `{name}`", ); - let encoding = *metadata + let encoding = metadata .import_encodings - .get(&(module.to_string(), field.to_string())) + .get(resolve, key, name) .ok_or_else(|| { anyhow::anyhow!( "missing component metadata for import of \ @@ -1741,7 +1783,7 @@ impl<'a> Shims<'a> { debug_name: format!("indirect-{module}-{field}"), options: *options, kind: ShimKind::IndirectLowering { - interface: key, + interface: interface_key, index, realloc: for_module, encoding, @@ -1836,7 +1878,7 @@ pub struct LibraryInfo { } /// Represents an adapter or library to be instantiated as part of the component -struct Adapter { +pub(super) struct Adapter { /// The wasm of the module itself, with `component-type` sections stripped wasm: Vec, @@ -1858,13 +1900,14 @@ struct Adapter { #[derive(Default)] pub struct ComponentEncoder { module: Vec, - metadata: Bindgen, + pub(super) metadata: Bindgen, validate: bool, - main_module_exports: IndexSet, - adapters: IndexMap, + pub(super) main_module_exports: IndexSet, + pub(super) adapters: IndexMap, import_name_map: HashMap, realloc_via_memory_grow: bool, merge_imports_based_on_semver: Option, + pub(super) reject_legacy_names: bool, } impl ComponentEncoder { @@ -1917,6 +1960,19 @@ impl ComponentEncoder { self } + /// Sets whether to reject the historical mangling/name scheme for core wasm + /// imports/exports as they map to the component model. + /// + /// The `wit-component` crate supported a different set of names prior to + /// WebAssembly/component-model#378 and this can be used to disable this + /// support. + /// + /// This is disabled by default. + pub fn reject_legacy_names(mut self, reject: bool) -> Self { + self.reject_legacy_names = reject; + self + } + /// Specifies a new adapter which is used to translate from a historical /// wasm ABI to the canonical ABI and the `interface` provided. /// @@ -2116,6 +2172,7 @@ impl ComponentWorld<'_> { mod test { use super::*; use crate::{dummy_module, embed_component_metadata}; + use wit_parser::Mangling; #[test] fn it_renames_imports() { @@ -2141,7 +2198,7 @@ world test { .unwrap(); let world = resolve.select_world(pkg, None).unwrap(); - let mut module = dummy_module(&resolve, world); + let mut module = dummy_module(&resolve, world, Mangling::Standard32); embed_component_metadata(&mut module, &resolve, world, StringEncoding::UTF8).unwrap(); diff --git a/crates/wit-component/src/encoding/world.rs b/crates/wit-component/src/encoding/world.rs index d969e39798..acf126a174 100644 --- a/crates/wit-component/src/encoding/world.rs +++ b/crates/wit-component/src/encoding/world.rs @@ -1,14 +1,13 @@ 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}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; -use wasmparser::FuncType; use wit_parser::{ - abi::{AbiVariant, WasmSignature, WasmType}, + abi::{AbiVariant, WasmSignature}, Function, InterfaceId, LiveTypes, Resolve, TypeDefKind, TypeId, TypeOwner, WorldId, WorldItem, WorldKey, }; @@ -64,18 +63,7 @@ pub enum Lowering { impl<'a> ComponentWorld<'a> { pub fn new(encoder: &'a ComponentEncoder) -> Result { - let adapters = encoder - .adapters - .keys() - .map(|s| s.as_str()) - .collect::>(); - let info = validate_module( - &encoder.module, - &encoder.metadata, - &encoder.main_module_exports, - &adapters, - ) - .context("module was not valid")?; + let info = validate_module(encoder, &encoder.module).context("module was not valid")?; let mut ret = ComponentWorld { encoder, @@ -86,7 +74,7 @@ impl<'a> ComponentWorld<'a> { exports_used: HashMap::new(), }; - ret.process_adapters(&adapters)?; + ret.process_adapters()?; ret.process_imports()?; ret.process_exports_used(); ret.process_live_type_imports(); @@ -98,7 +86,7 @@ impl<'a> ComponentWorld<'a> { /// adapters and figure out what functions are required from the /// adapter itself, either because the functions are imported by the /// main module or they're part of the adapter's exports. - fn process_adapters(&mut self, adapters: &IndexSet<&str>) -> Result<()> { + fn process_adapters(&mut self) -> Result<()> { let resolve = &self.encoder.metadata.resolve; let world = self.encoder.metadata.world; for ( @@ -130,12 +118,49 @@ impl<'a> ComponentWorld<'a> { let wasm = if library_info.is_some() { Cow::Borrowed(wasm as &[u8]) } else { - let required = self.required_adapter_exports( - resolve, - world, - required_exports, + // Without `library_info` this means that this is an adapter. + // The goal of the adapter is to provide a suite of symbols that + // can be imported, but not all symbols may be imported. Here + // the module is trimmed down to only what's needed by the + // original main module. + // + // The main module requires `required_by_import` above, but + // adapters may themselves also export WIT items. To handle this + // the sequence of operations here are: + // + // 1. First the adapter is validated as-is. This ensures that + // everything looks good before GC. + // 2. The metadata from step (1) is used to determine the set of + // WIT-level exports that are needed. This includes things + // like realloc functions and such. + // 3. The set of WIT-level functions from (2) is unioned with + // `required_by_import` to create the set of required exports + // of the adapter. + // 4. This set of exports is used to delete some exports of the + // adapter and then perform a GC pass. + // + // Finally at the end of all of this the + // `validate_adapter_module` method is called for a second time + // on the minimized adapter. This is done because deleting + // imports may have deleted some imports which means that the + // final component may not need to import as many interfaces. + let info = validate_adapter_module( + self.encoder, + &wasm, &required_by_import, - ); + required_exports, + library_info.as_ref(), + ) + .with_context(|| { + format!("failed to validate the imports of the adapter module `{name}`") + })?; + let mut required = IndexSet::new(); + for (name, _ty) in required_by_import.iter() { + required.insert(name.to_string()); + } + for (name, _export) in info.exports.iter() { + required.insert(name.to_string()); + } Cow::Owned( crate::gc::run( @@ -151,13 +176,11 @@ impl<'a> ComponentWorld<'a> { ) }; let info = validate_adapter_module( + self.encoder, &wasm, - resolve, - world, &required_by_import, required_exports, library_info.as_ref(), - adapters, ) .with_context(|| { format!("failed to validate the imports of the minimized adapter module `{name}`") @@ -174,64 +197,6 @@ impl<'a> ComponentWorld<'a> { Ok(()) } - /// Returns the set of functions required to be exported from an adapter, - /// either because they're exported from the adapter's world or because - /// they're required as an import to the main module. - fn required_adapter_exports<'r>( - &self, - resolve: &'r Resolve, - world: WorldId, - required_exports: &IndexSet, - required_by_import: &IndexMap, - ) -> IndexMap)> { - use wasmparser::ValType; - - let mut required = IndexMap::new(); - for (name, ty) in required_by_import { - required.insert(name.to_string(), (ty.clone(), None)); - } - let mut add_func = |func: &'r Function, name: Option<&str>| { - let name = func.core_export_name(name); - let ty = resolve.wasm_signature(AbiVariant::GuestExport, func); - let prev = required.insert( - name.into_owned(), - ( - wasmparser::FuncType::new( - ty.params.iter().map(to_valty), - ty.results.iter().map(to_valty), - ), - Some(func), - ), - ); - assert!(prev.is_none()); - }; - for name in required_exports { - match &resolve.worlds[world].exports[name] { - WorldItem::Function(func) => add_func(func, None), - WorldItem::Interface { id, .. } => { - let name = resolve.name_world_key(name); - for (_, func) in resolve.interfaces[*id].functions.iter() { - add_func(func, Some(&name)); - } - } - WorldItem::Type(_) => {} - } - } - return required; - - fn to_valty(ty: &WasmType) -> ValType { - match ty { - WasmType::I32 => ValType::I32, - WasmType::I64 => ValType::I64, - WasmType::F32 => ValType::F32, - WasmType::F64 => ValType::F64, - WasmType::Pointer => ValType::I32, - WasmType::PointerOrI64 => ValType::I64, - WasmType::Length => ValType::I32, - } - } - } - /// Fills out the `import_map` field of `self` by determining the live /// functions from all imports. This additionally classifies imported /// functions into direct or indirect lowerings for managing shims. @@ -250,7 +215,7 @@ impl<'a> ComponentWorld<'a> { .chain(self.info.imports.imports()) { match import { - Import::WorldFunc(name) => { + Import::WorldFunc(_, name) => { required .interface_funcs .entry(None) @@ -264,7 +229,7 @@ impl<'a> ComponentWorld<'a> { .or_default() .insert(name); } - Import::ImportedResourceDrop(_, id) => { + Import::ImportedResourceDrop(_, _, id) => { required.resource_drops.insert(*id); } _ => {} @@ -481,7 +446,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/gc.rs b/crates/wit-component/src/gc.rs index a0a345afca..ab22187dcf 100644 --- a/crates/wit-component/src/gc.rs +++ b/crates/wit-component/src/gc.rs @@ -19,9 +19,9 @@ const PAGE_SIZE: i32 = 64 * 1024; /// /// This internally performs a "gc" pass after removing exports to ensure that /// the resulting module imports the minimal set of functions necessary. -pub fn run( +pub fn run( wasm: &[u8], - required: &IndexMap, + required: &IndexSet, main_module_realloc: Option<&str>, ) -> Result> { assert!(!required.is_empty()); @@ -31,21 +31,14 @@ pub fn run( // Make sure that all required names are present in the module, and then // remove all names that are not required. - for (name, _ty) in required { + for name in required { if !module.exports.contains_key(name.as_str()) { bail!("adapter module does not have export `{name}`") } } let mut not_required = IndexSet::new(); for name in module.exports.keys().copied() { - // If we need `name` then we also need cabi_post_`name`: - let name = if let Some(suffix) = name.strip_prefix("cabi_post_") { - suffix - } else { - name - }; - - if !required.contains_key(name) && !always_keep(name) { + if !required.contains(name) { not_required.insert(name); } } @@ -57,15 +50,6 @@ pub fn run( module.encode(main_module_realloc) } -fn always_keep(name: &str) -> bool { - match name { - // Explicitly keep `cabi_realloc` if it's there in case an interface - // needs it for a lowering. - "cabi_realloc" | "cabi_import_realloc" | "cabi_export_realloc" => true, - _ => false, - } -} - /// This function generates a Wasm function body which implements `cabi_realloc` in terms of `memory.grow`. It /// only accepts new, page-sized allocations. fn realloc_via_memory_grow() -> wasm_encoder::Function { diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 18b2608def..7eaf5a68d4 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -41,7 +41,6 @@ //! The dual of `encode` is the `decode_custom_section` fucntion which decodes //! the three arguments originally passed to `encode`. -use crate::validation::BARE_FUNC_MODULE_NAME; use crate::{DecodedWasm, StringEncoding}; use anyhow::{bail, Context, Result}; use indexmap::{IndexMap, IndexSet}; @@ -112,11 +111,105 @@ impl Default for Bindgen { pub struct ModuleMetadata { /// Per-function options imported into the core wasm module, currently only /// related to string encoding. - pub import_encodings: IndexMap<(String, String), StringEncoding>, + pub import_encodings: EncodingMap, /// Per-function options exported from the core wasm module, currently only /// related to string encoding. - pub export_encodings: IndexMap, + pub export_encodings: EncodingMap, +} + +/// Internal map that keeps track of encodings for various world imports and +/// exports. +/// +/// Stored in [`ModuleMetadata`]. +#[derive(Default)] +pub struct EncodingMap { + /// A map of an "identifying string" for world items to what string + /// encoding the import or export is using. + /// + /// The keys of this map are created by `EncodingMap::key` and are + /// specifically chosen to be able to be looked up during both insertion and + /// fetching. Note that in particular this map does not use `*Id` types such + /// as `InterfaceId` from `wit_parser`. This is due to the fact that during + /// world merging new interfaces are created for named imports (e.g. `import + /// x: interface { ... }`) as inline interfaces are copied from one world to + /// another. Additionally during world merging different interfaces at the + /// same version may be deduplicated. + /// + /// For these reasons a string-based key is chosen to avoid juggling IDs + /// through the world merging process. Additionally versions are chopped off + /// for now to help with a problem such as: + /// + /// * The main module imports a:b/c@0.1.0 + /// * An adapter imports a:b/c@0.1.1 + /// * The final world uses a:b/c@0.1.1, but the main module has no + /// encoding listed for that exact item. + /// + /// By chopping off versions this is able to get everything registered + /// correctly even in the fact of merging interfaces and worlds. + encodings: IndexMap, +} + +impl EncodingMap { + fn insert_all( + &mut self, + resolve: &Resolve, + set: &IndexMap, + encoding: StringEncoding, + ) { + for (name, item) in set { + match item { + WorldItem::Function(func) => { + let key = self.key(resolve, name, &func.name); + self.encodings.insert(key, encoding); + } + WorldItem::Interface { id, .. } => { + for (func, _) in resolve.interfaces[*id].functions.iter() { + let key = self.key(resolve, name, func); + self.encodings.insert(key, encoding); + } + } + WorldItem::Type(_) => {} + } + } + } + + /// Looks up the encoding of the function `func` which is scoped under `key` + /// in the world in question. + pub fn get(&self, resolve: &Resolve, key: &WorldKey, func: &str) -> Option { + let key = self.key(resolve, key, func); + self.encodings.get(&key).copied() + } + + fn key(&self, resolve: &Resolve, key: &WorldKey, func: &str) -> String { + format!( + "{}/{func}", + match key { + WorldKey::Name(name) => name.to_string(), + WorldKey::Interface(id) => { + let iface = &resolve.interfaces[*id]; + let pkg = &resolve.packages[iface.package.unwrap()]; + format!( + "{}:{}/{}", + pkg.name.namespace, + pkg.name.name, + iface.name.as_ref().unwrap() + ) + } + } + ) + } + + fn merge(&mut self, other: EncodingMap) -> Result<()> { + for (key, encoding) in other.encodings { + if let Some(prev) = self.encodings.insert(key.clone(), encoding) { + if prev != encoding { + bail!("conflicting string encodings specified for `{key}`"); + } + } + } + Ok(()) + } } /// This function will parse the core `wasm` binary given as input and return a @@ -313,38 +406,18 @@ impl Bindgen { producers, } = other; - let world = self + let remap = self .resolve .merge(resolve) - .context("failed to merge WIT package sets together")? - .map_world(world, None)?; + .context("failed to merge WIT package sets together")?; + let world = remap.map_world(world, None)?; let exports = self.resolve.worlds[world].exports.keys().cloned().collect(); self.resolve .merge_worlds(world, self.world) .context("failed to merge worlds from two documents")?; - for (name, encoding) in export_encodings { - let prev = self - .metadata - .export_encodings - .insert(name.clone(), encoding); - if let Some(prev) = prev { - if prev != encoding { - bail!("conflicting string encodings specified for export `{name}`"); - } - } - } - for ((module, name), encoding) in import_encodings { - let prev = self - .metadata - .import_encodings - .insert((module.clone(), name.clone()), encoding); - if let Some(prev) = prev { - if prev != encoding { - bail!("conflicting string encodings specified for import `{module}::{name}`"); - } - } - } + self.metadata.import_encodings.merge(import_encodings)?; + self.metadata.export_encodings.merge(export_encodings)?; if let Some(producers) = producers { if let Some(mine) = &mut self.producers { mine.merge(&producers); @@ -364,45 +437,10 @@ impl ModuleMetadata { let mut ret = ModuleMetadata::default(); let world = &resolve.worlds[world]; - for (name, item) in world.imports.iter() { - let name = resolve.name_world_key(name); - match item { - WorldItem::Function(_) => { - let prev = ret - .import_encodings - .insert((BARE_FUNC_MODULE_NAME.to_string(), name.clone()), encoding); - assert!(prev.is_none()); - } - WorldItem::Interface { id, .. } => { - for (func, _) in resolve.interfaces[*id].functions.iter() { - let prev = ret - .import_encodings - .insert((name.clone(), func.clone()), encoding); - assert!(prev.is_none()); - } - } - WorldItem::Type(_) => {} - } - } - - for (name, item) in world.exports.iter() { - let name = resolve.name_world_key(name); - match item { - WorldItem::Function(func) => { - let name = func.core_export_name(None).into_owned(); - let prev = ret.export_encodings.insert(name.clone(), encoding); - assert!(prev.is_none()); - } - WorldItem::Interface { id, .. } => { - for (_, func) in resolve.interfaces[*id].functions.iter() { - let name = func.core_export_name(Some(&name)).into_owned(); - let prev = ret.export_encodings.insert(name, encoding); - assert!(prev.is_none()); - } - } - WorldItem::Type(_) => {} - } - } + ret.export_encodings + .insert_all(resolve, &world.exports, encoding); + ret.import_encodings + .insert_all(resolve, &world.imports, encoding); ret } diff --git a/crates/wit-component/src/semver_check.rs b/crates/wit-component/src/semver_check.rs index d6f4c6c145..1c4a9f4f5e 100644 --- a/crates/wit-component/src/semver_check.rs +++ b/crates/wit-component/src/semver_check.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::{bail, Context, Result}; use wasm_encoder::{ComponentBuilder, ComponentExportKind, ComponentTypeRef}; use wasmparser::Validator; -use wit_parser::{Resolve, WorldId}; +use wit_parser::{Mangling, Resolve, WorldId}; /// Tests whether `new` is a semver-compatible upgrade from the world `prev`. /// @@ -63,7 +63,7 @@ pub fn semver_check(mut resolve: Resolve, prev: WorldId, new: WorldId) -> Result let mut root_component = ComponentBuilder::default(); // (1) above - create a dummy component which has the shape of `prev`. - let mut prev_as_module = dummy_module(&resolve, prev); + let mut prev_as_module = dummy_module(&resolve, prev, Mangling::Standard32); embed_component_metadata(&mut prev_as_module, &resolve, prev, StringEncoding::UTF8) .context("failed to embed component metadata")?; let prev_as_component = ComponentEncoder::default() diff --git a/crates/wit-component/src/validation.rs b/crates/wit-component/src/validation.rs index 3bb7dfdddf..9e170b99fa 100644 --- a/crates/wit-component/src/validation.rs +++ b/crates/wit-component/src/validation.rs @@ -1,5 +1,5 @@ use crate::encoding::{Instance, Item, LibraryInfo, MainOrAdapter}; -use crate::metadata::Bindgen; +use crate::ComponentEncoder; use anyhow::{bail, Context, Result}; use indexmap::{map::Entry, IndexMap, IndexSet}; use std::mem; @@ -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 @@ -65,11 +50,9 @@ pub struct ValidatedModule { impl ValidatedModule { fn new( + encoder: &ComponentEncoder, bytes: &[u8], - resolve: &Resolve, - world: WorldId, exports: &IndexSet, - adapters: &IndexSet<&str>, info: Option<&LibraryInfo>, ) -> Result { let mut validator = Validator::new(); @@ -90,21 +73,20 @@ impl ValidatedModule { Payload::ImportSection(s) => { for import in s { let import = import?; - ret.imports - .add(import, resolve, world, adapters, info, types)?; + ret.imports.add(import, encoder, info, types)?; } } Payload::ExportSection(s) => { for export in s { let export = export?; - ret.exports.add(export, resolve, world, &exports, types)?; + ret.exports.add(export, encoder, &exports, types)?; } } _ => continue, } } - ret.exports.validate(resolve, world, exports)?; + ret.exports.validate(encoder, exports)?; Ok(ret) } @@ -140,7 +122,7 @@ pub enum ImportInstance { pub enum Import { /// A top-level world function, with the name provided here, is imported /// into the module. - WorldFunc(String), + WorldFunc(WorldKey, String), /// An interface's function is imported into the module. /// @@ -154,7 +136,7 @@ pub enum Import { /// The key provided indicates whether it's for the top-level types of the /// world (`None`) or an interface (`Some` with the name of the interface). /// The `TypeId` is what resource is being dropped. - ImportedResourceDrop(Option, TypeId), + ImportedResourceDrop(WorldKey, Option, TypeId), /// A `canon resource.drop` intrinsic for an exported item is being /// imported. @@ -203,14 +185,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. @@ -224,7 +202,7 @@ impl ImportMap { /// Returns whether the specified resource's drop method is needed to import. pub fn uses_imported_resource_drop(&self, resource: TypeId) -> bool { self.imports().any(|(_, _, import)| match import { - Import::ImportedResourceDrop(_, id) => resource == *id, + Import::ImportedResourceDrop(_, _, id) => resource == *id, _ => false, }) } @@ -274,36 +252,28 @@ impl ImportMap { fn add( &mut self, import: wasmparser::Import<'_>, - resolve: &Resolve, - world: WorldId, - adapters: &IndexSet<&str>, + encoder: &ComponentEncoder, library_info: Option<&LibraryInfo>, types: TypesRef<'_>, ) -> Result<()> { if self.classify_import_with_library(import, library_info)? { return Ok(()); } - let item = self - .classify(import, resolve, world, adapters, types) - .with_context(|| { - format!( - "failed to resolve import `{}::{}`", - import.module, import.name, - ) - })?; + let item = self.classify(import, encoder, types).with_context(|| { + format!( + "failed to resolve import `{}::{}`", + import.module, import.name, + ) + })?; self.insert_import(import, item) } fn classify( &self, import: wasmparser::Import<'_>, - resolve: &Resolve, - world_id: WorldId, - adapters: &IndexSet<&str>, + encoder: &ComponentEncoder, 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 +281,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,19 +300,48 @@ 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 encoder.adapters.contains_key(import.module) { + return Ok(Import::AdapterExport(ty.clone())); + } + + let (module, names) = match import.module.strip_prefix("cm32p2") { + Some(suffix) => (suffix, STANDARD), + None if encoder.reject_legacy_names => (import.module, STANDARD), + None => (import.module, LEGACY), + }; + self.classify_component_model_import(module, import.name, encoder, 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, + encoder: &ComponentEncoder, + ty: &FuncType, + names: &dyn NameMangling, + ) -> Result { + let resolve = &encoder.metadata.resolve; + let world_id = encoder.metadata.world; + 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)?; - return Ok(Import::WorldFunc(func.name.clone())); + return Ok(Import::WorldFunc(key, func.name.clone())); } 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(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 +350,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(Some(key), 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)); - } + 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)); } } - - // 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}`") + bail!( + "import interface `{module}` is missing function \ + `{name}` that is required by the module", + ) } fn classify_import_with_library( @@ -570,19 +485,20 @@ 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. WorldFunc(String), /// A post-return for a top-level function of a world. - WorldFuncPostReturn(String), + WorldFuncPostReturn(WorldKey), /// An export of a function in an interface. InterfaceFunc(InterfaceId, String), /// A post-return for the above function. - InterfaceFuncPostReturn(InterfaceId, String), + InterfaceFuncPostReturn(WorldKey, String), /// A destructor for an exported resource. ResourceDtor(TypeId), @@ -610,12 +526,12 @@ impl ExportMap { fn add( &mut self, export: wasmparser::Export<'_>, - resolve: &Resolve, - world: WorldId, + encoder: &ComponentEncoder, exports: &IndexSet, types: TypesRef<'_>, ) -> Result<()> { - if let Some(item) = self.classify(export, resolve, world, exports, types)? { + if let Some(item) = self.classify(export, encoder, exports, types)? { + log::debug!("classifying export `{}` as {item:?}", export.name); let prev = self.names.insert(export.name.to_string(), item); assert!(prev.is_none()); } @@ -625,22 +541,20 @@ impl ExportMap { fn classify( &mut self, export: wasmparser::Export<'_>, - resolve: &Resolve, - world: WorldId, + encoder: &ComponentEncoder, exports: &IndexSet, types: TypesRef<'_>, ) -> Result> { match export.kind { - ExternalKind::Func => {} - ExternalKind::Memory => return Ok(Some(Export::Memory)), - _ => return Ok(None), + ExternalKind::Func => { + let ty = types[types.core_function_at(export.index)].unwrap_func(); + self.raw_exports.insert(export.name.to_string(), ty.clone()); + } + _ => {} } - 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 +562,59 @@ 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 if encoder.reject_legacy_names => return Ok(None), + None => (export.name, LEGACY), + }; + if let Some(export) = self + .classify_component_export(names, name, &export, encoder, 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<'_>, + encoder: &ComponentEncoder, + exports: &IndexSet, + types: TypesRef<'_>, + ) -> Result> { + let resolve = &encoder.metadata.resolve; + let world = encoder.metadata.world; + 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,121 +630,53 @@ 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}`") })?; match id { - Some(id) => { - return Ok(Some(Export::InterfaceFuncPostReturn(id, f.name.clone()))); + Some(_id) => { + return Ok(Some(Export::InterfaceFuncPostReturn( + key.clone(), + f.name.clone(), + ))); } None => { - return Ok(Some(Export::WorldFuncPostReturn(f.name.clone()))); + return Ok(Some(Export::WorldFuncPostReturn(key.clone()))); } } } } // 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, interface: Option, func: &Function) -> Option<&str> { - self.find(|m| match (m, interface) { - (Export::WorldFuncPostReturn(f), None) => func.name == *f, - (Export::InterfaceFuncPostReturn(i, f), Some(id)) => *i == id && func.name == *f, + pub fn post_return(&self, key: &WorldKey, func: &Function) -> Option<&str> { + self.find(|m| match m { + Export::WorldFuncPostReturn(k) => k == key, + Export::InterfaceFuncPostReturn(k, f) => k == key && func.name == *f, _ => false, }) } /// Returns the realloc that the exported function `interface` and `func` /// are using. - pub fn export_realloc_for( - &self, - interface: Option, - func: &Function, - ) -> Option<&str> { + pub fn export_realloc_for(&self, key: &WorldKey, func: &Function) -> Option<&str> { // TODO: This realloc detection should probably be improved with // some sort of scheme to have per-function reallocs like // `cabi_realloc_{name}` or something like that. - let _ = (interface, func); + let _ = (key, func); if let Some(name) = self.find(|m| matches!(m, Export::GeneralPurposeExportRealloc)) { return Some(name); @@ -847,12 +740,9 @@ impl ExportMap { self.names.iter().map(|(n, e)| (n.as_str(), e)) } - fn validate( - &self, - resolve: &Resolve, - world: WorldId, - exports: &IndexSet, - ) -> Result<()> { + fn validate(&self, encoder: &ComponentEncoder, exports: &IndexSet) -> Result<()> { + let resolve = &encoder.metadata.resolve; + let world = encoder.metadata.world; // Multi-memory isn't supported because otherwise we don't know what // memory to put things in. if self @@ -907,6 +797,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. @@ -918,20 +1182,8 @@ impl ExportMap { /// The `ValidatedModule` return value contains the metadata which describes the /// input module on success. This is then further used to generate a component /// for this module. -pub fn validate_module( - bytes: &[u8], - metadata: &Bindgen, - exports: &IndexSet, - adapters: &IndexSet<&str>, -) -> Result { - ValidatedModule::new( - bytes, - &metadata.resolve, - metadata.world, - exports, - adapters, - None, - ) +pub fn validate_module(encoder: &ComponentEncoder, bytes: &[u8]) -> Result { + ValidatedModule::new(encoder, bytes, &encoder.main_module_exports, None) } /// This function will validate the `bytes` provided as a wasm adapter module. @@ -952,15 +1204,13 @@ pub fn validate_module( /// allowing the module to import tables and globals, as well as import /// functions at the world level, not just at the interface level. pub fn validate_adapter_module( + encoder: &ComponentEncoder, bytes: &[u8], - resolve: &Resolve, - world: WorldId, required_by_import: &IndexMap, exports: &IndexSet, library_info: Option<&LibraryInfo>, - adapters: &IndexSet<&str>, ) -> Result { - let ret = ValidatedModule::new(bytes, resolve, world, exports, adapters, library_info)?; + let ret = ValidatedModule::new(encoder, bytes, exports, library_info)?; for (name, required_ty) in required_by_import { let actual = match ret.exports.raw_exports.get(name) { @@ -973,21 +1223,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..906f6a7a13 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use id_arena::{Arena, Id}; use indexmap::IndexMap; use semver::Version; @@ -21,7 +21,7 @@ pub use ast::{parse_use_path, ParsedUsePath}; mod sizealign; pub use sizealign::*; mod resolve; -pub use resolve::{InvalidTransitiveDependency, Package, PackageId, Remap, Resolve}; +pub use resolve::*; mod live; pub use live::{LiveTypes, TypeIdVisitor}; @@ -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,37 @@ 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 std::str::FromStr for Mangling { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "legacy" => Ok(Mangling::Legacy), + "standard32" => Ok(Mangling::Standard32), + _ => { + bail!( + "unknown name mangling `{s}`, \ + supported values are `legacy` or `standard32`" + ) + } + } + } +} + impl Function { pub fn item_name(&self) -> &str { match &self.kind { @@ -873,10 +921,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..02efb93867 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -18,9 +18,9 @@ use crate::ast::{parse_use_path, ParsedUsePath}; use crate::serde_::{serialize_arena, serialize_id_map}; use crate::{ AstItem, Docs, Error, Function, FunctionKind, Handle, IncludeName, Interface, InterfaceId, - InterfaceSpan, PackageName, Results, SourceMap, Stability, Type, TypeDef, TypeDefKind, TypeId, - TypeIdVisitor, TypeOwner, UnresolvedPackage, UnresolvedPackageGroup, World, WorldId, WorldItem, - WorldKey, WorldSpan, + InterfaceSpan, Mangling, PackageName, Results, SourceMap, Stability, Type, TypeDef, + TypeDefKind, TypeId, TypeIdVisitor, TypeOwner, UnresolvedPackage, UnresolvedPackageGroup, + World, WorldId, WorldItem, WorldKey, WorldSpan, }; mod clone; @@ -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`. /// @@ -1278,6 +1308,17 @@ package {name} is defined in two different locations:\n\ } } + /// Same as [`Resolve::name_world_key`] except that `WorldKey::Interfaces` + /// uses [`Resolve::canonicalized_id_of`]. + pub fn name_canonicalized_world_key(&self, key: &WorldKey) -> String { + match key { + WorldKey::Name(s) => s.to_string(), + WorldKey::Interface(i) => self + .canonicalized_id_of(*i) + .expect("unexpected anonymous interface"), + } + } + /// Returns the interface that `id` uses a type from, if it uses a type from /// a different interface than `id` is defined within. /// @@ -2173,6 +2214,226 @@ package {name} is defined in two different locations:\n\ let replacement_id = self.interfaces[*replace_with].types[name]; self.types[ty].kind = TypeDefKind::Type(Type::Id(replacement_id)); } + + /// Returns the core wasm module/field names for the specified `import`. + /// + /// This function will return the core wasm module/field that can be used to + /// use `import` with the name `mangling` scheme specified as well. This can + /// be useful for bindings generators, for example, and these names are + /// recognized by `wit-component` and `wasm-tools component new`. + pub fn wasm_import_name(&self, mangling: Mangling, import: WasmImport<'_>) -> (String, String) { + match mangling { + Mangling::Standard32 => match import { + WasmImport::Func { interface, func } => { + let module = match interface { + Some(key) => format!("cm32p2|{}", self.name_canonicalized_world_key(key)), + None => format!("cm32p2"), + }; + (module, func.name.clone()) + } + WasmImport::ResourceIntrinsic { + interface, + resource, + intrinsic, + } => { + let name = self.types[resource].name.as_ref().unwrap(); + let (prefix, name) = match intrinsic { + ResourceIntrinsic::ImportedDrop => ("", format!("{name}_drop")), + ResourceIntrinsic::ExportedDrop => ("_ex_", format!("{name}_drop")), + ResourceIntrinsic::ExportedNew => ("_ex_", format!("{name}_new")), + ResourceIntrinsic::ExportedRep => ("_ex_", format!("{name}_rep")), + }; + let module = match interface { + Some(key) => { + format!("cm32p2|{prefix}{}", self.name_canonicalized_world_key(key)) + } + None => { + assert_eq!(prefix, ""); + format!("cm32p2") + } + }; + (module, name) + } + }, + Mangling::Legacy => match import { + WasmImport::Func { interface, func } => { + let module = match interface { + Some(key) => self.name_world_key(key), + None => format!("$root"), + }; + (module, func.name.clone()) + } + WasmImport::ResourceIntrinsic { + interface, + resource, + intrinsic, + } => { + let name = self.types[resource].name.as_ref().unwrap(); + let (prefix, name) = match intrinsic { + ResourceIntrinsic::ImportedDrop => ("", format!("[resource-drop]{name}")), + ResourceIntrinsic::ExportedDrop => { + ("[export]", format!("[resource-drop]{name}")) + } + ResourceIntrinsic::ExportedNew => { + ("[export]", format!("[resource-new]{name}")) + } + ResourceIntrinsic::ExportedRep => { + ("[export]", format!("[resource-rep]{name}")) + } + }; + let module = match interface { + Some(key) => format!("{prefix}{}", self.name_world_key(key)), + None => { + assert_eq!(prefix, ""); + format!("$root") + } + }; + (module, name) + } + }, + } + } + + /// Returns the core wasm export name for the specified `import`. + /// + /// This is the same as [`Resovle::wasm_import_name`], except for exports. + pub fn wasm_export_name(&self, mangling: Mangling, import: WasmExport<'_>) -> String { + match mangling { + Mangling::Standard32 => match import { + WasmExport::Func { + interface, + func, + post_return, + } => { + let mut name = String::from("cm32p2|"); + if let Some(interface) = interface { + let s = self.name_canonicalized_world_key(interface); + name.push_str(&s); + } + name.push_str("|"); + name.push_str(&func.name); + if post_return { + name.push_str("_post"); + } + name + } + WasmExport::ResourceDtor { + interface, + resource, + } => { + let name = self.types[resource].name.as_ref().unwrap(); + let interface = self.name_canonicalized_world_key(interface); + format!("cm32p2|{interface}|{name}_dtor") + } + WasmExport::Memory => "cm32p2_memory".to_string(), + WasmExport::Initialize => "cm32p2_initialize".to_string(), + WasmExport::Realloc => "cm32p2_realloc".to_string(), + }, + Mangling::Legacy => match import { + WasmExport::Func { + interface, + func, + post_return, + } => { + let mut name = String::new(); + if post_return { + name.push_str("cabi_post_"); + } + if let Some(interface) = interface { + let s = self.name_world_key(interface); + name.push_str(&s); + name.push_str("#"); + } + name.push_str(&func.name); + name + } + WasmExport::ResourceDtor { + interface, + resource, + } => { + let name = self.types[resource].name.as_ref().unwrap(); + let interface = self.name_world_key(interface); + format!("{interface}#[dtor]{name}") + } + WasmExport::Memory => "memory".to_string(), + WasmExport::Initialize => "_initialize".to_string(), + WasmExport::Realloc => "cabi_realloc".to_string(), + }, + } + } +} + +/// Possible imports that can be passed to [`Resolve::wasm_import_name`]. +#[derive(Debug)] +pub enum WasmImport<'a> { + /// A WIT function is being imported. Optionally from an interface. + Func { + /// The name of the interface that the function is being imported from. + /// + /// If the function is imported directly from the world then this is + /// `Noen`. + interface: Option<&'a WorldKey>, + + /// The function being imported. + func: &'a Function, + }, + + /// A resource-related intrinsic is being imported. + ResourceIntrinsic { + /// The optional interface to import from, same as `WasmImport::Func`. + interface: Option<&'a WorldKey>, + + /// The resource that's being operated on. + resource: TypeId, + + /// The intrinsic that's being imported. + intrinsic: ResourceIntrinsic, + }, +} + +/// Intrinsic definitions to go with [`WasmImport::ResourceIntrinsic`] which +/// also goes with [`Resolve::wasm_import_name`]. +#[derive(Debug)] +pub enum ResourceIntrinsic { + ImportedDrop, + ExportedDrop, + ExportedNew, + ExportedRep, +} + +/// Different kinds of exports that can be passed to +/// [`Resolve::wasm_export_name`] to export from core wasm modules. +#[derive(Debug)] +pub enum WasmExport<'a> { + /// A WIT function is being exported, optionally from an interface. + Func { + /// An optional interface which owns `func`. Use `None` for top-level + /// world function. + interface: Option<&'a WorldKey>, + + /// The function being exported. + func: &'a Function, + + /// Whether or not this is a post-return function or not. + post_return: bool, + }, + + /// A destructor for a resource exported from this module. + ResourceDtor { + /// The interface that owns the resource. + interface: &'a WorldKey, + /// The resource itself that the destructor is for. + resource: TypeId, + }, + + /// Linear memory, the one that the canonical ABI uses. + Memory, + + /// An initialization function (not the core wasm `start`). + Initialize, + + /// The general-purpose realloc hook. + Realloc, } /// Structure returned by [`Resolve::merge`] which contains mappings from diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs index ed6961608e..f94d52dd2c 100644 --- a/fuzz/src/roundtrip_wit.rs +++ b/fuzz/src/roundtrip_wit.rs @@ -1,7 +1,7 @@ use arbitrary::{Result, Unstructured}; use std::path::Path; use wit_component::*; -use wit_parser::{PackageId, Resolve}; +use wit_parser::{Mangling, PackageId, Resolve}; pub fn run(u: &mut Unstructured<'_>) -> Result<()> { let wasm = u.arbitrary().and_then(|config| { @@ -36,7 +36,12 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { let mut decoded_bindgens = Vec::new(); for (id, world) in resolve.worlds.iter().take(20) { log::debug!("embedding world {} as in a dummy module", world.name); - let mut dummy = wit_component::dummy_module(&resolve, id); + let mangling = match u.int_in_range(0..=1)? { + 0 => Mangling::Legacy, + 1 => Mangling::Standard32, + _ => unreachable!(), + }; + let mut dummy = wit_component::dummy_module(&resolve, id, mangling); wit_component::embed_component_metadata(&mut dummy, &resolve, id, StringEncoding::UTF8) .unwrap(); write_file("dummy.wasm", &dummy); diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 9c95f14dc5..9c38da4aac 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -15,7 +15,7 @@ use wat::Detect; use wit_component::{ embed_component_metadata, ComponentEncoder, DecodedWasm, Linker, StringEncoding, WitPrinter, }; -use wit_parser::{PackageId, Resolve}; +use wit_parser::{Mangling, PackageId, Resolve}; /// WebAssembly wit-based component tooling. #[derive(Parser)] @@ -148,6 +148,17 @@ pub struct NewOpts { /// This is enabled by default. #[clap(long, value_name = "MERGE")] merge_imports_based_on_semver: Option, + + /// Reject usage of the "legacy" naming scheme of `wit-component` and + /// require the new naming scheme to be used. + /// + /// This flag can be used to ignore core module imports/exports that don't + /// conform to WebAssembly/component-model#378. This turns off + /// compatibility `wit-component`'s historical naming scheme. This is + /// intended to be used to test if a tool is compatible with a hypothetical + /// removal of the old scheme in the future. + #[clap(long)] + reject_legacy_names: bool, } impl NewOpts { @@ -158,7 +169,9 @@ impl NewOpts { /// Executes the application. fn run(self) -> Result<()> { let wasm = self.io.parse_input_wasm()?; - let mut encoder = ComponentEncoder::default().validate(!self.skip_validation); + let mut encoder = ComponentEncoder::default() + .validate(!self.skip_validation) + .reject_legacy_names(self.reject_legacy_names); if let Some(merge) = self.merge_imports_based_on_semver { encoder = encoder.merge_imports_based_on_semver(merge); @@ -278,9 +291,20 @@ pub struct EmbedOpts { /// imports/exports and the right signatures for the component model. This /// can be useful to, perhaps, inspect a template module and what it looks /// like to work with an interface in the component model. - #[clap(long)] + /// + /// This option is equivalent to `--dummy-names standard32` + #[clap(long, conflicts_with = "dummy_names")] dummy: bool, + /// Same as `--dummy`, but the style of core wasm names is specified. + /// + /// This flag is the same as `--dummy` where if specified a core wasm module + /// is not read but is instead generated. The value of the option here is + /// the name mangling scheme to use for core wasm names generated. Current + /// options are `legacy|standard32`. + #[clap(long, conflicts_with = "dummy")] + dummy_names: Option, + /// Print the output in the WebAssembly text format instead of binary. #[clap(long, short = 't')] wat: bool, @@ -293,14 +317,15 @@ impl EmbedOpts { /// Executes the application. fn run(self) -> Result<()> { - let wasm = if self.dummy { - None - } else { - Some(self.io.parse_input_wasm()?) - }; let (resolve, pkg_id) = self.resolve.load()?; let world = resolve.select_world(pkg_id, self.world.as_deref())?; - let mut wasm = wasm.unwrap_or_else(|| wit_component::dummy_module(&resolve, world)); + let mut wasm = if self.dummy { + wit_component::dummy_module(&resolve, world, Mangling::Standard32) + } else if let Some(mangling) = self.dummy_names { + wit_component::dummy_module(&resolve, world, mangling) + } else { + self.io.parse_input_wasm()? + }; embed_component_metadata( &mut wasm, diff --git a/tests/cli/embed-dummy.wit b/tests/cli/embed-dummy.wit new file mode 100644 index 0000000000..ff256eff07 --- /dev/null +++ b/tests/cli/embed-dummy.wit @@ -0,0 +1,13 @@ +// RUN[legacy]: component embed -t --dummy-names legacy % | strip -t +// RUN[standard32]: component embed -t --dummy-names standard32 % | strip -t + +package a:b; + +interface x { + y: func() -> string; +} + +world w { + import x; + export x; +} diff --git a/tests/cli/embed-dummy.wit.legacy.stdout b/tests/cli/embed-dummy.wit.legacy.stdout new file mode 100644 index 0000000000..43af3e09eb --- /dev/null +++ b/tests/cli/embed-dummy.wit.legacy.stdout @@ -0,0 +1,21 @@ +(module + (type (;0;) (func (param i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (type (;3;) (func)) + (import "a:b/x" "y" (func (;0;) (type 0))) + (memory (;0;) 0) + (export "a:b/x#y" (func 1)) + (export "cabi_post_a:b/x#y" (func 2)) + (export "memory" (memory 0)) + (export "cabi_realloc" (func 3)) + (export "_initialize" (func 4)) + (func (;1;) (type 1) (result i32) + unreachable + ) + (func (;2;) (type 0) (param i32)) + (func (;3;) (type 2) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;4;) (type 3)) +) diff --git a/tests/cli/embed-dummy.wit.standard32.stdout b/tests/cli/embed-dummy.wit.standard32.stdout new file mode 100644 index 0000000000..7b874d823a --- /dev/null +++ b/tests/cli/embed-dummy.wit.standard32.stdout @@ -0,0 +1,21 @@ +(module + (type (;0;) (func (param i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (type (;3;) (func)) + (import "cm32p2|a:b/x" "y" (func (;0;) (type 0))) + (memory (;0;) 0) + (export "cm32p2|a:b/x|y" (func 1)) + (export "cm32p2|a:b/x|y_post" (func 2)) + (export "cm32p2_memory" (memory 0)) + (export "cm32p2_realloc" (func 3)) + (export "cm32p2_initialize" (func 4)) + (func (;1;) (type 1) (result i32) + unreachable + ) + (func (;2;) (type 0) (param i32)) + (func (;3;) (type 2) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;4;) (type 3)) +) diff --git a/tests/cli/reject-legacy-names.wit b/tests/cli/reject-legacy-names.wit new file mode 100644 index 0000000000..2cc55e5183 --- /dev/null +++ b/tests/cli/reject-legacy-names.wit @@ -0,0 +1,8 @@ +// FAIL: component embed % --dummy-names legacy | component new --reject-legacy-names + +package a:b; + +world foo { + export f: func(); + import y: func(); +} diff --git a/tests/cli/reject-legacy-names.wit.stderr b/tests/cli/reject-legacy-names.wit.stderr new file mode 100644 index 0000000000..f4b37ce1f3 --- /dev/null +++ b/tests/cli/reject-legacy-names.wit.stderr @@ -0,0 +1,7 @@ +error: failed to encode a component from module + +Caused by: + 0: failed to decode world from module + 1: module was not valid + 2: failed to resolve import `$root::y` + 3: unknown or invalid component model import syntax diff --git a/tests/cli/semver-check-add-export.wit.stderr b/tests/cli/semver-check-add-export.wit.stderr index e7391d9617..76f4a07ff0 100644 --- a/tests/cli/semver-check-add-export.wit.stderr +++ b/tests/cli/semver-check-add-export.wit.stderr @@ -2,4 +2,4 @@ error: new world is not semver-compatible with the previous world Caused by: 0: type mismatch for import `new` - missing export named `a` (at offset 0xf0) + missing export named `a` (at offset 0x15a) diff --git a/tests/cli/semver-check-remove-import.wit.stderr b/tests/cli/semver-check-remove-import.wit.stderr index 339e755284..8e588913e1 100644 --- a/tests/cli/semver-check-remove-import.wit.stderr +++ b/tests/cli/semver-check-remove-import.wit.stderr @@ -2,4 +2,4 @@ error: new world is not semver-compatible with the previous world Caused by: 0: type mismatch for import `new` - missing import named `a` (at offset 0x119) + missing import named `a` (at offset 0x182)