Installation can be confirmed with:
$ wasm-tools --version
Subcommands can be explored with:
$ wasm-tools help
Basic validation/printing/parsing:
# Validate a WebAssembly file
$ wasm-tools validate foo.wasm
# Validate a WebAssembly module in the text format, automatically converting to
# binary.
$ wasm-tools validate foo.wat
# Validate a WebAssembly file enabling an off-by-default feature
$ wasm-tools validate foo.wasm --features=exception-handling
# Validate a WebAssembly file with a default-enabled feature disabled
$ wasm-tools validate foo.wasm --features=-simd
# Print the text format of a module to stdout
$ wasm-tools print foo.wasm
# Convert a binary module to text
$ wasm-tools print foo.wasm -o foo.wat
# Convert a text module to binary
$ wasm-tools parse foo.wat -o foo.wasm
Simple mutation as well as piping commands together:
# Mutate a WebAssembly module and print its text representation to stdout
$ wasm-tools mutate foo.wasm -t
# Mutate a WebAssembly module with a non-default seed and validate that the
# output is a valid module.
$ wasm-tools mutate foo.wasm --seed 192 | wasm-tools validate
# Demangle Rust/C++ symbol names in the `name` section, strip all other custom
# sections, and then print out what binary sections remain.
$ wasm-tools demangle foo.wasm | wasm-tools strip | wasm-tools objdump
Working with components:
# Print the WIT interface of a component
$ wasm-tools component wit component.wasm
# Convert WIT text files to a binary-encoded WIT package, printing the result to
# stdout
$ wasm-tools component wit ./wit -t
# Convert a WIT document to JSON
$ wasm-tools component wit ./wit --json
# Round trip WIT through the binary-encoded format to stdout.
$ wasm-tools component wit ./wit --wasm | wasm-tools component wit
# Convert a core WebAssembly binary into a component. Note that this requires
# WIT metadata having previously been embedded in the core wasm module.
$ wasm-tools component new my-core.wasm -o my-component.wasm
# Convert a core WebAssembly binary which uses WASI to a component.
$ wasm-tools component new my-core.wasm -o my-component.wasm --adapt wasi_snapshot_preview1.reactor.wasm
There are a few conventions that all CLI commands adhere to:
-h and "long help" with --help.-o or --output flag is not provided. Binary WebAssembly is not printed to a tty by default, however.-t or --wat flag to generate the WebAssembly text format instead.-v or --verbose flag can be passed to enable log messages throughout the tooling. Verbosity can be turned up by passing the flag multiple times such as -vvv.--color argument.The wasm-tools binary internally contains a number of subcommands for working with wasm modules and component. Many subcommands also come with Rust crates that can be use programmatically as well:
| CLI | Rust Crate | Playground | Description |
|---|---|---|---|
wasm-tools validate |
wasmparser | Validate a WebAssembly file | |
wasm-tools parse |
wat and wast | parse | Translate the WebAssembly text format to binary |
wasm-tools print |
wasmprinter | Translate the WebAssembly binary format to text | |
wasm-tools smith |
wasm-smith | Generate a valid WebAssembly module from an input seed | |
wasm-tools mutate |
wasm-mutate | Mutate an input wasm file into a new valid wasm file | |
wasm-tools shrink |
wasm-shrink | Shrink a wasm file while preserving a predicate | |
wasm-tools dump |
Print debugging information about the binary format | ||
wasm-tools objdump |
Print debugging information about section headers | ||
wasm-tools strip |
Remove custom sections from a WebAssembly file | ||
wasm-tools demangle |
Demangle Rust and C++ symbol names in the name section |
||
wasm-tools compose |
wasm-compose | Compose wasm components together (deprecated) | |
wasm-tools component new |
wit-component | Create a component from a core wasm binary | |
wasm-tools component wit |
Extract a *.wit interface from a component |
||
wasm-tools component embed |
Embed a component-type custom section in a core wasm binary |
||
wasm-tools component unbundle |
Extract core wasm modules from a component | ||
wasm-tools metadata show |
wasm-metadata | Show name and producer metadata in a component or module | |
wasm-tools metadata add |
Add name or producer metadata to a component or module | ||
wasm-tools addr2line |
Translate wasm offsets to filename/line numbers with DWARF | ||
wasm-tools completion |
Generate shell completion scripts for wasm-tools |
||
wasm-tools json-from-wast |
Convert a *.wast file into JSON commands |
||
wasm-tools wast |
Validate the structure of a *.wast file |
The wasm-tools CLI contains useful tools for debugging WebAssembly modules and components. The various subcommands all have --help explainer texts to describe more about their functionality as well.
WebAssembly was originally developed as a technology for running non-JavaScript workloads in the browser at near-native speed.
JavaScript WebAssembly component model support is provided by a combination of tools:
componentize-js a tool for building WebAssembly components from Javascript filesjco a multi-tool for componentizing, type generation, and running components in NodeJS and browser contextsNote that Typescript can also be used, given that it is transpiled to JS first by relevant tooling (tsc). jco generates type declaration files (.d.ts) by default, and also has a jco types subcommand which generates typings that can be used with a Typescript codebase.
!WARNING
While popular projects likeemscriptenalso build WebAssembly modules, those modules are not Component Model aware.Core WebAssembly modules do not contain the advanced features (rich types, structured language interoperation, composition) that the component model makes available.
jcoInstalling jco (which uses componentize-js can be done via standard NodeJS project tooling:
npm install -g @bytecodealliance/jco
!NOTE
jcoandcomponentize-jscan be installed in a project-local manner withnpm install -D
Building a WebAssembly component with JavaScript often consists of:
WebAssembly Interface Types ("WIT") is a featureful Interface Definition Language ("IDL") for defining functionality, but most of the time, you shouldn't need to write WIT from scratch. Often, it's sufficient to download a pre-existing interface that defines what your component should do.
The adder world contains an interface with a single add function that sums two numbers:
{{#include ../../exmaples/tutorial/wit/adder/world.wit}}
!NOTE
exporting theaddinterface means that environments that interact with the resulting WebAssembly component
will be able to call theaddfunction (fully qualified:docs:adder/add.add@0.1.0).To learn more about the WIT syntax, check out the WIT explainer
To implement the adder world, we can write a JavaScript ES module:
export const add = {
add(x, y) {
return x + y;
}
};
!WARNING
When building your JavaScript project, ensure to set the"type":"module"option inpackage.json, asjcoworks exclusively with JavaScript modules.
In the code above:
adder world is analogous to the JavaScript module (file) itselfadd object mirrors the exported add interface in WITadd function mirrors the add function inside the add interfaceWith the WIT and JavaScript in place, we can use jco to create a WebAssembly component from the JS module, using jco componentize.
!NOTE
You can also callcomponentize-jsdirectly -- it supports both API driven usage and has a CLI.
Our component is so simple (reminiscent of Core WebAssembly, which deals only in numeric values) that we're actually not using any of the WebAssembly System Interface (access to files, network, etc). This means that we can --disable all unneeded WASI functionality when we invoke jco componentize:
jco componentize \
--wit path/to/adder/world.wit \
--world-name example \
--out adder.wasm \
--disable all \
path/to/adder.js
!NOTE
If you're usingjcoas a project-local dependency, you can runnpx jco
You should see output like the following:
OK Successfully written adder.wasm.
!WARNING
By using--disable all, your component won't get access to any WASI interfaces that might be useful for debugging or logging.For example, you can't
console.log(...)orconsole.error(...)withoutstdio; you can't useMath.random()withoutrandom; and you can't useDate.now()ornew Date()withoutclocks.Please note that calls to
Math.random()orDate.now()will return seemingly valid outputs, but without actual randomness or timestamp correctness.
example-hostTo run the component we've built, we can use the example-host project:
cd component-model/examples/example-host
cargo run --release -- 1 2 ../path/to/adder.wasm
1 + 2 = 3
!WARNING
Theexample-hostRust project uses the Rust toolchain, in particularcargo,
so to run the code in this section you may need to install some more dependencies (like the Rust toolchain).
While the output isn't exciting, the code contained in example-host does a lot to make it happen:
../path/to/adder.wasm)exported add function inside the add interface with argumentsThe important Rust code looks something like this:
let component = Component::from_file(&engine, path).context("Component file not found")?;
let (instance, _) = Example::instantiate_async(&mut store, &component, &linker)
.await
.context("Failed to instantiate the example world")?;
instance
.call_add(&mut store, x, y)
.await
.context("Failed to call add function")
A quick reminder on the power and new capabilities afforded by WebAssembly -- we've written, loaded,
instantiated and executed JavaScript from Rust with a strict interface, without the need
for FFI, subprocesses or a network call.
While JavaScript runtimes available in browsers can execute WebAssembly core modules, they cannot yet execute WebAssembly components, so WebAssembly components (JavaScript or otherwise) must be "transpiled" into a JavaScript wrapper and one or more WebAssembly core modules which can be run by available WebAssembly engines.
Given an existing WebAssembly component (e.g. adder.wasm which implements the adder world), we can "transpile" the WebAssembly component into runnable JavaScript by using jco transpile:
jco transpile adder.wasm -o dist/transpiled
You should see output similar to the following:
Transpiled JS Component Files:
- dist/transpiled/adder.core.wasm 10.1 MiB
- dist/transpiled/adder.d.ts 0.1 KiB
- dist/transpiled/adder.js 1.57 KiB
!NOTE
To follow along, see thejcoexampleaddercomponent.With the project pulled locally, you also run
npm run transpilewhich outputs todist/transpiled
Thanks to jco transpilation, you can import the resulting dist/transpiled/adder.js file and run it from any JavaScript application using a runtime that supports the core WebAssembly specification as implemented for JavaScript.
To use this component from NodeJS, you can write code like the following:
import { add } from "./dist/transpiled/adder.js";
console.log("1 + 2 = " + add(1, 2));
You can execute the JavaScript module with node directly:
node run.js
You should see output like the following:
1 + 2 = 3
This is directly comparable to the Rust host code mentioned in the previous section. Here, we are able to use NodeJS as a host for running WebAssembly, thanks to jco's ability to transpile components.
With jco transpile any WebAssembly binary (compiled from any language) can be run in JavaScript natively.
jcoReactor components are WebAssembly components that are long running and meant to be called repeatedly over time. They're analogous to libraries of functionality rather than an executable (a "command" component).
Components expose their interfaces via WebAssembly Interface Types, hand-in-hand with the Component Model which enables components to use higher level types interchangeably.
jcoPackaging reusable functionality into WebAssembly components isn't useful if we have no way to expose that functionality.
This section offers a slightly deeper dive into the usage of WIT in WebAssembly components that can use the Component Model.
As in the previous example, exporting WIT interfaces for other components (or a WebAssembly host) to use is fundamental to developing WebAssembly programs.
Let's examine a jco example project called string-reverse that exposes functionality for reversing a string.
To build a project like string-reverse from the ground up, first we'd start with a WIT like the following:
package example:string-reverse@0.1.0
@since(version = 0.1.0)
interface reverse {
reverse-string: func(s: string) -> string;
}
world string-reverse {
export reverse;
}
As a slightly deeper crash course on WIT, here's what the above code describes:
examplestring-reverse inside the example namespace0.1.0 of example:string-reverse packagereverse which contains one function called reverse-stringreverse interface has existed since the 0.1.0 versionreverse-string function (AKA. example:reverse-string/reverse.reverse-string) takes a string and returns a stringworld called string-reverse which exports the functionality provided by the reverse interface!WARNING
How do we know thatreverseactually reverses a string?Unfortunately, that problem is not really solvable at this level -- this is between you and the writer of the component that implements the WIT interface.
Of course, with WebAssembly, you can enforce static checks if you're so inclined, before you run any given binary.
OK now let's see what the JS code looks like to implement the component world:
/**
* This module is the JS implementation of the `string-reverse` WIT world
*/
/**
* This JavaScript will be interpreted by `jco` and turned into a
* WebAssembly binary with a single export (this `reverse` function).
*/
function reverseString(s) {
return s.reverse();
}
/**
* The JavaScript export below represents the export of the `reverse` interface,
* which which contains `reverse-string` as it's primary exported function.
*/
export const reverse = {
reverseString,
};
!NOTE
To view the full code listing along with instructions, see theexamples/tutorials/jco/string-reversefolder
To use jco to compile this component, you can run the following from the string-reverse folder:
npx jco componentize \
--wit wit/component.wit \
--world-name component \
--out string-reverse.wasm \
--disable all \
string-reverse.mjs
!NOTE
Like the previous example, we're not using any of the advanced WebAssembly System Interface features, so we--disableall of themRather than typing out the
jco componentizecommand manually, you can also run
the build command withnpm run buildfrom thestring-reversefolder.
You should see output like the following:
OK Successfully written string-reverse.wasm.
Now that we have a WebAssembly binary, we can also use jco to run it in a native JavaScript context by transpiling the WebAssembly binary (which could have come from anywhere!) to a JavaScript module.
npx jco transpile string-reverse.wasm -o dist/transpiled
You should see the following output:
Transpiled JS Component Files:
- dist/transpiled/interfaces/example-string-reverse-reverse.d.ts 0.1 KiB
- dist/transpiled/string-reverse.core.wasm 10.1 MiB
- dist/transpiled/string-reverse.d.ts 0.15 KiB
- dist/transpiled/string-reverse.js 2.55 KiB
!TIP
A gentle reminder that, transpilation does produce Typescript declaration file, for use in Typescript projects.
Now that we have a transpiled module, we can run it from any JavaScript context that supports core WebAssembly (whether NodeJS or the browser).
For NodeJS, we can use code like the following:
// If this import listed below is missing, please run `npm run transpile`
import { reverse } from "./dist/transpiled/string-reverse.mjs";
const reversed = reverse.reverseString("!dlroW olleH");
console.log(`reverseString('!dlroW olleH') = ${reversed}`);
!NOTE
In thejcoexample project, you can runnpm run transpiled-jsto build the existing code.
Assuming you have the dist/transpiled folder populated (by running jco transpile in the previous step), you should see output like the following:
reverseString('!dlrow olleh') = hello world!
While it's somewhat redundant in this context, what we've done from NodeJS demonstrates the usefulness of WebAssembly and the jco toolchain. With the help of jco, we have:
jco compile), adhering to an interface defined via WITjco transpile)Just as exporting functionality is core to building useful WebAssembly components, and similarly importing and reusing functionality is key to using the strengths of WebAssembly.
Restated, WIT and the Component Model enable WebAssembly to compose. This means we can build on top of functionality that already exists and export new functionality that depends on existing functionality.
Let's say in addition to the reversing the string (in the previous example) we want to build shared functionality that also upper cases the text it receives.
We can reuse the reversing functionality and export a new interface which enables us to reverse and upper-case.
Let's examine a jco example project called string-reverse-upper that exposes functionality for reversing and upper-casing a string.
Here's the WIT one might write to enable this functionality:
package example:string-reverse-upper@0.1.0;
@since(version = 0.1.0)
interface reversed-upper {
reverse-and-uppercase: func(s: string) -> string;
}
world revup {
//
// NOTE, the import below translates to:
// <namespace>:<package>/<interface>@<package version>
//
import example:string-reverse/reverse@0.1.0;
export reversed-upper;
}
This time, the world named revup that we are building relies on the interface reverse in the package string-reverse from the namespace example.
We can make use of any WebAssembly component that matches that interface, as long as we compose their functionality with the component that implements the revup world.
The revup world imports (and makes use) of reverse in order to export (provide) the reversed-upper interface, which contains the reverse-and-uppercase function (in JS, reverseAndUppercase).
!NOTE
Functionality is imported via theinterface, not theworld.worlds can be included/used, but the syntax is slightly different for that.
The JavaScript to make this work (string-reverse-upper.mjs in jco/examples) looks like this:
/**
* This module is the JS implementation of the `revup` WIT world
*/
/**
* The import here is *virtual*. It refers to the `import`ed `reverse` interface in component.wit.
*
* These types *do not resolve* when the first `string-reverse-upper` component is built,
* but the types are relevant for the resulting *composed* component.
*/
import { reverseString } from 'example:string-reverse/reverse@0.1.0';
/**
* The JavaScript export below represents the export of the `reversed-upper` interface,
* which which contains `revup` as it's primary exported function.
*/
export const reversedUpper = {
/**
* Represents the implementation of the `reverse-and-uppercase` function in the `reversed-upper` interface
*
* This function makes use of `reverse-string` which is *imported* from another WebAssembly binary.
*/
reverseAndUppercase() {
return reverseString(s).toLocaleUpperCase();
},
};
We can build the component with jco componentize:
npx jco componentize \
string-reverse-upper.mjs \
--wit wit/ \
--world-name revup \
--out string-reverse-upper.incomplete.wasm \
--disable all
While we've successfully built a WebAssembly component, unlike the other examples, ours is not yet complete.
We can see that if we print the WIT of the generated component by running jco wit:
npx jco wit string-reverse-upper.incomplete.wasm
You should see output like the following:
package root:component;
world root {
import example:string-reverse/reverse@0.1.0;
export example:string-reverse-upper/reversed-upper@0.1.0;
}
This tells us that the component still has unfulfilled imports -- we use the reverseString function that's in reverse as if it exists, but it's not yet a real part of the WebAssembly component (hence we've named it .incomplete.wasm.
To compose the two components (string-reverse-upper/string-reverse-upper.incomplete.wasm and string-reverse/string-reverse.wasm we built earlier), we'll need the WebAssembly Composition tool (wac). We can use wac plug:
wac plug \
-o string-reverse-upper.wasm \
--plug ../string-reverse/string-reverse.wasm \
string-reverse-upper.incomplete.wasm
!NOTE
You can also run this step withnpm run compose.
A new component string-reverse-upper.wasm should now be present, which is a "complete" component -- we can check the output of jco wit to ensure that all the imports are satisfied:
package root:component;
world root {
export example:string-reverse-upper/reversed-upper@0.1.0;
}
It's as-if we never imported any functionality at all -- the functionality present in string-reverse.wasm has been merged into string-reverse-upper.wasm, and it now simply exports the advanced functionality.
We can run this completed component with in any WebAssembly-capable native JavaScript environment by using a the transpiled result:
npx jco transpile string-reverse-upper.wasm -o dist/transpiled
!NOTE
In the example project, you can runnpm run transpileinstead, which will also change the extension ondist/transpiled/string-reverse-upper.jsto.mjs
You should see output like the following:
Transpiled JS Component Files:
- dist/transpiled/interfaces/example-string-reverse-upper-reversed-upper.d.ts 0.12 KiB
- dist/transpiled/string-reverse-upper.core.wasm 10.1 MiB
- dist/transpiled/string-reverse-upper.core2.wasm 10.1 MiB
- dist/transpiled/string-reverse-upper.d.ts 0.19 KiB
- dist/transpiled/string-reverse-upper.js 6.13 KiB
!TIP
Notice that there are two core WebAssembly files? That's because two core WebAssembly modules were involved
in creating the ultimate functionality we needed.
To run the transpiled component, we can write code like the following:
/**
* If this import listed below is missing, please run
*
* ```
* npm run build && npm run compose && npm run transpile`
* ```
*/
import { reversedUpper } from "./dist/transpiled/string-reverse-upper.mjs";
const result = reversedUpper.reverseAndUppercase("!dlroW olleH");
console.log(`reverseAndUppercase('!dlroW olleH') = ${result}`);
!NOTE
In thejcoexample project, you can runnpm run transpiled-js
You should see output like the following:
reverseAndUppercase('!dlroW olleH') = HELLO WORLD!
WebAssembly components can be built from C and C++ using clang, the C language family frontend for LLVM.
wit-bindgen is a tool to generate guest language bindings from a
given .wit file.
Although wit-bindgen is a standalone tool (whereas some languages have more integrated toolchains like Rust's cargo-component),
wit-bindgen can generate source-level bindings for Rust, C, Java (TeaVM), and TinyGo, with the ability for more
language generators to be added in the future.
wit-bindgen can be used to build C applications that can be compiled directly to Wasm modules using clang with a wasm32-wasi target.
First, install the CLI for wit-bindgen, wasm-tools, and the WASI SDK.
The WASI SDK will install a local version of clang configured with a wasi-sysroot. Follow these instructions to configure it for use. Note that you can also use your installed system or emscripten clang by building with --target=wasm32-wasi but you will need some artifacts from WASI SDK to enable and link that build target (more information is available in WASI SDK's docs).
Start by generating a C skeleton from wit-bindgen using the sample adder/world.wit file:
> wit-bindgen c path/to/adder/world.wit
Generating "adder.c"
Generating "adder.h"
Generating "adder_component_type.o"
This has generated several files:
1.adder.h (based on the adder world) with the prototype of the add function (prefixed by exports_) - uint32_t exports_docs_adder_add_add(uint32_t x, uint32_t y);.
2. adder.c that interfaces with the component model ABI to call your function.
3. adder_component_type.o which contains object code referenced in adder.c from an extern that must be linked via clang.
Next, create an component.c that implements the adder world (i.e. the interface defined in adder.h):
#include "adder.h"
uint32_t exports_docs_adder_add_add(uint32_t x, uint32_t y)
{
return x + y;
}
clangNow, you can compile the function into a Wasm module via clang:
clang component.c adder.c adder_component_type.o -o adder.wasm -mexec-model=reactor
Use the
clangincluded in the WASI SDK installation, for example at<WASI_SDK_PATH>/bin/clang.Alternatively, you can also use the published
ghcr.io/webassembly/wasi-sdkcontainer images
for performing builds.For example, to enter a container with
wasi-sdkinstalled:docker run --rm -it --mount type=bind,src=path/to/app/src,dst=/app ghcr.io/webassembly/wasi-sdk:wasi-sdk-25See also:
Dockerfileinwasi-sdk
wasm-toolsNext, we need to transform the P1 component to a P2 component. To do this, we can use wasm-tools component new:
wasm-tools component new ./adder.wasm -o adder.component.wasm
!NOTE
The.component.extension has no special meaning --.wasmfiles can be either modules or components.
wasm-toolsDo note wasm-tools component new may fail if your code references any WASI APIs that must be imported, for
example via standard library imports like stdio.h.
Using WASI interfaces requires an additional step as the WASI SDK still references wasi_snapshot_preview1 APIs that are not compatible directly with components.
For example, modifying the above to reference printf() would compile:
#include "adder.h"
#include <stdio.h>
uint32_t exports_docs_adder_add_add(uint32_t x, uint32_t y)
{
uint32_t result = x + y;
printf("%d", result);
return result;
}
However, the module would fail to transform to a component:
>wasm-tools component new ./adder.wasm -o adder.component.wasm
error: failed to encode a component from module
Caused by:
0: failed to decode world from module
1: module was not valid
2: module requires an import interface named `wasi_snapshot_preview1`
To build a P2 component that uses WASI interfaces from a P1 component, we'll need to make use of adapter modules.
Install the appropriate reactor adapter module as documented here.
You can either get the linked release of wasi_snapshot_preview1.reactor.wasm and rename it to wasi_snapshot_preview1.wasm, or build it directly from source in wasmtime following the instructions here (make sure you git submodule update --init first).
Now, you can adapt preview1 to preview2 to build a component:
wasm-tools component new adder.wasm --adapt wasi_snapshot_preview1.wasm -o adder.component.wasm
Finally, you can inspect the embedded wit to see your component (including any WASI imports if necessary):
>wasm-tools component wit adder.component.wasm
package root:component;
world root {
import wasi:io/error@0.2.2;
import wasi:io/streams@0.2.2;
import wasi:cli/stdin@0.2.2;
import wasi:cli/stdout@0.2.2;
import wasi:cli/stderr@0.2.2;
import wasi:cli/terminal-input@0.2.2;
import wasi:cli/terminal-output@0.2.2;
import wasi:cli/terminal-stdin@0.2.2;
import wasi:cli/terminal-stdout@0.2.2;
import wasi:cli/terminal-stderr@0.2.2;
import wasi:clocks/wall-clock@0.2.2;
import wasi:filesystem/types@0.2.2;
import wasi:filesystem/preopens@0.2.2;
export add: func(x: s32, y: s32) -> s32;
}
...
!WARNING
You must be careful to use a version of the adapter (wasi_snapshot_preview1.wasm) that is compatible with the version of
wasmtimethat will be used, to ensure that WASI interface versions (and relevant implementation) match.
This repository contains an example WebAssembly host written in Rust that can run components that implement the adder world.
!NOTE
When hosts run components that use WASI interfaces, they must explicitly add WASI to the linker to run the built component.
A successful run should show the following output:
cargo run --release -- 1 2 adder.component.wasm
Compiling example-host v0.1.0 (/path/to/component-docs/component-model/examples/example-host)
Finished `release` profile [optimized] target(s) in 7.85s
Running `target/debug/example-host 1 2 /tmp/docs/c/adder.component.wasm`
1 + 2 = 3
If not configured correctly, you may see errors like the following:
cargo run --release -- 1 2 adder.component.wasm
Compiling example-host v0.1.0 (/path/to/component-docs/component-model/examples/example-host)
Finished `release` profile [optimized] target(s) in 7.85s
Running `target/release/example-host 1 2 adder.component.wasm`
Error: Failed to instantiate the example world
Caused by:
0: component imports instance `wasi:io/error@0.2.2`, but a matching implementation was not found in the linker
1: instance export `error` has the wrong type
2: resource implementation is missing
This kind of error normally indicates that the host in question does not contain satisfy WASI imports.
It is not yet possible to run a WebAssembly Component using the C API of wasmtime c-api. See wasmtime issue #6987 for more details.
The c-api is preferred over directly using the example host Rust crate in C++.
However, C/C++ language guest components can be composed with components written in any other language and run by their toolchains, or even composed with a C language command component and run via the wasmtime CLI or any other host.
See the Rust Tooling guide for instructions on how to run this component from the Rust example-host (replacing the path to add.wasm with your add-component above).
WebAssembly components in C# can be built with componentize-dotnet, a a NuGet package that can be used to create a fully AOT-compiled component, giving .NET developers a component experience comparable to those in Rust and TinyGo.
componentize-dotnetcomponentize-dotnet serves as a one-stop shop, wrapping several tools into one:
First, install the .NET SDK. For this walkthrough, we’ll use the .NET 10 SDK preview.
You should also have wasmtime installed so you can run the binary that you produce.
Once you have the .NET SDK installed, create a new project:
dotnet new install BytecodeAlliance.Componentize.DotNet.Templates
dotnet new componentize.wasi.cli -o adder
cd adder
Next, create or download the WIT world you would like to target.
For this example we will use the adder world, with an add function (e.g. to wit/component.wit):
package docs:adder@0.1.0;
interface add {
add: func(x: u32, y: u32) -> u32;
}
world adder {
export add;
}
In the adder.csproj project file, add a new <ItemGroup>:
<ItemGroup>
<Wit Update="wit/component.wit" World="adder" />
</ItemGroup>
Since this component will only export a function dotnet considers this a library project.
Let's update the <OutputType> to be a library in the adder.csproj:
- <OutputType>Exe</OutputType>
+ <OutputType>Library</OutputType>
And remove the automatically generated Program.cs file:
rm Program.cs
adder worldIf you try to build the project with dotnet build, you'll get an error like the following:
➜ dotnet build
Restore complete (8.6s)
You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
adder failed with 1 error(s) (25.6s)
/path/to/adder/obj/Debug/net10.0/wasi-wasm/wit_bindgen/AdderWorld.wit.exports.docs.adder.v0_1_0.AddInterop.cs(15,19): error CS0103: The name 'AddImpl' does not exist in the current context
Build failed with 1 error(s) in 34.6s
This is because we've promised an implementation, but haven't yet written one for the adder world.
To fix this, add the following code to your in a file called Component.cs:
namespace AdderWorld;
public class AddImpl : IAdderWorld
{
public static uint Add(uint x, uint y)
{
return x + y;
}
}
Then, we can build our component:
dotnet build
The component will be available at bin/Debug/net10.0/wasi-wasm/native/adder.wasm.
!WARNING
You must be careful to use a version of the adapter (wasi_snapshot_preview1.wasm) that is compatible with the version of
wasmtimethat will be used, to ensure that WASI interface versions (and relevant implementation) match.
This repository contains an example WebAssembly host written in Rust that can run components that implement the adder world.
!NOTE
When hosts run components that use WASI interfaces, they must explicitly add WASI to the linker to run the built component.
A successful run should show the following output:
cargo run --release -- 1 2 adder.component.wasm
Compiling example-host v0.1.0 (/path/to/component-docs/component-model/examples/example-host)
Finished `release` profile [optimized] target(s) in 7.85s
Running `target/debug/example-host 1 2 /tmp/docs/c/adder.component.wasm`
1 + 2 = 3
If not configured correctly, you may see errors like the following:
cargo run --release -- 1 2 adder.component.wasm
Compiling example-host v0.1.0 (/path/to/component-docs/component-model/examples/example-host)
Finished `release` profile [optimized] target(s) in 7.85s
Running `target/release/example-host 1 2 adder.component.wasm`
Error: Failed to instantiate the example world
Caused by:
0: component imports instance `wasi:io/error@0.2.2`, but a matching implementation was not found in the linker
1: instance export `error` has the wrong type
2: resource implementation is missing
This kind of error normally indicates that the host in question does not contain satisfy WASI imports.
The previous example uses a WIT file that exports a function. However, you'll often prefer to export an interface, either to comply with an existing specification or to capture a set of functions and types that tend to go together. Let's expand our example world to export an interface rather than directly export the function. We are also adding the hostapp world to our WIT file which we will implement in the next section to demonstrate how to build a component that imports an interface.
// adder/world.wit
package example:component;
interface add {
add: func(x: u32, y: u32) -> u32;
}
world example {
export add;
}
world hostapp {
import add;
}
If you peek at the bindings, you'll notice that we now implement a class for the add interface rather than for the example world -- this is a consistent pattern. As you export more interfaces from your world, you implement more classes.
Our Component.cs example gets the slight update of:
namespace ExampleWorld.wit.exports.example.component;
public class AddImpl : IAdd
{
public static uint Add(uint x, uint y)
{
return x + y;
}
}
Once again, compile an application to a Wasm component using dotnet build:
$ dotnet build
Restore complete (0.4s)
You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
adder succeeded (1.1s) → bin/Debug/net10.0/wasi-wasm/adder.dll
Build succeeded in 2.5s
The component will be available at bin/Debug/net10.0/wasi-wasm/native/adder.wasm.
So far, we've been dealing with library components. Now we will be creating a command component that implements the hostapp world. This component will import the add interface that is exported from our adder component and call the add function. We will later compose this command component with the adder library component we just built.
Now we will be taking the adder component and executing it from another WebAssembly component.
dotnet new componentize.wasi.cli creates a new project that creates an executable.
Back out of the current project and create a new one:
cd ..
dotnet new componentize.wasi.cli -o host-app
cd host-app
Copy the same WIT file as before into your project:
// adder/world.wit
package example:component;
interface add {
add: func(x: u32, y: u32) -> u32;
}
world example {
export add;
}
world hostapp {
import add;
}
Add it to your host-app.csproj project file as a new ItemGroup:
<ItemGroup>
<Wit Update="adder/add.wit" World="hostapp" />
</ItemGroup>
Notice how the World changed from example to hostapp. The previous examples focused on implementing the class library for this WIT file - the export functions. Now we'll be focusing on the executable side of the application - the hostapp world.
Modify Program.cs to look like this:
// Pull in all imports of the `hostapp` world, namely the `add` interface.
// example.component refers to the package name defined in the WIT file.
using HostappWorld.wit.imports.example.component;
uint left = 1;
uint right = 2;
var result = AddInterop.Add(left, right);
Console.WriteLine($"{left} + {right} = {result}");
Once again, compile your component with dotnet build:
$ dotnet build
Restore complete (0.4s)
You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
host-app succeeded (1.1s) → bin/Debug/net10.0/wasi-wasm/host-app.dll
Build succeeded in 2.5s
At this point, you'll have two Webassembly components:
example world.hostapp world.Since the host-app component depends on the add function which is defined in the example world, it needs to be composed the first component. You can compose your host-app component with your adder component by running wac plug:
wac plug \
bin/Debug/net10.0/wasi-wasm/native/host-app.wasm \
--plug ../adder/bin/Debug/net10.0/wasi-wasm/native/adder.wasm \
-o main.wasm
You can also automate the process by adding the following to your host-app.csproj:
<Target Name="ComposeWasmComponent" AfterTargets="Publish">
<PropertyGroup>
<EntrypointComponent>bin/$(Configuration)/$(TargetFramework)/wasi-wasm/native/host-app.wasm</EntrypointComponent>
<DependencyComponent>../adder/bin/$(Configuration)/$(TargetFramework)/wasi-wasm/native/adder.wasm</DependencyComponent>
</PropertyGroup>
<MakeDir Directories="dist" />
<Exec Command="$(WacExe) plug $(EntrypointComponent) --plug $(DependencyComponent) -o dist/main.wasm" />
</Target>
Run dotnet build again you will have a composed component in ./dist/main.wasm
Then you can run the composed component:
wasmtime run ./dist/main.wasm
1 + 2 = 3
Check out the componentize-dotnet docs for more configurations options.
wasm-toolswasm-tools can be used to create a component from WebAssembly Text (WAT). This walks through creating a component from WAT that implements the adder world and simply adds two numbers.
add function is defined inside the following world world:package docs:adder@0.1.0;
interface add {
add: func(x: u32, y: u32) -> u32;
}
world adder {
export add;
}
add core module in WAT that exports an add function that adds two parameters:(module
(func $add (param $lhs i32) (param $rhs i32) (result i32)
local.get $lhs
local.get $rhs
i32.add)
(export "docs:adder/add@0.1.0" (func $add))
)
wasm-tools to create a component from the core module, first embedding component metadata$ wasm-tools component embed adder/world.wit add.wat -o add.wasm
$ wasm-tools component new add.wasm -o add.component.wasm
You can "run" a component by calling one of its exports. Hosts and runtimes often only support running components with certain exports. The wasmtime CLI can only run "command" components, so in order to run the add function above, it first must be composed with a primary "command" component that calls it. See documentation on running components for more details.
The following references are helpful in understanding the Component Model and related ecosystem/projects.
wac)wkg)Last modified 02 November 2025