Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions component-model/examples/example-c-host/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
host
77 changes: 77 additions & 0 deletions component-model/examples/example-c-host/Dockerfile.guest_and_host
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file is more than the example host -- it is all in one, building the guest and the host, which i think is great, but a host-only one where you could pass in your adder.wasm may also be useful. I'll PR in a suggestion

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep with #333 in it should totally just use the checked in one.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
FROM ubuntu:24.04 AS build

ARG WASMTIME_VERSION=42.0.1
ARG WASI_SDK_VERSION=27
ARG TARGETARCH

RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libc6-dev curl xz-utils ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Install wasmtime C API
RUN set -eux; \
case "${TARGETARCH}" in \
amd64) WASMTIME_ARCH=x86_64 ;; \
arm64) WASMTIME_ARCH=aarch64 ;; \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-${WASMTIME_ARCH}-linux-c-api.tar.xz" \
| tar xJ --strip-components=1 -C /usr/local; \
ldconfig

# Install wasi-sdk (for building the guest component)
RUN set -eux; \
case "${TARGETARCH}" in \
amd64) WASI_SDK_ARCH=amd64 ;; \
arm64) WASI_SDK_ARCH=arm64 ;; \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
curl -sLO "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION}.0-${WASI_SDK_ARCH}-linux.deb"; \
dpkg -i "wasi-sdk-${WASI_SDK_VERSION}.0-${WASI_SDK_ARCH}-linux.deb"; \
rm "wasi-sdk-${WASI_SDK_VERSION}.0-${WASI_SDK_ARCH}-linux.deb"

# Install wit-bindgen and wasm-tools (pin versions)
ARG WIT_BINDGEN_VERSION=0.53.1
ARG WASM_TOOLS_VERSION=1.245.1
RUN set -eux; \
case "${TARGETARCH}" in \
amd64) TOOL_ARCH=x86_64 ;; \
arm64) TOOL_ARCH=aarch64 ;; \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
curl -sL "https://github.com/bytecodealliance/wit-bindgen/releases/download/v${WIT_BINDGEN_VERSION}/wit-bindgen-${WIT_BINDGEN_VERSION}-${TOOL_ARCH}-linux.tar.gz" \
| tar xz --strip-components=1 -C /usr/local/bin --wildcards '*/wit-bindgen'; \
curl -sL "https://github.com/bytecodealliance/wasm-tools/releases/download/v${WASM_TOOLS_VERSION}/wasm-tools-${WASM_TOOLS_VERSION}-${TOOL_ARCH}-linux.tar.gz" \
| tar xz --strip-components=1 -C /usr/local/bin --wildcards '*/wasm-tools'

WORKDIR /src

# Fetch guest source from upstream component-docs
ARG COMPONENT_DOCS_REV=7c1b5a9
RUN curl -sL -o world.wit \
https://raw.githubusercontent.com/bytecodealliance/component-docs/${COMPONENT_DOCS_REV}/component-model/examples/tutorial/wit/adder/world.wit \
&& mkdir -p wit/adder && mv world.wit wit/adder/world.wit
RUN curl -sL -o component.c \
https://raw.githubusercontent.com/bytecodealliance/component-docs/${COMPONENT_DOCS_REV}/component-model/examples/tutorial/c/adder/component.c

# Build the guest component
RUN wit-bindgen c wit/adder/world.wit
RUN /opt/wasi-sdk/bin/wasm32-wasip2-clang \
-o adder.wasm \
-mexec-model=reactor \
component.c adder.c adder_component_type.o

# Build the host
COPY host.c .
RUN gcc -o /usr/local/bin/adder-host host.c -lwasmtime

# Runtime image
FROM ubuntu:24.04

COPY --from=build /usr/local/lib/libwasmtime.so /usr/local/lib/
RUN ldconfig
COPY --from=build /usr/local/bin/adder-host /usr/local/bin/adder-host
COPY --from=build /src/adder.wasm /opt/adder.wasm

ENTRYPOINT ["adder-host"]
CMD ["1", "2", "/opt/adder.wasm"]
35 changes: 35 additions & 0 deletions component-model/examples/example-c-host/Dockerfile.host
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM ubuntu:24.04 AS build

ARG WASMTIME_VERSION=42.0.1
ARG TARGETARCH

RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libc6-dev curl xz-utils ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Install wasmtime C API
RUN set -eux; \
case "${TARGETARCH}" in \
amd64) WASMTIME_ARCH=x86_64 ;; \
arm64) WASMTIME_ARCH=aarch64 ;; \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
esac; \
curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-${WASMTIME_ARCH}-linux-c-api.tar.xz" \
| tar xJ --strip-components=1 -C /usr/local; \
ldconfig

WORKDIR /src

# Build the host
COPY host.c .
RUN gcc -o /usr/local/bin/adder-host host.c -lwasmtime

# Runtime image
FROM ubuntu:24.04

