From 17f94ed1270a98218e0e796ca1dad1feb7e5c507 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 24 Jan 2022 14:07:48 -0600 Subject: [PATCH 01/18] Add skeleton Explainer.md containing only AST and Binary.md defining binary format --- README.md | 31 +- design/mvp/Binary.md | 252 +++++ design/mvp/CanonicalABI.md | 3 + design/mvp/Explainer.md | 915 ++++++++++++++++++ design/mvp/FutureFeatures.md | 80 ++ design/mvp/Subtyping.md | 24 + design/mvp/examples/LinkTimeVirtualization.md | 78 ++ .../SharedEverythingDynamicLinking.md | 422 ++++++++ .../images/link-time-virtualization.svg | 1 + .../shared-everything-dynamic-linking.svg | 1 + design/proposals/README.md | 5 - spec/README.md | 5 +- 12 files changed, 1787 insertions(+), 30 deletions(-) create mode 100644 design/mvp/Binary.md create mode 100644 design/mvp/CanonicalABI.md create mode 100644 design/mvp/Explainer.md create mode 100644 design/mvp/FutureFeatures.md create mode 100644 design/mvp/Subtyping.md create mode 100644 design/mvp/examples/LinkTimeVirtualization.md create mode 100644 design/mvp/examples/SharedEverythingDynamicLinking.md create mode 100644 design/mvp/examples/images/link-time-virtualization.svg create mode 100644 design/mvp/examples/images/shared-everything-dynamic-linking.svg delete mode 100644 design/proposals/README.md diff --git a/README.md b/README.md index 209025b1..7543cd5c 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,13 @@ # Component Model design and specification -This repository contains documents describing the high-level [goals], -[use cases], [design choices] and [FAQ] of the component model. +This repository describes the high-level [goals], [use cases], [design choices] +and [FAQ] of the component model as well as a more-detailed [explainer] and +[binary format] covering the initial Minimum Viable Product (MVP) release. -In the future, as proposals get merged, the repository will additionally -contain the spec, a reference interpreter, a test suite, and directories for -each proposal with the proposal's explainer and specific design documents. +In the future, this repository will additionally contain a [formal spec], +reference interpreter and test suite. -## Design Process & Contributing - -At this early stage, this repository only contains high-level design documents -and discussions about the Component Model in general. Detailed explainers, -specifications and discussions are broken into the following two repositories -which, together, will form the "MVP" of the Component Model: - -* The [module-linking] proposal will initialize the Component Model - specification, adding the ability for WebAssembly to import, nest, - instantiate and link multiple Core WebAssembly modules without host-specific - support. - -* The [interface-types] proposal will extend the Component Model specification - with a new set of high-level types for defining shared-nothing, - language-neutral "components". +## Contributing All Component Model work is done as part of the [W3C WebAssembly Community Group]. To contribute to any of these repositories, see the Community Group's @@ -32,7 +18,8 @@ To contribute to any of these repositories, see the Community Group's [use cases]: design/high-level/UseCases.md [design choices]: design/high-level/Choices.md [FAQ]: design/high-level/FAQ.md -[module-linking]: https://github.com/webassembly/module-linking/ -[interface-types]: https://github.com/webassembly/interface-types/ +[explainer]: design/mvp/Explainer.md +[binary format]: design/mvp/Binary.md +[formal spec]: spec/ [W3C WebAssembly Community Group]: https://www.w3.org/community/webassembly/ [Contributing Guidelines]: https://webassembly.org/community/contributing/ diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md new file mode 100644 index 00000000..90447c56 --- /dev/null +++ b/design/mvp/Binary.md @@ -0,0 +1,252 @@ +# Component Model Binary Format Explainer + +This document defines the binary format for the AST defined in the +[explainer](Explainer.md). The top-level production is `component` and the +convention is that a file suffixed in `.wasm` may contain either a +[`core:module`] *or* a `component`, using the `kind` field to discriminate +between the two in the first 8 bytes (see [below](#component-definitions) for +more details). + +Note: this document is not meant to completely define the decoding or validation +rules, but rather merge the minimal need-to-know elements of both, with just +enough detail to create a prototype. A complete definition of the binary format +and validation will be present in the [formal specification](../../spec/). + + +## Component Definitions + +(See [Component Definitions](Explainer.md#component-definitions) in the explainer.) +``` +component ::= s*:
* => (component flatten(s*)) +preamble ::= +magic ::= 0x00 0x61 0x73 0x6D +version ::= 0x0a 0x00 +kind ::= 0x01 0x00 +section ::= section_0() => ϵ + | t*:section_1(vec()) => t* + | i*:section_2(vec()) => i* + | f*:section_3(vec()) => f* + | m: section_4() => m + | c: section_5() => c + | i*:section_6(vec()) => i* + | e*:section_7(vec()) => e* + | s: section_8() => s + | a*:section_9(vec()) => a* +``` +Notes: +* Reused Core binary rules: [`core:section`], [`core:custom`], [`core:module`] +* The `version` given above is pre-standard. As the proposal changes before + final standardization, `version` will be bumped from `0xa` upwards to + coordinate prototypes. When the standard is finalized, `version` will be + changed one last time to `0x1`. (This mirrors the path taken for the Core + WebAssembly 1.0 spec.) +* The `kind` field is meant to distinguish modules from components early in the + binary format. (Core WebAssembly modules already implicitly have a `kind` + field of `0x0` in their 4 byte [`core:version`] field.) + + +## Instance Definitions + +(See [Instance Definitions](Explainer.md#instance-definitions) in the explainer.) +``` +instance ::= ie: => (instance ie) +instanceexpr ::= 0x00 0x00 m: a*:vec() => (instantiate (module m) (import a)*) + | 0x00 0x01 c: a*:vec() => (instantiate (component c) (import a)*) + | 0x01 e*:vec() => e* + | 0x02 e*:vec() => e* +modulearg ::= n: 0x02 i: => n (instance i) +componentarg ::= n: 0x00 m: => n (module m) + | n: 0x01 c: => n (component c) + | n: 0x02 i: => n (instance i) + | n: 0x03 f: => n (func f) + | n: 0x04 v: => n (value v) + | n: 0x05 t: => n (type t) (t must be an ) +export ::= a: => (export a) +name ::= n: => n +``` +Notes: +* Reused Core binary rules: [`core:export`], [`core:name`] +* The indices in `modulearg`/`componentarg` are validated according to their + respective index space, which are built incrementally as each definition is + validated. In general, unlike core modules, which supports cyclic references + between (function) definitions, component definitions are strictly acyclic + and validated in a linear incremental manner, like core wasm instructions. +* The arguments supplied by `instantiate` are validated against the consuming + module/component according to the [subtyping](Subtyping.md) rules. + + +## Alias Definitions + +(See [Alias Definitions](Explainer.md#alias-definitions) in the explainer.) +``` +alias ::= 0x00 0x00 i: n: => (alias export i n (module)) + | 0x00 0x01 i: n: => (alias export i n (component)) + | 0x00 0x02 i: n: => (alias export i n (instance)) + | 0x00 0x03 i: n: => (alias export i n (func)) + | 0x00 0x04 i: n: => (alias export i n (value)) + | 0x01 0x00 i: n: => (alias export i n (func)) + | 0x01 0x01 i: n: => (alias export i n (table)) + | 0x01 0x02 i: n: => (alias export i n (memory)) + | 0x01 0x03 i: n: => (alias export i n (global)) + | ... other Post-MVP Core definition kinds + | 0x02 0x00 ct: i: => (alias outer ct i (module)) + | 0x02 0x01 ct: i: => (alias outer ct i (component)) + | 0x02 0x05 ct: i: => (alias outer ct i (type)) +``` +Notes: +* For instance-export aliases (opcodes `0x00` and `0x01`), `i` is validated to + refer to an instance in the instance index space that exports `n` with the + specified definition kind. +* For outer aliases (opcode `0x02`), `ct` is validated to be *less or equal + than* the number of enclosing components and `i` is validated to be a valid + index in the specified definition's index space of the enclosing component + indicated by `ct` (counting outward, starting with `0` referring to the + current component). + + +## Type Definitions + +(See [Type Definitions](Explainer.md#type-definitions) in the explainer.) +``` +type ::= dt: => dt + | it: => it +deftype ::= mt: => mt + | ct: => ct + | it: => it + | ft: => ft + | vt: => vt +moduletype ::= 0x4f mtd*:vec() => (module mtd*) +moduletype-def ::= 0x01 dt: => dt + | 0x02 i: => i + | 0x07 n: d: => (export n d) +core:deftype ::= ft: => ft + | ... Post-MVP additions => ... +componenttype ::= 0x4e ctd*:vec() => (component ctd*) +instancetype ::= 0x4d itd*:vec() => (instance itd*) +componenttype-def ::= itd: => itd + | 0x02 i: => i +instancetype-def ::= 0x01 t: => t + | 0x07 n: dt: => (export n dt) + | 0x09 a: => a +import ::= n: dt: => (import n dt) +deftypeuse ::= i: => type-index-space[i] (must be ) +functype ::= 0x4c param*:vec() t: => (func param* (result t)) +param ::= n: t: => (param n t) +valuetype ::= 0x4b t: => (value t) +intertypeuse ::= i: => type-index-space[i] (must be ) + | pit: => pit +primintertype ::= 0x7f => unit + | 0x7e => bool + | 0x7d => s8 + | 0x7c => u8 + | 0x7b => s16 + | 0x7a => u16 + | 0x79 => s32 + | 0x78 => u32 + | 0x77 => s64 + | 0x76 => u64 + | 0x75 => float32 + | 0x74 => float64 + | 0x73 => char + | 0x72 => string +intertype ::= pit: => pit + | 0x71 field*:vec() => (record field*) + | 0x70 case*:vec() => (variant case*) + | 0x6f t: => (list t) + | 0x6e t*:vec() => (tuple t*) + | 0x6d n*:vec() => (flags n*) + | 0x6c n*:vec() => (enum n*) + | 0x6b t*:vec() => (union t*) + | 0x6a t: => (optional t) + | 0x69 t: u: => (expected t u) +field ::= n: t: => (field n t) +case ::= n: t: 0x0 => (case n t) + | n: t: 0x1 i: => (case n t (defaults-to case-label[i])) +``` +Notes: +* Reused Core binary rules: [`core:import`], [`core:importdesc`], [`core:functype`] +* The type opcodes follow the same negative-SLEB128 scheme as Core WebAssembly, + with type opcodes starting at SLEB128(-1) (`0x7f`) and going down, + reserving the nonnegative SLEB128s for type indices. +* The (`module`|`component`|`instance`)`type-def` opcodes match the corresponding + section numbers. +* Module, component and instance types create fresh type index spaces that are + populated and referenced by their contained definitions. E.g., for a module + type that imports a function, the `import` `moduletype-def` must be preceded + by either a `type` or `alias` `moduletype-def` that adds the function type to + the type index space. +* Currently, the only allowed form of `alias` in instance and module types + is `(alias outer ct li (type))`. In the future, other kinds of aliases + will be needed and this restriction will be relaxed. + + +## Function Definitions + +(See [Function Definitions](Explainer.md#function-definitions) in the explainer.) +``` +func ::= body: => (func body) +funcbody ::= 0x00 ft: opt*:vec() f: => (canon.lift ft opt* f) + | 0x01 opt*:* f: => (canon.lower opt* f) +canonopt ::= 0x00 => string=utf8 + | 0x01 => string=utf16 + | 0x02 => string=latin1+utf16 + | 0x03 i: => (into i) +``` +Notes: +* Validation prevents duplicate or conflicting options. +* Validation of `canon.lift` requires `f` to have a `core:functype` that matches + the canonical-ABI-defined lowering of `ft`. The function defined by + `canon.lift` has type `ft`. +* Validation of `canon.lower` requires `f` to have a `functype`. The function + defined by `canon.lower` has a `core:functype` defined by the canonical ABI + lowering of `f`'s type. +* If the lifting/lowering operations implied by `canon.lift` or `canon.lower` + require access to `memory`, `realloc` or `free`, then validation will require + the `(into i)` `canonopt` be present and the corresponding export be present + in `i`'s `instancetype`. + + +## Start Definitions + +(See [Start Definitions](Explainer.md#start-definitions) in the explainer.) +``` +start ::= f: arg*:vec() => (start f (value arg)*) +``` +Notes: +* Validation requires `f` have `functype` with `param` arity and types matching `arg*`. +* Validation appends the `result` types of `f` to the value index space (making + them available for reference by subsequent definitions). + +In addition to the type-compatibility checks mentioned above, the validation +rules for value definitions additionally require that each value is consumed +exactly once. Thus, during validation, each value has an associated "consumed" +boolean flag. When a value is first added to the value index space (via +`import`, `instance`, `alias` or `start`), the flag is clear. When a value is +used (via `export`, `instantiate` or `start`), the flag is set. After +validating the last definition of a component, validation requires all values' +flags are set. + + +## Import and Export Definitions + +(See [Import and Export Definitions](Explainer.md#import-and-export-definitions) in the explainer.) + +As described in the explainer, the binary decode rules of `import` and `export` +have already been defined above. + +Notes: +* Validation requires all import and export `name`s are unique. + + + +[`core:version`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-version +[`core:section`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-section +[`core:custom`]: https://webassembly.github.io/spec/core/binary/modules.html#custom-section +[`core:module`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-module +[`core:export`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-export +[`core:name`]: https://webassembly.github.io/spec/core/binary/values.html#binary-name +[`core:import`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-import +[`core:importdesc`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-importdesc +[`core:functype`]: https://webassembly.github.io/spec/core/binary/types.html#binary-functype + +[Future Core Type]: https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP.md#type-definitions diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md new file mode 100644 index 00000000..1e4ab384 --- /dev/null +++ b/design/mvp/CanonicalABI.md @@ -0,0 +1,3 @@ +# Canonical ABI (sketch) + +TODO: import and update [interface-types/#132](https://github.com/WebAssembly/interface-types/pull/132) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md new file mode 100644 index 00000000..d3351190 --- /dev/null +++ b/design/mvp/Explainer.md @@ -0,0 +1,915 @@ +# Component Model Explainer + +This explainer walks through the assembly-level definition of a +[component](../high-level) and the proposed embedding of components into a +native JavaScript runtime. + +* [Grammar](#grammar) + * [Component definitions](#component-definitions) + * [Instance definitions](#instance-definitions) + * [Alias definitions](#alias-definitions) + * [Type definitions](#type-definitions) + * [Function definitions](#function-definitions) + * [Start definitions](#start-definitions) + * [Import and export definitions](#import-and-export-definitions) +* [Component invariants](#component-invariants) +* [JavaScript embedding](#JavaScript-embedding) + * [JS API](#JS-API) + * [ESM-integration](#ESM-integration) +* [Examples](#examples) + +(Based on the previous [scoping and layering] proposal to the WebAssembly CG, +this repo merges and supersedes the [Module Linking] and [Interface Types] +proposals, pushing some of their original features into the post-MVP [future +feature](FutureFeatures.md) backlog.) + + +## Grammar + +This section defines components using an EBNF grammar that parses something in +between a pure Abstract Syntax Tree (like the Core WebAssembly spec's +[Structure Section]) and a complete text format (like the Core WebAssembly +spec's [Text Format Section]). The goal is to balance completeness with +succinctness, with just enough detail to write examples and define a [binary +format](Binary.md) in the style of the [Binary Format Section], deferring full +precision to the [formal specification](../../spec/). + +The main way the grammar hand-waves is regarding definition uses, where indices +referring to `X` definitions (written ``) should, in the real text +format, explicitly allow identifiers (``), checking at parse time that the +identifier resolves to an `X` definition and then embedding the resolved index +into the AST. + +Additionally, standard [abbreviations] defined by the Core WebAssembly text +format (e.g., inline export definitions) are assumed but not explicitly defined +below. + + +### Component Definitions + +At the top-level, a `component` is a sequence of definitions of various kinds: +``` +component ::= (component ? *) +definition ::= + | + | + | + | + | + | + | + | +``` +Core WebAssembly modules (henceforth just "modules") are also sequences of +(different kinds of) definitions. However, unlike modules, components allow +arbitrarily interleaving the different kinds of definitions. As we'll see +below, this arbitrary interleaving reflects the need for different kinds of +definitions to be able to refer back to each other. Importantly, though, +component definitions are acyclic: definitions can only refer back to preceding +definitions (in the AST, text format or binary format). + +The first kind of component definition is a module, as defined by the existing +Core WebAssembly specification's [`core:module`] top-level production. Thus, +components physically embed one or more modules and can be thought of as a +kind of container format for modules. + +The second kind of definition is, recursively, a component itself. Thus, +components form trees with modules (and all other kinds of definitions) only +appearing at the leaves. + +With what's defined so far, we can define the following component: +```wasm +(component + (component + (module (func (export "one") (result i32) (i32.const 1))) + (module (func (export "two") (result f32) (f32.const 2))) + ) + (module (func (export "three") (result i64) (i64.const 3))) + (component + (component + (module (func (export "four") (result f64) (f64.const 4))) + ) + ) + (component) +) +``` +This top-level component roots a tree with 4 modules and 1 component as +leaves. However, in the absence of any `instance` definitions (introduced +next), nothing will be instantiated or executed at runtime: everything here is +dead code. + + +### Instance Definitions + +Whereas modules and components represent immutable *code*, instances associate +code with potentially-mutable *state* (e.g., linear memory) and thus are +necessary to create before being able to *run* the code. Instance definitions +create module or component instances by selecting a module/component and +supplying a set of named *arguments* which satisfy all the named *imports* of +the selected module/component: +``` +instance ::= (instance ? ) +instanceexpr ::= (instantiate (module ) (import )*) + | (instantiate (component ) (import )*) + | * + | + +modulearg ::= (instance ) + | (instance +) +componentarg ::= (module ) + | (component ) + | (instance ) + | (func ) + | (value ) + | (type ) + | (instance *) +export ::= (export ) +``` +When instantiating a module via `(instantiate (module $M) *)`, the +two-level imports of the module `$M` are resolved as follows: +1. The first `name` of an import is looked up in the named list of `modulearg` + to select a module instance. +2. The second `name` of an import is looked up in the named list of exports of + the module instance found by the first step to select the imported + core definition (a `func`, `memory`, `table`, `global`, etc). + +Based on this, we can link two modules `$A` and `$B` together with the +following component: +```wasm +(component + (module $A + (func (export "one") (result i32) (i32.const 1)) + ) + (module $B + (func (import "a" "one") (result i32)) + ) + (instance $a (instantiate (module $A))) + (instance $b (instantiate (module $B) (import "a" (instance $a)))) +) +``` +Components, as we'll see below, have single-level imports, i.e., each import +has only a single `name`, and thus every different kind of definition can be +passed as a `componentarg` when instantiating a component, not just instances. +Component instantiation will be revisited below after introducing the +prerequisite type and import definitions. + +Lastly, the `(instance *)` and `(instance +)` +expressions allow component and module instances to be created by directly +tupling together preceding definitions, without the need to `instantiate` +anything. To disambiguate the empty case, we observe that there is never +a need to import an empty module instance and thus `(instance)` is an empty +*component* instance. The "inline" forms of these expressions in `modulearg` +and `componentarg` are text format sugar for the "out of line" form in +`instanceexpr`. To show an example of how these instance-creation forms are +useful, we'll first need to introduce the `alias` definitions in the next +section. + + +### Alias Definitions + +Alias definitions project definitions out of other components' index spaces +into the current component's index spaces. As represented in the AST below, +there are two kinds of "targets" for an alias: the `export` of a component +instance, or a local definition of an `outer` component that contains the +current component: +``` +alias ::= (alias ) +aliastarget ::= export + | outer +aliaskind ::= (module ?) + | (component ?) + | (instance ?) + | (func ?) + | (value ?) + | (type ?) + | (table ?) + | (memory ?) + | (global ?) + | ... other Post-MVP Core definition kinds +``` +Aliases add a new element to the index space indicated by `aliaskind`. +(Validation ensures that the `aliastarget` does indeed refer to a matching +definition kind.) The `id` in `aliaskind` is bound to this new index and +thus can be used anywhere a normal `id` can be used. + +In the case of `export` aliases, validation requires that `instanceidx` refers +to an instance which exports `name`. + +In the case of `outer` aliases, the (`outeridx`, `idx`) pair serves as a +[de Bruijn index], with `outeridx` being the number of enclosing components to +skip and `idx` being an index into the target component's `aliaskind` index +space. In particular, `outeridx` can be `0`, in which case the outer alias +refers to the current component. To maintain the acyclicity of module +instantiation, outer aliases are only allowed to refer to *preceding* outer +definitions. + +Components containing outer aliases effectively produce a [closure] at +instantiation time, including a copy of the outer-aliased definitions. Because +of the prevalent assumption that components are (stateless) *values*, outer +aliases are restricted to only refer to stateless definitions: components, +modules and types. (In the future, outer aliases to all kinds of definitions +could be allowed by recording the statefulness of the resulting component in +its type via some kind of "`stateful`" type attribute.) + +Both kinds of aliases come with syntactic sugar for implicitly declaring them +inline: + +For `export` aliases, the inline sugar has the form `(kind +)` +and can be used anywhere a `kind` index appears in the AST. For example, the +following snippet uses an inline function alias: +```wasm +(instance $j (instantiate (component $J) (import "f" (func $i "f")))) +(export "x" (func $j "g" "h")) +``` +which is desugared into: +```wasm +(alias export $i "f" (func $f_alias)) +(instance $j (instantiate (component $J) (import "f" (func $f_alias)))) +(alias export $j "g" (instance $g_alias)) +(alias export $g_alias "h" (func $h_alias)) +(export "x" (func $h_alias)) +``` + +For `outer` aliases, the inline sugar is simply the identifier of the outer +definition, resolved using normal lexical scoping rules. For example, the +following component: +```wasm +(component + (module $M ...) + (component + (instance (instantiate (module $M))) + ) +) +``` +is desugared into: +```wasm +(component $C + (module $M ...) + (component + (alias outer $C $M (module $C_M)) + (instance (instantiate (module $C_M))) + ) +) +``` + +With what's defined so far, we're able to link modules with arbitrary renamings: +```wasm +(component + (module $A + (func (export "one") (result i32) (i32.const 1)) + (func (export "two") (result i32) (i32.const 2)) + (func (export "three") (result i32) (i32.const 3)) + ) + (module $B + (func (import "a" "one") (result i32)) + ) + (instance $a (instantiate (module $A))) + (instance $b1 (instantiate (module $B) + (import "a" (instance $a)) ;; no renaming + )) + (alias export $a "two" (func $a_two)) + (instance $b2 (instantiate (module $B) + (import "a" (instance + (export "one" (func $a_two)) ;; renaming, using explicit alias + )) + )) + (instance $b3 (instantiate (module $B) + (import "a" (instance + (export "one" (func $a "three")) ;; renaming, using inline alias sugar + )) + )) +) +``` +To show analogous examples of linking components, we'll first need to define +a new set of types and functions for components to use. + + +### Type Definitions + +The type grammar below defines two levels of types, with the second level +building on the first: +1. `intertype` (also referred to as "interface types" below): the set of + types of first-class, high-level values communicated across shared-nothing + component interface boundaries +2. `deftype`: the set of types of second-class component definitions which are + imported/exported at instantiation-time. + +The top-level `type` definition is used to define types out-of-line so that +they can be reused via `typeidx` by future definitions. +``` +type ::= (type ? ) +typeexpr ::= + | +deftype ::= + | + | + | + | +moduletype ::= (module *) +moduletype-def ::= + | + | (export ) +core:deftype ::= + | ... Post-MVP additions +componenttype ::= (component (componenttype-def)*) +componenttype-def ::= + | +import ::= (import ) +instancetype ::= (instance (instancetype-def)*) +instancetype-def ::= + | + | (export ) +functype ::= (func (param )* (result )) +valuetype ::= (value ) +intertype ::= unit | bool + | s8 | u8 | s16 | u16 | s32 | u32 | s64 | u64 + | float32 | float64 + | char | string + | (record (field )*) + | (variant (case (defaults-to )?)*) + | (list ) + | (tuple *) + | (flags *) + | (enum *) + | (union *) + | (optional ) + | (expected ) +``` +On a technical note: this type grammar uses `` and `` +recursively to allow it to more-precisely indicate the kinds of types allowed. +The formal spec AST would instead use a `` with validation rules to +restrict the target type while the formal text format would use something like +[`core:typeuse`], allowing any of: (1) a `typeidx`, (2) an identifier `$T` +resolving to a type definition (using `(type $T)` in cases where there is a +grammatical ambiguity), or (3) an inline type definition that is desugared into +a deduplicated out-of-line type definition. + +Starting with interface types, the set of values allowed for the *fundamental* +interface types is given by the following table: +| Type | Values | +| ------------------------- | ------ | +| `unit` | just one [uninteresting value] | +| `bool` | `true` and `false` | +| `s8`, `s16`, `s32`, `s64` | integers in the range [-2N-1, 2N-1-1] | +| `u8`, `u16`, `u32`, `u64` | integers in the range [0, 2N-1] | +| `float32`, `float64` | [IEEE754] floating-pointer numbers with a single, canonical "Not a Number" ([NaN]) value | +| `char` | [Unicode Scalar Values] | +| `record` | heterogeneous [tuples] of named `intertype` values | +| `variant` | heterogeneous [tagged unions] of named `intertype` values | +| `list` | homogeneous, variable-length [sequences] of `intertype` values | + +The sets of values allowed for the remaining *specialized* interface types are +defined by the following mapping: +``` + string ↦ (list char) + (tuple *) ↦ (record ("𝒊" )*) for 𝒊=0,1,... + (flags *) ↦ (record (field bool)*) + (enum *) ↦ (variant (case unit)*) + (optional ) ↦ (variant (case "none") (case "some" )) + (union *) ↦ (variant (case "𝒊" )*) for 𝒊=0,1,... +(expected ) ↦ (variant (case "ok" ) (case "error" )) +``` +Building on these interface types, there are four kinds of types describing the +four kinds of importable/exportable component definitions. (In the future, a +fifth type will be added for [resource types][Resource and Handle Types].) + +A `functype` describes a component function whose parameters and results are +`intertype` values. Thus `functype` is completely disjoint from +[`core:functype`] in the WebAssembly Core spec, whose parameters and results +are [`core:valtype`] values. Morever, since `core:functype` can only appear +syntactically within the `(module ...)` S-expression of a `moduletype`, there +is never a need to syntactically distinguish `functype` from `core:functype` +in the text format: the context dictates which one a `(func ...)` S-expression +parses into. + +A `valuetype` describes a single `intertype` value this is to be consumed +exactly once during component instantiation. How this happens is described +below along with [`start` definitions](#start-definitions). + +As described above, components and modules are immutable values representing +code that cannot be run until instantiated via `instance` definition. Thus, +`moduletype` and `componenttype` describe *uninstantiated code*. `moduletype` +and `componenttype` contain not just import and export definitions, but also +type and alias definitions, allowing them to capture type sharing relationships +between imports and exports. This type sharing becomes necessary (not just a +size optimization) with the upcoming addition of [type imports and exports] to +Core WebAssembly and, symmetrically, [resource and handle types] to the +Component Model. + +The `instancetype` type constructor describes component instances, which are +named tuples of other definitions. Although `instance` definitions can produce +both module *and* component instances, only *component* instances can be +imported or exported (due to the overall [shared-nothing design](../high-level/Choices.md) +of the Component Model) and thus only *component* instances need explicit type +definitions. Consequently, the text format of `instancetype` does not include +a syntax for defining *module* instance types. As with `componenttype` and +`moduletype`, `instancetype` allows nested type and alias definitions to allow +type sharing. + +Lastly, to ensure cross-language interoperability, `moduletype`, +`componenttype` and `instancetype` all require import and export names to be +unique (within a particular module, component, instance or type thereof). In +the case of `moduletype` and two-level imports, this translates to requiring +that import name *pairs* must be *pair*-wise unique. Since the current Core +WebAssembly validation rules allow duplicate imports, this means that some +valid modules will not be typeable and will fail validation if used with the +Component Model. + +The subtyping between all these types is described in a separate +[subtyping explainer](Subtyping.md). + +With what's defined so far, we can define component types using a mix of inline +and out-of-line type definitions: +```wasm +(component $C + (type $T (list (tuple string bool))) + (type $U (option $T)) + (type $G (func (param (list $T)) (result $U))) + (type $D (component + (alias outer $C $T (type $C_T)) + (type $L (list $C_T)) + (import "f" (func (param $L) (result (list u8)))) + (import "g" $G) + (export "g" $G) + (export "h" (func (result $U))) + )) +) +``` +Note that the inline use of `$G` and `$U` are inline `outer` aliases. + + +### Function Definitions + +To implement or call functions of type [`functype`](#type-definitions), we need +to be able to call across a shared-nothing boundary. Traditionally, this +problem is solved by defining a serialization format for copying data across +the boundary. The Component Model MVP takes roughly this same approach, +defining a linear-memory-based [ABI] called the *Canonical ABI* which +specifies, for any imported or exported `functype`, a corresponding +`core:functype` and rules for copying values into or out of linear memory. The +Component Model differs from traditional approaches, though, in that the ABI is +configurable, allowing different memory representations for the same abstract +value. In the MVP, this configurability is limited to the small set of +`canonopt` shown below. However, Post-MVP, [adapter functions] could be added +to allow far more programmatic control. + +The Canonical ABI, which is described in a separate [explainer](CanonicalABI.md), +is explicitly applied to "wrap" existing functions in one of two directions: +* `canon.lift` wraps a Core WebAssembly function (of type `core:functype`) + inside the current component to produce a Component Model function (of type + `functype`) that can be exported to other components. +* `canon.lower` wraps a Component Model function (of type `functype`) that can + have been imported from another component to produce a Core WebAssembly + function (of type `core:functype`) that can be imported and called from Core + WebAssembly code within the current component. + +Based on this, MVP function definitions simply specify one of these two +wrapping directions along with a set of Canonical ABI configurations. +``` +func ::= (func ? ) +funcbody ::= (canon.lift * ) + | (canon.lower * ) +canonopt ::= string=utf8 + | string=utf16 + | string=latin1+utf16 + | (into ) +``` +Validation fails if multiple conflicting options, such as two `string` +encodings, are given. The `latin1+utf16` encoding is [defined](CanonicalABI.md#latin1-utf16) +in the Canonical ABI explainer. If no string-encoding option is specified, the +default is `string=utf8`. + +The `into` option specifies a target instance which supplies the memory that +the canonical ABI should operate on as well as functions that the canonical ABI +can call to allocate, reallocate and free linear memory. Validation requires that +the given `instanceidx` is a module instance exporting the following fields: +``` +(export "memory" (memory 1)) +(export "realloc" (func (param i32 i32 i32 i32) (result i32))) +(export "free" (func (param i32 i32 i32))) +``` +The 4 parameters of `realloc` are: original allocation (or `0` for none), original +size (or `0` if none), alignment and new desired size. The 3 parameters of `free` +are the pointer, size and alignment. + +With this, we can finally write a non-trivial component that takes a string, +does some logging, then returns a string. +```wasm +(component + (import "wasi:logging" (instance $logging + (export "log" (func (param string))) + )) + (import "libc" (module $Libc + (export "memory" (memory 1)) + (export "realloc" (func (param i32 i32) (result i32))) + (export "free" (func (param i32))) + )) + (instance $libc (instantiate (module $Libc))) + (func $log + (canon.lower (into $libc) (func $logging "log")) + ) + (module $Main + (import "libc" "memory" (memory 1)) + (import "libc" "realloc" (func (param i32 i32) (result i32))) + (import "libc" "free" (func (param i32))) + (import "wasi:logging" "log" (func $log (param i32 i32))) + (func (export "run") (param i32 i32) (result i32 i32) + ... (call $log) ... + ) + ) + (instance $main (instantiate (module $Main) + (import "libc" (instance $libc)) + (import "wasi:logging" (instance (export "log" (func $log)))) + )) + (func (export "run") + (canon.lift (func (param string) (result string)) (into $libc) (func $main "run")) + ) +) +``` +This example shows the pattern of splitting out a reusable language runtime +module (`$Libc`) from a component-specific, non-reusable module (`$Main`). In +addition to reducing code size and increasing code-sharing in multi-component +scenarios, this separation allows `$libc` to be created first, so that its +exports are available for reference by `canon.lower`. Without this separation +(if `$Main` contained the `memory` and allocation functions), there would be a +cyclic dependency between `canon.lower` and `$Main` that would have to be +broken by the toolchain emitting an auxiliary module that broke the cycle using +a shared `funcref` table and `call_indirect`. + +Component Model functions are different from Core WebAssembly functions in that +all control flow transfer is explicitly reflected in their type (`functype`). +For example, with Core WebAssembly [exception handling] and [stack switching], +a `(func (result i32))` can return an `i32`, throw, suspend or trap. In +contrast, a Component Model `(func (result string))` may only return a `string` +or trap. To express failure, Component Model functions should return an +[`expected`](#type-definitions) type and languages with exception handling will +bind exceptions to the `error` case. Similarly, the future addition of +[future and stream types] would explicitly declare patterns of stack-switching +in Component Model function signatures. + + +### Start Definitions + +Like modules, components can have start functions that are called during +instantiation. Unlike modules, components can call start functions at multiple +points during instantiation with each such call having interface-typed +parameters and results. Thus, `start` definitions in components look like +function calls: +``` +start ::= (start (value )* (result (value ))?) +``` +The `(value )*` list specifies the arguments passed to `funcidx` by +indexing into the *value index space*. Value definitions (in the value index +space) are like immutable `global` definitions in Core WebAssembly except they +must be consumed exactly once at instantiation-time. + +As with any other definition kind, value definitions may be supplied to +components through `import` definitions. Using the grammar of `import` already +defined [above](#type-definitions), an example *value import* can be written: +``` +(import "env" (value $env (record (field "locale" (optional string))))) +``` +As this example suggests, value imports can serve as generalized [environment +variables], allowing not just `string`, but the full range of interface types +to describe the imported configuration schema. + +With this, we can define a component that imports a string and computes a new +exported string, all at instantiation time: +```wasm +(component + (import "name" (value $name string)) + (import "libc" (module $Libc + (export "memory" (memory 1)) + (export "realloc" (func (param i32 i32 i32 i32) (result i32))) + (export "free" (func (param i32 i32 i32))) + )) + (instance $libc (instantiate (module $Libc))) + (module $Main + (import "libc" ...) + (func (export "start") (param i32 i32) (result i32 i32) + ... general-purpose compute + ) + ) + (instance $main (instantiate (module $Main) (import "libc" (instance $libc)))) + (func $start + (canon.lift (func (param string) (result string)) (into $libc) (func $main "start")) + ) + (start $start (value $name) (result (value $greeting))) + (export "greeting" (value $greeting)) +) +``` +As this example shows, start functions reuse the same Canonical ABI machinery +as normal imports and exports for getting interface typed values into and out +of linear memory. + + +### Import and Export Definitions + +The rules for [`import`](#type-definitions) and [`export`](#instance-definitions) +definitions have actually already been defined above (with the caveat that the +real text format for `import` definitions would additionally allow binding an +identifier (e.g., adding the `$foo` in `(import "foo" (func $foo))`): +``` +import ::= already defined above as part of , but allow binding an +export ::= already defined above as part of +``` + +With what's defined so far, we can define 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 (component $D) + (import "c" (instance $c)) + )) + (instance $d2 (instantiate (component $D) + (import "c" (instance + (export "f" (func $d1 "g")) + )) + )) + (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. + + +## Component Invariants + +As a consequence of the shared-nothing design described above, all calls into +or out of a component instance necessarily transit through a component function +definition. Thus, component functions form a "membrane" around the collection +of module instances contained by a component instance, allowing the Component +Model to establish invariants that increase optimizability and composability in +ways not otherwise possible in the shared-everything setting of Core +WebAssembly. The Component Model proposes establishing the following three +runtime invariants: +1. Components define a "lockdown" state that prevents continued execution + after a trap. This both prevents continued execution with corrupt state and + also allows more-aggressive compiler optimizations (e.g., store reordering). + This was considered early in Core WebAssembly standardization but rejected + due to the lack of clear trapping boundary. With components, each component + instance is given a mutable "lockdown" state that is set upon trap and + implicitly checked at every execution step by component functions. Thus, + after a trap, it's no longer possible to observe the internal state of a + component instance. +2. Components prevent unexpected reentrance by setting the "lockdown" state + (in the previous bullet) whenever calling out through an import, clearing + the lockdown state on return, thereby preventing reentrant export calls in + the interim. This establishes a clear contract between separate components + that both prevents obscure composition-time bugs and also enables + more-efficient non-reentrant runtime glue code (particularly in the middle + of the [Canonical ABI](CanonicalABI.md)). +3. Components enforce the current informal rule that `start` functions are + only for "internal" initialization by trapping if a component attempts to + call a component import during instantiation. In Core WebAssembly, this + invariant is not viable since cross-module calls are often necessary when + initializing shared linear memory (e.g., calling `libc`'s `malloc`). + However, at the granularity of components, this invariant appears viable and + would allow runtimes and toolchains considerable optimization flexibility + based on the resulting purity of instantiation. As one example, tools like + [`wizer`] could be used to *transparently* snapshot the post-instantiation + state of a component to reuse in future instantiations. As another example, + a component runtime could optimize the instantiation of a component DAG by + transparently instantiating non-root components lazily and/or in parallel. + + +## JavaScript Embedding + +### JS API + +The [JS API] currently provides `WebAssembly.compile(Streaming)` which take +raw bytes from an `ArrayBuffer` or `Response` object and produces +`WebAssembly.Module` objects that represent decoded and validated modules. To +natively support the Component Model, the JS API would be extended to allow +these same JS API functions to accept component binaries and produce new +`WebAssembly.Component` objects that represent decoded and validated +components. The [binary format of components](Binary.md) is designed to allow +modules and components to be distinguished by the first 8 bytes of the binary +(splitting the 32-bit [`version`] field into a 16-bit `version` field and a +16-bit `kind` field with `0` for modules and `1` for components). + +Once compiled, a `WebAssemby.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 +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. + +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 `version` field to eagerly +distinguish between modules and components). + +For example, the following component: +```wasm +;; a.wasm +(component + (import "one" (func)) + (import "two" (value string)) + (import "three" (instance + (export "four" (instance + (export "five" (module + (import "six" "a" (func)) + (export "six" "b" (func)) + )) + )) + )) + ... +) +``` +and module: +```wasm +;; b.wasm +(module + (import "six" "a" (func)) + (import "six" "b" (func)) + ... +) +``` +could be successfully instantiated via: +```js +WebAssembly.instantiateStreaming(fetch('./a.wasm'), { + one: () => (), + two: "hi", + three: { + four: { + five: await WebAssembly.instantiateStreaming(fetch('./b.wasm')) + } + } +}); +``` + +The other significant addition to the JS API would be the expansion of the set +of WebAssembly types coerced to and from JavaScript values (by [`ToJSValue`] +and [`ToWebAssemblyValue`]) to include all of [`intertype`](#type-definitions). +At a high level, the additional coercions would be: + +| Interface Type | `ToJSValue` | `ToWebAssemblyValue` | +| -------------- | ----------- | -------------------- | +| `unit` | `null` | accept everything | +| `bool` | `true` or `false` | `ToBoolean` | +| `s8`, `s16`, `s32` | as a Number value | `ToInt32` | +| `u8`, `u16`, `u32` | as a Number value | `ToUint32` | +| `s64` | as a BigInt value | `ToBigInt64` | +| `u64` | as a BigInt value | `ToBigUint64` | +| `float32`, `float64` | as a Number, mapping the canonical NaN to [JS NaN] | `ToNumber` mapping [JS NaN] to the canonical NaN | +| `char` | same as [`USVString`] | same as [`USVString`], throw if the USV length is not 1 | +| `record` | TBD: maybe a [JS Record]? | same as [`dictionary`] | +| `variant` | TBD | TBD | +| `list` | same as [`sequence`] | same as [`sequence`] | +| `string` | same as [`USVString`] | same as [`USVString`] | +| `tuple` | TBD: maybe a [JS Tuple]? | TBD | +| `flags` | TBD: maybe a [JS Record]? | same as [`dictionary`] of `boolean` fields | +| `enum` | same as [`enum`] | same as [`enum`] | +| `optional` | same as [`T?`] | same as [`T?`] | +| `union` | same as [`union`] | same as [`union`] | +| `expected` | same as `variant`, but coerce a top-level `error` return value to a thrown exception | same as `variant`, but coerce uncaught exceptions to top-level `error` return values | + +Notes: +* The forthcoming addition of [resource and handle types] would additionally + allow coercion to and from the remaining Symbol and Object JavaScript value + types. +* The forthcoming addition of [future and stream types] would allow `Promise` + and `ReadableStream` values to be passed directly to and from components + without requiring handles or callbacks. +* When an imported JavaScript function is a built-in function wrapping a Web + IDL function, the specified behavior should allow the intermediate JavaScript + call to be optimized away when the types are sufficiently compatible, falling + back to a plain call through JavaScript when the types are incompatible or + when the engine does not provide a separate optimized call path. + + +### ESM-integration + +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 `kind` +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 +single string as well as the new importable component, module and instance +types. Going through these one by one: + +For component imports of module type, we need a new way to request that the ESM +loader parse or decode a module without *also* instantiating that module. +Recognizing this same need from JavaScript, there is a TC39 proposal called +[Import Reflection] that adds the ability to write, in JavaScript: +```js +import Foo from "./foo.wasm" as "wasm-module"; +assert(Foo instanceof WebAssembly.Module); +``` +With this extension to JavaScript and the ESM loader, a component import +of module type can be treated the same as `import ... as "wasm-module"`. + +Component imports of component type would work the same way as modules, +potentially replacing `"wasm-module"` with `"wasm-component"`. + +In all other cases, the (single) string imported by a component is first +resolved to a [Module Record] using the same process as resolving the +[Module Specifier] of a JavaScript `import`. After this, the handling of the +imported Module Record is determined by the import type: + +For imports of instance type, the ESM loader would treat the exports of the +instance type as if they were the [Named Imports] of a JavaScript `import`. +Thus, single-level imports of instance type act like the two-level imports +of Core WebAssembly modules where the first-level has been factored out. Since +the exports of an instance type can themselves be instance types, this process +must be performed recursively. + +Otherwise, function or value imports are treated like an [Imported Default Binding] +and the Module Record is converted to its default value. This allows the following +component: +```wasm +(component + (import "./foo.js" (func (result string))) +) +``` +to be satisfied by a JavaScript module via ESM-integration: +```js +// foo.js +export default () => "hi"; +``` + + +## Examples + +For some use-case-focused, worked examples, see: +* [Link-time virtualization example](examples/LinkTimeVirtualization.md) +* [Shared-everything dynamic linking example](examples/SharedEverythingDynamicLinking.md) +* [Component Examples presentation](https://docs.google.com/presentation/d/11lY9GBghZJ5nCFrf4MKWVrecQude0xy_buE--tnO9kQ) + + + +[Structure Section]: https://webassembly.github.io/spec/core/syntax/index.html +[`core:module`]: https://webassembly.github.io/spec/core/syntax/modules.html#syntax-module +[`core:export`]: https://webassembly.github.io/spec/core/syntax/modules.html#syntax-export +[`core:import`]: https://webassembly.github.io/spec/core/syntax/modules.html#syntax-import +[`core:importdesc`]: https://webassembly.github.io/spec/core/syntax/modules.html#syntax-importdesc +[`core:functype`]: https://webassembly.github.io/spec/core/syntax/types.html#syntax-functype +[`core:valtype`]: https://webassembly.github.io/spec/core/syntax/types.html#value-types + +[Text Format Section]: https://webassembly.github.io/spec/core/text/index.html +[Abbreviations]: https://webassembly.github.io/spec/core/text/conventions.html#abbreviations +[`core:typeuse`]: https://webassembly.github.io/spec/core/text/modules.html#type-uses +[func-import-abbrev]: https://webassembly.github.io/spec/core/text/modules.html#text-func-abbrev + +[Binary Format Section]: https://webassembly.github.io/spec/core/binary/index.html +[`version`]: https://webassembly.github.io/spec/core/binary/modules.html#binary-version + +[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 +[`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 +[`sequence`]: https://webidl.spec.whatwg.org/#es-sequence +[`dictionary`]: https://webidl.spec.whatwg.org/#es-dictionary +[`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 +[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 +[Module Specifier]: https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#prod-ModuleSpecifier +[Named Imports]: https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#prod-NamedImports +[Imported Default Binding]: https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#prod-ImportedDefaultBinding + +[JS Tuple]: https://github.com/tc39/proposal-record-tuple +[JS Record]: https://github.com/tc39/proposal-record-tuple + +[De Bruijn Index]: https://en.wikipedia.org/wiki/De_Bruijn_index +[Closure]: https://en.wikipedia.org/wiki/Closure_(computer_programming) +[Uninteresting Value]: https://en.wikipedia.org/wiki/Unit_type#In_programming_languages +[IEEE754]: https://en.wikipedia.org/wiki/IEEE_754 +[NaN]: https://en.wikipedia.org/wiki/NaN +[Unicode Scalar Values]: https://unicode.org/glossary/#unicode_scalar_value +[Tuples]: https://en.wikipedia.org/wiki/Tuple +[Tagged Unions]: https://en.wikipedia.org/wiki/Tagged_union +[Sequences]: https://en.wikipedia.org/wiki/Sequence +[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface +[Environment Variables]: https://en.wikipedia.org/wiki/Environment_variable + +[Module Linking]: https://github.com/webassembly/module-linking/ +[Interface Types]: https://github.com/webassembly/interface-types/ +[Type Imports and Exports]: https://github.com/WebAssembly/proposal-type-imports +[Exception Handling]: https://github.com/webAssembly/exception-handling +[Stack Switching]: https://github.com/WebAssembly/stack-switching +[ESM-integration]: https://github.com/WebAssembly/esm-integration + +[Adapter Functions]: FutureFeatures.md#custom-abis-via-adapter-functions +[Canonical ABI]: CanonicalABI.md + +[`wizer`]: https://github.com/bytecodealliance/wizer + +[Scoping and Layering]: https://docs.google.com/presentation/d/1PSC3Q5oFsJEaYyV5lNJvVgh-SNxhySWUqZ6puyojMi8 +[Resource and Handle Types]: https://docs.google.com/presentation/d/1ikwS2Ps-KLXFofuS5VAs6Bn14q4LBEaxMjPfLj61UZE +[Future and Stream Types]: https://docs.google.com/presentation/d/1WtnO_WlaoZu1wp4gI93yc7T_fWTuq3RZp8XUHlrQHl4 diff --git a/design/mvp/FutureFeatures.md b/design/mvp/FutureFeatures.md new file mode 100644 index 00000000..cf986b66 --- /dev/null +++ b/design/mvp/FutureFeatures.md @@ -0,0 +1,80 @@ +# Future Features + +As with Core WebAssembly 1.0, the Component Model 1.0 aims to be a Minimum +Viable Product (MVP), assuming incremental, backwards-compatible +standardization to continue after the initial "1.0" release. The following is +an incomplete list of specific features intentionally punted from the MVP. See +also the high-level [post-MVP use cases](../high-level/UseCases.md#post-mvp) +and [non-goals](../high-level/Goals.md#non-goals). + + +## Custom ABIs via "adapter functions" + +The original Interface Types proposal includes the goal of avoiding a fixed +serialization format, as this often incurs extra copying when the source or +destination language-runtime data structures don't precisely match the fixed +serialization format. A significant amount of work was spent designing a +language of [adapter functions] that provided fairly general programmatic +control over the process of serializing and deserializing interface-typed values. +(The Interface Types Explainer currently contains a snapshot of this design.) +However, a significant amount of additional design work remained, including +(likely) changing the underlying semantic foundations from lazy evaluation to +algebraic effects. + +In pursuit of a timely MVP and as part of the overall [scoping and layering proposal], +the goal of avoiding a fixed serialization format was dropped from the MVP, by +instead defining a [Canonical ABI](CanonicalABI.md) in the MVP. However, the +current design of [function definitions](Explainer.md#function-definitions) +anticipates a future extension whereby function bodies can contain not just the +fixed Canonical ABI-following `canon.lift` and `canon.lower` but, +alternatively, general adapter function code. + +In this future state, `canon.lift` and `canon.lower` could be specified by +simple expansion into the adapter code, making these instructions effectively +macros. However, even in this future state, there is still concrete value in +having a fixedly-defined Canonical ABI as it allows more-aggressive +optimization of calls between components (which both use the Canonical ABI) and +between a component and the host (which often must use a fixed ABI for calling +to and from the statically-compiled host implementation language). See +[`list.lift_canon` and `list.lower_canon`] for more details. + + +## Shared-some-things linking via "adapter modules" + +The original [Interface Types proposal] and the re-layered [Module Linking +proposal] both included an "adapter module" definition that allowed import and +export of both Core WebAssembly and Component Model Definitions and thus did +not establish a shared-nothing boundary. Since [component invariants] and +[GC-free runtime instantiation] both require a shared-nothing boundary, two +distinct "component" and "adapter module" concepts would need to be defined, +with all their own distinct types, index spaces, etc. Having both features in +the MVP adds non-trivial implementation complexity over having just one. +Additionally, having two similar-but-different, partially-overlapping concepts +makes the whole proposal harder to explain. Thus, the MVP drops the concept of +"adapter modules", including only shared-nothing "components". However, if +concrete future use cases emerged for creating modules that partially used +interface types and partially shared linear memory, "adapter modules" could be +added as a future feature. + + +## Shared-everything Module Linking in Core WebAssembly + +[Originally][Core Module Linking], Module Linking was proposed as an addition +to the Core WebAssembly specification, adding only the new concepts of instance +and module definitions (which, like other kinds of definitions, could be +imported and exported). As part of the overall [scoping and layering proposal], +Module Linking as moved into a layer above WebAssembly and merged with the +Interface Types proposal. However, it may still make sense and be complementary +to the Component Model to add Module Linking to Core WebAssembly in the future +as originally proposed. + + + +[Interface Types Proposal]: https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md +[Module Linking Proposal]: https://github.com/WebAssembly/module-linking/blob/main/design/proposals/module-linking/Explainer.md +[Adapter Functions]: https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md#adapter-functions +[Scoping and Layering Proposal]: https://docs.google.com/presentation/d/1PSC3Q5oFsJEaYyV5lNJvVgh-SNxhySWUqZ6puyojMi8 +[`list.lift_canon` and `list.lower_canon`]: https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md#optimization-canonical-representation +[Component Invariants]: Explainer.md#component-invariants +[GC-free Runtime Instantiation]: https://docs.google.com/presentation/d/1PSC3Q5oFsJEaYyV5lNJvVgh-SNxhySWUqZ6puyojMi8/edit#slide=id.gd06989d984_1_274 +[Core Module Linking]: https://github.com/WebAssembly/module-linking/blob/63cd6c0e3ac5c0cdb798a985790f51ccdd77af00/proposals/module-linking/Explainer.md diff --git a/design/mvp/Subtyping.md b/design/mvp/Subtyping.md new file mode 100644 index 00000000..241f8cd6 --- /dev/null +++ b/design/mvp/Subtyping.md @@ -0,0 +1,24 @@ +# Subtyping + +TODO: write this up in more detail. + +But roughly speaking: + +| Type | Subtyping | +| ------------------------- | --------- | +| `unit` | every interface type is a subtype of `unit` | +| `bool` | | +| `s8`, `s16`, `s32`, `s64`, `u8`, `u16`, `u32`, `u64` | lossless coercions are allowed | +| `float32`, `float64` | `float32 <: float64` | +| `char` | | +| `record` | fields can be reordered; covariant field payload subtyping; superfluous fields can be ignored in the subtype; `optional` fields can be ignored in the supertype | +| `variant` | cases can be reordered; contravariant case payload subtyping; superfluous cases can be ignored in the supertype; `defaults-to` cases can be ignored in the subtype | +| `list` | covariant element subtyping | +| `tuple` | `(tuple T ...) <: T` | +| `optional` | `T <: (optional T)` | +| `expected` | `T <: (expected T _)` | +| `union` | `T <: (union ... T ...)` | +| `func` | parameter names must match in order; covariant parameter subtyping; superfluous parameters can be ignored in the subtype; `optional` parameters can be ignored in the supertype; contravariant result subtyping | + +The remaining specialized interface types inherit their subtyping from their +fundamental interface types. diff --git a/design/mvp/examples/LinkTimeVirtualization.md b/design/mvp/examples/LinkTimeVirtualization.md new file mode 100644 index 00000000..584e6c13 --- /dev/null +++ b/design/mvp/examples/LinkTimeVirtualization.md @@ -0,0 +1,78 @@ +# Link-time Virtualization + +The idea with **link-time virtualization** use cases is to take the static +dependency graph on the left (where all 3 components import the +`wasi:filesystem` interface) and produce the runtime instance graph on the +right, where the `parent` instance has created a `virtualized` instance and +supplied it to a new `child` instance as the `wasi:filesystem` implementation. + +

+ +Importantly, the `child` instance has no access to the `wasi:filesystem` +instance imported by the `parent` instance. + +We start with the `child.wat` that has been written and compiled separately, +without regard to `parent.wasm`: +```wasm +;; child.wat +(component + (import "wasi:filesystem" (instance + (export "read" (func ...)) + (export "write" (func ...)) + )) + ... +) +``` + +We want to write a parent component that reuses the child component, giving the +child component a virtual filesystem. This virtual filesystem can be factored +out and reused as a separate component: +```wasm +;; virtualize.wat +(component + (import "wasi:filesystem" (instance $fs + (export "read" (func ...)) + (export "write" (func ...)) + )) + (func (export "read") + ... transitively calls (func $fs "read) + ) + (func (export "write") + ... transitively calls (func $fs "write") + ) +) +``` + +We now write the parent component by composing `child.wasm` with +`virtualize.wasm`: +```wasm +;; parent.wat +(component + (import "wasi:filesystem" (instance $real-fs ...)) + (import "./virtualize.wasm" (component $Virtualize ...)) + (import "./child.wasm" (component $Child ...)) + (instance $virtual-fs (instantiate (component $Virtualize) + (import "wasi:filesystem" (instance $real-fs)) + )) + (instance $child (instantiate (component $Child) + (import "wasi:filesystem" (instance $virtual-fs)) + )) +) +``` +Here we import the `child` and `virtualize` components, but they could also be +trivially copied in-line into the `parent` component using nested component +definitions in place of imports: +```wasm +;; parent.wat +(component + (import "wasi:filesystem" (instance $real-fs ...)) + (component $Virtualize ... copied inline ...) + (component $Child ... copied inline ...) + (instance $virtual-fs (instantiate (component $Virtualize) + (import "wasi:filesystem" (instance $real-fs)) + )) + (instance $child (instantiate (component $Child) + (import "wasi:filesystem" (instance $virtual-fs)) + )) +) +``` diff --git a/design/mvp/examples/SharedEverythingDynamicLinking.md b/design/mvp/examples/SharedEverythingDynamicLinking.md new file mode 100644 index 00000000..b03d1695 --- /dev/null +++ b/design/mvp/examples/SharedEverythingDynamicLinking.md @@ -0,0 +1,422 @@ +# Shared-Everything Dynamic Linking + +*Shared-everything dynamic linking* refers to the ability to create a component +out of multiple Core WebAssembly modules so that common modules can be shared +with other components. This provides an alternative to *static linking* which +forces common code to be copied into each component. This type of linking +should be able to leverage of existing support for native dynamic linking (of +`.dll`s or `.so`s) which a single shared linear memory (hence +*shared-everything* dynamic linking). + +Shared-everything dynamic linking should be *complementary* to the +shared-nothing dynamic linking of components described in the +[explainer](Explainer.md). In particular, dynamically-linked modules must not +share linear memory across component instance boundaries. For example, we want +the static dependency graph on the left to produce the runtime instance graph +on the right: create the runtime instance graph on the right: + +

+ +Here, `libc` defines and exports a linear memory that is imported by the other +moudle instances contained within the same component instance. Thus, at +runtime, the composite application creates *three* instances of the `libc` +module (creating *three* linear memories) yet contains only *one* copy of the +`libc` code. This use case is tricky to implement in many module systems where +sharing module code implies sharing module instance state. + + +## `libc` + +As with native dynamic linking, shared-everything dynamic linking requires +toolchain conventions that are followed by all the toolchains producing the +participating modules. Here, as in most conventions, `libc` serves a special +role and is assumed to be bundled with the compiler. As part of this special +role, `libc` defines and exports linear memory, with the convention that +every other module imports memory from `libc`: +```wasm +;; libc.wat +(module + (memory (export "memory") 1) + (func (export "malloc") (param i32) (result i32) ...impl) + ... +) +``` + +Our compiler will also bundle standard library headers which contain +declarations compatible with `libc`. First, though, we first need some helper +macros, which we'll place in `stddef.h`: +```c +/* stddef.h */ +#define WASM_IMPORT(module,name) __attribute__((import_module(#module), import_name(#name))) name +#define WASM_EXPORT(name) __attribute__((export_name(#name))) name +``` + +These macros use the clang-specific attributes [`import_module`], [`import_name`] +and [`export_name`] to ensure that the annotated C functions produce the +correct wasm import or export definitions. Other compilers would use their own +magic syntax to achieve the same effect. Using these macros, we can declare +`malloc` in `stdlib.h`: +```c +/* stdlib.h */ +#include +#define LIBC(name) WASM_IMPORT(libc, name) +void* LIBC(malloc)(size_t n); +``` + +With these annotations, C programs that include and call this function will be +compiled to contain the following import: +```wasm +(import "libc" "malloc" (func (param i32) (result i32))) +``` + + +## `libzip` + +The interface exposed by `libzip` to its clients is a header file: +```c +/* libzip.h */ +#include +#define LIBZIP(name) WASM_IMPORT(libzip, name) +void* LIBZIP(zip)(void* in, size_t in_size, size_t* out_size); +``` +which can be implemented by the following source file: +```c +/* libzip.c */ +#include +void* WASM_EXPORT(zip)(void* in, size_t in_size, size_t* out_size) { + ... + void *p = malloc(n); + ... +} +``` + +Note that `libzip.h` annotates the `zip` declaration with an *import* attribute +so that client modules generate proper wasm *import definitions* while `libzip.c` +annotates the `zip` definition with an *export* attribute so that this function +generates a proper *export definition* in the compiled module. Compiling with +`clang -shared libzip.c` produces a module shaped like: +```wasm +;; libzip.wat +(module + (import "libc" "memory" (memory 1)) + (import "libc" "malloc" (func (param i32) (result i32))) + (func (export "zip") (param i32 i32 i32) (result i32) + ... + ) +) +``` + + +## `zipper` + +The main module of the `zipper` component is implemented by the following +source file: +```c +/* zipper.c */ +#include +#include "libzip.h" +int main(int argc, char* argv[]) { + ... + void *in = malloc(n); + ... + void *out = zip(in, n, &out_size); + ... +} +``` + +When compiled by a (future) component-aware `clang`, the resulting component +would look like: +```wasm +;; zipper.wat +(component + (import "libc" (module $Libc + (export "memory" (memory 1)) + (export "malloc" (func (param i32) (result i32))) + )) + (import "libzip" (module $Libzip + (import "libc" "memory" (memory 1)) + (import "libc" "malloc" (func (param i32) (result i32))) + (export "zip" (func (param i32 i32 i32) (result i32))) + )) + + (module $Main + (import "libc" "memory" (memory 1)) + (import "libc" "malloc" (func (param i32) (result i32))) + (import "libzip" "zip" (func (param i32 i32 i32) (result i32))) + ... + (func (export "zip") (param i32 i32) (result i32 i32) + ... + ) + ) + + (instance $libc (instantiate (module $Libc))) + (instance $libzip (instantiate (module $Libzip)) + (import "libc" (instance $libc)) + )) + (instance $main (instantiate (module $Main) + (import "libc" (instance $libc)) + (import "libzip" (instance $libzip)) + )) + (func (export "zip") + (canon.lift (func (param (list u8)) (result (list u8))) (into $libc) (func $main "zip")) + ) +) +``` +Here, `zipper` links its own private module code (`$Main`) with the shareable +`libc` and `libzip` module code, ensuring that each new instance of `zipper` +gets a fresh, private instance of `libc` and `libzip`. + + +## `libimg` + +Next we create a shared module `libimg` that depends on `libzip`: +```c +/* libimg.h */ +#include +#define LIBIMG(name) WASM_IMPORT(libimg, name) +void* LIBIMG(compress)(void* in, size_t in_size, size_t* out_size); +``` +```c +/* libimg.c */ +#include +#include "libzip.h" +void* WASM_EXPORT(compress)(void* in, size_t in_size, size_t* out_size) { + ... + void *out = zip(in, in_size, &out_size); + ... +} +``` +Compiling with `clang -shared libimg.c` produces a `libimg` module: +```wat +;; libimg.wat +(module + (import "libc" "memory" (memory 1)) + (import "libc" "malloc" (func (param i32) (result i32))) + (import "libzip" "zip" (func (param i32 i32 i32) (result i32))) + (func (export "compress") (param i32 i32 i32) (result i32) + ... + ) +) +``` + + +## `imgmgk` + +The main module of the `imgmgk` component is implemented by including +`stddef.h`, `libzip.h` and `libimg.h`. When compiled by a (future) +component-aware `clang`, the resulting component would look like: +```wasm +;; imgmgk.wat +(component $Imgmgk + (import "libc" (module $Libc ...)) + (import "libzip" (module $Libzip ...)) + (import "libimg" (module $Libimg ...)) + + (module $Main + (import "libc" "memory" (memory 1)) + (import "libc" "malloc" (func (param i32) (result i32))) + (import "libimg" "compress" (func (param i32 i32 i32) (result i32))) + ... + (func (export "transform") (param i32 i32) (result i32 i32) + ... + ) + ) + + (instance $libc (instantiate (module $Libc))) + (instance $libzip (instantiate (module $Libzip) + (import "libc" (instance $libc)) + )) + (instance $libimg (instantiate (module $Libimg) + (import "libc" (instance $libc)) + (import "libzip" (instance $libzip)) + )) + (instance $main (instantiate (module $Main) + (import "libc" (instance $libc)) + (import "libimg" (instance $libimg)) + )) + (func (export "transform") + (canon.lift (func (param (list u8)) (result (list u8))) (into $libc) (func $main "transform")) + ) +) +``` +Here, we see the general pattern emerging of the dependency DAG between +dynamically-linked modules expressed through `instance` definitions. + + +## `app` + +Finally, we can create the `app` component by composing the `zipper` and `imgmgk` +components. The resulting component could look like: +```wasm +;; app.wat +(component + (import "libc" (module $Libc ...)) + (import "libzip" (module $Libzip ...)) + (import "libimg" (module $Libimg ...)) + + (import "zipper" (component $Zipper ...)) + (import "imgmgk" (component $Imgmgk ...)) + + (module $Main + (import "libc" "memory" (memory 1)) + (import "libc" "malloc" (func (param i32) (result i32))) + (import "zipper" "zip" (func (param i32 i32) (result i32 i32))) + (import "imgmgk" "transform" (func (param i32 i32) (result i32 i32))) + ... + (func (export "run") (param i32 i32) (result i32 i32) + ... + ) + ) + + (instance $zipper (instantiate (component $Zipper) + (import "libc" (module $Libc)) + (import "libzip" (module $Libzip)) + )) + (instance $imgmgk (instantiate (component $Imgmgk) + (import "libc" (module $Libc)) + (import "libzip" (module $Libzip)) + (import "libimg" (module $Libimg)) + )) + + (instance $libc (instantiate (module $Libc))) + (func $zip + (canon.lower (into $libc) (func $zipper "zip")) + ) + (func $transform + (canon.lower (into $libc) (func $imgmgk "transform")) + ) + (instance $main (instantiate (module $Main) + (import "libc" (instance $libc)) + (import "zipper" (instance (export "zip" (func $zipper "zip")))) + (import "imgmgk" (instance (export "transform" (func $imgmgk "transform")))) + )) + (func (export "run") + (canon.lift (func (param string) (result string)) (func $main "run")) + ) +) +``` +Note here that `$Libc` is passed to the nested `zipper` and `imgmgk` instances +as an (uninstantiated) module before `app` creates its own private instance +of `libc` linked with its private `$Main` module instance. Thus, the three +components share `libc` *code* without sharing `libc` *state*, realizing the +instance diagram at the beginning. + + +## Cyclic Dependencies + +If cyclic dependencies are necessary, such cycles can be broken by: +* identifying a [spanning] DAG over the module dependency graph; +* keeping the calls along the spanning DAG's edges as normal function imports + and direct calls (as shown above); then +* converting calls along "back edges" into indirect calls (`call_indirect`) of + an imported `(global i32)` containing the index in the function table. + +For example, a cycle between modules `$A` and `$B` could be broken by arbitrarily +saying that `$B` gets to directly import `$A` and then routing `$A`'s imports +through a shared mutable `funcref` table via `call_indirect`: +```wat +(module $A + ;; A imports B.bar indirectly via table+index + (import "linkage" "table" (table funcref)) + (import "linkage" "bar-index" (global $bar-index (mut i32))) + + (type $FooType (func)) + (func $some_use + (call_indirect $FooType (global.get $bar-index)) + ) + + ;; A exports A.foo directly to B + (func (export "foo") + ... + ) +) +``` +```wat +(module $B + ;; B directly imports A.foo + (import "a" "foo" (func $a_foo)) ;; B gets to directly import A + + ;; B indirectly exports B.bar to A + (func $bar ...) + (import "linkage" "table" (table $ftbl funcref)) + (import "linkage" "bar-index" (global $bar-index (mut i32))) + (elem (table $ftbl) (offset (i32.const 0)) $bar) + (func $start (global.set $bar-index (i32.const 0))) + (start $start) +) +``` +Lastly, a toolchain can link these together into a whole program by emitting +a wrapper adapter module that supplies both `$A` and `$B` with a shared +function table and `bar-index` mutable global. +```wat +(component + (import "A" (module $A ...)) + (import "B" (module $B ...)) + (module $Linkage + (global (export "bar-index") (mut i32)) + (table (export "table") funcref 1) + ) + (instance $linkage (instantiate (module $Linkage))) + (instance $a (instantiate (module $A) + (import "linkage" (instance $linkage)) + )) + (instance $b (instantiate (module $B) + (import "a" (instance $a)) + (import "linkage" (instance $linkage)) + )) +) +``` + + +## Function Pointer Identity + +To ensure C function pointer identity across shared libraries, for each exported +function, a shared library will need to export both the `func` definition and a +`(global (mut i32))` containing that `func`'s index in the global `funcref` table. + +Because a shared library can't know the absolute offset in the global `funcref` +table for all of its exported functions, the table slots' offsets must be +dynamic. One way this could be achieved is by the shared library calling into a +`ftalloc` export of `libc` (analogous to `malloc`, but for allocating from the +global `funcref` table) from the shared library's `start` function. Elements could +then be written into the table at the allocated offset and their indices +written into the exported `(global (mut i32))`s. + +(In theory, more efficient schemes are possible when the main program has more +static knowledge of its shared libraries.) + + +## Linear-memory stack pointer + +To implement address-taken local variables, varargs, and other corner cases, +wasm compilers maintain a stack in linear memory that is maintained in +lock-step with the native WebAssembly stack. The pointer to the top of the +linear-memory stack is usually maintained in a single `(global (mut i32))` +variable that must be shared by all linked instances. Following the above +linking scheme, this global would naturally be exported by `libc` along +with linear memory. + + +## Runtime Dynamic Linking + +The general case of runtime dynamic linking in the style of `dlopen`, where an +*a priori unknown* module is linked into the program at runtime, is not possible +to do purely within wasm with this proposal. Additional host-provided APIs are +required for: +* compiling files or bytes into a module; +* reading the import strings of a module; +* dynamically instantiating a module given a list of import values; and +* dynamically extracting the exports of an instance. + +Such APIs could be standardized as part of [WASI]. Moreover, the [JS API] +possesses all the above capabilities allowing the WASI APIs to be prototyped and +implemented in the browser. + + + +[`import_module`]: https://clang.llvm.org/docs/AttributeReference.html#import-module +[`import_name`]: https://clang.llvm.org/docs/AttributeReference.html#import-name +[`export_name`]: https://clang.llvm.org/docs/AttributeReference.html#export-name +[Spanning]: https://en.wikipedia.org/wiki/Spanning_tree +[WASI]: https://github.com/webassembly/wasi +[JS API]: https://webassembly.github.io/spec/js-api/index.html diff --git a/design/mvp/examples/images/link-time-virtualization.svg b/design/mvp/examples/images/link-time-virtualization.svg new file mode 100644 index 00000000..19b8bae4 --- /dev/null +++ b/design/mvp/examples/images/link-time-virtualization.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/mvp/examples/images/shared-everything-dynamic-linking.svg b/design/mvp/examples/images/shared-everything-dynamic-linking.svg new file mode 100644 index 00000000..294acc87 --- /dev/null +++ b/design/mvp/examples/images/shared-everything-dynamic-linking.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/design/proposals/README.md b/design/proposals/README.md deleted file mode 100644 index 36725210..00000000 --- a/design/proposals/README.md +++ /dev/null @@ -1,5 +0,0 @@ -This subdirectory will contain the explainers specific to each proposal, -starting initially with [module-linking] and [interface-types]. - -[module-linking]: https://github.com/webassembly/module-linking/ -[interface-types]: https://github.com/webassembly/interface-types/ diff --git a/spec/README.md b/spec/README.md index 241758ec..c9a24170 100644 --- a/spec/README.md +++ b/spec/README.md @@ -1,5 +1,4 @@ -This directory will be initialized by the [module-linking] proposal to contain -the Component Model specification, analogous to the [Core spec repo]. +This directory will contain the formal Component Model specification, a +reference interpreter and test suite, similar to the [Core spec repo]. -[module-linking]: https://github.com/webassembly/module-linking/ [Core spec repo]: https://github.com/WebAssembly/spec/ From 8d429ed629ca2ea46451932b8232e74b9099ee14 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Tue, 22 Feb 2022 19:14:24 -0600 Subject: [PATCH 02/18] Factor out into --- 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 d3351190..061d7ab2 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -172,9 +172,9 @@ there are two kinds of "targets" for an alias: the `export` of a component instance, or a local definition of an `outer` component that contains the current component: ``` -alias ::= (alias ) -aliastarget ::= export - | outer +alias ::= (alias ) +aliastarget ::= export + | outer aliaskind ::= (module ?) | (component ?) | (instance ?) From 0f8e2d0e11c8ce0f256d82a7ae9fb13b51c2770f Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 24 Feb 2022 10:55:04 -0600 Subject: [PATCH 03/18] Fix bug in JS API example --- 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 061d7ab2..52720298 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -745,7 +745,7 @@ WebAssembly.instantiateStreaming(fetch('./a.wasm'), { two: "hi", three: { four: { - five: await WebAssembly.instantiateStreaming(fetch('./b.wasm')) + five: await WebAssembly.compileStreaming(fetch('./b.wasm')) } } }); From 63c8c0283a6da84c1e78b56ac1628d766ece49fe Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 24 Feb 2022 10:56:40 -0600 Subject: [PATCH 04/18] Fix other bug in JS API example --- 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 52720298..81da04dc 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -722,7 +722,7 @@ For example, the following component: (export "four" (instance (export "five" (module (import "six" "a" (func)) - (export "six" "b" (func)) + (import "six" "b" (func)) )) )) )) From 7946c16f7b8343cb86b05f8352accc11bd324ada Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 24 Feb 2022 11:03:18 -0600 Subject: [PATCH 05/18] Extend ESM-integration example a bit --- design/mvp/Explainer.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 81da04dc..7967dceb 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -830,8 +830,10 @@ Otherwise, function or value imports are treated like an [Imported Default Bindi and the Module Record is converted to its default value. This allows the following component: ```wasm +;; bar.wasm (component (import "./foo.js" (func (result string))) + ... ) ``` to be satisfied by a JavaScript module via ESM-integration: @@ -839,6 +841,10 @@ to be satisfied by a JavaScript module via ESM-integration: // foo.js export default () => "hi"; ``` +when `bar.wasm` is loaded as an ESM: +``` + +``` ## Examples From 8bb66dc0b2a240877692548567a1835965511d35 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 24 Feb 2022 12:38:04 -0600 Subject: [PATCH 06/18] Fix 'type' typo in Binary.md, use varu32 consistently --- design/mvp/Binary.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 90447c56..00674354 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -160,8 +160,8 @@ intertype ::= pit: => pit | 0x6a t: => (optional t) | 0x69 t: u: => (expected t u) field ::= n: t: => (field n t) -case ::= n: t: 0x0 => (case n t) - | n: t: 0x1 i: => (case n t (defaults-to case-label[i])) +case ::= n: t: 0x0 => (case n t) + | n: t: 0x1 i: => (case n t (defaults-to case-label[i])) ``` Notes: * Reused Core binary rules: [`core:import`], [`core:importdesc`], [`core:functype`] From 3ee971141d1a063e850888043e56dad11ab4e8c2 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 24 Feb 2022 12:55:22 -0600 Subject: [PATCH 07/18] Put ? into the deftype type constructors --- design/mvp/Explainer.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 7967dceb..a6f4b5ea 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -304,22 +304,22 @@ deftype ::= | | | -moduletype ::= (module *) +moduletype ::= (module ? *) moduletype-def ::= | | (export ) core:deftype ::= | ... Post-MVP additions -componenttype ::= (component (componenttype-def)*) +componenttype ::= (component ? *) componenttype-def ::= | import ::= (import ) -instancetype ::= (instance (instancetype-def)*) +instancetype ::= (instance ? *) instancetype-def ::= | | (export ) -functype ::= (func (param )* (result )) -valuetype ::= (value ) +functype ::= (func ? (param )* (result )) +valuetype ::= (value ? ) intertype ::= unit | bool | s8 | u8 | s16 | u16 | s32 | u32 | s64 | u64 | float32 | float64 @@ -343,6 +343,11 @@ resolving to a type definition (using `(type $T)` in cases where there is a grammatical ambiguity), or (3) an inline type definition that is desugared into a deduplicated out-of-line type definition. +On another technical note: the optional `id` in all the `deftype` type +constructors (e.g., `(module ? ...)`) is only allowed to be present in the +context of `import` since this is the only context in which binding an +identifier makes sense. + Starting with interface types, the set of values allowed for the *fundamental* interface types is given by the following table: | Type | Values | @@ -609,7 +614,7 @@ definitions have actually already been defined above (with the caveat that the real text format for `import` definitions would additionally allow binding an identifier (e.g., adding the `$foo` in `(import "foo" (func $foo))`): ``` -import ::= already defined above as part of , but allow binding an +import ::= already defined above as part of export ::= already defined above as part of ``` From 9620c37f695a36baeb6d108ba2c16bc1a5b007fd Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 24 Feb 2022 14:25:16 -0600 Subject: [PATCH 08/18] Add TODO section --- design/mvp/Explainer.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index a6f4b5ea..3c15745f 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -17,6 +17,7 @@ native JavaScript runtime. * [JS API](#JS-API) * [ESM-integration](#ESM-integration) * [Examples](#examples) +* [TODO](#TODO) (Based on the previous [scoping and layering] proposal to the WebAssembly CG, this repo merges and supersedes the [Module Linking] and [Interface Types] @@ -860,6 +861,17 @@ For some use-case-focused, worked examples, see: * [Component Examples presentation](https://docs.google.com/presentation/d/11lY9GBghZJ5nCFrf4MKWVrecQude0xy_buE--tnO9kQ) +## TODO + +The following features are needed to address the [MVP Use Cases](../high-level/UseCases.md) +and will be added over the coming months to complete the MVP proposal: +* concurrency support ([slides][Future And Stream Types]) +* abstract ("resource") types ([slides][Resource and Handle Types]) +* optional imports, definitions and exports (subsuming + [WASI Optional Imports](https://github.com/WebAssembly/WASI/blob/main/legacy/optional-imports.md) + and maybe [conditional-sections](https://github.com/WebAssembly/conditional-sections/issues/22)) + + [Structure Section]: https://webassembly.github.io/spec/core/syntax/index.html [`core:module`]: https://webassembly.github.io/spec/core/syntax/modules.html#syntax-module From df13b2d062db52ad39d2028beef4d82b7876a39d Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Thu, 24 Feb 2022 15:17:06 -0600 Subject: [PATCH 09/18] Clarify no multi-threading in Component Invariants Co-authored-by: Dave Bakker --- design/mvp/Explainer.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 3c15745f..400cf274 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -674,7 +674,8 @@ runtime invariants: the interim. This establishes a clear contract between separate components that both prevents obscure composition-time bugs and also enables more-efficient non-reentrant runtime glue code (particularly in the middle - of the [Canonical ABI](CanonicalABI.md)). + of the [Canonical ABI](CanonicalABI.md)). This implies that components by + default don't allow concurrency and multi-threaded access will trap. 3. Components enforce the current informal rule that `start` functions are only for "internal" initialization by trapping if a component attempts to call a component import during instantiation. In Core WebAssembly, this From 274af9d7c8c6f6fe2ab40276e8d0f36a1713143c Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Sat, 26 Feb 2022 10:27:47 -0600 Subject: [PATCH 10/18] Add explicit 'core' discriminant to --- design/mvp/Explainer.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 400cf274..39ae2bd5 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -113,9 +113,9 @@ instance ::= (instance ? ) instanceexpr ::= (instantiate (module ) (import )*) | (instantiate (component ) (import )*) | * - | + + | core * modulearg ::= (instance ) - | (instance +) + | (instance *) componentarg ::= (module ) | (component ) | (instance ) @@ -153,12 +153,10 @@ passed as a `componentarg` when instantiating a component, not just instances. Component instantiation will be revisited below after introducing the prerequisite type and import definitions. -Lastly, the `(instance *)` and `(instance +)` +Lastly, the `(instance *)` and `(instance *)` expressions allow component and module instances to be created by directly tupling together preceding definitions, without the need to `instantiate` -anything. To disambiguate the empty case, we observe that there is never -a need to import an empty module instance and thus `(instance)` is an empty -*component* instance. The "inline" forms of these expressions in `modulearg` +anything. The "inline" forms of these expressions in `modulearg` and `componentarg` are text format sugar for the "out of line" form in `instanceexpr`. To show an example of how these instance-creation forms are useful, we'll first need to introduce the `alias` definitions in the next From b20acb3c95e454bb3370fcf740faa620a2286d28 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Sat, 26 Feb 2022 10:56:33 -0600 Subject: [PATCH 11/18] Make parameter names optional --- design/mvp/Binary.md | 3 ++- design/mvp/Explainer.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 00674354..f48a48f5 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -131,7 +131,8 @@ instancetype-def ::= 0x01 t: => t import ::= n: dt: => (import n dt) deftypeuse ::= i: => type-index-space[i] (must be ) functype ::= 0x4c param*:vec() t: => (func param* (result t)) -param ::= n: t: => (param n t) +param ::= 0x00 t: => (param t) + | 0x01 n: t: => (param n t) valuetype ::= 0x4b t: => (value t) intertypeuse ::= i: => type-index-space[i] (must be ) | pit: => pit diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 39ae2bd5..b833a202 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -317,7 +317,7 @@ instancetype ::= (instance ? *) instancetype-def ::= | | (export ) -functype ::= (func ? (param )* (result )) +functype ::= (func ? (param ? )* (result )) valuetype ::= (value ? ) intertype ::= unit | bool | s8 | u8 | s16 | u16 | s32 | u32 | s64 | u64 From a6f9e4640459a5136cbb6ec84f7168b2ee65f3fe Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Sat, 26 Feb 2022 11:09:05 -0600 Subject: [PATCH 12/18] Revert option/optional name change --- design/mvp/Binary.md | 2 +- design/mvp/Explainer.md | 8 ++++---- design/mvp/Subtyping.md | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index f48a48f5..1b75a422 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -158,7 +158,7 @@ intertype ::= pit: => pit | 0x6d n*:vec() => (flags n*) | 0x6c n*:vec() => (enum n*) | 0x6b t*:vec() => (union t*) - | 0x6a t: => (optional t) + | 0x6a t: => (option t) | 0x69 t: u: => (expected t u) field ::= n: t: => (field n t) case ::= n: t: 0x0 => (case n t) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index b833a202..93a079e8 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -330,7 +330,7 @@ intertype ::= unit | bool | (flags *) | (enum *) | (union *) - | (optional ) + | (option ) | (expected ) ``` On a technical note: this type grammar uses `` and `` @@ -368,7 +368,7 @@ defined by the following mapping: (tuple *) ↦ (record ("𝒊" )*) for 𝒊=0,1,... (flags *) ↦ (record (field bool)*) (enum *) ↦ (variant (case unit)*) - (optional ) ↦ (variant (case "none") (case "some" )) + (option ) ↦ (variant (case "none") (case "some" )) (union *) ↦ (variant (case "𝒊" )*) for 𝒊=0,1,... (expected ) ↦ (variant (case "ok" ) (case "error" )) ``` @@ -570,7 +570,7 @@ As with any other definition kind, value definitions may be supplied to components through `import` definitions. Using the grammar of `import` already defined [above](#type-definitions), an example *value import* can be written: ``` -(import "env" (value $env (record (field "locale" (optional string))))) +(import "env" (value $env (record (field "locale" (option string))))) ``` As this example suggests, value imports can serve as generalized [environment variables], allowing not just `string`, but the full range of interface types @@ -778,7 +778,7 @@ At a high level, the additional coercions would be: | `tuple` | TBD: maybe a [JS Tuple]? | TBD | | `flags` | TBD: maybe a [JS Record]? | same as [`dictionary`] of `boolean` fields | | `enum` | same as [`enum`] | same as [`enum`] | -| `optional` | same as [`T?`] | same as [`T?`] | +| `option` | same as [`T?`] | same as [`T?`] | | `union` | same as [`union`] | same as [`union`] | | `expected` | same as `variant`, but coerce a top-level `error` return value to a thrown exception | same as `variant`, but coerce uncaught exceptions to top-level `error` return values | diff --git a/design/mvp/Subtyping.md b/design/mvp/Subtyping.md index 241f8cd6..f697078c 100644 --- a/design/mvp/Subtyping.md +++ b/design/mvp/Subtyping.md @@ -11,14 +11,14 @@ But roughly speaking: | `s8`, `s16`, `s32`, `s64`, `u8`, `u16`, `u32`, `u64` | lossless coercions are allowed | | `float32`, `float64` | `float32 <: float64` | | `char` | | -| `record` | fields can be reordered; covariant field payload subtyping; superfluous fields can be ignored in the subtype; `optional` fields can be ignored in the supertype | +| `record` | fields can be reordered; covariant field payload subtyping; superfluous fields can be ignored in the subtype; `option` fields can be ignored in the supertype | | `variant` | cases can be reordered; contravariant case payload subtyping; superfluous cases can be ignored in the supertype; `defaults-to` cases can be ignored in the subtype | | `list` | covariant element subtyping | | `tuple` | `(tuple T ...) <: T` | -| `optional` | `T <: (optional T)` | +| `option` | `T <: (option T)` | | `expected` | `T <: (expected T _)` | | `union` | `T <: (union ... T ...)` | -| `func` | parameter names must match in order; covariant parameter subtyping; superfluous parameters can be ignored in the subtype; `optional` parameters can be ignored in the supertype; contravariant result subtyping | +| `func` | parameter names must match in order; covariant parameter subtyping; superfluous parameters can be ignored in the subtype; `option` parameters can be ignored in the supertype; contravariant result subtyping | The remaining specialized interface types inherit their subtyping from their fundamental interface types. From 4ebd8afa42ba182d439f19510317e973f19d5ac1 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 28 Feb 2022 09:58:10 -0600 Subject: [PATCH 13/18] Relax type export restriction in binary format to allow any type definition --- design/mvp/Binary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 1b75a422..1438956f 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -60,7 +60,7 @@ componentarg ::= n: 0x00 m: => n (module | n: 0x02 i: => n (instance i) | n: 0x03 f: => n (func f) | n: 0x04 v: => n (value v) - | n: 0x05 t: => n (type t) (t must be an ) + | n: 0x05 t: => n (type t) export ::= a: => (export a) name ::= n: => n ``` From 38d0a3c3d2c8ee3d7f9baac9c908420133fc8ef7 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Tue, 1 Mar 2022 15:41:05 -0600 Subject: [PATCH 14/18] Fix typo and variance direction in subtyping sketch --- design/mvp/Explainer.md | 2 +- design/mvp/Subtyping.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 93a079e8..7af9985b 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -365,7 +365,7 @@ The sets of values allowed for the remaining *specialized* interface types are defined by the following mapping: ``` string ↦ (list char) - (tuple *) ↦ (record ("𝒊" )*) for 𝒊=0,1,... + (tuple *) ↦ (record (field "𝒊" )*) for 𝒊=0,1,... (flags *) ↦ (record (field bool)*) (enum *) ↦ (variant (case unit)*) (option ) ↦ (variant (case "none") (case "some" )) diff --git a/design/mvp/Subtyping.md b/design/mvp/Subtyping.md index f697078c..36c6277b 100644 --- a/design/mvp/Subtyping.md +++ b/design/mvp/Subtyping.md @@ -12,13 +12,13 @@ But roughly speaking: | `float32`, `float64` | `float32 <: float64` | | `char` | | | `record` | fields can be reordered; covariant field payload subtyping; superfluous fields can be ignored in the subtype; `option` fields can be ignored in the supertype | -| `variant` | cases can be reordered; contravariant case payload subtyping; superfluous cases can be ignored in the supertype; `defaults-to` cases can be ignored in the subtype | +| `variant` | cases can be reordered; covariant case payload subtyping; superfluous cases can be ignored in the supertype; `defaults-to` cases can be ignored in the subtype | | `list` | covariant element subtyping | | `tuple` | `(tuple T ...) <: T` | | `option` | `T <: (option T)` | | `expected` | `T <: (expected T _)` | | `union` | `T <: (union ... T ...)` | -| `func` | parameter names must match in order; covariant parameter subtyping; superfluous parameters can be ignored in the subtype; `option` parameters can be ignored in the supertype; contravariant result subtyping | +| `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 | The remaining specialized interface types inherit their subtyping from their fundamental interface types. From 0ccd2680df5972ca9964e11c5bd610a82e793147 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 7 Mar 2022 09:52:15 -0600 Subject: [PATCH 15/18] Update URL in CanonicalABI.md Co-authored-by: Liam Murphy --- design/mvp/CanonicalABI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md index 1e4ab384..e0ead6b9 100644 --- a/design/mvp/CanonicalABI.md +++ b/design/mvp/CanonicalABI.md @@ -1,3 +1,3 @@ # Canonical ABI (sketch) -TODO: import and update [interface-types/#132](https://github.com/WebAssembly/interface-types/pull/132) +TODO: import and update [interface-types/#140](https://github.com/WebAssembly/interface-types/pull/140) From c339dc58f739f6f791fd3e925e72a7c72bef473c Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 7 Mar 2022 12:53:23 -0800 Subject: [PATCH 16/18] Change "import" to "arg" in the instantiate argument syntax. Fixes #2. --- design/mvp/Binary.md | 4 +-- design/mvp/Explainer.md | 31 ++++++++-------- design/mvp/examples/LinkTimeVirtualization.md | 8 ++--- .../SharedEverythingDynamicLinking.md | 36 +++++++++---------- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 1438956f..b6bc29cf 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -50,8 +50,8 @@ Notes: (See [Instance Definitions](Explainer.md#instance-definitions) in the explainer.) ``` instance ::= ie: => (instance ie) -instanceexpr ::= 0x00 0x00 m: a*:vec() => (instantiate (module m) (import a)*) - | 0x00 0x01 c: a*:vec() => (instantiate (component c) (import a)*) +instanceexpr ::= 0x00 0x00 m: a*:vec() => (instantiate (module m) (arg a)*) + | 0x00 0x01 c: a*:vec() => (instantiate (component c) (arg a)*) | 0x01 e*:vec() => e* | 0x02 e*:vec() => e* modulearg ::= n: 0x02 i: => n (instance i) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 7af9985b..fa275b12 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -110,8 +110,8 @@ supplying a set of named *arguments* which satisfy all the named *imports* of the selected module/component: ``` instance ::= (instance ? ) -instanceexpr ::= (instantiate (module ) (import )*) - | (instantiate (component ) (import )*) +instanceexpr ::= (instantiate (module ) (arg )*) + | (instantiate (component ) (arg )*) | * | core * modulearg ::= (instance ) @@ -125,8 +125,9 @@ componentarg ::= (module ) | (instance *) export ::= (export ) ``` -When instantiating a module via `(instantiate (module $M) *)`, the -two-level imports of the module `$M` are resolved as follows: +When instantiating a module via +`(instantiate (module $M) (arg )*)`, the two-level imports of +the module `$M` are resolved as follows: 1. The first `name` of an import is looked up in the named list of `modulearg` to select a module instance. 2. The second `name` of an import is looked up in the named list of exports of @@ -144,7 +145,7 @@ following component: (func (import "a" "one") (result i32)) ) (instance $a (instantiate (module $A))) - (instance $b (instantiate (module $B) (import "a" (instance $a)))) + (instance $b (instantiate (module $B) (arg "a" (instance $a)))) ) ``` Components, as we'll see below, have single-level imports, i.e., each import @@ -216,13 +217,13 @@ For `export` aliases, the inline sugar has the form `(kind + and can be used anywhere a `kind` index appears in the AST. For example, the following snippet uses an inline function alias: ```wasm -(instance $j (instantiate (component $J) (import "f" (func $i "f")))) +(instance $j (instantiate (component $J) (arg "f" (func $i "f")))) (export "x" (func $j "g" "h")) ``` which is desugared into: ```wasm (alias export $i "f" (func $f_alias)) -(instance $j (instantiate (component $J) (import "f" (func $f_alias)))) +(instance $j (instantiate (component $J) (arg "f" (func $f_alias)))) (alias export $j "g" (instance $g_alias)) (alias export $g_alias "h" (func $h_alias)) (export "x" (func $h_alias)) @@ -263,16 +264,16 @@ With what's defined so far, we're able to link modules with arbitrary renamings: ) (instance $a (instantiate (module $A))) (instance $b1 (instantiate (module $B) - (import "a" (instance $a)) ;; no renaming + (arg "a" (instance $a)) ;; no renaming )) (alias export $a "two" (func $a_two)) (instance $b2 (instantiate (module $B) - (import "a" (instance + (arg "a" (instance (export "one" (func $a_two)) ;; renaming, using explicit alias )) )) (instance $b3 (instantiate (module $B) - (import "a" (instance + (arg "a" (instance (export "one" (func $a "three")) ;; renaming, using inline alias sugar )) )) @@ -521,8 +522,8 @@ does some logging, then returns a string. ) ) (instance $main (instantiate (module $Main) - (import "libc" (instance $libc)) - (import "wasi:logging" (instance (export "log" (func $log)))) + (arg "libc" (instance $libc)) + (arg "wasi:logging" (instance (export "log" (func $log)))) )) (func (export "run") (canon.lift (func (param string) (result string)) (into $libc) (func $main "run")) @@ -593,7 +594,7 @@ exported string, all at instantiation time: ... general-purpose compute ) ) - (instance $main (instantiate (module $Main) (import "libc" (instance $libc)))) + (instance $main (instantiate (module $Main) (arg "libc" (instance $libc)))) (func $start (canon.lift (func (param string) (result string)) (into $libc) (func $main "start")) ) @@ -631,10 +632,10 @@ exports other components: (export "g" (func (result string))) )) (instance $d1 (instantiate (component $D) - (import "c" (instance $c)) + (arg "c" (instance $c)) )) (instance $d2 (instantiate (component $D) - (import "c" (instance + (arg "c" (instance (export "f" (func $d1 "g")) )) )) diff --git a/design/mvp/examples/LinkTimeVirtualization.md b/design/mvp/examples/LinkTimeVirtualization.md index 584e6c13..f03ca400 100644 --- a/design/mvp/examples/LinkTimeVirtualization.md +++ b/design/mvp/examples/LinkTimeVirtualization.md @@ -52,10 +52,10 @@ We now write the parent component by composing `child.wasm` with (import "./virtualize.wasm" (component $Virtualize ...)) (import "./child.wasm" (component $Child ...)) (instance $virtual-fs (instantiate (component $Virtualize) - (import "wasi:filesystem" (instance $real-fs)) + (arg "wasi:filesystem" (instance $real-fs)) )) (instance $child (instantiate (component $Child) - (import "wasi:filesystem" (instance $virtual-fs)) + (arg "wasi:filesystem" (instance $virtual-fs)) )) ) ``` @@ -69,10 +69,10 @@ definitions in place of imports: (component $Virtualize ... copied inline ...) (component $Child ... copied inline ...) (instance $virtual-fs (instantiate (component $Virtualize) - (import "wasi:filesystem" (instance $real-fs)) + (arg "wasi:filesystem" (instance $real-fs)) )) (instance $child (instantiate (component $Child) - (import "wasi:filesystem" (instance $virtual-fs)) + (arg "wasi:filesystem" (instance $virtual-fs)) )) ) ``` diff --git a/design/mvp/examples/SharedEverythingDynamicLinking.md b/design/mvp/examples/SharedEverythingDynamicLinking.md index b03d1695..1ae3dd16 100644 --- a/design/mvp/examples/SharedEverythingDynamicLinking.md +++ b/design/mvp/examples/SharedEverythingDynamicLinking.md @@ -151,11 +151,11 @@ would look like: (instance $libc (instantiate (module $Libc))) (instance $libzip (instantiate (module $Libzip)) - (import "libc" (instance $libc)) + (arg "libc" (instance $libc)) )) (instance $main (instantiate (module $Main) - (import "libc" (instance $libc)) - (import "libzip" (instance $libzip)) + (arg "libc" (instance $libc)) + (arg "libzip" (instance $libzip)) )) (func (export "zip") (canon.lift (func (param (list u8)) (result (list u8))) (into $libc) (func $main "zip")) @@ -224,15 +224,15 @@ component-aware `clang`, the resulting component would look like: (instance $libc (instantiate (module $Libc))) (instance $libzip (instantiate (module $Libzip) - (import "libc" (instance $libc)) + (arg "libc" (instance $libc)) )) (instance $libimg (instantiate (module $Libimg) - (import "libc" (instance $libc)) - (import "libzip" (instance $libzip)) + (arg "libc" (instance $libc)) + (arg "libzip" (instance $libzip)) )) (instance $main (instantiate (module $Main) - (import "libc" (instance $libc)) - (import "libimg" (instance $libimg)) + (arg "libc" (instance $libc)) + (arg "libimg" (instance $libimg)) )) (func (export "transform") (canon.lift (func (param (list u8)) (result (list u8))) (into $libc) (func $main "transform")) @@ -269,13 +269,13 @@ components. The resulting component could look like: ) (instance $zipper (instantiate (component $Zipper) - (import "libc" (module $Libc)) - (import "libzip" (module $Libzip)) + (arg "libc" (module $Libc)) + (arg "libzip" (module $Libzip)) )) (instance $imgmgk (instantiate (component $Imgmgk) - (import "libc" (module $Libc)) - (import "libzip" (module $Libzip)) - (import "libimg" (module $Libimg)) + (arg "libc" (module $Libc)) + (arg "libzip" (module $Libzip)) + (arg "libimg" (module $Libimg)) )) (instance $libc (instantiate (module $Libc))) @@ -286,9 +286,9 @@ components. The resulting component could look like: (canon.lower (into $libc) (func $imgmgk "transform")) ) (instance $main (instantiate (module $Main) - (import "libc" (instance $libc)) - (import "zipper" (instance (export "zip" (func $zipper "zip")))) - (import "imgmgk" (instance (export "transform" (func $imgmgk "transform")))) + (arg "libc" (instance $libc)) + (arg "zipper" (instance (export "zip" (func $zipper "zip")))) + (arg "imgmgk" (instance (export "transform" (func $imgmgk "transform")))) )) (func (export "run") (canon.lift (func (param string) (result string)) (func $main "run")) @@ -358,11 +358,11 @@ function table and `bar-index` mutable global. ) (instance $linkage (instantiate (module $Linkage))) (instance $a (instantiate (module $A) - (import "linkage" (instance $linkage)) + (arg "linkage" (instance $linkage)) )) (instance $b (instantiate (module $B) (import "a" (instance $a)) - (import "linkage" (instance $linkage)) + (arg "linkage" (instance $linkage)) )) ) ``` From 3b104680a9fe544e9710a2bcef9755fa458d77ba Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 9 Mar 2022 07:02:10 -0800 Subject: [PATCH 17/18] Change `arg` to `with`. --- design/mvp/Binary.md | 4 +-- design/mvp/Explainer.md | 28 +++++++-------- design/mvp/examples/LinkTimeVirtualization.md | 8 ++--- .../SharedEverythingDynamicLinking.md | 36 +++++++++---------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index b6bc29cf..6e9bae44 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -50,8 +50,8 @@ Notes: (See [Instance Definitions](Explainer.md#instance-definitions) in the explainer.) ``` instance ::= ie: => (instance ie) -instanceexpr ::= 0x00 0x00 m: a*:vec() => (instantiate (module m) (arg a)*) - | 0x00 0x01 c: a*:vec() => (instantiate (component c) (arg a)*) +instanceexpr ::= 0x00 0x00 m: a*:vec() => (instantiate (module m) (with a)*) + | 0x00 0x01 c: a*:vec() => (instantiate (component c) (with a)*) | 0x01 e*:vec() => e* | 0x02 e*:vec() => e* modulearg ::= n: 0x02 i: => n (instance i) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index fa275b12..296704f7 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -110,8 +110,8 @@ supplying a set of named *arguments* which satisfy all the named *imports* of the selected module/component: ``` instance ::= (instance ? ) -instanceexpr ::= (instantiate (module ) (arg )*) - | (instantiate (component ) (arg )*) +instanceexpr ::= (instantiate (module ) (with )*) + | (instantiate (component ) (with )*) | * | core * modulearg ::= (instance ) @@ -126,7 +126,7 @@ componentarg ::= (module ) export ::= (export ) ``` When instantiating a module via -`(instantiate (module $M) (arg )*)`, the two-level imports of +`(instantiate (module $M) (with )*)`, the two-level imports of the module `$M` are resolved as follows: 1. The first `name` of an import is looked up in the named list of `modulearg` to select a module instance. @@ -145,7 +145,7 @@ following component: (func (import "a" "one") (result i32)) ) (instance $a (instantiate (module $A))) - (instance $b (instantiate (module $B) (arg "a" (instance $a)))) + (instance $b (instantiate (module $B) (with "a" (instance $a)))) ) ``` Components, as we'll see below, have single-level imports, i.e., each import @@ -217,13 +217,13 @@ For `export` aliases, the inline sugar has the form `(kind + and can be used anywhere a `kind` index appears in the AST. For example, the following snippet uses an inline function alias: ```wasm -(instance $j (instantiate (component $J) (arg "f" (func $i "f")))) +(instance $j (instantiate (component $J) (with "f" (func $i "f")))) (export "x" (func $j "g" "h")) ``` which is desugared into: ```wasm (alias export $i "f" (func $f_alias)) -(instance $j (instantiate (component $J) (arg "f" (func $f_alias)))) +(instance $j (instantiate (component $J) (with "f" (func $f_alias)))) (alias export $j "g" (instance $g_alias)) (alias export $g_alias "h" (func $h_alias)) (export "x" (func $h_alias)) @@ -264,16 +264,16 @@ With what's defined so far, we're able to link modules with arbitrary renamings: ) (instance $a (instantiate (module $A))) (instance $b1 (instantiate (module $B) - (arg "a" (instance $a)) ;; no renaming + (with "a" (instance $a)) ;; no renaming )) (alias export $a "two" (func $a_two)) (instance $b2 (instantiate (module $B) - (arg "a" (instance + (with "a" (instance (export "one" (func $a_two)) ;; renaming, using explicit alias )) )) (instance $b3 (instantiate (module $B) - (arg "a" (instance + (with "a" (instance (export "one" (func $a "three")) ;; renaming, using inline alias sugar )) )) @@ -522,8 +522,8 @@ does some logging, then returns a string. ) ) (instance $main (instantiate (module $Main) - (arg "libc" (instance $libc)) - (arg "wasi:logging" (instance (export "log" (func $log)))) + (with "libc" (instance $libc)) + (with "wasi:logging" (instance (export "log" (func $log)))) )) (func (export "run") (canon.lift (func (param string) (result string)) (into $libc) (func $main "run")) @@ -594,7 +594,7 @@ exported string, all at instantiation time: ... general-purpose compute ) ) - (instance $main (instantiate (module $Main) (arg "libc" (instance $libc)))) + (instance $main (instantiate (module $Main) (with "libc" (instance $libc)))) (func $start (canon.lift (func (param string) (result string)) (into $libc) (func $main "start")) ) @@ -632,10 +632,10 @@ exports other components: (export "g" (func (result string))) )) (instance $d1 (instantiate (component $D) - (arg "c" (instance $c)) + (with "c" (instance $c)) )) (instance $d2 (instantiate (component $D) - (arg "c" (instance + (with "c" (instance (export "f" (func $d1 "g")) )) )) diff --git a/design/mvp/examples/LinkTimeVirtualization.md b/design/mvp/examples/LinkTimeVirtualization.md index f03ca400..2af28970 100644 --- a/design/mvp/examples/LinkTimeVirtualization.md +++ b/design/mvp/examples/LinkTimeVirtualization.md @@ -52,10 +52,10 @@ We now write the parent component by composing `child.wasm` with (import "./virtualize.wasm" (component $Virtualize ...)) (import "./child.wasm" (component $Child ...)) (instance $virtual-fs (instantiate (component $Virtualize) - (arg "wasi:filesystem" (instance $real-fs)) + (with "wasi:filesystem" (instance $real-fs)) )) (instance $child (instantiate (component $Child) - (arg "wasi:filesystem" (instance $virtual-fs)) + (with "wasi:filesystem" (instance $virtual-fs)) )) ) ``` @@ -69,10 +69,10 @@ definitions in place of imports: (component $Virtualize ... copied inline ...) (component $Child ... copied inline ...) (instance $virtual-fs (instantiate (component $Virtualize) - (arg "wasi:filesystem" (instance $real-fs)) + (with "wasi:filesystem" (instance $real-fs)) )) (instance $child (instantiate (component $Child) - (arg "wasi:filesystem" (instance $virtual-fs)) + (with "wasi:filesystem" (instance $virtual-fs)) )) ) ``` diff --git a/design/mvp/examples/SharedEverythingDynamicLinking.md b/design/mvp/examples/SharedEverythingDynamicLinking.md index 1ae3dd16..b5b5370d 100644 --- a/design/mvp/examples/SharedEverythingDynamicLinking.md +++ b/design/mvp/examples/SharedEverythingDynamicLinking.md @@ -151,11 +151,11 @@ would look like: (instance $libc (instantiate (module $Libc))) (instance $libzip (instantiate (module $Libzip)) - (arg "libc" (instance $libc)) + (with "libc" (instance $libc)) )) (instance $main (instantiate (module $Main) - (arg "libc" (instance $libc)) - (arg "libzip" (instance $libzip)) + (with "libc" (instance $libc)) + (with "libzip" (instance $libzip)) )) (func (export "zip") (canon.lift (func (param (list u8)) (result (list u8))) (into $libc) (func $main "zip")) @@ -224,15 +224,15 @@ component-aware `clang`, the resulting component would look like: (instance $libc (instantiate (module $Libc))) (instance $libzip (instantiate (module $Libzip) - (arg "libc" (instance $libc)) + (with "libc" (instance $libc)) )) (instance $libimg (instantiate (module $Libimg) - (arg "libc" (instance $libc)) - (arg "libzip" (instance $libzip)) + (with "libc" (instance $libc)) + (with "libzip" (instance $libzip)) )) (instance $main (instantiate (module $Main) - (arg "libc" (instance $libc)) - (arg "libimg" (instance $libimg)) + (with "libc" (instance $libc)) + (with "libimg" (instance $libimg)) )) (func (export "transform") (canon.lift (func (param (list u8)) (result (list u8))) (into $libc) (func $main "transform")) @@ -269,13 +269,13 @@ components. The resulting component could look like: ) (instance $zipper (instantiate (component $Zipper) - (arg "libc" (module $Libc)) - (arg "libzip" (module $Libzip)) + (with "libc" (module $Libc)) + (with "libzip" (module $Libzip)) )) (instance $imgmgk (instantiate (component $Imgmgk) - (arg "libc" (module $Libc)) - (arg "libzip" (module $Libzip)) - (arg "libimg" (module $Libimg)) + (with "libc" (module $Libc)) + (with "libzip" (module $Libzip)) + (with "libimg" (module $Libimg)) )) (instance $libc (instantiate (module $Libc))) @@ -286,9 +286,9 @@ components. The resulting component could look like: (canon.lower (into $libc) (func $imgmgk "transform")) ) (instance $main (instantiate (module $Main) - (arg "libc" (instance $libc)) - (arg "zipper" (instance (export "zip" (func $zipper "zip")))) - (arg "imgmgk" (instance (export "transform" (func $imgmgk "transform")))) + (with "libc" (instance $libc)) + (with "zipper" (instance (export "zip" (func $zipper "zip")))) + (with "imgmgk" (instance (export "transform" (func $imgmgk "transform")))) )) (func (export "run") (canon.lift (func (param string) (result string)) (func $main "run")) @@ -358,11 +358,11 @@ function table and `bar-index` mutable global. ) (instance $linkage (instantiate (module $Linkage))) (instance $a (instantiate (module $A) - (arg "linkage" (instance $linkage)) + (with "linkage" (instance $linkage)) )) (instance $b (instantiate (module $B) (import "a" (instance $a)) - (arg "linkage" (instance $linkage)) + (with "linkage" (instance $linkage)) )) ) ``` From 84bdfe6bcc3affd15952bff9e77c4c607b275425 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Wed, 9 Mar 2022 18:55:33 -0600 Subject: [PATCH 18/18] Make (result) optional as syntactic sugar for (result unit) --- design/mvp/Explainer.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index 296704f7..55a7a33a 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -380,11 +380,16 @@ fifth type will be added for [resource types][Resource and Handle Types].) A `functype` describes a component function whose parameters and results are `intertype` values. Thus `functype` is completely disjoint from [`core:functype`] in the WebAssembly Core spec, whose parameters and results -are [`core:valtype`] values. Morever, since `core:functype` can only appear -syntactically within the `(module ...)` S-expression of a `moduletype`, there -is never a need to syntactically distinguish `functype` from `core:functype` -in the text format: the context dictates which one a `(func ...)` S-expression -parses into. +are [`core:valtype`] values. As a low-level compiler target, `core:functype` +returns zero or more results. In contrast, as a high-level interface type +designed to be maximally bound to a variety of source languages, `functype` +always returns a single type, with `unit` being used for functions that don't +return an interesting value (analogous to "void" in some languages). As +syntactic sugar, the text format of `functype` additionally allows `result` to +be absent, interpreting this as `(result unit)`. Since `core:functype` can only +appear syntactically within a `(module ...)` S-expression, there is never a +need to syntactically distinguish `functype` from `core:functype` in the text +format: the context dictates which one a `(func ...)` S-expression parses into. A `valuetype` describes a single `intertype` value this is to be consumed exactly once during component instantiation. How this happens is described