C/C++ Tooling

Building a Component with wit-bindgen and wasm-tools

wit-bindgen is a tool to generate guest language bindings from a given .wit file. Although it is less integrated into language toolchains than other tools such as cargo-component, it can currently 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 generate 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 add.wit file:

>wit-bindgen c add.wit
Generating "example.c"
Generating "example.h"
Generating "example_component_type.o"

This has generated several files - an example.h (based on the name of your world) with the prototype of the add function - int32_t example_add(int32_t x, int32_t y);, as well as some generated code in example.c that interfaces with the component model ABI to call your function. Additionally, example_component_type.o contains object code referenced in example.c from an extern that must be linked via clang.

Next, create an add.c that implements your function defined in example.h:

#include "example.h"

int32_t example_add(int32_t x, int32_t y)
{
	return x + y;
}

Now, you can compile the function into a Wasm module via clang:

clang add.c example.c example_component_type.o -o add-core.wasm -mexec-model=reactor

Use the clang included in the WASI SDK installation, for example at <WASI_SDK_PATH>/bin/clang.

Next, you need to transform the module into a component. For this example, you can use wasm-tools component new:

wasm-tools component new ./add-core.wasm -o add-component.wasm

Do note this will fail if your code references any WASI APIs that must be imported. This 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 "example.h"
#include <stdio.h>

int32_t example_add(int32_t x, int32_t y)
{
	int32_t result = x + y;
	printf("%d", result);
	return result;
}

However, the module would fail to transform to a component:

>wasm-tools component new ./add-core.wasm -o add-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`  

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 add-core.wasm --adapt wasi_snapshot_preview1.wasm -o add-component.wasm

Finally, you can inspect the embedded wit to see your component (including any WASI imports if necessary):

>wasm-tools component wit add-component.wasm
package root:component;

world root {
  import wasi:io/error@0.2.0;
  import wasi:io/streams@0.2.0;
  import wasi:cli/stdin@0.2.0;
  import wasi:cli/stdout@0.2.0;
  import wasi:cli/stderr@0.2.0;
  import wasi:cli/terminal-input@0.2.0;
  import wasi:cli/terminal-output@0.2.0;
  import wasi:cli/terminal-stdin@0.2.0;
  import wasi:cli/terminal-stdout@0.2.0;
  import wasi:cli/terminal-stderr@0.2.0;
  import wasi:clocks/wall-clock@0.2.0;
  import wasi:filesystem/types@0.2.0;
  import wasi:filesystem/preopens@0.2.0;

  export add: func(x: s32, y: s32) -> s32;
}

Running a Component from C/C++ Applications

It is not yet possible to run a Component using the wasmtime c-api - see this issue. The c-api is preferred to trying to directly use the 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.