Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[wasmparser] Add [implements=<I>]L component name support#2453

Draft
ricochet wants to merge 5 commits intobytecodealliance:mainfrom
ricochet:wasmparser-implements
Draft

[wasmparser] Add [implements=<I>]L component name support#2453
ricochet wants to merge 5 commits intobytecodealliance:mainfrom
ricochet:wasmparser-implements

Conversation

@ricochet
Copy link
Contributor

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

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
@ricochet ricochet requested a review from a team as a code owner February 27, 2026 20:45
@ricochet ricochet requested review from dicej and removed request for a team February 27, 2026 20:45
@ricochet ricochet marked this pull request as draft February 27, 2026 20:48
@ricochet ricochet force-pushed the wasmparser-implements branch from e875d9d to 6944e34 Compare February 28, 2026 20:02
ricochet added a commit to ricochet/wasmtime that referenced this pull request Feb 28, 2026
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/[email protected]>]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/[email protected]>]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
@ricochet ricochet force-pushed the wasmparser-implements branch 2 times, most recently from ce9d98b to 9334ce8 Compare March 1, 2026 00:12
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.
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.
@ricochet ricochet force-pushed the wasmparser-implements branch from 3c7046d to 7c5a1ba Compare March 1, 2026 02:46
ricochet added a commit to ricochet/wasmtime that referenced this pull request Mar 1, 2026
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/[email protected]>]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/[email protected]>]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 alexcrichton requested review from alexcrichton and removed request for dicej March 2, 2026 18:27
Copy link
Member

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +1055 to +1067

// implements names
assert!(parse_kebab_name("[implements=<a:b/c>]name").is_some());
assert!(parse_kebab_name("[implements=<a:b/[email protected]>]name").is_some());
assert!(parse_kebab_name("[implements=<ns:pkg/iface>]my-label").is_some());
// invalid: not a valid interface name (no colon/slash)
assert!(parse_kebab_name("[implements=<not-valid>]name").is_none());
// invalid: empty interface name
assert!(parse_kebab_name("[implements=<>]name").is_none());
// invalid: missing label
assert!(parse_kebab_name("[implements=<a:b/c>]").is_none());
// invalid: label not kebab
assert!(parse_kebab_name("[implements=<a:b/c>]NOT_KEBAB").is_none());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to unit tests here, could you add some unit tests as *.wast files in tests/cli/component-model/*.wast? Maybe something like tests/cli/component-model/implements.wast or similar. It's ok to move these tests outright there where necessary, but by having a *.wast test we can help other implementations' tests as well in the future by eventually sharing wast tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding wit-smith support! I always forget to do that...

Can you run the fuzzer locally for a few minutes to make sure nothing crops up? You can do that with:

$ FUZZER=roundtrip_wit cargo +nightly fuzz run -s none run

serialize_with = "serialize_optional_id"
)
)]
implements: Option<InterfaceId>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AST-wise I think I might bikeshed this a bit. I find this a bit confusing where there's two InterfaceIds listed here, one above with id and one for implements, and for import foo: bar; I'd assume implements would point to bar but it's not clear to me what id above would point to.

Additionally foundationally I'd ideally like to keep things such that name_world_key is sufficient for generating the import name of a component/instance import.

Given all that, WDYT about augmenting WorldKey instead of augmenting WorldItem? What I'm imagining is:

  • Add a new WorldKey::Implements { name: String, interface: InterfaceId } - this'd corresponds to import foo: bar with name being foo and interface pointing to bar
  • Reuse WorldItem::Interface for the WorldItem, in this case with id also pointing to bar

To me that feels like it'll mesh better with the existing generators and such, but want to double-check with how you're thinking about this.

/// - `[implements=<I>]label` — named import/export implementing interface I
/// - `ns:pkg/iface` — qualified interface name, keyed by `InterfaceId`
/// - `plain-name` — unqualified name for an inline or local interface
fn decode_world_instance<'a>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing the refactor here to split this out!

/// Parses an `[implements=<interface_name>]label` name, returning
/// the interface name and label if the name matches this pattern.
fn parse_implements_name(name: &str) -> Option<(&str, &str)> {
let rest = name.strip_prefix("[implements=<")?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it'd make sense, but one option here would be to use ComponentName from wasmparser to do the parsing here (I think that's what it's called) which could alleviate the string processing from here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants