Skip to content

Interface version canonicalization #536

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions design/mvp/Binary.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,13 @@ flags are set.
(See [Import and Export Definitions](Explainer.md#import-and-export-definitions)
in the explainer.)
```ebnf
import ::= in:<importname'> ed:<externdesc> => (import in ed)
export ::= en:<exportname'> si:<sortidx> ed?:<externdesc>? => (export en si ed?)
importname' ::= 0x00 len:<u32> in:<importname> => in (if len = |in|)
exportname' ::= 0x00 len:<u32> en:<exportname> => en (if len = |en|)
import ::= in:<importname'> ed:<externdesc> => (import in ed)
export ::= en:<exportname'> si:<sortidx> ed?:<externdesc>? => (export en si ed?)
importname' ::= 0x00 len:<u32> in:<importname> => in (if len = |in|)
| 0x01 len:<u32> in:<importname> vs:<versionsuffix'> => in vs (if len = |in|)
exportname' ::= 0x00 len:<u32> en:<exportname> => en (if len = |en|)
| 0x01 len:<u32> en:<exportname> vs:<versionsuffix'> => in vs (if len = |in|)
versionsuffix' ::= len:<u32> vs:<semver suffix> => (versionsuffix vs) (if len = |vs|)
```

Notes:
Expand All @@ -399,7 +402,10 @@ Notes:
`(result (own $R))`, where `$R` is the resource labeled `r`.
* Validation of `[method]` names requires the first parameter of the function
to be `(param "self" (borrow $R))`, where `$R` is the resource labeled `r`.
* `<valid semver>` is as defined by [https://semver.org](https://semver.org/)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

<valid semver> wasn't referenced in this file.

* Validation requires that `versionsuffix` is preceded by an `interfaceversion`
with a `canonversion` and that the concatenation of the `canonversion` and the
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
with a `canonversion` and that the concatenation of the `canonversion` and the
matches `canonversion` and that the concatenation of the `canonversion` and the

`versionsuffix` results in a `valid semver` as defined by
[https://semver.org](https://semver.org/)
* `<integrity-metadata>` is as defined by the
[SRI](https://www.w3.org/TR/SRI/#dfn-integrity-metadata) spec.

Expand Down Expand Up @@ -494,7 +500,9 @@ named once.

* The opcodes (for types, canon built-ins, etc) should be re-sorted
* The two `list` type codes should be merged into one with an optional immediate.
* The `0x00` prefix byte of `importname'` and `exportname'` will be removed or repurposed.
* The `0x00` variant of `importname'` and `exportname'` will be removed. Any
remaining variant(s) will be renumbered or the prefix byte will be removed or
repurposed.


[`core:byte`]: https://webassembly.github.io/spec/core/binary/values.html#binary-byte
Expand Down
67 changes: 58 additions & 9 deletions design/mvp/Explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ more user-focused explanation, take a look at the
* [Start definitions](#-start-definitions)
* [Import and export definitions](#import-and-export-definitions)
* [Name uniqueness](#name-uniqueness)
* [Canonical interface name](#canonical-interface-name)
* [Component invariants](#component-invariants)
* [JavaScript embedding](#JavaScript-embedding)
* [JS API](#JS-API)
Expand Down Expand Up @@ -294,7 +295,7 @@ sort ::= core <core:sort>
| type
| component
| instance
inlineexport ::= (export <exportname> <sortidx>)
inlineexport ::= (export "<exportname>" <versionsuffix>? <sortidx>)
```
Because component-level function, type and instance definitions are different
than core-level function, type and instance definitions, they are put into
Expand Down Expand Up @@ -574,8 +575,8 @@ instancedecl ::= core-prefix(<core:type>)
| <alias>
| <exportdecl>
| <value> 🪙
importdecl ::= (import <importname> bind-id(<externdesc>))
exportdecl ::= (export <exportname> bind-id(<externdesc>))
importdecl ::= (import "<importname>" <versionsuffix>? bind-id(<externdesc>))
exportdecl ::= (export "<exportname>" <versionsuffix>? bind-id(<externdesc>))
externdesc ::= (<sort> (type <u32>) )
| core-prefix(<core:moduletype>)
| <functype>
Expand Down Expand Up @@ -2242,8 +2243,9 @@ the identifier `$x`). In the case of exports, the `<id>?` right after the
preceding definition being exported (e.g., `(export $x "x" (func $f))` binds a
new identifier `$x`).
```ebnf
import ::= (import "<importname>" bind-id(<externdesc>))
export ::= (export <id>? "<exportname>" <sortidx> <externdesc>?)
import ::= (import "<importname>" <versionsuffix>? bind-id(<externdesc>))
export ::= (export <id>? "<exportname>" <versionsuffix>? <sortidx> <externdesc>?)
versionsuffix ::= (versionsuffix "<semver suffix>")
Copy link
Member

Choose a reason for hiding this comment

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

Should <semver suffix> be defined below (it doesn't seem to be on semver.org)? If so, then could it be semversuffix (since we otherwise don't use spaces for production names defined by this doc).

```
All import names are required to be [strongly-unique]. Separately, all export
names are also required to be [strongly-unique]. The rest of the grammar for
Expand Down Expand Up @@ -2276,17 +2278,23 @@ fragment ::= <word>
| <acronym>
word ::= [a-z] [0-9a-z]*
acronym ::= [A-Z] [0-9A-Z]*
interfacename ::= <namespace> <label> <projection> <version>?
| <namespace>+ <label> <projection>+ <version>? 🪺
interfacename ::= <namespace> <label> <projection> <interfaceversion>?
| <namespace>+ <label> <projection>+ <interfaceversion>? 🪺
namespace ::= <words> ':'
words ::= <word>
| <words> '-' <word>
projection ::= '/' <label>
version ::= '@' <valid semver>
# FIXME: surrounding alignment
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TODO after final review

interfaceversion ::= '@' <valid semver>
| '@' <canonversion>
canonversion ::= [1-9] [0-9]*
| '0.' [1-9] [0-9]*
| '0.0.' [1-9] [0-9]*
Comment on lines +2288 to +2292
Copy link
Contributor Author

@lann lann Jul 3, 2025

Choose a reason for hiding this comment

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

There is some ambiguity here; '0.0.' [1-9] [0-9]* matches a subset of <valid semver>. I think this makes sense in the context of non-canonical versions getting phased out but could add more to clarify if needed.

Copy link
Member

Choose a reason for hiding this comment

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

Good point and I think you're right; it's fine if we word the validation rules in terms of "matching canonversion" as suggested above.

depname ::= 'unlocked-dep=<' <pkgnamequery> '>'
| 'locked-dep=<' <pkgname> '>' ( ',' <hashname> )?
pkgnamequery ::= <pkgpath> <verrange>?
pkgname ::= <pkgpath> <version>?
pkgname ::= <pkgpath> <pkgversion>?
pkgversion ::= '@' <valid semver>
pkgpath ::= <namespace> <words>
| <namespace>+ <words> <projection>* 🪺
verrange ::= '@*'
Expand Down Expand Up @@ -2539,6 +2547,46 @@ annotations. For example, the validation rules for `[constructor]foo` require
for details.


### Canonical Interface Name
Copy link
Member

Choose a reason for hiding this comment

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

Can you find the place in the preceding "Import and export definitions" section where valid semver is mentioned and update that paragraph to link down to this new section?


An `interfacename` (as defined above) is **canonical** iff it either:

- has no `interfaceversion`
- has an `interfaceversion` matching the `canonversion` production

The purpose of `canonversion` is to simplify the matching of compatible import
and export versions. For example, if a guest imports some interface from
`wasi:http/[email protected]` and a host provides the (subtype-compatible) interface
`wasi:http/[email protected]`, we'd like to make it easy for the host to link with the
guest. The `canonversion` for both of these interfaces would be `0.2`, so this
linking could be done by matching canonical interface names literally.
Copy link
Member

Choose a reason for hiding this comment

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

Could you also mention the more-nuanced "forward compatibility" case (a component imports the new version, but uses only the functions present in the hold version with compatible types)?


Any `valid semver` (as used in WIT) can be canonicalized by splitting it into
two parts - the `canonversion` prefix and the remaining `semver suffix`. Using
the `<major>.<minor>.<patch>` syntax of [Semantic Versioning 2.0], the split
point is chosen as follows:

- if `major` > 0, split immediately after `major`
- `1.2.3` &rarr; `1` / `.2.3`
- otherwise if `minor` > 0, split immediately after `minor`
- `0.2.6-rc.1` &rarr; `0.2` / `.6-rc.1`
- otherwise, split immediately after `patch`
- `0.0.1-alpha` &rarr; `0.0.1` / `-alpha`

When a version is canonicalized, any `semver suffix` that was split off of the
version should be preserved in the `versionsuffix` field of any resulting
`import`s and `export`s. This gives component runtimes and other tools access to
the original version for error messages, documentation, and other development
purposes. Where a `versionsuffix` is present the preceding `interfacename` must
have a `canonversion`, and the concatenation of the `canonversion` and
`versionsuffix` must be a `valid semver`.

For compatibility with older versions of this spec, non-canonical
`interfacename`s (with `interfaceversion`s matching any `valid semver`) are
temporarily permitted. These non-canonical names may trigger warnings and will
start being rejected some time after after [WASI Preview 3] is released.


## Component Invariants

As a consequence of the shared-nothing design described above, all calls into
Expand Down Expand Up @@ -2894,6 +2942,7 @@ For some use-case-focused, worked examples, see:
[`rectype`]: https://webassembly.github.io/gc/core/text/types.html#text-rectype
[shared-everything-threads]: https://github.com/WebAssembly/shared-everything-threads
[WASI Preview 2]: https://github.com/WebAssembly/WASI/tree/main/wasip2#readme
[WASI Preview 3]: https://github.com/WebAssembly/WASI/tree/main/wasip2#looking-forward-to-preview-3
[reference types]: https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md

[Strongly-unique]: #name-uniqueness
Expand Down