From e7a22d817cd5338be5521fc9fa7de30e84e612d1 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Fri, 19 Aug 2022 14:57:48 -0500 Subject: [PATCH 1/6] Split import/export names into kebab-case name + optional URL fields --- design/mvp/Binary.md | 58 +++++---- design/mvp/Explainer.md | 260 +++++++++++++++++++++++++++++++--------- design/mvp/Subtyping.md | 1 + design/mvp/WIT.md | 8 +- 4 files changed, 243 insertions(+), 84 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 78f6a2c6..ec8fcd23 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -55,8 +55,8 @@ Notes: ``` core:instance ::= ie: => (instance ie) core:instanceexpr ::= 0x00 m: arg*:vec() => (instantiate m arg*) - | 0x01 e*:vec() => e* -core:instantiatearg ::= n: 0x12 i: => (with n (instance i)) + | 0x01 e*:vec() => e* +core:instantiatearg ::= n: 0x12 i: => (with n (instance i)) core:sortidx ::= sort: idx: => (sort idx) core:sort ::= 0x00 => func | 0x01 => table @@ -65,11 +65,11 @@ core:sort ::= 0x00 => fu | 0x10 => type | 0x11 => module | 0x12 => instance -core:export ::= n: si: => (export n si) +core:inlineexport ::= n: si: => (export n si) instance ::= ie: => (instance ie) instanceexpr ::= 0x00 c: arg*:vec() => (instantiate c arg*) - | 0x01 e*:vec() => e* + | 0x01 e*:vec() => e* instantiatearg ::= n: si: => (with n si) sortidx ::= sort: idx: => (sort idx) sort ::= 0x00 cs: => core cs @@ -78,7 +78,12 @@ sort ::= 0x00 cs: => co | 0x03 => type | 0x04 => component | 0x05 => instance -export ::= n: si: => (export n si) +inlineexport ::= n: si: => (export n si) +name ::= len: n: => n (if len = |n|) +name-chars ::= w: => w + | n: 0x2d w: => n-w +word ::= w:[0x61-0x7a] x*:[0x30-0x39,0x61-0x7a]* => char(w)char(x)* + | W:[0x41-0x5a] X*:[0x30-0x39,0x41-0x5a]* => char(W)char(X)* ``` Notes: * Reused Core binary rules: [`core:name`], (variable-length encoded) [`core:u32`] @@ -92,6 +97,8 @@ Notes: for aliases (below). * Validation of `core:instantiatearg` initially only allows the `instance` sort, but would be extended to accept other sorts as core wasm is extended. +* Validation of `instantiate` requires that `name` is present in an + `externname` of `c` (with a matching type). * The indices in `sortidx` are validated according to their `sort`'s index spaces, which are built incrementally as each definition is validated. @@ -99,10 +106,10 @@ Notes: (See [Alias Definitions](Explainer.md#alias-definitions) in the explainer.) ``` -alias ::= s: t: => (alias t (s)) -aliastarget ::= 0x00 i: n: => export i n - | 0x01 i: n: => core export i n - | 0x02 ct: idx: => outer ct idx +alias ::= s: t: => (alias t (s)) +aliastarget ::= 0x00 i: n: => export i n + | 0x01 i: n: => core export i n + | 0x02 ct: idx: => outer ct idx ``` Notes: * Reused Core binary rules: (variable-length encoded) [`core:u32`] @@ -133,7 +140,7 @@ core:moduledecl ::= 0x00 i: => i core:alias ::= s: t: => (alias t (s)) core:aliastarget ::= 0x01 ct: idx: => outer ct idx core:importdecl ::= i: => i -core:exportdecl ::= n: d: => (export n d) +core:exportdecl ::= n: d: => (export n d) ``` Notes: * Reused Core binary rules: [`core:import`], [`core:importdesc`], [`core:functype`] @@ -175,12 +182,11 @@ defvaltype ::= pvt: => pvt | 0x6d n*:vec() => (enum n*) | 0x6c t*:vec() => (union t*) | 0x6b t: => (option t) - | 0x6a t?: u?: => (result t? (error u)?) + | 0x6a t?:? u?:? => (result t? (error u)?) namedvaltype ::= n: t: => n t -case ::= n: t?: 0x0 => (case n t?) - | n: t?: 0x1 i: => (case n t? (refines case-label[i])) -casetype ::= 0x00 => - | 0x01 t: => t +case ::= n: t?:? r?:? => (case n t? (refines case-label[r])?) +? ::= 0x00 => + | 0x01 t: => t valtype ::= i: => i | pvt: => pvt functype ::= 0x40 p*: r*: => (func (param p)* (result r)*) @@ -194,8 +200,8 @@ instancedecl ::= 0x00 t: => t | 0x01 t: => t | 0x02 a: => a | 0x04 ed: => ed -importdecl ::= n: ed: => (import n ed) -exportdecl ::= n: ed: => (export n ed) +importdecl ::= en: ed: => (import en ed) +exportdecl ::= en: ed: => (export en ed) externdesc ::= 0x00 0x11 i: => (core module (type i)) | 0x01 i: => (func (type i)) | 0x02 t: => (value t) @@ -214,6 +220,8 @@ Notes: * As described in the explainer, each component and instance type is validated with an initially-empty type index space. Outer aliases can be used to pull in type definitions from containing components. +* The uniqueness validation rules for `externname` described below are also + applied at the instance- and component-type level. * Validation of `externdesc` requires the various `typeidx` type constructors to match the preceding `sort`. * Validation of function parameter and result names, record field names, @@ -285,13 +293,21 @@ flags are set. (See [Import and Export Definitions](Explainer.md#import-and-export-definitions) in the explainer.) ``` -import ::= n: ed: => (import n ed) -export ::= n: si: => (export n si) +import ::= en: ed: => (import en ed) +export ::= en: si: => (export en si) +externname ::= n: u?:? => n u? +URL ::= b*:vec(byte) => char(b)*, if char(b)* parses as a URL ``` Notes: -* Validation requires all import and export `name`s are unique. +* The "parses as a URL" condition is defined by executing the [basic URL + parser] with `char(b)*` as *input*, no optional parameters and non-fatal + validation errors (which coincides with definition of `URL` in JS and `rust-url`). * Validation requires any exported `sortidx` to have a valid `externdesc` (which disallows core sorts other than `core module`). +* The `name` fields of `externname` must be unique among imports and exports, + respectively. The `URL` fields of `externname` (that are present) must + independently unique among imports and exports, respectively. +* URLs are compared for equality by plain byte identity. [`core:u32`]: https://webassembly.github.io/spec/core/binary/values.html#integers @@ -306,3 +322,5 @@ Notes: [type-imports]: https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md [module-linking]: https://github.com/WebAssembly/module-linking/blob/main/proposals/module-linking/Explainer.md + +[Basic URL Parser]: https://url.spec.whatwg.org/#concept-basic-url-parser diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index ef543d9f..bb71242b 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -129,9 +129,9 @@ The syntax for defining a core module instance is: ``` core:instance ::= (instance ? ) core:instanceexpr ::= (instantiate *) - | * -core:instantiatearg ::= (with (instance )) - | (with (instance *)) + | * +core:instantiatearg ::= (with (instance )) + | (with (instance *)) core:sortidx ::= ( ) core:sort ::= func | table @@ -140,17 +140,17 @@ core:sort ::= func | type | module | instance -core:export ::= (export ) +core:inlineexport ::= (export ) ``` When instantiating a module via `instantiate`, the two-level imports of the core modules are resolved as follows: -1. The first `name` of the import is looked up in the named list of +1. The first `core:name` of the import is looked up in the named list of `core:instantiatearg` to select a core module instance. (In the future, other `core:sort`s could be allowed if core wasm adds single-level imports.) -2. The second `name` of the import is looked up in the named list of exports of - the core module instance found by the first step to select the imported - core definition. +2. The second `core:name` of the import is looked up in the named list of + exports of the core module instance found by the first step to select the + imported core definition. Each `core:sort` corresponds 1:1 with a distinct [index space] that contains only core definitions of that *sort*. The `u32` field of `core:sortidx` @@ -173,22 +173,23 @@ following component: To see examples of other sorts, we'll need `alias` definitions, which are introduced in the next section. -The `*` form of `core:instanceexpr` allows module instances to be -created by directly tupling together preceding definitions, without the need to -`instantiate` a helper module. The "inline" form of `*` inside -`(with ...)` is syntactic sugar that is expanded during text format parsing -into an out-of-line instance definition referenced by `with`. To show an -example of these, we'll also need the `alias` definitions introduced in the +The `*` form of `core:instanceexpr` allows module instances +to be created by directly tupling together preceding definitions, without the +need to `instantiate` a helper module. The `*` form of +`core:instantiatearg` is syntactic sugar that is expanded during text format +parsing into an out-of-line instance definition referenced by `with`. To show +an example of these, we'll also need the `alias` definitions introduced in the next section. The syntax for defining component instances is symmetric to core module -instances, but with an expanded component-level definition of `sort`: +instances, but with an expanded component-level definition of `sort` and +more restricted version of `name`: ``` instance ::= (instance ? ) instanceexpr ::= (instantiate *) - | * + | * instantiatearg ::= (with ) - | (with (instance *)) + | (with (instance *)) sortidx ::= ( ) sort ::= core | func @@ -196,7 +197,11 @@ sort ::= core | type | component | instance -export ::= (export ) +inlineexport ::= (export ) +name ::= + | - +word ::= [a-z][0-9a-z]* + | [A-Z][0-9A-Z]* ``` Because component-level function, type and instance definitions are different than core-level function, type and instance definitions, they are put into @@ -211,6 +216,21 @@ The `value` sort refers to a value that is provided and consumed during instantiation. How this works is described in the [start definitions](#start-definitions) section. +The component-level definition of `name` above corresponds to [kebab case]. The +reason for this particular form of casing is to unambiguously separate words +and acronyms (represented as all-caps words) so that source language bindings +can convert a `name` into the idiomatic casing of that language. (Indeed, +because hyphens are often invalid in identifiers, kebab case practically forces +language bindings to make such a conversion.) For example, the `name` `is-XML` +could be mapped to `isXML`, `IsXml` or `is_XML`, depending on the target +language. The highly-restricted character set ensures that capitalization is +trivial and does not require consulting Unicode tables. Having this structured +data encoded as a plain string provides a single canonical name for use in +tools and language-agnostic contexts, without requiring each to invent its own +custom interpretation. While the use of `name` above is mostly for internal +wiring, `name` is used in a number of productions below that are +developer-facing and imply bindings generation. + To see a non-trivial example of component instantiation, we'll first need to introduce a few other definitions below that allow components to import, define and export component functions. @@ -226,7 +246,7 @@ instance, the `core export` of a core module instance and a definition of an ``` alias ::= (alias ( ?)) aliastarget ::= export - | core export + | core export | outer ``` If present, the `id` of the alias is bound to the new index added by the alias @@ -358,8 +378,8 @@ core:moduledecl ::= | core:alias ::= (alias ( ?)) core:aliastarget ::= outer -core:importdecl ::= (import ) -core:exportdecl ::= (export ) +core:importdecl ::= (import ) +core:exportdecl ::= (export ) core:exportdesc ::= strip-id() where strip-id(X) parses '(' sort Y ')' when X parses '(' sort ? Y ')' @@ -467,8 +487,8 @@ instancedecl ::= core-prefix() | | | -importdecl ::= (import bind-id()) -exportdecl ::= (export ) +importdecl ::= (import bind-id()) +exportdecl ::= (export ) externdesc ::= ( (type ) ) | core-prefix() | @@ -560,10 +580,11 @@ core module declarators introduced above. As with core modules, `importdecl` and `exportdecl` classify component `import` and `export` definitions, with `importdecl` allowing an identifier to be -bound for use within the type. Following the precedent of [`core:typeuse`], the -text format allows both references to out-of-line type definitions (via -`(type )`) and inline type expressions that the text format desugars -into out-of-line type definitions. +bound for use within the type. The definition of `externname` is given in the +[imports and exports](#import-and-export-definitions) section below. Following +the precedent of [`core:typeuse`], the text format allows both references to +out-of-line type definitions (via `(type )`) and inline type +expressions that the text format desugars into out-of-line type definitions. The `value` case of `externdesc` describes a runtime value that is imported or exported at instantiation time as described in the @@ -803,41 +824,144 @@ of core linear memory. ### Import and Export Definitions -Lastly, imports and exports are defined in terms of the above as: +Lastly, imports and exports are defined as: ``` -import ::= -export ::= (export ) +import ::= (import bind-id()) +export ::= (export ) +externname ::= ? +``` +Components split the single externally-visible name of imports and exports into +two sub-fields: a kebab-case `name` (as defined [above](#instance-definitions)) +and a `URL` (defined by the [URL Standard], noting that, in this URL Standard, +the term "URL" subsumes what has historically been called a [URI], including +URLs that "identify" as opposed to "locate"). This subdivision of external +names allows component producers to represent a variety of intentions for how a +component is to be instantiated and executed so that a variety of hosts can +portably execute the component. + +The `name` field of `externname` is required to be unique. Thus, a single +`name` has been used in the preceding definitions of `with` and `alias` to +uniquely identify imports and exports. + +In guest source-code bindings, the `name` is meant to be translated to +source-language identifiers (applying case-conversion, as described +[above](#instance-definitions)) attached to whatever source-language constructs +represent the imports and exports (functions, globals, types, classes, etc). +For example, given an import in a component type: +``` +(import "one-two" (instance + (export "three-four" (func (param string) (result string))) +)) +``` +a Rust bindings generator for a component targeting this type could produce an +`extern crate one_two` containing the function `three_four`. Similarly, a +[JS Embedding](#js-embedding) could allow `import {threeFour} from 'one-two'` +to resolve to the imported function. Conversely, given an export in a component +type: +``` +(export "one-two" (instance + (export "three-four" (func (param string) (result string))) +)) +``` +a Rust bindings generator for a component with this export could produce a +trait `OneTwo` requiring a function `three_four` while the JS Embedding would +expect the JS module implementing this component type to export a function +`oneTwo` containing an object with a field `threeFour` containing a function. + +The `name` field can also be used by *host* source-code bindings, defining the +source-language identifiers that are to be used when instantiating a component +and accessing its exports. For example, the [JS API]'s +[`WebAssembly.instantiate()`] would use import `name`s in the [*read the +imports*] step and use export `name`s in the [*create an exports object*] step. + +The optional `URL` field of `externname` allows a component author to refer to +an *externally-defined* specification of what an import "wants" or what an +export has "implemented". One example is a URL naming a standard interface such +as `wasi:filesystem` (assuming that WASI registered the `wasi:` URI scheme with +IANA). Pre-standard, non-standard or proprietary interfaces could be referred +to by an `http:` URL in an interface registry. For imports, a URL could +alternatively refer to a *particular implementation* (e.g., at a hosted storage +location) or a *query* for a *set of possible implementations* (e.g., using the +query API of a public registry). Because of the wide variety of hosts executing +components, the Component Model doesn't specify how URLs are to be interpreted, +just that they are grammatically URLs. Even `http:` URLs may or may not be +literally fetched by the host (c.f. [import maps]). + +When present, `URL`s must *also* be unique (*in addition* the abovementioned +uniqueness of `name`s). Thus, a `URL` can *also* be used to uniquely identify +the subset of imports or exports that have `URL`s. + +While the `name` field is meant for source-code bindings generators, the `URL` +field is meant for automated interpretation by hosts and toolchains. In +particular, hosts are expected to identify their host-implemented imports and +host-called exports by `URL`, not `name`. This allows hosts to implement a +wide collection of independently-developed interfaces where `name`s are chosen +for developer ergonomics (and name collisions are handled independently in +the binding generators, which is needed in any case) and `URL`s serve as +the invariant identifier that concretely links the guest to host. If there was +only a `name`, interface authors would be forced to implicitly coordinate +across the ecosystem to avoid collisions (which in general, isn't possible) +while if there was only a `URL`, the developer-friendly identifiers would have +to be specified manually by every developer or derived in an ad hoc fashion +from the `URL`, whose contents may vary widely. This dual-name scheme is thus +proposed to resolve these competing requirements. + +Inside the component model, this dual-name scheme shows up in [subtyping](#Subtyping.md), +where the component subtyping simply ignores the `name` field when the `URL` +field is present. For example, the component: ``` -All import and export names within a component must be unique, respectively. - -With what's defined so far, we can write a component that imports, links and -exports other components: -```wasm (component - (import "c" (instance $c - (export "f" (func (result string))) - )) - (import "d" (component $D - (import "c" (instance $c - (export "f" (func (result string))) - )) - (export "g" (func (result string))) - )) - (instance $d1 (instantiate $D - (with "c" (instance $c)) - )) - (instance $d2 (instantiate $D - (with "c" (instance - (export "f" (func $d1 "g")) - )) + (import "fs" "wasi:filesystem" ...) +) +``` +can be supplied for the `x` import of the component: +``` +(component + (import "x" (component + (import "filesystem" "wasi:filesystem" ...) )) - (export "d2" (instance $d2)) ) ``` -Here, the imported component `d` is instantiated *twice*: first, with its -import satisfied by the imported instance `c`, and second, with its import -satisfied with the first instance of `d`. While this seems a little circular, -note that all definitions are acyclic as is the resulting instance graph. +because the `name`s are ignored and the `URL`s match. This subtyping is +symmetric to what was described above for hosts, allowing components to +serve as the "host" of other components, enabling [virtualization](examples/LinkTimeVirtualization.md). + +Since the concrete artifacts defining the host/guest interface is a collection +of [Wit files](WIT.md), Wit must naturally allow interface authors to specify +both the `name` and `URL` of component imports and exports. While the syntax is +still very much [in flux](https://github.com/WebAssembly/component-model/pull/83), +a hypothetical simplified interface between a guest and host might look like: +``` +// wasi:cli/Command +default world Command { + import fs: "wasi:filesystem" + import console: "wasi:cli/console" + export main: "wasi:cli/main" +} +``` +where `wasi:filesystem`, `wasi:log` and `wasi:main` are separately defined +interfaces that map to instance types. This "World" definition then maps to the +following component type: +``` +(component $Command + (import "fs" "wasi:filesystem" (instance ... filesystem function exports ...)) + (import "console" "wasi:cli/console" (instance ... log function exports ...)) + (export "main" "wasi:cli/main" (instance (export "main" (func ...)))) +) +``` +A component *targeting* `wasi:cli/Command` would thus need to be a *subtype* of +`$Command` (importing a subset of these imports and exporting a superset of +these exports) while a host *implementing* `wasi:cli/Command` would need to be +a *supertype* of `$Command` (offering a superset of these imports and expecting +to call a subset of these exports). + +Importantly, this `wasi:cli/Command` World has been able to define the short +developer-facing names like `fs` and `console` without worrying if there are +any other Worlds that conflict with these names. If a host wants to implement +`wasi:cli/Command` and some other World that also happens to pick `fs`, either +the `URL` fields are the same, and so the two imports can be unified, or the +`URL` fields are different, and the host supplies two distinct imports, +identified by `URL`. ## Component Invariants @@ -910,6 +1034,10 @@ of `WebAssembly.instantiate(Streaming)` would inherit the compound behavior of the abovementioned functions (again, using the `layer` field to eagerly distinguish between modules and components). +TODO: describe how kebab-names are mapped to JS identifiers + +TODO: describe how the fields can accept either a name or a URL (which are disjoint sets of strings) + For example, the following component: ```wasm ;; a.wasm @@ -1006,10 +1134,15 @@ Notes: ### ESM-integration -Like the JS API, [esm-integration] can be extended to load components in all +Like the JS API, [ESM-integration] can be extended to load components in all the same places where modules can be loaded today, branching on the `layer` field in the binary format to determine whether to decode as a module or a -component. The main question is how to deal with component imports having a +component. + +TODO: explain how `URL` field is used as module specifier, if present, falling +back to the `name` field, which can be implemented by [import maps] + +The main question is how to deal with component imports having a single string as well as the new importable component, module and instance types. Going through these one by one: @@ -1087,6 +1220,7 @@ and will be added over the coming months to complete the MVP proposal: [Index Space]: https://webassembly.github.io/spec/core/syntax/modules.html#indices [Abbreviations]: https://webassembly.github.io/spec/core/text/conventions.html#abbreviations +[`core:name`]: https://webassembly.github.io/spec/core/syntax/values.html#syntax-name [`core:module`]: https://webassembly.github.io/spec/core/text/modules.html#text-module [`core:type`]: https://webassembly.github.io/spec/core/text/modules.html#types [`core:importdesc`]: https://webassembly.github.io/spec/core/text/modules.html#text-importdesc @@ -1097,8 +1231,11 @@ and will be added over the coming months to complete the MVP proposal: [func-import-abbrev]: https://webassembly.github.io/spec/core/text/modules.html#text-func-abbrev [`core:version`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-version +[`WebAssembly.instantiate()`]: https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiate + [JS API]: https://webassembly.github.io/spec/js-api/index.html [*read the imports*]: https://webassembly.github.io/spec/js-api/index.html#read-the-imports +[*create the exports*]: https://webassembly.github.io/spec/js-api/index.html#create-an-exports-object [`ToJSValue`]: https://webassembly.github.io/spec/js-api/index.html#tojsvalue [`ToWebAssemblyValue`]: https://webassembly.github.io/spec/js-api/index.html#towebassemblyvalue [`USVString`]: https://webidl.spec.whatwg.org/#es-USVString @@ -1116,6 +1253,7 @@ and will be added over the coming months to complete the MVP proposal: [JS Tuple]: https://github.com/tc39/proposal-record-tuple [JS Record]: https://github.com/tc39/proposal-record-tuple +[Kebab Case]: https://en.wikipedia.org/wiki/Letter_case#Kebab_case [De Bruijn Index]: https://en.wikipedia.org/wiki/De_Bruijn_index [Closure]: https://en.wikipedia.org/wiki/Closure_(computer_programming) [Empty Type]: https://en.wikipedia.org/w/index.php?title=Empty_type @@ -1131,6 +1269,10 @@ and will be added over the coming months to complete the MVP proposal: [Linear]: https://en.wikipedia.org/wiki/Substructural_type_system#Linear_type_systems [Interface Definition Language]: https://en.wikipedia.org/wiki/Interface_description_language +[URL Standard]: https://url.spec.whatwg.org +[URI]: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier +[Import Maps]: https://wicg.github.io/import-maps/ + [module-linking]: https://github.com/WebAssembly/module-linking/blob/main/design/proposals/module-linking/Explainer.md [interface-types]: https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md [type-imports]: https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md @@ -1142,6 +1284,8 @@ and will be added over the coming months to complete the MVP proposal: [Adapter Functions]: FutureFeatures.md#custom-abis-via-adapter-functions [Canonical ABI]: CanonicalABI.md [Shared-Nothing]: ../high-level/Choices.md +[Use Cases]: ../high-level/UseCases.md +[Host Embeddings]: ../high-level/UseCases.md#hosts-embedding-components [`wizer`]: https://github.com/bytecodealliance/wizer diff --git a/design/mvp/Subtyping.md b/design/mvp/Subtyping.md index e6f86f73..2bcd52b0 100644 --- a/design/mvp/Subtyping.md +++ b/design/mvp/Subtyping.md @@ -18,6 +18,7 @@ But roughly speaking: | `expected` | `T <: (expected T _)` | | `union` | `T <: (union ... T ...)` | | `func` | parameter names must match in order; contravariant parameter subtyping; superfluous parameters can be ignored in the subtype; `option` parameters can be ignored in the supertype; covariant result subtyping | +| `component` | all imports in the subtype must be present in the supertype with matching types; all exports in the supertype must be present in the subtype; the `URL` is treated as the complete name, when present, ignoring the `name` field | The remaining specialized value types inherit their subtyping from their fundamental value types. diff --git a/design/mvp/WIT.md b/design/mvp/WIT.md index 276286ee..96e794fd 100644 --- a/design/mvp/WIT.md +++ b/design/mvp/WIT.md @@ -476,9 +476,8 @@ through a `use` statement or they can be defined locally. ## Identifiers Identifiers in `wit` can be defined with two different forms. The first is a -lower-case [stream-safe] [NFC] [kebab-case] identifier where each part delimited -by '-'s starts with a `XID_Start` scalar value with a zero Canonical Combining -Class: +[kebab-case] identifier defined by the [`name`](Explainer.md#instance-definitions) +production in the Component Model text format. ```wit foo: func(bar: u32) -> () @@ -500,9 +499,6 @@ prefixed with '%': ``` [kebab-case]: https://en.wikipedia.org/wiki/Letter_case#Kebab_case -[Unicode identifier]: http://www.unicode.org/reports/tr31/ -[stream-safe]: https://unicode.org/reports/tr15/#Stream_Safe_Text_Format -[NFC]: https://unicode.org/reports/tr15/#Norm_Forms ## Name resolution From b1050353982754979f69e14b6fe3dbc2b608ff67 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 22 Sep 2022 16:25:16 -0500 Subject: [PATCH 2/6] Fill in some JS Embedding details --- design/mvp/Explainer.md | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index bb71242b..13869810 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -1022,22 +1022,30 @@ a 16-bit `layer` field with `0` for modules and `1` for components). Once compiled, a `WebAssembly.Component` could be instantiated using the existing JS API `WebAssembly.instantiate(Streaming)`. Since components have the -same basic import/export structure as modules, this mostly just means extending -the [*read the imports*] logic to support single-level imports as well as +same basic import/export structure as modules, this means extending the [*read +the imports*] logic to support single-level imports (of kebab-case component +import names converted to lowerCamelCase JavaScript identifiers) as well as imports of modules, components and instances. Since the results of instantiating a component is a record of JavaScript values, just like an instantiated module, `WebAssembly.instantiate` would always produce a -`WebAssembly.Instance` object for both module and component arguments. +`WebAssembly.Instance` object for both module and component arguments +(again, with kebab-case component export names converted to lowerCamelCase). + +Since the JavaScript embedding is generic, loading all component types, it +needs to allow the JS client to refer to either of the `name` or `URL` fields +of component `externname`s. On the import side, this means that, when a `URL` +is present, *read the imports* will first attempt to [`Get`] the `URL` and, on +failure, `Get` the `name`. On the export side, this means that *both* the +`name` and `URL` are exposed as exports in the export object (both holding the +same value). Since `name` and `URL` are necessarily disjoint sets of strings +(in particular, `URL`s must contain a `:`, `name` must not), there should not +be any conflicts in either of these cases. Lastly, when given a component binary, the compile-then-instantiate overloads of `WebAssembly.instantiate(Streaming)` would inherit the compound behavior of the abovementioned functions (again, using the `layer` field to eagerly distinguish between modules and components). -TODO: describe how kebab-names are mapped to JS identifiers - -TODO: describe how the fields can accept either a name or a URL (which are disjoint sets of strings) - For example, the following component: ```wasm ;; a.wasm @@ -1139,8 +1147,10 @@ the same places where modules can be loaded today, branching on the `layer` field in the binary format to determine whether to decode as a module or a component. -TODO: explain how `URL` field is used as module specifier, if present, falling -back to the `name` field, which can be implemented by [import maps] +When the `URL` field of an imported `externname` is present, the `URL` is +used as the module specifier, using the same resolution path as JS module. +Otherwise, the `name` field is used as the module specifier, which requires +[Import Maps] support to resolve to a `URL`. The main question is how to deal with component imports having a single string as well as the new importable component, module and instance @@ -1244,6 +1254,7 @@ and will be added over the coming months to complete the MVP proposal: [`enum`]: https://webidl.spec.whatwg.org/#es-enumeration [`T?`]: https://webidl.spec.whatwg.org/#es-nullable-type [`union`]: https://webidl.spec.whatwg.org/#es-union +[`Get`]: https://tc39.es/ecma262/#sec-get-o-p [JS NaN]: https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type [Import Reflection]: https://github.com/tc39-transfer/proposal-import-reflection [Module Record]: https://tc39.es/ecma262/#sec-abstract-module-records From 50c0f0f58a377b2782ede04c229ebb7d1ab46e86 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 26 Sep 2022 12:26:41 -0500 Subject: [PATCH 3/6] Fix typo Co-authored-by: Liam Murphy --- design/mvp/Explainer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 13869810..c36d807c 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -865,7 +865,7 @@ type: ``` a Rust bindings generator for a component with this export could produce a trait `OneTwo` requiring a function `three_four` while the JS Embedding would -expect the JS module implementing this component type to export a function +expect the JS module implementing this component type to export a variable `oneTwo` containing an object with a field `threeFour` containing a function. The `name` field can also be used by *host* source-code bindings, defining the From 1f7d00964f4e02795afbf10f71982a23e5c80a1d Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 26 Sep 2022 12:29:19 -0500 Subject: [PATCH 4/6] Sync prose with code snippet --- design/mvp/Explainer.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index c36d807c..0b8afe3a 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -939,9 +939,9 @@ default world Command { export main: "wasi:cli/main" } ``` -where `wasi:filesystem`, `wasi:log` and `wasi:main` are separately defined -interfaces that map to instance types. This "World" definition then maps to the -following component type: +where `wasi:filesystem`, `wasi:cli/console` and `wasi:cli/main` are separately +defined interfaces that map to instance types. This "World" definition then +maps to the following component type: ``` (component $Command (import "fs" "wasi:filesystem" (instance ... filesystem function exports ...)) From eafc45f25049c16423fe7d9b06f8f86165307dd5 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 26 Sep 2022 16:37:35 -0500 Subject: [PATCH 5/6] Change 'implementing' to 'supporting' as word for what a host does with a world --- design/mvp/Explainer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 0b8afe3a..d042f53d 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -951,7 +951,7 @@ maps to the following component type: ``` A component *targeting* `wasi:cli/Command` would thus need to be a *subtype* of `$Command` (importing a subset of these imports and exporting a superset of -these exports) while a host *implementing* `wasi:cli/Command` would need to be +these exports) while a host *supporting* `wasi:cli/Command` would need to be a *supertype* of `$Command` (offering a superset of these imports and expecting to call a subset of these exports). From cc637249c6e0a6e2bcc69d1ea52d013e7a1daa7a Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 29 Sep 2022 16:07:37 -0500 Subject: [PATCH 6/6] Use 'https:' not 'http:' in example --- design/mvp/Explainer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index d042f53d..21f47fbb 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -879,12 +879,12 @@ an *externally-defined* specification of what an import "wants" or what an export has "implemented". One example is a URL naming a standard interface such as `wasi:filesystem` (assuming that WASI registered the `wasi:` URI scheme with IANA). Pre-standard, non-standard or proprietary interfaces could be referred -to by an `http:` URL in an interface registry. For imports, a URL could +to by an `https:` URL in an interface registry. For imports, a URL could alternatively refer to a *particular implementation* (e.g., at a hosted storage location) or a *query* for a *set of possible implementations* (e.g., using the query API of a public registry). Because of the wide variety of hosts executing components, the Component Model doesn't specify how URLs are to be interpreted, -just that they are grammatically URLs. Even `http:` URLs may or may not be +just that they are grammatically URLs. Even `https:` URLs may or may not be literally fetched by the host (c.f. [import maps]). When present, `URL`s must *also* be unique (*in addition* the abovementioned