Skip to content

Commit 649c4ea

Browse files
Add (implements "...") component name support (#2453)
* [wasmparser] Add `[implements=<I>]L` component name support Add parsing, validation, and uniqueness rules for the new `[implements=<interface>]label` extern name form from the component model `implements` proposal. An implements name labels an instance import/export that implements a named interface. Uniqueness: `[implements=<I>]L` conflicts with bare label `L` and with `[method]L.L` / `[static]L.L` (the existing l.l edge case), but is strongly unique from interface names, constructors, and normal method/static names. See implements feature: WebAssembly/component-model#613 * [wit-parser] Add `implements` syntax for named interface imports/exports Support the `import label: iface;` and `export label: iface;` WIT syntax, which encodes as `[implements=<I>]label` in the binary format. This allows importing or exporting the same interface multiple times under different names. Changes include: - Add `implements: Option<InterfaceId>` field to `WorldItem::Interface` - Parse `NamedPath` variant in the WIT AST with disambiguation against fully-qualified `namespace:package/interface` paths - Decode `[implements=<I>]label` names in all binary decoding paths via a new `decode_world_instance` helper - Thread `implements` through world elaboration and ID remapping - Add `name_world_key_with_item` for binary encoding On the API change for `name_world_key_with_item`, I opted to introduce this new fn that is used only at the few call sites that need it vs updating the ~50 sites for name_world_key. * [wit-component] Add implements encoding support to wit-component Update component encoding to use `name_world_key_with_item` at sites that produce component-level extern names, so that `implements` imports and exports are encoded as `[implements=<I>]L` in the binary format. Five call sites are changed from `name_world_key` to the implements-aware variant: import_map key construction, component export names, ImportedResourceDrop lookups, and both direct and indirect InterfaceFunc lookups. * [wit-dylib] add implements to WorldItem::Interface * [wit-smith] Add implements interface generation Teach wit-smith to generate `ImplementsInterface` items in worlds, producing `%label: path;` WIT syntax which encodes as `[implements=<I>]L` in the component binary. This enables fuzzing of the implements feature through the existing roundtrip_wit fuzzer. * Generate nominal interface/type ids in `wit-component` This commit is a long-overdue change in the componentization process in `wit-component` to ensure that when a component is generated there are unique `TypeId` and `InterfaceId` entries corresponding to all types/interfaces which map to the final component. This is distinct from the structure of WIT where the same `InterfaceId`, for example, can be both imported and exported. When generating a component this means that two distinct interfaces are imported/exported. Previously `wit-component` has had a lot of very carefully filled out and handled maps and such to ensure everything works, and this should make things significantly easier because the possibility over overlap is now nonexistent. * Leverage nominal types/interfaces in encoding This commit builds on the previous commit to simplify internal structures within the encoding process of a component. Notably there's no longer any need to have separate maps for imports/exports and instead everything is located in a single set of maps. Not major changes, but this is hoped to unlock future changes and additionally pave the way to simplifying some internals in the future. * Add `cm-implements` feature Gate the new parsing behind this feature. * Shift where `implements` is in the AST Use a new `WorldKey` instead of `WorldItem`. * Remove hand-rolled parsing * Only write blessed files if they change Helps run-on-file-change tools not infinite loop * Reimplement how `implements` is represented In the component model binary format this is no longer packed into import/export names but is instead an auxiliary piece of metadata on imports/exports. This required quite a few updates to parsing, AST structures, etc, throughout. Additionally within `wit-parser` this is now represented with no AST changes from before. Instead `WorldKey::Name` is used with a `WorldItem::Interface`, and the only difference from the previous "anonymous interfaces" is that the interface pointed to has a name. This is what's used to indicate an `implements` value is desired. Initial plumbing within `wit-component` is done, but tests are not fully passing yet. * Handle `import a: b` in `generate_nominal_type_ids` This commit updates the `generate_nominal_type_ids` to handle the new form of imports that are showing up with the `implements` form. This is going to be necessary for bindings generators to handle this neatly and this is also required for wit-component after #2516 * Fix recording stability of named interfaces * Fix the order nominalization happens in * Use the 2024 edition for rustfmt in this workspace Possible now that MSRV is high enough * Fix configured build * Reduce the diff with `main` * Improve ergonomics of wasm-encoder * Undo another interim change * More diff minimizing * Add failing test * Don't permute imports/exports Add a test case that broke in Wasmtime, and add a lot of words for why things are the way they are. * Update binary encoding * Review comments --------- Co-authored-by: Alex Crichton <alex@alexcrichton.com>
1 parent 3450e9a commit 649c4ea

102 files changed

Lines changed: 2856 additions & 495 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/wasm-compose/src/encoding.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ impl<'a> TypeEncoder<'a> {
455455
id: ComponentInstanceTypeId,
456456
) -> u32 {
457457
let ty = &self.0.types[id];
458-
let instance = self.instance(state, ty.exports.iter().map(|(n, t)| (n.as_str(), *t)));
458+
let instance = self.instance(state, ty.exports.iter().map(|(n, t)| (n.as_str(), t.ty)));
459459
let index = state.cur.encodable.type_count();
460460
state.cur.encodable.ty().instance(&instance);
461461
index
@@ -466,8 +466,8 @@ impl<'a> TypeEncoder<'a> {
466466

467467
let component = self.component(
468468
state,
469-
ty.imports.iter().map(|(n, t)| (n.as_str(), *t)),
470-
ty.exports.iter().map(|(n, t)| (n.as_str(), *t)),
469+
ty.imports.iter().map(|(n, t)| (n.as_str(), t.ty)),
470+
ty.exports.iter().map(|(n, t)| (n.as_str(), t.ty)),
471471
);
472472

473473
let index = state.cur.encodable.type_count();
@@ -884,7 +884,7 @@ impl ArgumentImport<'_> {
884884

885885
let mut map = IndexMap::with_capacity(exports.len());
886886
for (name, ty) in exports {
887-
map.insert(name.as_str(), vec![(*component, *ty)]);
887+
map.insert(name.as_str(), vec![(*component, ty.ty)]);
888888
}
889889

890890
self.kind = ArgumentImportKind::Instance(map);
@@ -907,7 +907,7 @@ impl ArgumentImport<'_> {
907907
existing_component,
908908
*existing_type,
909909
new_component,
910-
*new_type,
910+
new_type.ty,
911911
remapping,
912912
) {
913913
continue;
@@ -923,7 +923,7 @@ impl ArgumentImport<'_> {
923923
ecname = existing_component.name,
924924
)
925925
}
926-
dst.push((new_component, *new_type));
926+
dst.push((new_component, new_type.ty));
927927
}
928928
}
929929
// Otherwise, an attempt to merge an instance with a non-instance is an error
@@ -1244,14 +1244,14 @@ impl DependencyRegistrar<'_, '_> {
12441244

12451245
fn component(&mut self, ty: ComponentTypeId) {
12461246
let ty = &self.types[ty];
1247-
for (_, ty) in ty.imports.iter().chain(&ty.exports) {
1248-
self.entity(*ty);
1247+
for ty in ty.imports.values().chain(ty.exports.values()) {
1248+
self.entity(ty.ty);
12491249
}
12501250
}
12511251

12521252
fn instance(&mut self, ty: ComponentInstanceTypeId) {
12531253
for (_, ty) in self.types[ty].exports.iter() {
1254-
self.entity(*ty);
1254+
self.entity(ty.ty);
12551255
}
12561256
}
12571257

crates/wasm-compose/src/graph.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,14 @@ impl<'a> Component<'a> {
144144
Payload::ComponentImportSection(s) => {
145145
for import in s {
146146
let import = import?;
147-
let name = import.name.0.to_string();
147+
let name = import.name.name.to_string();
148148
imports.insert(name, import.ty);
149149
}
150150
}
151151
Payload::ComponentExportSection(s) => {
152152
for export in s {
153153
let export = export?;
154-
let name = export.name.0.to_string();
154+
let name = export.name.name.to_string();
155155
exports.insert(name, (export.kind, export.index));
156156
}
157157
}
@@ -277,7 +277,7 @@ impl<'a> Component<'a> {
277277
let (name, _kind, _index) = self.export(index)?;
278278
Some((
279279
name,
280-
self.types.as_ref().component_entity_type_of_export(name)?,
280+
self.types.as_ref().component_item_for_export(name)?.ty,
281281
))
282282
}
283283

@@ -288,7 +288,7 @@ impl<'a> Component<'a> {
288288
let (name, _ty) = self.import(index)?;
289289
Some((
290290
name,
291-
self.types.as_ref().component_entity_type_of_import(name)?,
291+
self.types.as_ref().component_item_for_import(name)?.ty,
292292
))
293293
}
294294

@@ -333,7 +333,7 @@ impl<'a> Component<'a> {
333333
match self.exports.get_full(k.as_str()) {
334334
Some((ai, _, _)) => {
335335
let (_, a) = self.export_entity_type(ExportIndex(ai)).unwrap();
336-
if !ComponentEntityType::is_subtype_of(&a, self.types(), b, types) {
336+
if !ComponentEntityType::is_subtype_of(&a, self.types(), &b.ty, types) {
337337
return false;
338338
}
339339
}
@@ -497,7 +497,7 @@ impl ResourceMapping {
497497
if let ComponentEntityType::Type {
498498
referenced: ComponentAnyTypeId::Resource(resource_id),
499499
..
500-
} = ty
500+
} = ty.ty
501501
{
502502
exports.insert(export_name, (export_component, resource_id.resource()));
503503
}
@@ -508,7 +508,7 @@ impl ResourceMapping {
508508
if let ComponentEntityType::Type {
509509
referenced: ComponentAnyTypeId::Resource(resource_id),
510510
..
511-
} = ty
511+
} = ty.ty
512512
{
513513
let import_resource = resource_id.resource();
514514
if let Some((export_component, export_resource)) =
@@ -591,16 +591,17 @@ impl<'a> CompositionGraph<'a> {
591591
let ty = component
592592
.types
593593
.as_ref()
594-
.component_entity_type_of_import(import_name)
595-
.unwrap();
594+
.component_item_for_import(import_name)
595+
.unwrap()
596+
.ty;
596597

597598
if let ComponentEntityType::Instance(instance_id) = ty {
598599
for (export_name, ty) in &component.types[instance_id].exports {
599600
// TODO: support nested instances
600601
if let ComponentEntityType::Type {
601602
referenced: ComponentAnyTypeId::Resource(resource_id),
602603
..
603-
} = ty
604+
} = ty.ty
604605
{
605606
let set = resource_imports
606607
.entry(vec![import_name.to_string(), export_name.to_string()])

crates/wasm-encoder/src/component/builder.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -327,14 +327,19 @@ impl ComponentBuilder {
327327
}
328328

329329
/// Imports a new item into this component with the `name` and `ty` specified.
330-
pub fn import(&mut self, name: &str, ty: ComponentTypeRef) -> u32 {
330+
pub fn import<'a>(
331+
&mut self,
332+
name: impl Into<ComponentExternName<'a>>,
333+
ty: ComponentTypeRef,
334+
) -> u32 {
335+
let name = name.into();
331336
let ret = match &ty {
332-
ComponentTypeRef::Instance(_) => self.instances.add(Some(name)),
333-
ComponentTypeRef::Func(_) => self.funcs.add(Some(name)),
334-
ComponentTypeRef::Type(..) => self.types.add(Some(name)),
335-
ComponentTypeRef::Component(_) => self.components.add(Some(name)),
336-
ComponentTypeRef::Module(_) => self.core_modules.add(Some(name)),
337-
ComponentTypeRef::Value(_) => self.values.add(Some(name)),
337+
ComponentTypeRef::Instance(_) => self.instances.add(Some(&name.name)),
338+
ComponentTypeRef::Func(_) => self.funcs.add(Some(&name.name)),
339+
ComponentTypeRef::Type(..) => self.types.add(Some(&name.name)),
340+
ComponentTypeRef::Component(_) => self.components.add(Some(&name.name)),
341+
ComponentTypeRef::Module(_) => self.core_modules.add(Some(&name.name)),
342+
ComponentTypeRef::Value(_) => self.values.add(Some(&name.name)),
338343
};
339344
self.imports().import(name, ty);
340345
ret
@@ -345,15 +350,16 @@ impl ComponentBuilder {
345350
///
346351
/// The `idx` is the item to export and the `ty` is an optional type to
347352
/// ascribe to the export.
348-
pub fn export(
353+
pub fn export<'a>(
349354
&mut self,
350-
name: &str,
355+
name: impl Into<ComponentExternName<'a>>,
351356
kind: ComponentExportKind,
352357
idx: u32,
353358
ty: Option<ComponentTypeRef>,
354359
) -> u32 {
355-
self.exports().export(name, kind, idx, ty);
356-
self.inc_kind(Some(name), kind)
360+
let name = name.into();
361+
self.exports().export(name.clone(), kind, idx, ty);
362+
self.inc_kind(Some(&name.name), kind)
357363
}
358364

359365
/// Creates a new encoder for the next core type in this component.

crates/wasm-encoder/src/component/exports.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ use super::{
22
COMPONENT_SORT, CORE_MODULE_SORT, CORE_SORT, FUNCTION_SORT, INSTANCE_SORT, TYPE_SORT,
33
VALUE_SORT,
44
};
5-
use crate::{ComponentSection, ComponentSectionId, ComponentTypeRef, Encode, encode_section};
5+
use crate::{
6+
ComponentExternName, ComponentSection, ComponentSectionId, ComponentTypeRef, Encode,
7+
encode_section,
8+
};
69
use alloc::vec::Vec;
710

811
/// Represents the kind of an export from a WebAssembly component.
@@ -87,14 +90,14 @@ impl ComponentExportSection {
8790
}
8891

8992
/// Define an export in the export section.
90-
pub fn export(
93+
pub fn export<'a>(
9194
&mut self,
92-
name: &str,
95+
name: impl Into<ComponentExternName<'a>>,
9396
kind: ComponentExportKind,
9497
index: u32,
9598
ty: Option<ComponentTypeRef>,
9699
) -> &mut Self {
97-
crate::encode_component_export_name(&mut self.bytes, name);
100+
name.into().encode(&mut self.bytes);
98101
kind.encode(&mut self.bytes);
99102
index.encode(&mut self.bytes);
100103
match ty {
@@ -122,9 +125,3 @@ impl ComponentSection for ComponentExportSection {
122125
ComponentSectionId::Export.into()
123126
}
124127
}
125-
126-
/// For more information on this see `encode_component_import_name`.
127-
pub(crate) fn encode_component_export_name(bytes: &mut Vec<u8>, name: &str) {
128-
bytes.push(0x00);
129-
name.encode(bytes);
130-
}

crates/wasm-encoder/src/component/imports.rs

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use crate::{
22
ComponentExportKind, ComponentSection, ComponentSectionId, ComponentValType, Encode,
33
encode_section,
44
};
5+
use alloc::borrow::Cow;
6+
use alloc::string::String;
57
use alloc::vec::Vec;
68

79
/// Represents the possible type bounds for type references.
@@ -131,8 +133,12 @@ impl ComponentImportSection {
131133
}
132134

133135
/// Define an import in the component import section.
134-
pub fn import(&mut self, name: &str, ty: ComponentTypeRef) -> &mut Self {
135-
encode_component_import_name(&mut self.bytes, name);
136+
pub fn import<'a>(
137+
&mut self,
138+
name: impl Into<ComponentExternName<'a>>,
139+
ty: ComponentTypeRef,
140+
) -> &mut Self {
141+
name.into().encode(&mut self.bytes);
136142
ty.encode(&mut self.bytes);
137143
self.num_added += 1;
138144
self
@@ -151,20 +157,91 @@ impl ComponentSection for ComponentImportSection {
151157
}
152158
}
153159

154-
/// Prior to WebAssembly/component-model#263 import and export names were
155-
/// discriminated with a leading byte indicating what kind of import they are.
156-
/// After that PR though names are always prefixed with a 0x00 byte.
157-
///
158-
/// On 2023-10-28 in bytecodealliance/wasm-tools#1262 was landed to start
159-
/// transitioning to "always lead with 0x00". That updated the validator/parser
160-
/// to accept either 0x00 or 0x01 but the encoder wasn't updated at the time.
161-
///
162-
/// On 2024-09-03 in bytecodealliance/wasm-tools#TODO this encoder was updated
163-
/// to always emit 0x00 as a leading byte.
164-
///
165-
/// This function corresponds with the `importname'` production in the
166-
/// specification.
167-
pub(crate) fn encode_component_import_name(bytes: &mut Vec<u8>, name: &str) {
168-
bytes.push(0x00);
169-
name.encode(bytes);
160+
/// Full options for encoding a component name.
161+
#[derive(Debug, Clone)]
162+
pub struct ComponentExternName<'a> {
163+
/// The name to encode.
164+
pub name: Cow<'a, str>,
165+
/// An optional `(implements ...)` directive (See 🏷️ in the component model
166+
/// explainer).
167+
pub implements: Option<Cow<'a, str>>,
168+
}
169+
170+
impl Encode for ComponentExternName<'_> {
171+
fn encode(&self, bytes: &mut Vec<u8>) {
172+
let mut options = Vec::new();
173+
174+
if let Some(s) = &self.implements {
175+
options.push((0x00, s.as_bytes()));
176+
}
177+
178+
if options.is_empty() {
179+
// Prior to WebAssembly/component-model#263 import and export names
180+
// were discriminated with a leading byte indicating what kind of
181+
// import they are. After that PR though names are always prefixed
182+
// with a 0x00 byte.
183+
//
184+
// On 2023-10-28 in bytecodealliance/wasm-tools#1262 was landed to
185+
// start transitioning to "always lead with 0x00". That updated the
186+
// validator/parser to accept either 0x00 or 0x01 but the encoder
187+
// wasn't updated at the time.
188+
//
189+
// On 2024-09-03 in bytecodealliance/wasm-tools#TODO this encoder
190+
// was updated to always emit 0x00 as a leading byte.
191+
//
192+
// This corresponds with the `importname'` production in the
193+
// specification.
194+
bytes.push(0x00);
195+
} else {
196+
bytes.push(0x02);
197+
}
198+
199+
self.name.encode(bytes);
200+
201+
if !options.is_empty() {
202+
options.len().encode(bytes);
203+
for (kind, val) in options {
204+
bytes.push(kind);
205+
val.encode(bytes);
206+
}
207+
}
208+
}
209+
}
210+
211+
impl<'a> From<&'a str> for ComponentExternName<'a> {
212+
fn from(name: &'a str) -> Self {
213+
ComponentExternName {
214+
name: Cow::Borrowed(name),
215+
implements: None,
216+
}
217+
}
218+
}
219+
220+
impl<'a> From<&'a String> for ComponentExternName<'a> {
221+
fn from(name: &'a String) -> Self {
222+
ComponentExternName {
223+
name: Cow::Borrowed(name),
224+
implements: None,
225+
}
226+
}
227+
}
228+
229+
impl<'a> From<String> for ComponentExternName<'a> {
230+
fn from(name: String) -> Self {
231+
ComponentExternName {
232+
name: Cow::Owned(name),
233+
implements: None,
234+
}
235+
}
236+
}
237+
238+
#[cfg(feature = "wasmparser")]
239+
impl<'a> From<wasmparser::ComponentExternName<'a>> for ComponentExternName<'a> {
240+
fn from(name: wasmparser::ComponentExternName<'a>) -> Self {
241+
let wasmparser::ComponentExternName { name, implements } = name;
242+
ComponentExternName {
243+
name: name.into(),
244+
implements: implements.map(|s| s.into()),
245+
}
246+
}
170247
}

crates/wasm-encoder/src/component/instances.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::CORE_INSTANCE_SORT;
22
use crate::{
3-
ComponentExportKind, ComponentSection, ComponentSectionId, Encode, ExportKind, encode_section,
3+
ComponentExportKind, ComponentExternName, ComponentSection, ComponentSectionId, Encode,
4+
ExportKind, encode_section,
45
};
56
use alloc::vec::Vec;
67

@@ -169,16 +170,17 @@ impl ComponentInstanceSection {
169170
}
170171

171172
/// Define an instance by exporting items.
172-
pub fn export_items<'a, E>(&mut self, exports: E) -> &mut Self
173+
pub fn export_items<'a, N, E>(&mut self, exports: E) -> &mut Self
173174
where
174-
E: IntoIterator<Item = (&'a str, ComponentExportKind, u32)>,
175+
E: IntoIterator<Item = (N, ComponentExportKind, u32)>,
175176
E::IntoIter: ExactSizeIterator,
177+
N: Into<ComponentExternName<'a>>,
176178
{
177179
let exports = exports.into_iter();
178180
self.bytes.push(0x01);
179181
exports.len().encode(&mut self.bytes);
180182
for (name, kind, index) in exports {
181-
crate::encode_component_export_name(&mut self.bytes, name);
183+
name.into().encode(&mut self.bytes);
182184
kind.encode(&mut self.bytes);
183185
index.encode(&mut self.bytes);
184186
}

0 commit comments

Comments
 (0)