From 3c83513fab1a42a6fcbf8a5cb7b0b87aa5545aa1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 13 May 2026 13:26:07 -0700 Subject: [PATCH] Plumb `(implements "...")` support throughout This commit plumbs the type system support for `(implement "...")` in the component model throughout Wasmtime. Notably the fields and such are all faithfully carried over from wasmparser into the type reflection within Wasmtime itself. This'll be needed for loading components with this field. Note that no bindings generation is not updated yet, this is only the compiler/runtime side of `(implement "...")` and bindings generation will be a subsequent update. --- crates/c-api/src/component/types/component.rs | 8 +-- crates/c-api/src/component/types/instance.rs | 4 +- crates/cli-flags/src/lib.rs | 4 ++ crates/environ/src/component/dfg.rs | 14 ++--- crates/environ/src/component/info.rs | 6 +- crates/environ/src/component/translate.rs | 14 +++-- .../environ/src/component/translate/inline.rs | 60 ++++++++++++------- crates/environ/src/component/types.rs | 22 ++++++- crates/environ/src/component/types_builder.rs | 45 +++++++++----- crates/fuzzing/src/generators/config.rs | 3 + crates/fuzzing/src/generators/module.rs | 2 + crates/test-util/src/wasmtime_wast.rs | 3 + crates/test-util/src/wast.rs | 1 + crates/wasmtime/src/config.rs | 13 +++- .../src/runtime/component/component.rs | 21 ++++--- .../src/runtime/component/instance.rs | 6 +- .../wasmtime/src/runtime/component/linker.rs | 13 ++-- .../src/runtime/component/matching.rs | 4 +- .../wasmtime/src/runtime/component/types.rs | 55 ++++++++++++----- crates/wast/src/wast.rs | 2 +- src/commands/run.rs | 4 +- tests/all/component_model/aot.rs | 33 +++++++++- tests/all/component_model/dynamic.rs | 36 +++++------ tests/all/component_model/linker.rs | 2 +- .../component-model/implements-disabled.wast | 5 ++ .../component-model/implements.wast | 34 +++++++++++ 26 files changed, 295 insertions(+), 119 deletions(-) create mode 100644 tests/misc_testsuite/component-model/implements-disabled.wast create mode 100644 tests/misc_testsuite/component-model/implements.wast diff --git a/crates/c-api/src/component/types/component.rs b/crates/c-api/src/component/types/component.rs index 9079e69f2d4c..d70096256877 100644 --- a/crates/c-api/src/component/types/component.rs +++ b/crates/c-api/src/component/types/component.rs @@ -37,7 +37,7 @@ pub unsafe extern "C" fn wasmtime_component_type_import_get( }; match ty.ty.get_import(&engine.engine, name) { Some(item) => { - ret.write(item.into()); + ret.write(item.ty.into()); true } None => false, @@ -58,7 +58,7 @@ pub extern "C" fn wasmtime_component_type_import_nth( let name: &str = name; name_ret.write(name.as_ptr()); name_len_ret.write(name.len()); - type_ret.write(item.into()); + type_ret.write(item.ty.into()); true } None => false, @@ -87,7 +87,7 @@ pub unsafe extern "C" fn wasmtime_component_type_export_get( }; match ty.ty.get_export(&engine.engine, name) { Some(item) => { - ret.write(item.into()); + ret.write(item.ty.into()); true } None => false, @@ -108,7 +108,7 @@ pub extern "C" fn wasmtime_component_type_export_nth( let name: &str = name; name_ret.write(name.as_ptr()); name_len_ret.write(name.len()); - type_ret.write(item.into()); + type_ret.write(item.ty.into()); true } None => false, diff --git a/crates/c-api/src/component/types/instance.rs b/crates/c-api/src/component/types/instance.rs index 55ea85f09b44..4e3bafaa23d4 100644 --- a/crates/c-api/src/component/types/instance.rs +++ b/crates/c-api/src/component/types/instance.rs @@ -33,7 +33,7 @@ pub unsafe extern "C" fn wasmtime_component_instance_type_export_get( }; match ty.ty.get_export(&engine.engine, name) { Some(item) => { - ret.write(item.into()); + ret.write(item.ty.into()); true } None => false, @@ -54,7 +54,7 @@ pub extern "C" fn wasmtime_component_instance_type_export_nth( let name: &str = name; name_ret.write(name.as_ptr()); name_len_ret.write(name.len()); - type_ret.write(item.into()); + type_ret.write(item.ty.into()); true } None => false, diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index e8d15bc5bdc7..5a81ca87fc46 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -460,6 +460,9 @@ wasmtime_option_group! { /// Component model support for fixed-length lists: this corresponds /// to the 🔧 emoji in the component model specification pub component_model_fixed_length_lists: Option, + /// Component model support for `(implements ...)`, corresponds to the + /// 🏷️ emoji in the upstream spec. + pub component_model_implements: Option, /// Whether or not any concurrency infrastructure in Wasmtime is /// enabled or not. pub concurrency_support: Option, @@ -1223,6 +1226,7 @@ impl CommonOptions { ("component-model", component_model_error_context, wasm_component_model_error_context) ("component-model", component_model_map, wasm_component_model_map) ("component-model", component_model_fixed_length_lists, wasm_component_model_fixed_length_lists) + ("component-model", component_model_implements, wasm_component_model_implements) ("threads", threads, wasm_threads) ("gc", gc, wasm_gc) ("gc", reference_types, wasm_reference_types) diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs index 9e8211cf6788..4f604dda09c3 100644 --- a/crates/environ/src/component/dfg.rs +++ b/crates/environ/src/component/dfg.rs @@ -43,13 +43,13 @@ use wasmparser::component_types::ComponentCoreModuleTypeId; #[derive(Default)] pub struct ComponentDfg { /// Same as `Component::import_types` - pub import_types: PrimaryMap, + pub import_types: PrimaryMap, /// Same as `Component::imports` pub imports: PrimaryMap)>, /// Same as `Component::exports` - pub exports: IndexMap, + pub exports: IndexMap, /// All trampolines and their type signature which will need to get /// compiled by Cranelift. @@ -254,7 +254,7 @@ pub enum Export { }, Instance { ty: TypeComponentInstanceIndex, - exports: IndexMap, + exports: IndexMap, }, Type(TypeDef), } @@ -639,10 +639,10 @@ impl ComponentDfg { // creating some lowered imports, perhaps some saved modules, etc. let mut export_items = PrimaryMap::new(); let mut exports = NameMap::default(); - for (name, export) in self.exports.iter() { + for (name, (export, data)) in self.exports.iter() { let export = linearize.export(export, &mut export_items, wasmtime_types, wasmparser_types)?; - exports.insert(name, &mut NameMapNoIntern, false, export)?; + exports.insert(name, &mut NameMapNoIntern, false, (export, data.clone()))?; } // With all those pieces done the results of the dataflow-based @@ -802,10 +802,10 @@ impl LinearizeDfg<'_> { ty: *ty, exports: { let mut map = NameMap::default(); - for (name, export) in exports { + for (name, (export, data)) in exports { let export = self.export(export, items, wasmtime_types, wasmparser_types)?; - map.insert(name, &mut NameMapNoIntern, false, export)?; + map.insert(name, &mut NameMapNoIntern, false, (export, data.clone()))?; } map }, diff --git a/crates/environ/src/component/info.rs b/crates/environ/src/component/info.rs index a0fb76d0a0fc..ad8850f48108 100644 --- a/crates/environ/src/component/info.rs +++ b/crates/environ/src/component/info.rs @@ -76,7 +76,7 @@ pub struct Component { /// /// Note that each name is given an `ImportIndex` here for the next map to /// refer back to. - pub import_types: PrimaryMap, + pub import_types: PrimaryMap, /// A list of "flattened" imports that are used by this instance. /// @@ -107,7 +107,7 @@ pub struct Component { pub imports: PrimaryMap)>, /// This component's own root exports from the component itself. - pub exports: NameMap, + pub exports: NameMap, /// All exports of this component and exported instances of this component. /// @@ -491,7 +491,7 @@ pub enum Export { /// Instance type index, if such is assigned ty: TypeComponentInstanceIndex, /// Instance export map - exports: NameMap, + exports: NameMap, }, /// An exported type from a component or instance, currently only /// informational. diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 0bf7dd5d0bad..447ec4faa853 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -159,7 +159,7 @@ struct Translation<'data> { /// The list of exports from this component, as pairs of names and an /// index into an index space of what's being exported. - exports: IndexMap<&'data str, ComponentItem>, + exports: IndexMap<&'data str, (ComponentItem, wasmparser::ComponentExternName<'data>)>, /// Type information produced by `wasmparser` for this component. /// @@ -355,7 +355,10 @@ enum LocalInitializer<'data> { HashMap<&'data str, ComponentItem>, ComponentInstanceTypeId, ), - ComponentSynthetic(HashMap<&'data str, ComponentItem>, ComponentInstanceTypeId), + ComponentSynthetic( + HashMap<&'data str, (ComponentItem, wasmparser::ComponentExternName<'data>)>, + ComponentInstanceTypeId, + ), // alias section AliasExportFunc(ModuleInstanceIndex, &'data str), @@ -1310,7 +1313,10 @@ impl<'a, 'data> Translator<'a, 'data> { for export in s { let export = export?; let item = self.kind_to_item(export.kind, export.index)?; - let prev = self.result.exports.insert(export.name.name, item); + let prev = self + .result + .exports + .insert(export.name.name, (item, export.name)); assert!(prev.is_none()); self.result .initializers @@ -1452,7 +1458,7 @@ impl<'a, 'data> Translator<'a, 'data> { let mut map = HashMap::with_capacity(exports.len()); for export in exports { let idx = self.kind_to_item(export.kind, export.index)?; - map.insert(export.name.name, idx); + map.insert(export.name.name, (idx, export.name)); } Ok(LocalInitializer::ComponentSynthetic(map, ty)) diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs index ca174a6ee07c..a4916df832f1 100644 --- a/crates/environ/src/component/translate/inline.rs +++ b/crates/environ/src/component/translate/inline.rs @@ -114,10 +114,13 @@ pub(super) fn run( if let TypeDef::Interface(_) = ty { continue; } - let index = inliner - .result - .import_types - .push((name.name.to_string(), ty)); + let index = inliner.result.import_types.push(( + name.name.to_string(), + ComponentExtern { + ty, + data: ComponentExternData::new(name), + }, + )); let path = ImportPath::root(index); args.insert(name.name, ComponentItemDef::from_import(path, ty)?); } @@ -134,8 +137,9 @@ pub(super) fn run( assert!(frames.is_empty()); let mut export_map = Default::default(); - for (name, def) in exports { - inliner.record_export(name, def, types, &mut export_map)?; + for (name, (def, data)) in exports { + let data = ComponentExternData::new(data); + inliner.record_export(name, def, data, types, &mut export_map)?; } inliner.result.exports = export_map; inliner.result.num_future_tables = types.num_future_tables(); @@ -346,7 +350,7 @@ enum ComponentInstanceDef<'a> { // FIXME: same as the issue on `ComponentClosure` where this is cloned a lot // and may need `Rc`. Items( - IndexMap<&'a str, ComponentItemDef<'a>>, + IndexMap<&'a str, (ComponentItemDef<'a>, wasmparser::ComponentExternName<'a>)>, TypeComponentInstanceIndex, ), } @@ -376,7 +380,8 @@ impl<'a> Inliner<'a> { &mut self, types: &mut ComponentTypesBuilder, frames: &mut Vec<(InlinerFrame<'a>, ResourcesBuilder)>, - ) -> Result>> { + ) -> Result, wasmparser::ComponentExternName<'a>)>> + { // This loop represents the execution of the instantiation of a // component. This is an iterative process which is finished once all // initializers are processed. Currently this is modeled as an infinite @@ -407,7 +412,7 @@ impl<'a> Inliner<'a> { .translation .exports .iter() - .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) + .map(|(name, (item, data))| Ok((*name, (frame.item(*item, types)?, *data)))) .collect::>()?; let instance_ty = frame.instance_ty; let (_, snapshot) = frames.pop().unwrap(); @@ -1295,7 +1300,7 @@ impl<'a> Inliner<'a> { ComponentSynthetic(map, ty) => { let items = map .iter() - .map(|(name, index)| Ok((*name, frame.item(*index, types)?))) + .map(|(name, (index, data))| Ok((*name, (frame.item(*index, types)?, *data)))) .collect::>()?; let types_ref = frame.translation.types_ref(); let ty = types.convert_instance(types_ref, *ty)?; @@ -1401,7 +1406,8 @@ impl<'a> Inliner<'a> { // item is then pushed in the relevant index space. ComponentInstanceDef::Import(path, ty) => { let path = path.push(*name); - let def = ComponentItemDef::from_import(path, types[*ty].exports[*name])?; + let def = + ComponentItemDef::from_import(path, types[*ty].exports[*name].ty)?; frame.push_item(def); } @@ -1409,7 +1415,7 @@ impl<'a> Inliner<'a> { // through instantiation of a component or through a // synthetic renaming of items we just schlep around the // definitions of various items here. - ComponentInstanceDef::Items(map, _) => frame.push_item(map[*name].clone()), + ComponentInstanceDef::Items(map, _) => frame.push_item(map[*name].0.clone()), } } @@ -1615,8 +1621,9 @@ impl<'a> Inliner<'a> { &mut self, name: &str, def: ComponentItemDef<'a>, + data: ComponentExternData, types: &'a ComponentTypesBuilder, - map: &mut IndexMap, + map: &mut IndexMap, ) -> Result<()> { let export = match def { // Exported modules are currently saved in a `PrimaryMap`, at @@ -1676,8 +1683,8 @@ impl<'a> Inliner<'a> { ComponentInstanceDef::Import(path, ty) => { for (name, ty) in types[ty].exports.iter() { let path = path.push(name); - let def = ComponentItemDef::from_import(path, *ty)?; - self.record_export(name, def, types, &mut exports)?; + let def = ComponentItemDef::from_import(path, ty.ty)?; + self.record_export(name, def, ty.data.clone(), types, &mut exports)?; } dfg::Export::Instance { ty, exports } } @@ -1686,8 +1693,9 @@ impl<'a> Inliner<'a> { // translated recursively here to our `exports` map which is // the bag of items we're exporting. ComponentInstanceDef::Items(map, ty) => { - for (name, def) in map { - self.record_export(name, def, types, &mut exports)?; + for (name, (def, data)) in map { + let data = ComponentExternData::new(data); + self.record_export(name, def, data.clone(), types, &mut exports)?; } dfg::Export::Instance { ty, exports } } @@ -1703,7 +1711,7 @@ impl<'a> Inliner<'a> { ComponentItemDef::Type(def) => dfg::Export::Type(def), }; - map.insert(name.to_string(), export); + map.insert(name.to_string(), (export, data)); Ok(()) } } @@ -1838,7 +1846,7 @@ impl<'a> InlinerFrame<'a> { /// and which component instantiated it. fn finish_instantiate( &mut self, - exports: IndexMap<&'a str, ComponentItemDef<'a>>, + exports: IndexMap<&'a str, (ComponentItemDef<'a>, wasmparser::ComponentExternName<'a>)>, ty: ComponentInstanceTypeId, types: &mut ComponentTypesBuilder, ) -> Result<()> { @@ -1852,7 +1860,7 @@ impl<'a> InlinerFrame<'a> { &mut path, &mut |path| match path { [] => unreachable!(), - [name, rest @ ..] => exports[name].lookup_resource(rest, types), + [name, rest @ ..] => exports[name].0.lookup_resource(rest, types), }, ); } @@ -1916,7 +1924,7 @@ impl<'a> ComponentItemDef<'a> { cur = match instance { // If this instance is a "bag of things" then this is as easy as // looking up the name in the bag of names. - ComponentInstanceDef::Items(names, _) => names[element].clone(), + ComponentInstanceDef::Items(names, _) => names[element].0.clone(), // If, however, this instance is an imported instance then this // is a further projection within the import with one more path @@ -1925,7 +1933,7 @@ impl<'a> ComponentItemDef<'a> { // in conjunction with a one-longer `path` to produce a new item // definition. ComponentInstanceDef::Import(path, ty) => { - ComponentItemDef::from_import(path.push(element), types[ty].exports[element]) + ComponentItemDef::from_import(path.push(element), types[ty].exports[element].ty) .unwrap() } ComponentInstanceDef::Intrinsics => { @@ -1948,3 +1956,11 @@ enum InstanceModule { Static(StaticModuleIndex), Import(TypeModuleIndex), } + +impl ComponentExternData { + fn new(data: wasmparser::ComponentExternName<'_>) -> Self { + ComponentExternData { + implements: data.implements.map(|s| s.to_string()), + } + } +} diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index fb46dfce8ccb..1400159cbe15 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -449,6 +449,22 @@ where } } +/// TODO +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ComponentExtern { + /// TODO + pub data: ComponentExternData, + /// TODO + pub ty: TypeDef, +} + +/// TODO +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ComponentExternData { + /// TODO + pub implements: Option, +} + /// Types of imports and exports in the component model. /// /// These types are what's available for import and export in components. Note @@ -549,9 +565,9 @@ impl TypeTrace for TypeModule { #[derive(Serialize, Deserialize, Default)] pub struct TypeComponent { /// The named values that this component imports. - pub imports: IndexMap, + pub imports: IndexMap, /// The named values that this component exports. - pub exports: IndexMap, + pub exports: IndexMap, } /// The type of a component instance in the component model, or an instantiated @@ -561,7 +577,7 @@ pub struct TypeComponent { #[derive(Serialize, Deserialize, Default)] pub struct TypeComponentInstance { /// The list of exports that this component has along with their types. - pub exports: IndexMap, + pub exports: IndexMap, } /// A component function type in the component model. diff --git a/crates/environ/src/component/types_builder.rs b/crates/environ/src/component/types_builder.rs index a91c73e2403f..3f8f7d94957d 100644 --- a/crates/environ/src/component/types_builder.rs +++ b/crates/environ/src/component/types_builder.rs @@ -146,12 +146,15 @@ impl ComponentTypesBuilder { pub fn finish(mut self, component: &Component) -> (ComponentTypes, TypeComponentIndex) { let mut component_ty = TypeComponent::default(); for (_, (name, ty)) in component.import_types.iter() { - component_ty.imports.insert(name.clone(), *ty); + component_ty.imports.insert(name.clone(), ty.clone()); } - for (name, ty) in component.exports.raw_iter() { + for (name, (ty, data)) in component.exports.raw_iter() { component_ty.exports.insert( name.clone_panic_on_oom().into(), - self.export_type_def(&component.export_items, *ty), + ComponentExtern { + data: data.clone(), + ty: self.export_type_def(&component.export_items, *ty), + }, ); } let ty = self.component_types.components.push(component_ty); @@ -266,6 +269,21 @@ impl ComponentTypesBuilder { Ok(self.add_func_type(ty)) } + /// Converts a wasmparser `wasmparser::ComponentItem` into Wasmtime's type + /// representation. + pub fn convert_component_item( + &mut self, + types: TypesRef<'_>, + ty: &wasmparser::component_types::ComponentItem, + ) -> Result { + Ok(ComponentExtern { + ty: self.convert_component_entity_type(types, ty.ty)?, + data: ComponentExternData { + implements: ty.implements.clone(), + }, + }) + } + /// Converts a wasmparser `ComponentEntityType` into Wasmtime's type /// representation. pub fn convert_component_entity_type( @@ -326,17 +344,15 @@ impl ComponentTypesBuilder { let mut result = TypeComponent::default(); for (name, ty) in ty.imports.iter() { self.register_abstract_component_entity_type(types, ty.ty); - result.imports.insert( - name.clone(), - self.convert_component_entity_type(types, ty.ty)?, - ); + result + .imports + .insert(name.clone(), self.convert_component_item(types, ty)?); } for (name, ty) in ty.exports.iter() { self.register_abstract_component_entity_type(types, ty.ty); - result.exports.insert( - name.clone(), - self.convert_component_entity_type(types, ty.ty)?, - ); + result + .exports + .insert(name.clone(), self.convert_component_item(types, ty)?); } Ok(self.component_types.components.push(result)) } @@ -351,10 +367,9 @@ impl ComponentTypesBuilder { let mut result = TypeComponentInstance::default(); for (name, ty) in ty.exports.iter() { self.register_abstract_component_entity_type(types, ty.ty); - result.exports.insert( - name.clone(), - self.convert_component_entity_type(types, ty.ty)?, - ); + result + .exports + .insert(name.clone(), self.convert_component_item(types, ty)?); } Ok(self.component_types.component_instances.push(result)) } diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs index f3fea142f498..ce43cffa3bef 100644 --- a/crates/fuzzing/src/generators/config.rs +++ b/crates/fuzzing/src/generators/config.rs @@ -146,6 +146,7 @@ impl Config { component_model_gc, component_model_map, component_model_fixed_length_lists, + component_model_implements, simd, exceptions, legacy_exceptions: _, @@ -174,6 +175,7 @@ impl Config { self.module_config.component_model_map = component_model_map.unwrap_or(false); self.module_config.component_model_fixed_length_lists = component_model_fixed_length_lists.unwrap_or(false); + self.module_config.component_model_implements = component_model_implements.unwrap_or(false); self.module_config.stack_switching = stack_switching.unwrap_or(false); // Enable/disable proposals that wasm-smith has knobs for which will be @@ -326,6 +328,7 @@ impl Config { cfg.wasm.component_model_map = Some(self.module_config.component_model_map); cfg.wasm.component_model_fixed_length_lists = Some(self.module_config.component_model_fixed_length_lists); + cfg.wasm.component_model_implements = Some(self.module_config.component_model_implements); cfg.wasm.custom_page_sizes = Some(self.module_config.config.custom_page_sizes_enabled); cfg.wasm.epoch_interruption = Some(self.wasmtime.epoch_interruption); cfg.wasm.extended_const = Some(self.module_config.config.extended_const_enabled); diff --git a/crates/fuzzing/src/generators/module.rs b/crates/fuzzing/src/generators/module.rs index 40f58964fc7a..6b31472232de 100644 --- a/crates/fuzzing/src/generators/module.rs +++ b/crates/fuzzing/src/generators/module.rs @@ -24,6 +24,7 @@ pub struct ModuleConfig { pub component_model_gc: bool, pub component_model_map: bool, pub component_model_fixed_length_lists: bool, + pub component_model_implements: bool, pub legacy_exceptions: bool, pub shared_memory: bool, pub stack_switching: bool, @@ -84,6 +85,7 @@ impl<'a> Arbitrary<'a> for ModuleConfig { component_model_gc: false, component_model_map: false, component_model_fixed_length_lists: false, + component_model_implements: false, legacy_exceptions: false, shared_memory: false, stack_switching: false, diff --git a/crates/test-util/src/wasmtime_wast.rs b/crates/test-util/src/wasmtime_wast.rs index e4fe7d43fa89..cf7a3bca6f9c 100644 --- a/crates/test-util/src/wasmtime_wast.rs +++ b/crates/test-util/src/wasmtime_wast.rs @@ -47,6 +47,7 @@ pub fn apply_test_config(config: &mut Config, test_config: &wast::TestConfig) { component_model_gc, component_model_map, component_model_fixed_length_lists, + component_model_implements, nan_canonicalization, simd, exceptions, @@ -79,6 +80,7 @@ pub fn apply_test_config(config: &mut Config, test_config: &wast::TestConfig) { let component_model_gc = component_model_gc.unwrap_or(false); let component_model_map = component_model_map.unwrap_or(false); let component_model_fixed_length_lists = component_model_fixed_length_lists.unwrap_or(false); + let component_model_implements = component_model_implements.unwrap_or(false); let nan_canonicalization = nan_canonicalization.unwrap_or(false); let relaxed_simd = relaxed_simd.unwrap_or(false); let legacy_exceptions = legacy_exceptions.unwrap_or(false); @@ -121,6 +123,7 @@ pub fn apply_test_config(config: &mut Config, test_config: &wast::TestConfig) { .wasm_component_model_gc(component_model_gc) .wasm_component_model_map(component_model_map) .wasm_component_model_fixed_length_lists(component_model_fixed_length_lists) + .wasm_component_model_implements(component_model_implements) .wasm_exceptions(exceptions) .wasm_stack_switching(stack_switching) .cranelift_nan_canonicalization(nan_canonicalization); diff --git a/crates/test-util/src/wast.rs b/crates/test-util/src/wast.rs index be8a7ce244eb..cc394ee8cf5c 100644 --- a/crates/test-util/src/wast.rs +++ b/crates/test-util/src/wast.rs @@ -283,6 +283,7 @@ macro_rules! foreach_config_option { component_model_gc component_model_map component_model_fixed_length_lists + component_model_implements simd gc_types exceptions diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 7fdc18e8cc65..3180c64be3d0 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -1314,6 +1314,16 @@ impl Config { self } + /// This corresponds to the 🏷️ emoji in the component model specification. + /// + /// Please note that Wasmtime's support for this feature is a work in + /// progress. + #[cfg(feature = "component-model")] + pub fn wasm_component_model_implements(&mut self, enable: bool) -> &mut Self { + self.wasm_features(WasmFeatures::CM_IMPLEMENTS, enable); + self + } + /// Configures whether the [Exception-handling proposal][proposal] is enabled or not. /// /// [proposal]: https://github.com/WebAssembly/exception-handling @@ -2326,7 +2336,8 @@ impl Config { | WasmFeatures::CM_ERROR_CONTEXT | WasmFeatures::CM_GC | WasmFeatures::CM_MAP - | WasmFeatures::CM_FIXED_LENGTH_LISTS; + | WasmFeatures::CM_FIXED_LENGTH_LISTS + | WasmFeatures::CM_IMPLEMENTS; #[allow(unused_mut, reason = "easier to avoid #[cfg]")] let mut unsupported = !features_known_to_wasmtime; diff --git a/crates/wasmtime/src/runtime/component/component.rs b/crates/wasmtime/src/runtime/component/component.rs index cbf29f542540..00f519bb7093 100644 --- a/crates/wasmtime/src/runtime/component/component.rs +++ b/crates/wasmtime/src/runtime/component/component.rs @@ -294,14 +294,16 @@ impl Component { /// (component (import "x" (type (sub resource)))) /// "#)?; /// - /// let (_, a_ty) = a.component_type().imports(&engine).next().unwrap(); - /// let (_, b_ty) = b.component_type().imports(&engine).next().unwrap(); + /// let aty = a.component_type(); + /// let bty = b.component_type(); + /// let (_, a_ty) = aty.imports(&engine).next().unwrap(); + /// let (_, b_ty) = bty.imports(&engine).next().unwrap(); /// - /// let a_ty = match a_ty { + /// let a_ty = match a_ty.ty { /// ComponentItem::Resource(ty) => ty, /// _ => unreachable!(), /// }; - /// let b_ty = match b_ty { + /// let b_ty = match b_ty.ty { /// ComponentItem::Resource(ty) => ty, /// _ => unreachable!(), /// }; @@ -329,14 +331,15 @@ impl Component { /// ) /// "#)?; /// - /// let (_, import) = a.component_type().imports(&engine).next().unwrap(); - /// let (_, export) = a.component_type().exports(&engine).next().unwrap(); + /// let ty = a.component_type(); + /// let (_, import) = ty.imports(&engine).next().unwrap(); + /// let (_, export) = ty.exports(&engine).next().unwrap(); /// - /// let import = match import { + /// let import = match import.ty { /// ComponentItem::Resource(ty) => ty, /// _ => unreachable!(), /// }; - /// let export = match export { + /// let export = match export.ty { /// ComponentItem::Resource(ty) => ty, /// _ => unreachable!(), /// }; @@ -890,7 +893,7 @@ impl Component { } None => &info.exports, }; - exports.get(name, &NameMapNoIntern).copied() + exports.get(name, &NameMapNoIntern).map(|pair| pair.0) } pub(crate) fn id(&self) -> CompiledModuleId { diff --git a/crates/wasmtime/src/runtime/component/instance.rs b/crates/wasmtime/src/runtime/component/instance.rs index 0f5145fc35c5..f099d2674f41 100644 --- a/crates/wasmtime/src/runtime/component/instance.rs +++ b/crates/wasmtime/src/runtime/component/instance.rs @@ -681,11 +681,11 @@ where impl InstanceExportLookup for str { fn lookup(&self, component: &Component) -> Option { - component + let (index, _) = component .env_component() .exports - .get(self, &NameMapNoIntern) - .copied() + .get(self, &NameMapNoIntern)?; + Some(*index) } } diff --git a/crates/wasmtime/src/runtime/component/linker.rs b/crates/wasmtime/src/runtime/component/linker.rs index 889cfc8a7195..eef604098953 100644 --- a/crates/wasmtime/src/runtime/component/linker.rs +++ b/crates/wasmtime/src/runtime/component/linker.rs @@ -178,8 +178,13 @@ impl Linker { let env_component = component.env_component(); for (_idx, (name, ty)) in env_component.import_types.iter() { let import = self.map.get(name, &self.strings); - cx.definition(ty, import) - .with_context(|| format!("component imports {desc} `{name}`, but a matching implementation was not found in the linker", desc = ty.desc()))?; + cx.definition(&ty.ty, import).with_context(|| { + format!( + "component imports {desc} `{name}`, but \ + a matching implementation was not found in the linker", + desc = ty.ty.desc() + ) + })?; } Ok(cx) } @@ -386,7 +391,7 @@ impl Linker { stub_item( &mut linker_instance, export_name, - export, + &export.ty, Some(item_name), types, )?; @@ -408,7 +413,7 @@ impl Linker { stub_item( &mut self.root(), import_name, - import_type, + &import_type.ty, None, component.types(), )?; diff --git a/crates/wasmtime/src/runtime/component/matching.rs b/crates/wasmtime/src/runtime/component/matching.rs index 318e01cbdba5..47aeeabaf980 100644 --- a/crates/wasmtime/src/runtime/component/matching.rs +++ b/crates/wasmtime/src/runtime/component/matching.rs @@ -163,11 +163,11 @@ impl TypeChecker<'_> { // Interface types may be exported from a component in order to give them a name, but // they don't have a definition in the sense that this search is interested in, so // ignore them. - if let TypeDef::Interface(_) = expected { + if let TypeDef::Interface(_) = expected.ty { continue; } let actual = actual.and_then(|actual| actual.get(name, self.strings)); - self.definition(expected, actual) + self.definition(&expected.ty, actual) .with_context(|| format!("instance export `{name}` has the wrong type"))?; } Ok(()) diff --git a/crates/wasmtime/src/runtime/component/types.rs b/crates/wasmtime/src/runtime/component/types.rs index b175a7d9a3e5..d559aae9ad0f 100644 --- a/crates/wasmtime/src/runtime/component/types.rs +++ b/crates/wasmtime/src/runtime/component/types.rs @@ -1031,43 +1031,43 @@ impl Component { } /// Returns import associated with `name`, if such exists in the component - pub fn get_import(&self, engine: &Engine, name: &str) -> Option { + pub fn get_import<'a>(&'a self, engine: &'a Engine, name: &str) -> Option> { self.0.types[self.0.index] .imports .get(name) - .map(|ty| ComponentItem::from(engine, ty, &self.0.instance())) + .map(|e| ComponentExtern::new(engine, &self.0.instance(), e)) } /// Iterates over imports of the component pub fn imports<'a>( &'a self, engine: &'a Engine, - ) -> impl ExactSizeIterator + 'a { - self.0.types[self.0.index].imports.iter().map(|(name, ty)| { + ) -> impl ExactSizeIterator)> + 'a { + self.0.types[self.0.index].imports.iter().map(|(name, e)| { ( name.as_str(), - ComponentItem::from(engine, ty, &self.0.instance()), + ComponentExtern::new(engine, &self.0.instance(), e), ) }) } /// Returns export associated with `name`, if such exists in the component - pub fn get_export(&self, engine: &Engine, name: &str) -> Option { + pub fn get_export<'a>(&'a self, engine: &'a Engine, name: &str) -> Option> { self.0.types[self.0.index] .exports .get(name) - .map(|ty| ComponentItem::from(engine, ty, &self.0.instance())) + .map(|e| ComponentExtern::new(engine, &self.0.instance(), e)) } /// Iterates over exports of the component pub fn exports<'a>( &'a self, engine: &'a Engine, - ) -> impl ExactSizeIterator + 'a { - self.0.types[self.0.index].exports.iter().map(|(name, ty)| { + ) -> impl ExactSizeIterator)> + 'a { + self.0.types[self.0.index].exports.iter().map(|(name, e)| { ( name.as_str(), - ComponentItem::from(engine, ty, &self.0.instance()), + ComponentExtern::new(engine, &self.0.instance(), e), ) }) } @@ -1091,27 +1091,52 @@ impl ComponentInstance { } /// Returns export associated with `name`, if such exists in the component instance - pub fn get_export(&self, engine: &Engine, name: &str) -> Option { + pub fn get_export<'a>(&'a self, engine: &'a Engine, name: &str) -> Option> { self.0.types[self.0.index] .exports .get(name) - .map(|ty| ComponentItem::from(engine, ty, &self.0.instance())) + .map(|e| ComponentExtern::new(engine, &self.0.instance(), e)) } /// Iterates over exports of the component instance pub fn exports<'a>( &'a self, engine: &'a Engine, - ) -> impl ExactSizeIterator { - self.0.types[self.0.index].exports.iter().map(|(name, ty)| { + ) -> impl ExactSizeIterator)> { + self.0.types[self.0.index].exports.iter().map(|(name, e)| { ( name.as_str(), - ComponentItem::from(engine, ty, &self.0.instance()), + ComponentExtern::new(engine, &self.0.instance(), e), ) }) } } +/// An import or an export from either [`Component`] or [`ComponentInstance`]. +/// +/// This records the type of the item that is being imported or exported along +/// with any other metadata associated. +#[derive(Clone, Debug)] +pub struct ComponentExtern<'a> { + /// The type of this item. + pub ty: ComponentItem, + /// The `(implements "...")` annotation, if present. + pub implements: Option<&'a str>, +} + +impl<'a> ComponentExtern<'a> { + fn new( + engine: &'a Engine, + instance_ty: &InstanceType<'_>, + env: &'a wasmtime_environ::component::ComponentExtern, + ) -> Self { + Self { + implements: env.data.implements.as_deref(), + ty: ComponentItem::from(engine, &env.ty, instance_ty), + } + } +} + /// Type of an item contained within the component #[derive(Clone, Debug)] pub enum ComponentItem { diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 668361490c5c..d2dcb42c35da 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -380,7 +380,7 @@ impl WastContext { let engine = self.engine().clone(); let mut linker = self.component_linker.instance(name)?; for (name, item) in ty.exports(&engine) { - match item { + match item.ty { component::types::ComponentItem::Module(_) => { let module = instance.get_module(&mut store, name).unwrap(); linker.module(name, &module)?; diff --git a/src/commands/run.rs b/src/commands/run.rs index 100ab696f01b..1a69fab5476d 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -960,7 +960,7 @@ impl RunCommand { .flat_map(move |(name, item)| { let mut names = basename.clone(); names.push(name.to_string()); - collect_exports(engine, item, names) + collect_exports(engine, item.ty, names) }) .collect::>(), CItem::ComponentInstance(c) => c @@ -968,7 +968,7 @@ impl RunCommand { .flat_map(move |(name, item)| { let mut names = basename.clone(); names.push(name.to_string()); - collect_exports(engine, item, names) + collect_exports(engine, item.ty, names) }) .collect::>(), _ => vec![(basename, item)], diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs index 43db793943a0..53adee2048a7 100644 --- a/tests/all/component_model/aot.rs +++ b/tests/all/component_model/aot.rs @@ -1,7 +1,7 @@ use wasmtime::Result; use wasmtime::component::types::ComponentItem; use wasmtime::component::{Component, Linker, Type}; -use wasmtime::{Engine, Module, Precompiled, Store}; +use wasmtime::{Config, Engine, Module, Precompiled, Store}; #[test] fn module_component_mismatch() -> Result<()> { @@ -152,11 +152,11 @@ fn reflect_resource_import() -> Result<()> { let mut imports = ty.imports(&engine); let (_, x) = imports.next().unwrap(); let (_, y) = imports.next().unwrap(); - let x = match x { + let x = match x.ty { ComponentItem::Resource(t) => t, _ => unreachable!(), }; - let y = match y { + let y = match y.ty { ComponentItem::ComponentFunc(t) => t, _ => unreachable!(), }; @@ -208,3 +208,30 @@ fn truncated_component_binaries_dont_panic() -> Result<()> { Ok(()) } + +#[test] +#[cfg_attr(miri, ignore)] +fn implements_shows_up() -> Result<()> { + let mut config = Config::new(); + config.wasm_component_model_implements(true); + let engine = Engine::new(&config)?; + let component = Component::new( + &engine, + r#" + (component + (import "a" (implements "a1:b1/c1") (instance $a)) + (export "b" (implements "a2:b2/c2") (instance $a)) + ) + "#, + )?; + + let ty = component.component_type(); + let mut imports = ty.imports(&engine); + let (_, a) = imports.next().unwrap(); + assert_eq!(a.implements.as_deref(), Some("a1:b1/c1")); + let mut exports = ty.exports(&engine); + let (_, b) = exports.next().unwrap(); + assert_eq!(b.implements.as_deref(), Some("a2:b2/c2")); + + Ok(()) +} diff --git a/tests/all/component_model/dynamic.rs b/tests/all/component_model/dynamic.rs index e1a0202728f3..95f6d6ba278f 100644 --- a/tests/all/component_model/dynamic.rs +++ b/tests/all/component_model/dynamic.rs @@ -994,28 +994,28 @@ fn introspection() -> Result<()> { assert_eq!(imports.len(), 3); let (name, res_ty) = imports.next().unwrap(); assert_eq!(name, "res"); - let ComponentItem::Resource(res_ty) = res_ty else { + let ComponentItem::Resource(res_ty) = res_ty.ty else { panic!("`res` import item of wrong type") }; assert_eq!(res_ty, ResourceType::host::()); let (name, ai_ty) = imports.next().unwrap(); assert_eq!(name, "ai"); - let ComponentItem::ComponentInstance(ai_ty) = ai_ty else { + let ComponentItem::ComponentInstance(ai_ty) = ai_ty.ty else { panic!("`ai` import item of wrong type") }; assert_eq!(ai_ty.exports(linker.engine()).len(), 0); let (name, bi_ty) = imports.next().unwrap(); assert_eq!(name, "bi"); - let ComponentItem::ComponentInstance(bi_ty) = bi_ty else { + let ComponentItem::ComponentInstance(bi_ty) = bi_ty.ty else { panic!("`bi` import item of wrong type") }; let mut bi_exports = bi_ty.exports(linker.engine()); assert_eq!(bi_exports.len(), 1); let (name, bi_m_ty) = bi_exports.next().unwrap(); assert_eq!(name, "m"); - let ComponentItem::Module(bi_m_ty) = bi_m_ty else { + let ComponentItem::Module(bi_m_ty) = bi_m_ty.ty else { panic!("`bi.m` import item of wrong type") }; assert_eq!(bi_m_ty.imports(linker.engine()).len(), 0); @@ -1026,7 +1026,7 @@ fn introspection() -> Result<()> { let (name, run_ty) = exports.next().unwrap(); assert_eq!(name, "run"); - let ComponentItem::ComponentFunc(run_ty) = run_ty else { + let ComponentItem::ComponentFunc(run_ty) = run_ty.ty else { panic!("`run` export item of wrong type") }; assert_eq!(run_ty.params().len(), 0); @@ -1037,21 +1037,21 @@ fn introspection() -> Result<()> { let (name, i_ty) = exports.next().unwrap(); assert_eq!(name, "i"); - let ComponentItem::ComponentInstance(i_ty) = i_ty else { + let ComponentItem::ComponentInstance(i_ty) = i_ty.ty else { panic!("`i` export item of wrong type") }; let mut i_ty_exports = i_ty.exports(linker.engine()); assert_eq!(i_ty_exports.len(), 1); let (name, i_i_ty) = i_ty_exports.next().unwrap(); assert_eq!(name, "i"); - let ComponentItem::ComponentInstance(i_i_ty) = i_i_ty else { + let ComponentItem::ComponentInstance(i_i_ty) = i_i_ty.ty else { panic!("`i.i` import item of wrong type") }; let mut i_i_ty_exports = i_i_ty.exports(linker.engine()); assert_eq!(i_i_ty_exports.len(), 1); let (name, i_i_m_ty) = i_i_ty_exports.next().unwrap(); assert_eq!(name, "m"); - let ComponentItem::Module(i_i_m_ty) = i_i_m_ty else { + let ComponentItem::Module(i_i_m_ty) = i_i_m_ty.ty else { panic!("`i.i.m` import item of wrong type") }; assert_eq!(i_i_m_ty.imports(linker.engine()).len(), 0); @@ -1059,21 +1059,21 @@ fn introspection() -> Result<()> { let (name, r_ty) = exports.next().unwrap(); assert_eq!(name, "r"); - let ComponentItem::ComponentInstance(r_ty) = r_ty else { + let ComponentItem::ComponentInstance(r_ty) = r_ty.ty else { panic!("`r` export item of wrong type") }; assert_eq!(r_ty.exports(linker.engine()).len(), 0); let (name, r2_ty) = exports.next().unwrap(); assert_eq!(name, "r2"); - let ComponentItem::ComponentInstance(r2_ty) = r2_ty else { + let ComponentItem::ComponentInstance(r2_ty) = r2_ty.ty else { panic!("`r2` export item of wrong type") }; let mut r2_exports = r2_ty.exports(linker.engine()); assert_eq!(r2_exports.len(), 1); let (name, r2_m_ty) = r2_exports.next().unwrap(); assert_eq!(name, "m"); - let ComponentItem::Module(r2_m_ty) = r2_m_ty else { + let ComponentItem::Module(r2_m_ty) = r2_m_ty.ty else { panic!("`r2.m` export item of wrong type") }; assert_eq!(r2_m_ty.imports(linker.engine()).len(), 0); @@ -1081,14 +1081,14 @@ fn introspection() -> Result<()> { let (name, b_ty) = exports.next().unwrap(); assert_eq!(name, "b"); - let ComponentItem::Type(b_ty) = b_ty else { + let ComponentItem::Type(b_ty) = b_ty.ty else { panic!("`b` export item of wrong type") }; assert_eq!(b_ty.unwrap_enum().names().collect::>(), ["a", "b"]); let (name, c_ty) = exports.next().unwrap(); assert_eq!(name, "c"); - let ComponentItem::Type(c_ty) = c_ty else { + let ComponentItem::Type(c_ty) = c_ty.ty else { panic!("`c` export item of wrong type") }; let mut fields = c_ty.unwrap_record().fields(); @@ -1103,7 +1103,7 @@ fn introspection() -> Result<()> { let (name, f_ty) = exports.next().unwrap(); assert_eq!(name, "f"); - let ComponentItem::Type(f_ty) = f_ty else { + let ComponentItem::Type(f_ty) = f_ty.ty else { panic!("`f` export item of wrong type") }; assert_eq!( @@ -1113,7 +1113,7 @@ fn introspection() -> Result<()> { let (name, m_ty) = exports.next().unwrap(); assert_eq!(name, "m"); - let ComponentItem::Type(m_ty) = m_ty else { + let ComponentItem::Type(m_ty) = m_ty.ty else { panic!("`m` export item of wrong type") }; { @@ -1128,7 +1128,7 @@ fn introspection() -> Result<()> { let (name, j_ty) = exports.next().unwrap(); assert_eq!(name, "j"); - let ComponentItem::Type(j_ty) = j_ty else { + let ComponentItem::Type(j_ty) = j_ty.ty else { panic!("`j` export item of wrong type") }; let mut cases = j_ty.unwrap_variant().cases(); @@ -1146,7 +1146,7 @@ fn introspection() -> Result<()> { let (name, foo_ty) = exports.next().unwrap(); assert_eq!(name, "foo"); - let ComponentItem::Type(foo_ty) = foo_ty else { + let ComponentItem::Type(foo_ty) = foo_ty.ty else { panic!("`foo` export item of wrong type") }; { @@ -1213,7 +1213,7 @@ fn introspection() -> Result<()> { let (name, fn_ty) = exports.next().unwrap(); assert_eq!(name, "fn"); - let ComponentItem::ComponentFunc(fn_ty) = fn_ty else { + let ComponentItem::ComponentFunc(fn_ty) = fn_ty.ty else { panic!("`fn` export item of wrong type") }; let mut params = fn_ty.params(); diff --git a/tests/all/component_model/linker.rs b/tests/all/component_model/linker.rs index 7564ec52eea3..6c770c369a30 100644 --- a/tests/all/component_model/linker.rs +++ b/tests/all/component_model/linker.rs @@ -135,7 +135,7 @@ fn linker_substituting_types_issue_8003() -> Result<()> { let component_ty = linker.substituted_component_type(&component)?; let exports = component_ty.exports(&engine); for (_name, item) in exports { - match item { + match item.ty { ComponentItem::ComponentInstance(instance) => { for _ in instance.exports(&engine) { // .. diff --git a/tests/misc_testsuite/component-model/implements-disabled.wast b/tests/misc_testsuite/component-model/implements-disabled.wast new file mode 100644 index 000000000000..e4db929d9641 --- /dev/null +++ b/tests/misc_testsuite/component-model/implements-disabled.wast @@ -0,0 +1,5 @@ +;;! component_model_implements = false + +(assert_invalid + (component (import "a" (implements "a:b/c") (instance))) + "the `cm-implements` feature is not active") diff --git a/tests/misc_testsuite/component-model/implements.wast b/tests/misc_testsuite/component-model/implements.wast new file mode 100644 index 000000000000..1a84a6289da6 --- /dev/null +++ b/tests/misc_testsuite/component-model/implements.wast @@ -0,0 +1,34 @@ +;;! component_model_implements = true + +(component + (component + (import "a" (implements "a:b/c") (instance)) + (import "b" (implements "a:b/c") (instance)) + (import "c" (implements "a:b/c@1.0.0") (instance)) + (import "my-label" (implements "ns:pkg/iface") (instance)) + (import "a:b/c" (instance)) + (import "a:b/c@1.0.0" (instance)) + + (instance $a) + + (export "a" (implements "a:b/c") (instance $a)) + (export "b" (implements "a:b/c") (instance $a)) + (export "c" (implements "a:b/c@1.0.0") (instance $a)) + (export "my-label" (implements "ns:pkg/iface") (instance $a)) + (export "a:b/c" (instance $a)) + (export "a:b/c@1.0.0" (instance $a)) + ) + + (type (instance + (export "a" (implements "a:b/c") (instance)) + )) + (type (component + (import "a" (implements "a:b/c") (instance)) + (export "a" (implements "a:b/c") (instance)) + )) + + (instance $a) + (instance + (export "a" (implements "a:b/c") (instance $a)) + ) +)