For any given WIT world, the Component Model defines multiple Core
WebAssembly build targets that all logically represent the same world but
provide Core WebAssembly producer toolchains different low-level representation
choices.
- Motivation
- Build Targets
- Target World
- Imports and Exports
- Support Definitions
- Example
- Relation to Compiler Flags
While the Component Model's binary format provides more linking functionality and optimization choices than the Core WebAssembly build targets listed below, there are several reasons for having the Component Model additionally define build targets that use the existing standard Core WebAssembly Binary Format:
- It allows interface authors (e.g., in WASI) to write their interface just once (in WIT) to target both core and component runtimes.
- It allows existing Core WebAssembly producer toolchains to more easily and incrementally target the Component Model.
- It allows existing Core WebAssembly runtimes to implement WIT-defined interfaces without having to implement the full Component Model, providing an incremental implementation step towards the full Component Model.
Furthermore, any module matching a Core WebAssembly build target can be
trivially wrapped (e.g., by wasm-tools component new) to become a
semantically-equivalent component and thus these modules can be considered
simple components.
Currently, there is only one build target defined:
wasm32: uses one 32-bit linear memory
Other build targets can be added as needed in the future. In particular, the following additional build targets are anticipated:
wasm64: uses one 64-bit linear memory (based on memory64).wasmgc: uses managed memory (based on wasm-gc).
When the async and shared-everything-threads proposals stabilize, they could either be added backwards-compatibly to existing build targets or added as separate build targets.
The rest of this document assumes a single, fixed "target world". This target world determines a fixed list of import and export names, each with associated component-level types. For example, if a runtime wants to support "all of WASI Preview 2", the target world (currently) would be:
world all-of-WASI {
include wasi:http/proxy@0.2.0;
include wasi:cli/command@0.2.0;
}However, a runtime can also choose to support just wasi:http/proxy or
wasi:cli/command or a subset of one of these worlds: this is a choice every
producer and consumer gets to make independently. WASI and the Component Model
only establish a fixed set of names that MAY appear in imports and exports
and, when present, what the agreed-upon semantics MUST be.
Additionally, we assume that the target world has been fully resolved into a
pure componenttype in the same way as when creating a WIT package which
means that includes have been inlined and used types have been replaced by
direct aliases to the resolved type.
Every build target defines the following set of imports:
and the union of the following sets of exports:
Every Core WebAssembly import and export name defined by the build target
starts with a prefix string that incorporates the build target:
- For
wasm32thisprefixstring iscm32p2.
The leading cm of the prefix indicates that this import or export is using
the Component Model's Canonical ABI. The trailing p2 of the prefix
indicates the version of the Canonical ABI assumed by the compiled module,
allowing the Canonical ABI to change as part of future Component Model Preview
or RC releases without ambiguity. Thus, p2 is distinct from the version of
any particular WASI or other WIT interface and is instead more analogous to the
version field of the Component Model binary format.
A Core WebAssembly module MAY include imports and exports other than those
defined by the build target for the target world. However, if an import or
export starts with the prefix, it MUST be included in the target world's
set of imports and exports defined by the build target and rejected by the
runtime otherwise.
The runtime behavior of the WIT-derived imports and exports is entirely defined by the Canonical ABI, as explained in detail below. The only additional global runtime behavioral rule that does not strictly follow from the Canonical ABI is:
- Modules MUST NOT call any imports during the Core WebAssembly
startfunction that needs memory (as defined by whetherflatten_functypesetsneeds.memory). Hosts MUST trap eagerly (at the start of the import call) in this case.
This rule allows modules to be run in a wide variety of runtimes (such as
browsers) which do not expose memory until after the start function returns.
This matches the general Core WebAssembly toolchain convention that start
functions should only be used for module-internal initialization.
General-purpose initialization code that may call imports should instead run
during initialization.
Other runtime behaviors that are implied by the Canonical ABI but are worth stating explicitly here are:
- The host may not reenter guest wasm code on the same callstack (i.e., once a wasm module calls a host import, the host may not recursively call back into the calling wasm module instance).
- Once a module instance traps, the host MUST NOT execute any code with that module instance in the future.
- Any WIT-derived export MAY be called repeatedly and in any order by the host. Producer toolchains MUST handle this case without trapping or triggering Undefined Behavior (but MAY eagerly return an error value according to the declared return type).
The following subsections specify the sets of imports and exports enumerated above:
The Core WebAssembly function imports derived from the target world are
defined as follows (with prefix as defined above and
the ABI Options and current component instance and task as defined below):
- For each import of a WIT interface
iwith canonicalized interface namecin:- For each function in
iwith namefnand typeft, the build target includes:(import "<prefix>|<cin>" "<fn>" <flat-type>), where:flat-typeisflatten_functype(ft, 'lower'),- the runtime behavior is defined by
canon_lower.
- For each original resource type in
iwith namern, the build target includes:(import "<prefix>|<cin>" "<rn>_drop" (func (param i32))), where:- the runtime behavior is defined by
canon_resource_drop.
- the runtime behavior is defined by
- For each function in
- For each import of a function with name
fnand typeft, the build target includes:(import "<prefix>" "<fn>" <flat-type>), where:flat-typeand the runtime behavior are defined the same as in the interface case above.
- For each export of a WIT interface
iwith canonicalized interface namecin:- For each original resource type in
iwith namern, the build target includes:(import "<prefix>|_ex_<cin>" "<rn>_drop" (func (param i32))),(import "<prefix>|_ex_<cin>" "<rn>_new" (func (param i32) (result i32))), and(import "<prefix>|_ex_<cin>" "<rn>_rep" (func (param i32) (result i32))), where:- the runtime behavior is defined by
canon_resource_drop,canon_resource_newandcanon_resource_rep, resp.
- the runtime behavior is defined by
- For each original resource type in
Above, the word "original" in "original resource type" means a resource type
that isn't defined to be equal to another resource type (using type foo = bar
in WIT).
The Core WebAssembly function exports derived from the target world are
defined as follows (with prefix as defined above and
the ABI Options and current component instance and task as defined below):
- For each export of a WIT interface
iwith canonicalized interface namecin:- For each function in
iwith namefnand typeft, the build target includes:(export "<prefix>|<cin>|<fn>" <flat-type>)and(export "<prefix>|<cin>|<fn>_post" (func (params <flat-params>))), where:flat-typeisflatten_functype(ft, 'lift'),flat-paramsisflat-type.results, and- the runtime behavior is defined by
canon_liftwithNoneas thecaller, a no-opon_blockcallback, anon_startcallback that provides the host's arguments to the callee and anon_returncallback that returns the callee's results back to the host.
- For each original resource type in
iwith namern, the build target includes:(export "<prefix>|<cin>|<rn>_dtor" (func (param i32))), which is:- called by the host when an owned handle returned by a previous export call is dropped by the host,
- passed the same
i32value that was passed by the guest to<rn>_newfor the resource that is now being destroyed.
- For each function in
- For each export of a function with name
fnand typeft, the build target includes:(export "<prefix>||<fn>" <flat-type>)and(export "<prefix>||<fn>_post" (func (params <flat-params>))), where:flat-type,flat-paramsand the runtime behavior are defined the same as in the interface case above.
These exports are not required to exist in a module (if they aren't present,
they just won't be called), but if an export is present with the given name,
it MUST have the given type. If there is a <fn>_post function export,
though, there MUST also be a corresponding exported <fn>. This <fn>_post
function MUST only be called by the host immediately following a call to
<fn> as defined by canon_lift.
If flatten_functype sets needs.memory for any WIT-derived function import
or export used by the module, the following export MUST be present:
(export "<prefix>_memory" (memory 0))
This exported linear memory is used as the memory field of canonopt in
the Canonical ABI.
If flatten_functype sets needs.realloc for any WIT-derived function
import or export used by the module, the following export MUST be present:
(export "<prefix>_realloc" (func (param i32 i32 i32 i32) (result i32)))
This exported allocation function is used as the realloc field of
canonopt in the Canonical ABI.
Every build target includes the following optional Core WebAssembly function export:
(export "<prefix>_initialize" (func))
A producer toolchain can rely on this initialization function being called some time before any other export call.
The following supporting definitions are referenced above as part of defining the WIT-derived imports and exports:
Given an interfacename in, the canonicalization of in is given by:
- If
inhas no trailing@<version>, the canonicalization is justin. - Otherwise, the canonicalization is
<base>@<canonicalized-version>where:inis split into<base>@<version>,versionis split into<major>.<minor>.<patch>followed by the optional-<prerelease>and+<build>fields, as defined by SemVer 2.0, andcanonicalized-versionis:- if the optional
prereleasefield is present:<major>.<minor>.<patch>-<prerelease>
- otherwise, if
majorandminorare0:0.0.<patch>
- otherwise, if
majoris0:0.<minor>
- otherwise:
<major>.
- if the optional
For example:
| Interface name | Canonicalized interface name |
|---|---|
a:b/c |
a:b/c |
a:b/c@1.2.3+alpha |
a:b/c@1 |
a:b/c@0.1.2+alpha |
a:b/c@0.1 |
a:b/c@0.0.1+alpha |
a:b/c@0.0.1 |
a:b/c@1.2.3-nightly+alpha |
a:b/c@1.2.3-nightly |
The reason for this canonicalization is to avoid requiring every runtime to implement semver-aware matching wherein all imports of names in the left column match if they are the same in the right column. Instead, producer toolchains perform canonicalization at build time so that Core WebAssembly runtimes can continue to use a simple table of imports matched by string equality.
The Canonical ABI is parameterized by a small set of ABI options
(canonopt) which are set as follows:
The memory and realloc options are set by the Memory Exports defined
above.
The post-return option for a WIT function named fn is set to be the
exported <fn>_post function defined above
if present (otherwise None).
Additionally:
- The
string-encodingis fixed toutf8. - The (unstable Preview 3)
asyncfield is unset.
When gc and memory64 fields are added to canonopt, they would be
mentioned here and configured by the build target.
The Canonical ABI maintains per-component-instance spec-level state that affects the lifting and lowering of parameters and results in imports and exports. As described above, Core WebAssembly build targets treat each Core WebAssembly module as a simple component, and thus there is always a current component instance created for each Core WebAssembly module instance that is passed as an argument to each Canonical ABI import.
Canonical ABI imports also take a current task which contains per-call
spec-level state used to dynamically enforce the rules for reentrance,
borrow, and, in a Preview 3 timeframe, async calls). Until Preview 3 async is
enabled for a build target, there is only ever at most one task per instance
(corresponding to an active synchronous host-to-wasm call, noting that |host →
wasm → host → wasm| reentrance is disallowed) and thus the current component
instance mentioned above also implies the current task.
Given the following world:
package ns:pkg@0.2.1;
interface i {
resource r {
constructor(s: string);
m: func() -> string;
}
frob: func(in: r) -> r;
}
world w {
import f: func() -> string;
import i;
import j: interface {
resource r {
constructor(s: string);
m: func() -> string;
}
frob: func(in: r) -> r;
}
export g: func() -> string;
export i;
export j: interface {
resource r {
constructor(s: string);
m: func() -> string;
}
frob: func(in: r) -> r;
}
}the wasm32 build target includes the following imports and exports (noting
that any particular module can import and export a subset of these, subject to
the requirements mentioned above):
(module
(import "cm32p2" "f" (func (param i32)))
(import "cm32p2|ns:pkg/i@0.2" "[constructor]r" (func (param i32 i32) (result i32)))
(import "cm32p2|ns:pkg/i@0.2" "[method]r.m" (func (param i32 i32)))
(import "cm32p2|ns:pkg/i@0.2" "frob" (func (result i32) (result i32)))
(import "cm32p2|ns:pkg/i@0.2" "r_drop" (func (param i32)))
(import "cm32p2|j" "[constructor]r" (func (param i32 i32) (result i32)))
(import "cm32p2|j" "[method]r.m" (func (param i32 i32)))
(import "cm32p2|j" "frob" (func (result i32) (result i32)))
(import "cm32p2|j" "r_drop" (func (param i32)))
(import "cm32p2|_ex_ns:pkg/i@0.2" "r_drop" (func (param i32)))
(import "cm32p2|_ex_ns:pkg/i@0.2" "r_new" (func (param i32) (result i32)))
(import "cm32p2|_ex_ns:pkg/i@0.2" "r_rep" (func (param i32) (result i32)))
(import "cm32p2|_ex_j" "r_drop" (func (param i32)))
(import "cm32p2|_ex_j" "r_new" (func (param i32) (result i32)))
(import "cm32p2|_ex_j" "r_rep" (func (param i32) (result i32)))
(export "cm32p2||g" (func (result i32)))
(export "cm32p2||g_post" (func (param i32)))
(export "cm32p2|ns:pkg/i@0.2|[constructor]r" (func (param i32 i32) (result i32)))
(export "cm32p2|ns:pkg/i@0.2|[method]r.m" (func (param i32) (result i32)))
(export "cm32p2|ns:pkg/i@0.2|frob" (func (result i32) (result i32)))
(export "cm32p2|ns:pkg/i@0.2|r_dtor" (func (param i32)))
(export "cm32p2|j|[constructor]r" (func (param i32 i32) (result i32)))
(export "cm32p2|j|[method]r.m" (func (param i32) (result i32)))
(export "cm32p2|j|frob" (func (param i32) (result i32)))
(export "cm32p2|j|r_dtor" (func (param i32) (result i32)))
(export "cm32p2_memory" (memory 0))
(export "cm32p2_realloc" (func (param i32 i32 i32 i32) (result i32)))
(export "cm32p2_initialize" (func))
)As defined by the Component Model, but worth calling out explicitly: this world
contains 4 distinct resource types with 4 distinct resource tables that have
overlapping index spaces, even though all 4 resources are syntactically named
r in the WIT:
- The
rin the importedns:pkg/iinterface, used by the importedns:pkg/i@0.2functions. - The
rin the imported anonymousjinterface, used by the importedjfunctions. - The
rin the exportedns:pkg/iinterface, used by the exportedns:pkg/i@0.2functions and the imported_ex_ns:pkg/i@0.2functions. - The
rin the exported anonymousjinterface, used by the exportedjfunctions and the imported_ex_jfunctions.
Each resource type has a unique *_drop function import, so the number of
*_drop functions is the number of resource tables.
One option for producer toolchains is to always emit a Core WebAssembly build target and then optionally wrap the result into a component binary as a final build step (which aligns well with the overall linking toolchain pipeline). In this case, the toolchain unconditionally takes the Core WebAssembly build target as an argument and then takes an independent flag specifying whether to emit a Core WebAssembly module or a component.
For example:
- For
wasi-libc-based toolchains likewasi-sdkorrustc:- The
--targetiswasm32-wasip2, combining thewasm32build target defined in this document with the additional information that the language runtime can import WASI Preview 2-defined interfaces. - The default output is a component, but
-Wl,--emit-modulecan be passed to instruct the linker to only emit a module.
- The
- For Go:
- The
GOARCHiswasm32and theGOOSiswasip2 - The
-buildmodewould select whether to emit a module or component
- The