Add (implements "...") component name support#2453
Conversation
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
e875d9d to
6944e34
Compare
DO NOT MERGE until wasm-tools release with bytecodealliance/wasm-tools#2453 Points wasm-tools to PR branch `wasmparser-implements` Add support for the component model `[implements=<I>]L` (spec PR [bytecodealliance#613](WebAssembly/component-model#613)), which allows components to import/export the same interface multiple times under different plain names. A component can import the same interface twice under different labels, each bound to a distinct host implementation: ```wit import primary: wasi:keyvalue/store; import secondary: wasi:keyvalue/store; ``` Guest code sees two separate namespaces with identical shapes: ```rust let val = primary::get("my-key"); // calls the primary store let val = secondary::get("my-key"); // calls the secondary store ``` From the host, wit-bindgen generates a separate Host trait per label: ```rust impl primary::Host for MyState { fn get(&mut self, key: String) -> String { self.primary_db.get(&key).cloned().unwrap_or_default() } } impl secondary::Host for MyState { fn get(&mut self, key: String) -> String { self.secondary_db.get(&key).cloned().unwrap_or_default() } } primary::add_to_linker(&mut linker, |state| state)?; secondary::add_to_linker(&mut linker, |state| state)?; ``` The linker also supports registering by plain label without knowing the annotation: ```rust // Component imports [implements=<wasi:keyvalue/store>]primary // but the host just registers "primary" — label fallback handles it linker.root().instance("primary")?.func_wrap("get", /* ... */)?; ``` Users can also register to the linker with the full encoded `implements` name ```rust let mut linker = Linker::<()>::new(engine); linker .root() .instance("[implements=<wasi:keyvalue/store>]primary")? .func_wrap("get", |_, (key,): (String,)| Ok((String::new(),)))?; ``` Semver matching works inside the implements annotation, just like regular interface imports: ```rust // Host provides v1.0.1 linker .root() .instance("[implements=<wasi:keyvalue/store@1.0.1>]primary")? .func_wrap("get", |_, (key,): (String,)| Ok((String::new(),)))?; // Component requests v1.0.0, matches via semver let component = Component::new(&engine, r#"(component (type $store (instance (export "get" (func (param "key" string) (result string))) )) (import "[implements=<wasi:keyvalue/store@1.0.0>]primary" (instance (type $store))) )"#)?; linker.instantiate(&mut store, &component)?; // works, 1.0.1 is semver-compatible with 1.0.0 ``` ## Changes ### Runtime name resolution - Add three-tier lookup in NameMap::get: exact → semver → label fallback - Add implements_label_key() helper for extracting plain labels from `[implements=<I>]L` - Add unit tests for all lookup tiers ### Code generation for multi-import/export - Track first-seen implements imports/exports per `InterfaceId` - Duplicate imports: re-export types via `pub use super::{first}::*`, generate fresh Host trait + add_to_linker - Duplicate exports: same pattern with fresh Guest/GuestIndices, plus regenerate resource wrapper structs to reference the local Guest type - Use `name_world_key_with_item` for export instance name lookups - Guard `populate_world_and_interface_options` with `entry()` to avoid overwriting link options for duplicate interfaces
ce9d98b to
9334ce8
Compare
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.
9334ce8 to
3c7046d
Compare
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.
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.
3c7046d to
7c5a1ba
Compare
DO NOT MERGE until wasm-tools release with bytecodealliance/wasm-tools#2453 Points wasm-tools to PR branch `wasmparser-implements` Add support for the component model `[implements=<I>]L` (spec PR [bytecodealliance#613](WebAssembly/component-model#613)), which allows components to import/export the same interface multiple times under different plain names. A component can import the same interface twice under different labels, each bound to a distinct host implementation: ```wit import primary: wasi:keyvalue/store; import secondary: wasi:keyvalue/store; ``` Guest code sees two separate namespaces with identical shapes: ```rust let val = primary::get("my-key"); // calls the primary store let val = secondary::get("my-key"); // calls the secondary store ``` Host Import-side codegen: shared trait + label-parameterized add_to_linker For imports, wit-bindgen generates one Host trait per interface (not per label). The add_to_linker function takes a name: &str parameter so the same trait implementation can be registered under different instance labels. Duplicate implements imports don't generate separate modules — only the first import produces bindings. ```rust struct PrimaryBackend; impl primary::Host for PrimaryBackend { fn get(&mut self, key: String) -> String { self.primary_db.get(&key).cloned().unwrap_or_default() } } struct SecondaryBackend; impl primary::Host for SecondaryBackend { fn get(&mut self, key: String) -> String { self.secondary_db.get(&key).cloned().unwrap_or_default() } } // Same add_to_linker, different labels and host_getter closures primary::add_to_linker(&mut linker, "primary", |s| &mut s.primary)?; primary::add_to_linker(&mut linker, "secondary", |s| &mut s.secondary)?; ``` Export-side codegen: per-label modules with shared types For exports, each label gets its own module with fresh Guest/GuestIndices types but re-exports shared interface types from the first module via `pub use super::{first}::*`. Runtime name resolution The linker supports registering by plain label without knowing the annotation: ```rust // Component imports [implements=<wasi:keyvalue/store>]primary // but the host just registers "primary" — label fallback handles it linker.root().instance("primary")?.func_wrap("get", /* ... */)?; Users can also register to the linker with the full encoded implements name: linker .root() .instance("[implements=<wasi:keyvalue/store>]primary")? .func_wrap("get", |_, (key,): (String,)| Ok((String::new(),)))?; ``` Semver matching works inside the implements annotation, just like regular interface imports: ```rust // Host provides v1.0.1 linker .root() .instance("[implements=<wasi:keyvalue/store@1.0.1>]primary")? .func_wrap("get", |_, (key,): (String,)| Ok((String::new(),)))?; // Component requests v1.0.0, matches via semver let component = Component::new(&engine, r#"(component (type $store (instance (export "get" (func (param "key" string) (result string))) )) (import "[implements=<wasi:keyvalue/store@1.0.0>]primary" (instance (type $store))) )"#)?; linker.instantiate(&mut store, &component)?; // works, 1.0.1 is semver-compatible with 1.0.0 ``` - Add three-tier lookup in NameMap::get: exact → semver → label fallback - Add implements_label_key() helper for extracting plain labels from `[implements=<I>]L` - Add unit tests for all lookup tiers - Track first-seen implements imports per `InterfaceId` - One `Host` trait per interface; `generate_add_to_linker` takes `named: bool` — when true, emits `name: &str` parameter instead of hardcoding the instance name - Duplicate `implements` imports: just record the label in `implements_labels`, no module generation - `world_add_to_linker`: iterate over `implements_labels` to emit one `add_to_linker` call per label, passing label as name argument - Guard `populate_world_and_interface_options` with `entry()` to avoid overwriting link options for duplicate interfaces - Duplicate exports: re-export types via `pub use super::{first}::*`, generate fresh `Guest`/`GuestIndices`, plus regenerate resource wrapper structs to reference the local `Guest` type - Use `name_world_key_with_item` for export instance name lookups
alexcrichton
left a comment
There was a problem hiding this comment.
Thanks again for this! I've left some comments below, the most consequential one being about the AST structure of wit-parser itself. I'd like to still look more closely at some details once that's settled, but also wanted to give some initial feedback. I'll need to catch up on the wasmtime/wit-bindgen discussions too
|
Oh, another point -- I forgot this in my first pass so wanted to leave a note here as well -- can you add a new feature to |
|
@ricochet do you mind if I pick this up this week and work on getting this landed? |
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.
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.
Gate the new parsing behind this feature.
Use a new `WorldKey` instead of `WorldItem`.
Helps run-on-file-change tools not infinite loop
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.
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 bytecodealliance#2516
9f8dde1 to
cec17d3
Compare
[implements=<I>]L component name support(implements "...") component name support
|
Ok I've performed major updates to this PR. Everything is now up-to-date, the history's a bit messy, but hey it's all there. This PR is coupled with updates to WebAssembly/component-model#613 as well. Major changes are:
|
|
My plan is to perform a final once-over on review tomorrow, and then I'll ask for another sign-off from someone since so much of this is now self-authored by me. I've verified the fuzzer runs for ~1h locally finding no issues so I'm reasonably confident in the coverage and feature. After a bit of time to bikeshed the binary format details upstream in the spec I plan on landing and then working on the Wasmtime side of things. |
(implements "...") component name support(implements "...") component name support
|
Ok I've done my own once-over on everything now and I feel this is in a good spot. @dicej would you be willing to look over this as well to confirm you don't find anything awry? I realize this is a pretty big PR, though, so no worries if you're too busy |
Add a test case that broke in Wasmtime, and add a lot of words for why things are the way they are.
Add parsing, validation, and uniqueness rules for the new
(implements "...")extern name form from the componentmodel
implementsproposal. An implements name labels an instanceimport/export that implements a named interface.
See implements feature:
WebAssembly/component-model#613