COPY --from=build /usr/local/lib/libwasmtime.so /usr/local/lib/
RUN ldconfig
COPY --from=build /usr/local/bin/adder-host /usr/local/bin/adder-host

ENTRYPOINT ["adder-host"]
CMD ["1", "2", "/component/add.wasm"]
1 change: 1 addition & 0 deletions component-model/examples/example-c-host/adder.wasm
127 changes: 127 additions & 0 deletions component-model/examples/example-c-host/host.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* C host for the adder WebAssembly component.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* C host for the adder WebAssembly component.
* C application that embeds Wasmtime and executes a component that does addition

*
* Uses the Wasmtime C API's component model support to load and run
* a component that exports: docs:adder/add.add(u32, u32) -> u32
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wasmtime.h>

static void exit_if_error(const char *step, wasmtime_error_t *error) {
if (error == NULL)
return;
wasm_byte_vec_t error_message;
wasmtime_error_message(error, &error_message);
wasmtime_error_delete(error);
fprintf(stderr, "error: failed to %s\n%.*s\n", step, (int)error_message.size,
error_message.data);
wasm_byte_vec_delete(&error_message);
exit(1);
}

int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "Usage: %s <x> <y> <component.wasm>\n", argv[0]);
return 1;
}

uint32_t x = (uint32_t)atoi(argv[1]);
uint32_t y = (uint32_t)atoi(argv[2]);
const char *path = argv[3];

// 1. Create engine with component model enabled
wasm_config_t *config = wasm_config_new();
wasmtime_config_wasm_component_model_set(config, true);
wasm_engine_t *engine = wasm_engine_new_with_config(config);

// 2. Read the component file
FILE *f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "error: could not open %s\n", path);
return 1;
}
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *wasm_bytes = malloc(fsize);
fread(wasm_bytes, 1, fsize, f);
fclose(f);

// 3. Compile the component
wasmtime_component_t *component = NULL;
exit_if_error("compile component",
wasmtime_component_new(engine, wasm_bytes, fsize, &component));
free(wasm_bytes);

// 4. Create linker and add WASI P2
wasmtime_component_linker_t *linker =
wasmtime_component_linker_new(engine);
exit_if_error("add WASI to linker",
wasmtime_component_linker_add_wasip2(linker));

// 5. Create store with WASI config
wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
wasmtime_context_t *context = wasmtime_store_context(store);
exit_if_error("set WASI config",
wasmtime_context_set_wasi(context, wasi_config_new()));

// 6. Instantiate
wasmtime_component_instance_t instance;
exit_if_error("instantiate component",
wasmtime_component_linker_instantiate(linker, context, component, &instance));

// 7. Look up the exported "add" function.
// The export is nested: first find the "docs:adder/add@0.1.0" instance,
// then the "add" function within it.
wasmtime_component_export_index_t *iface_idx =
wasmtime_component_instance_get_export_index(
&instance, context, NULL,
"docs:adder/add@0.1.0", strlen("docs:adder/add@0.1.0"));
if (iface_idx == NULL) {
fprintf(stderr, "error: could not find export 'docs:adder/add@0.1.0'\n");
return 1;
}

wasmtime_component_export_index_t *func_idx =
wasmtime_component_instance_get_export_index(
&instance, context, iface_idx,
"add", strlen("add"));
wasmtime_component_export_index_delete(iface_idx);
if (func_idx == NULL) {
fprintf(stderr, "error: could not find function 'add'\n");
return 1;
}

wasmtime_component_func_t func;
bool found = wasmtime_component_instance_get_func(
&instance, context, func_idx, &func);
wasmtime_component_export_index_delete(func_idx);
if (!found) {
fprintf(stderr, "error: could not get function handle for 'add'\n");
return 1;
}

// 8. Call the function: add(x, y) -> u32
wasmtime_component_val_t args[2] = {
{.kind = WASMTIME_COMPONENT_U32, .of.u32 = x},
{.kind = WASMTIME_COMPONENT_U32, .of.u32 = y},
};
wasmtime_component_val_t results[1] = {0};

exit_if_error("call 'add'",
wasmtime_component_func_call(&func, context, args, 2, results, 1));

printf("%u + %u = %u\n", x, y, results[0].of.u32);

// 9. Cleanup
wasmtime_component_val_delete(&results[0]);
wasmtime_store_delete(store);
wasmtime_component_linker_delete(linker);
wasmtime_component_delete(component);
wasm_engine_delete(engine);

return 0;
}
128 changes: 100 additions & 28 deletions component-model/src/language-support/building-a-simple-component/c.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ with the ability to add more language generators in the future.
[wasi]: https://wasi.dev/
[rust]: https://www.rust-lang.org/learn/get-started
[sample-wit]: https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/tutorial/wit/adder/world.wit
[cargo-config]: https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/example-host/Cargo.toml

## 1. Download dependencies

