C/C++ Tooling
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.
When compiling C or C++ code to WebAssembly components,
we say that C or C++ is the "guest" language,
and WebAssembly is the "host" language.
In this case, "bindings" are C or C++ declarations: type signatures that
correspond to WIT functions, and type definitions that correspond to WIT types.
The bindings generator only generates declarations; you have to write
the code that actually implements these declarations,
if you're developing your own .wit
files.
For WIT interfaces that are built in to WASI, the code is part of the
WebAssembly runtime that you will be using.
C/C++ currently lacks an integrated toolchain (like Rust's cargo-component
).
However, wit-bindgen
can generate source-level bindings for
Rust, C, Java (TeaVM), and TinyGo,
with the ability to add more language generators in the future.
wit-bindgen
can be used to build C applications that can be compiled directly to WebAssembly modules using clang
with a wasm32-wasi
target.
1. Download dependencies
First, install the following dependencies:
wit-bindgen
CLIwasm-tools
wasm-tools
can be used to inspect compiled WebAssembly modules and components, as well as converting between preview1 modules and preview2 components in the optional manual workflow.
- The
WASI SDK
- WASI SDK is a WASI enabled C/C++ toolchain which includes a version of the C standard
library (
libc
) implemented with WASI interfaces, among other artifacts necessary to compile C/C++ to WebAssembly. - On a Linux system, you can skip to the "Install" section. To build from source, start from the beginning of the README.
- WASI SDK is a WASI enabled C/C++ toolchain which includes a version of the C standard
library (
A WASI SDK installation will include a local version of clang
configured with a WASI sysroot.
(A sysroot is a directory containing header files and libraries
for a particular target platform.)
Follow these instructions to configure WASI SDK for use.
note
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 (see the text about libclang_rt.*.a
objects in
the WASI SDK README).
2. Generate program skeleton from WIT
Start by pasting the contents of the sample adder/world.wit
file
into a local file.
Then generate a C skeleton from wit-bindgen
using this file:
$ wit-bindgen c path/to/adder/world.wit
Generating "adder.c"
Generating "adder.h"
Generating "adder_component_type.o"
This command generates several files:
adder.h
(based on theadder
world). This header file contains, amidst some boilerplate, the prototype of theadd
function, which should look like this. (The name of the function has been prefixed with "exports
".)
uint32_t exports_docs_adder_add_add(uint32_t x, uint32_t y);
adder.c
, which interfaces with the component model ABI to call your function. This file contains anextern
declaration that looks like:
extern void __component_type_object_force_link_adder(void);
adder_component_type.o
, which contains object code, including the definition of the__component_type_object_force_link_adder
function, which must be linked viaclang
.
3. Write program code
Next, create a file named component.c
with code that implements the adder
world:
that is, code which fulfills the definition of the interface function declared in adder.h
.
#include "adder.h"
uint32_t exports_docs_adder_add_add(uint32_t x, uint32_t y)
{
return x + y;
}
4. Compile a WebAssembly Preview 2 component with wasi-sdk
's wasm32-wasip2-clang
"P1" refers to WASI Preview 1, the initial version of the WASI APIs. "P2" refers to WASI Preview 2, which introduced the component model.
While in the past building a P2 component required conversion from a P1 component,
we can now build a P2 component directly by using the wasm32-wasip2-clang
binary
that was installed by the WASI SDK.
If necessary, change /opt/wasi-sdk
to the path where you installed
the WASI SDK.
/opt/wasi-sdk/bin/wasm32-wasip2-clang \
-o adder.wasm \
-mexec-model=reactor \
component.c \
adder.c \
adder_component_type.o
Breaking down each part of this command:
-o adder.wasm
configures the output file that will contain binary WebAssembly code.-mexec-model=reactor
controls the desired execution model of the generated code. The argument can be eitherreactor
orcommand
. In this case, we pass in-mexec-model=reactor
to build a reactor component. A reactor component is more like a library, while a command component is more like an executable.component.c
contains the code you wrote to implement theadder
world.adder.c
andadder_component_type.o
were generated bywit-bindgen
and contain necessary scaffolding (e.g. function exports) to enable buildingcomponent.c
into a WebAssembly binary.
After this command completes, you will have a new file named adder.wasm
available in the source folder.
You can verify that adder.wasm
is a valid WebAssembly component with the following command:
> wasm-tools print adder.wasm | head -1
(component
For use cases that require building a P1 module and/or
adapting an existing P1 module into a P2 module,
such as building for a platform that does not support P2,
details on a more manual approach using wasi-sdk
's clang
and wasm-tools
can be found below:
Manual P1 and P2 build
Compile the component code into a WebAssembly P1 module via clang
:
Assuming you defined WASI_SDK_PATH
according to
the "Use" section
in the WASI SDK README, execute:
$WASI_SDK_PATH/bin/clang \
-o adder.wasm \
-mexec-model=reactor \
component.c \
adder.c \
adder_component_type.o
You can verify that adder.wasm
is a valid WebAssembly P1 component (i.e. a WebAssembly core module) with the following command:
> wasm-tools print adder.wasm | head -1
(module $adder.wasm
Alternatively, you can also use the published
ghcr.io/webassembly/wasi-sdk
container images for performing builds.For example, to enter a container with
wasi-sdk
installed:docker run --rm -it \ --mount type=bind,src=path/to/app/src,dst=/app \ ghcr.io/webassembly/wasi-sdk:wasi-sdk-27
Replace
path/to/app/src
with the absolute path of the directory containing the code for your sample app.Inside the container your source code will be available at
/app
. After changing to that directory, you can run:/opt/wasi-sdk/bin/clang \ -o adder.wasm \ -mexec-model=reactor \ component.c \ adder.c \ adder_component_type.o
Using the Dockerfile avoids the need to install the WASI SDK on your system.
See also:
Dockerfile
inwasi-sdk
Transform the P1 component to a P2 component with wasm-tools
Next, 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—.wasm
files can be either modules or components.
(optional) Build a WASI-enabled WebAssembly (P2) component with wasm-tools
Note that 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 Preview 1 APIs (those with wasi_snapshot_preview1
in their names)
that are not compatible directly with components.
For example, if we modify the above code to reference printf()
,
it would compile to a P1 component:
#include "adder.h"
#include <stdio.h>
uint32_t exports_docs_adder_add_add(uint32_t x, uint32_t y)
{
uint32_t result = x + y;
// On traditional platforms, printf() prints to stdout, but on Wasm platforms,
// stdout and the idea of printing to an output stream is
// introduced and managed by WASI.
//
// When building this code with wasi-libc (as a part of wasi-sdk), the printf call
// below is implemented with code that uses `wasi:cli/stdout` and `wasi:io/streams`.
printf("%d", result);
return result;
}
However, the module would fail to transform to a P2 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: failed to resolve import `wasi_snapshot_preview1::fd_close`
3: 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. An adapter module provides definitions for WASI Preview 1 API functions in terms of WASI Preview 2 API functions.
Download the appropriate reactor adapter module as documented here
and save it to the same directory that contains the .c
and .wasm
files you have been working with.
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
5. Inspect the built component
Finally, you can inspect a WIT representation of the imports and exports of your component (including any WASI imports if you used them):
$ 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;
}
...
6. Run the component from the example host
The following section requires you to have a Rust toolchain installed.
warning
You must be careful to use a version of the adapter (wasi_snapshot_preview1.wasm
)
that is compatible with the version of wasmtime
that will be used,
to ensure that WASI interface versions (and relevant implementation) match.
(The wasmtime
version is specified in the Cargo configuration file
for the example host.)
This repository contains an example WebAssembly host written in Rust
that can run components that implement the adder
world.
git clone https://github.com/bytecodealliance/component-docs.git
cd component-docs/component-model/examples/example-host
cargo run --release -- 1 2 <PATH>/adder.wasm
- The double dashes separate the flags passed to
cargo
from the flags passed in to your code. - The arguments 1 and 2 are the arguments to the adder.
- In place of
<PATH>
, substitute the directory that contains your generatedadder.wasm
file.
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
(of course, the paths to your example host and adder component will vary,
and you should substitute adder.wasm
with adder.component.wasm
if you followed the manual instructions above):
cargo run --release -- 1 2 adder.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 /path/to/adder.wasm`
1 + 2 = 3
If not configured correctly, you may see errors like the following:
cargo run --release -- 1 2 adder.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 /path/to/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 satisfy WASI imports.
7. Run the component from C/C++ Applications
It is not yet possible to run a WebAssembly Component using the 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 adder.wasm
or adder.component.wasm
above).