Skip to content

Commit 5ef8bdd

Browse files
ohAitchexedev-shelleykate-goldenringexe.dev user
authored
Proof of concept C host (#334)
* Proof of concept C host TODO deduplicate with add.wasm see #333 ```sh cd component-model/examples/example-c-host gcc -o host host.c -lwasmtime && ./host 6 7 adder.wasm ``` Co-authored-by: Shelley <shelley@exe.dev> * (reproducibility) add Dockerfile I had some difficulty setting up the C toolchains, here's everything pinned down as a sanity check ```sh docker build -t example-c-host . docker run example-c-host ``` Co-authored-by: Shelley <shelley@exe.dev> * Revise instructions for running components in C/C++ Documentation is not my forté but the current "running things from C is impossible due to #issue (closed)" is _definitely_ out of date. Needs revision into a more helpful pointer to the code introduced in this PR, whatever its project name and contents shall end up being. Possibly broken out into a separate .md fragments like the rust one, possibly that was only necessary to share instructions with rust.md and the existence of the c/c++ host can stay local to this file. * Suggested updates to proof of concept C host documentation - Fills in documentation on how to use the C host - Updates Dockerfile to be cross architecture - Adds a new Dockerfile that is just the host Signed-off-by: Kate Goldenring <kgoldenr@akamai.com> * Add wasmtime invoke to the C docs Signed-off-by: Kate Goldenring <kgoldenr@akamai.com> * Simplify Dockerfiles: drop guest_and_host, rename to Dockerfile, use volume mount - Delete Dockerfile.guest_and_host (broken wasi-sdk arch mapping; unnecessary now that upstream add.wasm is available via rebase) - Rename Dockerfile.host → Dockerfile - Fix missing trailing newline - Update c.md: remove guest_and_host references, fix Docker instructions to use volume mount with $(pwd)-relative path Co-authored-by: Shelley <shelley@exe.dev> * Add CI workflow for C host example Tests both the native build (gcc + wasmtime C API) and the Docker workflow against the checked-in add.wasm. Co-authored-by: Shelley <shelley@exe.dev> --------- Signed-off-by: Kate Goldenring <kgoldenr@akamai.com> Co-authored-by: Shelley <shelley@exe.dev> Co-authored-by: Kate Goldenring <kgoldenr@akamai.com> Co-authored-by: exe.dev user <exedev@wasm-component-test.exe.xyz>
1 parent 1f670b9 commit 5ef8bdd

6 files changed

Lines changed: 321 additions & 28 deletions

File tree

.github/workflows/c-host.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: c-host
2+
3+
on:
4+
merge_group:
5+
push:
6+
branches:
7+
- main
8+
9+
pull_request:
10+
branches:
11+
- main
12+
13+
defaults:
14+
run:
15+
shell: bash
16+
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
env:
22+
WASMTIME_VERSION: "42.0.1"
23+
24+
jobs:
25+
build-and-test:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
29+
30+
- name: Install Wasmtime C API
31+
run: |
32+
curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-x86_64-linux-c-api.tar.xz" \
33+
| sudo tar xJ --strip-components=1 -C /usr/local
34+
sudo ldconfig
35+
36+
- name: Build the C host
37+
working-directory: component-model/examples/example-c-host
38+
run: gcc -o adder-host host.c -lwasmtime
39+
40+
- name: Run the C host
41+
working-directory: component-model/examples/example-c-host
42+
run: |
43+
./adder-host 1 2 ../example-host/add.wasm | tee output.txt
44+
grep -q '1 + 2 = 3' output.txt
45+
46+
docker:
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
50+
51+
- name: Build Docker image
52+
working-directory: component-model/examples/example-c-host
53+
run: docker build -t example-c-host:latest .
54+
55+
- name: Test Docker image
56+
working-directory: component-model/examples/example-c-host
57+
run: |
58+
docker run --rm \
59+
-v "$(pwd)/../example-host/add.wasm":/component/add.wasm:ro \
60+
example-c-host:latest | tee output.txt
61+
grep -q '1 + 2 = 3' output.txt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
host
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
FROM ubuntu:24.04 AS build
2+
3+
ARG WASMTIME_VERSION=42.0.1
4+
ARG TARGETARCH
5+
6+
RUN apt-get update && apt-get install -y --no-install-recommends \
7+
gcc libc6-dev curl xz-utils ca-certificates \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
# Install wasmtime C API
11+
RUN set -eux; \
12+
case "${TARGETARCH}" in \
13+
amd64) WASMTIME_ARCH=x86_64 ;; \
14+
arm64) WASMTIME_ARCH=aarch64 ;; \
15+
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \
16+
esac; \
17+
curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-${WASMTIME_ARCH}-linux-c-api.tar.xz" \
18+
| tar xJ --strip-components=1 -C /usr/local; \
19+
ldconfig
20+
21+
WORKDIR /src
22+
23+
# Build the host
24+
COPY host.c .
25+
RUN gcc -o /usr/local/bin/adder-host host.c -lwasmtime
26+
27+
# Runtime image
28+
FROM ubuntu:24.04
29+
30+
COPY --from=build /usr/local/lib/libwasmtime.so /usr/local/lib/
31+
RUN ldconfig
32+
COPY --from=build /usr/local/bin/adder-host /usr/local/bin/adder-host
33+
34+
ENTRYPOINT ["adder-host"]
35+
CMD ["1", "2", "/component/add.wasm"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../example-host/add.wasm
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* C host for the adder WebAssembly component.
3+
*
4+
* Uses the Wasmtime C API's component model support to load and run
5+
* a component that exports: docs:adder/add.add(u32, u32) -> u32
6+
*/
7+
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include <string.h>
11+
#include <wasmtime.h>
12+
13+
static void exit_if_error(const char *step, wasmtime_error_t *error) {
14+
if (error == NULL)
15+
return;
16+
wasm_byte_vec_t error_message;
17+
wasmtime_error_message(error, &error_message);
18+
wasmtime_error_delete(error);
19+
fprintf(stderr, "error: failed to %s\n%.*s\n", step, (int)error_message.size,
20+
error_message.data);
21+
wasm_byte_vec_delete(&error_message);
22+
exit(1);
23+
}
24+
25+
int main(int argc, char *argv[]) {
26+
if (argc != 4) {
27+
fprintf(stderr, "Usage: %s <x> <y> <component.wasm>\n", argv[0]);
28+
return 1;
29+
}
30+
31+
uint32_t x = (uint32_t)atoi(argv[1]);
32+
uint32_t y = (uint32_t)atoi(argv[2]);
33+
const char *path = argv[3];
34+
35+
// 1. Create engine with component model enabled
36+
wasm_config_t *config = wasm_config_new();
37+
wasmtime_config_wasm_component_model_set(config, true);
38+
wasm_engine_t *engine = wasm_engine_new_with_config(config);
39+
40+
// 2. Read the component file
41+
FILE *f = fopen(path, "rb");
42+
if (!f) {
43+
fprintf(stderr, "error: could not open %s\n", path);
44+
return 1;
45+
}
46+
fseek(f, 0, SEEK_END);
47+
long fsize = ftell(f);
48+
fseek(f, 0, SEEK_SET);
49+
uint8_t *wasm_bytes = malloc(fsize);
50+
fread(wasm_bytes, 1, fsize, f);
51+
fclose(f);
52+
53+
// 3. Compile the component
54+
wasmtime_component_t *component = NULL;
55+
exit_if_error("compile component",
56+
wasmtime_component_new(engine, wasm_bytes, fsize, &component));
57+
free(wasm_bytes);
58+
59+
// 4. Create linker and add WASI P2
60+
wasmtime_component_linker_t *linker =
61+
wasmtime_component_linker_new(engine);
62+
exit_if_error("add WASI to linker",
63+
wasmtime_component_linker_add_wasip2(linker));
64+
65+
// 5. Create store with WASI config
66+
wasmtime_store_t *store = wasmtime_store_new(engine, NULL, NULL);
67+
wasmtime_context_t *context = wasmtime_store_context(store);
68+
exit_if_error("set WASI config",
69+
wasmtime_context_set_wasi(context, wasi_config_new()));
70+
71+
// 6. Instantiate
72+
wasmtime_component_instance_t instance;
73+
exit_if_error("instantiate component",
74+
wasmtime_component_linker_instantiate(linker, context, component, &instance));
75+
76+
// 7. Look up the exported "add" function.
77+
// The export is nested: first find the "docs:adder/add@0.1.0" instance,
78+
// then the "add" function within it.
79+
wasmtime_component_export_index_t *iface_idx =
80+
wasmtime_component_instance_get_export_index(
81+
&instance, context, NULL,
82+
"docs:adder/add@0.1.0", strlen("docs:adder/add@0.1.0"));
83+
if (iface_idx == NULL) {
84+
fprintf(stderr, "error: could not find export 'docs:adder/add@0.1.0'\n");
85+
return 1;
86+
}
87+
88+
wasmtime_component_export_index_t *func_idx =
89+
wasmtime_component_instance_get_export_index(
90+
&instance, context, iface_idx,
91+
"add", strlen("add"));
92+
wasmtime_component_export_index_delete(iface_idx);
93+
if (func_idx == NULL) {
94+
fprintf(stderr, "error: could not find function 'add'\n");
95+
return 1;
96+
}
97+
98+
wasmtime_component_func_t func;
99+
bool found = wasmtime_component_instance_get_func(
100+
&instance, context, func_idx, &func);
101+
wasmtime_component_export_index_delete(func_idx);
102+
if (!found) {
103+
fprintf(stderr, "error: could not get function handle for 'add'\n");
104+
return 1;
105+
}
106+
107+
// 8. Call the function: add(x, y) -> u32
108+
wasmtime_component_val_t args[2] = {
109+
{.kind = WASMTIME_COMPONENT_U32, .of.u32 = x},
110+
{.kind = WASMTIME_COMPONENT_U32, .of.u32 = y},
111+
};
112+
wasmtime_component_val_t results[1] = {0};
113+
114+
exit_if_error("call 'add'",
115+
wasmtime_component_func_call(&func, context, args, 2, results, 1));
116+
117+
printf("%u + %u = %u\n", x, y, results[0].of.u32);
118+
119+
// 9. Cleanup
120+
wasmtime_component_val_delete(&results[0]);
121+
wasmtime_store_delete(store);
122+
wasmtime_component_linker_delete(linker);
123+
wasmtime_component_delete(component);
124+
wasm_engine_delete(engine);
125+
126+
return 0;
127+
}

component-model/src/language-support/building-a-simple-component/c.md

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ with the ability to add more language generators in the future.
2929
[wasi]: https://wasi.dev/
3030
[rust]: https://www.rust-lang.org/learn/get-started
3131
[sample-wit]: https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/tutorial/wit/adder/world.wit
32-
[cargo-config]: https://github.com/bytecodealliance/component-docs/blob/main/component-model/examples/example-host/Cargo.toml
3332

3433
## 1. Download dependencies
3534

@@ -303,40 +302,109 @@ world root {
303302
...
304303
```
305304

306-
### 6. Run the component from the example host
305+
## 6. Run the component with `wasmtime --invoke`
307306

308-
The following section requires you to have [a Rust toolchain][rust] installed.
307+
If you want to quickly run the `add` export without writing a host application that embeds Wasmtime,
308+
you can invoke it directly with the Wasmtime CLI.
309309

310-
> [!WARNING]
311-
> You must be careful to use a version of the adapter (`wasi_snapshot_preview1.wasm`)
312-
> that is compatible with the version of `wasmtime` that will be used,
313-
> to ensure that WASI interface versions (and relevant implementation) match.
314-
> (The `wasmtime` version is specified in [the Cargo configuration file][cargo-config]
315-
> for the example host.)
310+
```console
311+
wasmtime run --invoke 'add(2, 2)' adder.wasm
312+
```
313+
314+
Depending on your Wasmtime version, the shorthand form may also work:
315+
316+
```console
317+
wasmtime --invoke 'add(2,2)' adder.wasm
318+
```
319+
320+
## 7. Run the component from the example C host
321+
322+
This repository includes a C application that can execute components that implement the add interface. This application embeds Wasmtime using the Wasmtime C API:
323+
`component-model/examples/example-c-host/host.c`.
324+
325+
The application expects three arguments: the two numbers to add and the Wasm component that executed the addition. For example:
326+
327+
```sh
328+
./adder-host <x> <y> <path-to-component.wasm>
329+
```
330+
331+
You can either use a Dockerfile to execute your add component with the C application or directly run the application.
332+
333+
### Option A: Compile and run the host directly
334+
335+
If the Wasmtime C API headers and library are installed on your system,
336+
you can compile and run the host directly:
337+
338+
On Linux, the following commands install the C API artifacts in `/usr/local`
339+
using the same approach as the `Dockerfile`:
340+
341+
```console
342+
sudo apt-get update
343+
sudo apt-get install -y --no-install-recommends \
344+
gcc libc6-dev curl xz-utils ca-certificates
345+
346+
WASMTIME_VERSION=42.0.1
347+
case "$(uname -m)" in
348+
x86_64) WASMTIME_ARCH=x86_64 ;;
349+
aarch64|arm64) WASMTIME_ARCH=aarch64 ;;
350+
*) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
351+
esac
352+
353+
curl -sL "https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-${WASMTIME_ARCH}-linux-c-api.tar.xz" \
354+
| sudo tar xJ --strip-components=1 -C /usr/local
355+
356+
sudo ldconfig
357+
```
358+
359+
```console
360+
cd component-model/examples/example-c-host
361+
gcc -o adder-host host.c -lwasmtime
362+
./adder-host 1 2 /absolute/path/to/adder.wasm
363+
```
364+
365+
If `libwasmtime.so` is not in a default library path on Linux,
366+
set `LD_LIBRARY_PATH` before running:
367+
368+
```console
369+
LD_LIBRARY_PATH=/path/to/wasmtime/lib ./adder-host 1 2 /absolute/path/to/adder.wasm
370+
```
371+
372+
Expected output:
373+
374+
```sh
375+
1 + 2 = 3
376+
```
377+
378+
### Option B: Run with Docker
379+
380+
Instead of installing the Wasmtime C API, you can use the provided Dockerfile which builds the C application.
316381

317-
{{#include ../example-host-part1.md}}
382+
From `component-model/examples/example-c-host`:
318383

319-
A successful run should show the following output
320-
(of course, the paths to your example host and adder component will vary,
321-
and you should substitute `adder.wasm` with `adder.component.wasm`
322-
if you followed the manual instructions above):
384+
```console
385+
cd component-model/examples/example-c-host
386+
docker build -t example-c-host:latest .
387+
```
323388

324-
{{#include ../example-host-part2.md}}
389+
Then run the container, passing in the component as a volume:
325390

326-
## 7. Run the component from C/C++ Applications
391+
```console
392+
docker run --rm \
393+
-v "$(pwd)/../example-host/add.wasm":/component/add.wasm:ro \
394+
example-c-host:latest
395+
```
327396

328-
It is not yet possible to run a WebAssembly Component using the `wasmtime` C API.
329-
See [`wasmtime` issue #6987](https://github.com/bytecodealliance/wasmtime/issues/6987) for more details.
330-
The C API is preferred over directly using the example host Rust crate in C++.
397+
Expected output:
331398

332-
However, C/C++ language guest components can be composed with components written in any other language
333-
and run by their toolchains,
334-
or even composed with a C language command component and run via the `wasmtime` CLI
335-
or any other host.
399+
```sh
400+
1 + 2 = 3
401+
```
336402

337-
See the [Rust Tooling guide](./rust.md#running-a-component-from-rust-applications)
338-
for instructions on how to run this component from the Rust `example-host`
339-
(replacing the path to `add.wasm` with your `adder.wasm` or `adder.component.wasm` above).
403+
The default command runs `adder-host 1 2 /component/add.wasm`,
404+
so you can also override the arguments:
340405

341-
[!NOTE]: #
342-
[!WARNING]: #
406+
```console
407+
docker run --rm \
408+
-v "$(pwd)/../example-host/add.wasm":/component/add.wasm:ro \
409+
example-c-host:latest 40 2 /component/add.wasm
410+
```

0 commit comments

Comments
 (0)