Expand Down Expand Up @@ -303,40 +302,113 @@ world root {
...
```

### 6. Run the component from the example host
## 6. Run the component with `wasmtime --invoke`

The following section requires you to have [a Rust toolchain][rust] installed.
If you want to quickly run the `add` export without writing a host application that embeds Wasmtime,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you want to quickly run the `add` export without writing a host application that embeds Wasmtime,
If you want to quickly run the `add` export without writing an application that embeds Wasmtime,

you can invoke it directly with the Wasmtime CLI.

> [!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][cargo-config]
> for the example host.)
```console
wasmtime run --invoke 'add(2, 2)' adder.wasm
```

Depending on your Wasmtime version, the shorthand form may also work:

```console
wasmtime --invoke 'add(2,2)' adder.wasm
```

## 7. Run the component from the example C host
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## 7. Run the component from the example C host
## 7. Run the component from C/C++ Applications


This repository includes a C application that can execute components that implement the add interface. This application embeds Wasmtime using the Wasmtime C API:
`component-model/examples/example-c-host/host.c`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`component-model/examples/example-c-host/host.c`.
`component-model/examples/example-c-application/app.c`.


The application expects three arguments: the two numbers to add and the Wasm component that executed the addition. For example:

```sh
./adder-host <x> <y> <path-to-component.wasm>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
./adder-host <x> <y> <path-to-component.wasm>
./app <x> <y> <path-to-component.wasm>

```

You can either use a Dockerfile to execute your add component with the C application or directly run the application.

### Option A: Compile and run the host directly
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Option A: Compile and run the host directly
### Option A: Run the C application directly


If the Wasmtime C API headers and library are installed on your system,
you can compile and run the host directly:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
you can compile and run the host directly:
you can compile and run the application directly:


On Linux, the following commands install the C API artifacts in `/usr/local`
using the same approach as `Dockerfile.host`:

{{#include ../example-host-part1.md}}
```console
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc libc6-dev curl xz-utils ca-certificates

WASMTIME_VERSION=42.0.1
case "$(uname -m)" in
x86_64) WASMTIME_ARCH=x86_64 ;;
aarch64|arm64) WASMTIME_ARCH=aarch64 ;;
*) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac

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):
curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-${WASMTIME_ARCH}-linux-c-api.tar.xz" \
| sudo tar xJ --strip-components=1 -C /usr/local

{{#include ../example-host-part2.md}}
sudo ldconfig
```

## 7. Run the component from C/C++ Applications
```console
cd component-model/examples/example-c-host
gcc -o adder-host host.c -lwasmtime
./adder-host 1 2 /absolute/path/to/adder.wasm
Comment on lines +360 to +362
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cd component-model/examples/example-c-host
gcc -o adder-host host.c -lwasmtime
./adder-host 1 2 /absolute/path/to/adder.wasm
cd component-model/examples/example-c-application
gcc -o app app.c -lwasmtime
./app 1 2 /absolute/path/to/adder.wasm

```

It is not yet possible to run a WebAssembly Component using the `wasmtime` C API.
See [`wasmtime` issue #6987](https://github.com/bytecodealliance/wasmtime/issues/6987) for more details.
The C API is preferred over directly using the example host Rust crate in C++.
If `libwasmtime.so` is not in a default library path on Linux,
set `LD_LIBRARY_PATH` before running:

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.
```console
LD_LIBRARY_PATH=/path/to/wasmtime/lib ./adder-host 1 2 /absolute/path/to/adder.wasm
```

Expected output:

```sh
1 + 2 = 3
```

### Option B: Run with Docker (`Dockerfile.host`)

Instead of installing the Wasmtime C API, you can use the provided Dockerfile which builds the C application.

From `component-model/examples/example-c-host`:

```console
cp ../../examples/example-host/add.wasm ./adder.wasm

docker build \
-f Dockerfile.host \
-t example-c-host:latest \
.
```

Then run the container, passing in the component as a volume.

```console
docker run --rm \
-v /absolute/path/to/component-docs/component-model/examples/example-c-host/adder.wasm:/component/add.wasm:ro \
example-c-host:latest 1 2 /component/add.wasm
```

Expected output:

```sh
1 + 2 = 3
```

See the [Rust Tooling guide](./rust.md#running-a-component-from-rust-applications)
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).
`Dockerfile.guest_and_host` is also provided in the same directory if you want
an all-in-one image that builds both the guest component and the C host.

[!NOTE]: #
[!WARNING]: #
Its guest build path follows the same sequence described in steps 2-4:
`wit-bindgen c ...`, then `/opt/wasi-sdk/bin/wasm32-wasip2-clang ...`.
The Dockerfile additionally automates fetching `world.wit` and `component.c`
from this repository and pins tool versions for reproducibility.
At the time of writing, `Dockerfile.guest_and_host` is Linux `x86_64`-specific.
Loading