Skip to content

Commit f167b78

Browse files
authored
Merge pull request #6 from topheman/feat/c_modules
Add C Language Plugin Support
2 parents 281de64 + a4753ab commit f167b78

File tree

22 files changed

+560
-130
lines changed

22 files changed

+560
-130
lines changed

.env.original

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# see https://github.com/WebAssembly/wasi-sdk/releases for exact possibilities
2+
WASI_OS=macos # 'macos' | 'linux' | ''windows'
3+
WASI_ARCH=x86_64 # 'x86_64' | 'arm64'
4+
WASI_VERSION=25
5+
WASI_VERSION_FULL=${WASI_VERSION}.0

.github/workflows/rust-host.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ jobs:
55
build-and-test:
66
runs-on: ubuntu-latest
77
steps:
8+
- name: Set variables based on OS and architecture for just dl-wasi-sdk
9+
run: |
10+
if [ "${{ runner.arch }}" = "X64" ]; then
11+
echo "WASI_ARCH=x86_64" >> $GITHUB_ENV
12+
else
13+
echo "WASI_ARCH=arm64" >> $GITHUB_ENV
14+
fi
15+
if [ "${{ runner.os }}" = "Windows" ]; then
16+
echo "WASI_OS=windows" >> $GITHUB_ENV
17+
else
18+
echo "WASI_OS=linux" >> $GITHUB_ENV
19+
fi
20+
echo "WASI_VERSION_FULL=25.0" >> $GITHUB_ENV
21+
echo "WASI_VERSION=25" >> $GITHUB_ENV
822
- uses: actions/checkout@v4
923
- uses: actions-rs/toolchain@v1
1024
with:
@@ -15,6 +29,14 @@ jobs:
1529
- uses: extractions/setup-just@v3
1630
- name: Install cargo-component
1731
run: cargo binstall cargo-component@0.21.1
32+
- name: Install wasm-tools
33+
run: cargo binstall wasm-tools@1.235.0
34+
- name: Install wit-bindgen
35+
run: cargo install wit-bindgen-cli@0.43.0
36+
- name: Install wasi-sdk
37+
run: |
38+
mkdir c_deps
39+
just dl-wasi-sdk
1840
- name: Build
1941
run: just build
2042
- name: Test

.github/workflows/web-host.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ jobs:
55
build:
66
runs-on: ubuntu-latest
77
steps:
8+
- name: Set variables based on OS and architecture for just dl-wasi-sdk
9+
run: |
10+
if [ "${{ runner.arch }}" = "X64" ]; then
11+
echo "WASI_ARCH=x86_64" >> $GITHUB_ENV
12+
else
13+
echo "WASI_ARCH=arm64" >> $GITHUB_ENV
14+
fi
15+
if [ "${{ runner.os }}" = "Windows" ]; then
16+
echo "WASI_OS=windows" >> $GITHUB_ENV
17+
else
18+
echo "WASI_OS=linux" >> $GITHUB_ENV
19+
fi
20+
echo "WASI_VERSION_FULL=25.0" >> $GITHUB_ENV
21+
echo "WASI_VERSION=25" >> $GITHUB_ENV
822
- uses: actions/checkout@v4
923
- uses: actions-rs/toolchain@v1
1024
with:
@@ -18,6 +32,14 @@ jobs:
1832
- uses: extractions/setup-just@v3
1933
- name: Install cargo-component
2034
run: cargo binstall cargo-component@0.21.1
35+
- name: Install wasm-tools
36+
run: cargo binstall wasm-tools@1.235.0
37+
- name: Install wit-bindgen
38+
run: cargo install wit-bindgen-cli@0.43.0
39+
- name: Install wasi-sdk
40+
run: |
41+
mkdir c_deps
42+
just dl-wasi-sdk
2143
- name: Install JavaScript dependencies
2244
run: npm ci
2345
- name: Build

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ target/
1717
### JavaScript ###
1818
node_modules/
1919
dist/
20+
21+
# C
22+
.env
23+
c_deps/

.vscode/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"skellock.just",
66
"biomejs.biome",
77
"bradlc.vscode-tailwindcss",
8-
"ms-playwright.playwright"
8+
"ms-playwright.playwright",
9+
"ms-vscode.cpptools"
910
]
1011
}

README.md

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ There are two kinds of hosts:
2323
Those hosts then run the same codebase which is compiled to WebAssembly:
2424

2525
- the REPL logic
26-
- the plugins
26+
- the plugins (made a few in rust, C and TypeScript)
2727

2828
Security model: the REPL cli implements a security model inspired by [deno](https://docs.deno.com/runtime/fundamentals/security/#permissions):
2929

@@ -63,7 +63,7 @@ In the last seven years I've done a few projects involving rust and WebAssembly:
6363

6464
## Usage
6565

66-
### pluginlab (rust)
66+
### pluginlab (rust) - REPL cli host
6767

6868
#### Install
6969

@@ -82,6 +82,7 @@ pluginlab\
8282
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_echo.wasm\
8383
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_weather.wasm\
8484
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_cat.wasm\
85+
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-c.wasm\
8586
--allow-all
8687
```
8788

@@ -105,6 +106,7 @@ pluginlab\
105106
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_echo.wasm\
106107
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_weather.wasm\
107108
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_cat.wasm\
109+
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-c.wasm\
108110
--allow-all
109111
[Host] Starting REPL host...
110112
[Host] Loading REPL logic from: https://topheman.github.io/webassembly-component-model-experiments/plugins/repl_logic_guest.wasm
@@ -169,7 +171,31 @@ npm install
169171
npx playwright install
170172
```
171173

172-
### pluginlab (rust)
174+
#### C tooling
175+
176+
[From the WebAssembly Component Model section for C tooling](https://component-model.bytecodealliance.org/language-support/c.html)
177+
178+
```bash
179+
# Initialize the .env file tracking the WASI SDK version for C development
180+
# You will be asked to update the WASI_OS and WASI_ARCH variables if needed
181+
just init-env-file
182+
```
183+
184+
```bash
185+
cargo install wit-bindgen-cli@0.43.0
186+
```
187+
188+
```bash
189+
# Install the wasm-tools tool - you can also use cargo install wasm-tools@1.235.0 if you don't have cargo-binstall
190+
cargo binstall wasm-tools@1.235.0
191+
```
192+
193+
```bash
194+
# Download the WASI SDK into ./c_deps/wasi-sdk folder
195+
just dl-wasi-sdk
196+
```
197+
198+
### pluginlab (rust) - REPL cli host
173199

174200
#### Build
175201

@@ -182,6 +208,7 @@ This will (see [justfile](./justfile)):
182208
- compile the pluginlab crate from rust to a binary file
183209
- compile the repl-logic-guest crate from rust to wasm
184210
- compile the plugin-* crates from rust to wasm
211+
- compile the c_modules/plugin-* C plugins to wasm
185212

186213
#### Run
187214

@@ -193,6 +220,7 @@ This will (see [justfile](./justfile)):
193220
--plugins ./target/wasm32-wasip1/debug/plugin_echo.wasm\
194221
--plugins ./target/wasm32-wasip1/debug/plugin_weather.wasm\
195222
--plugins ./target/wasm32-wasip1/debug/plugin_cat.wasm\
223+
--plugins ./c_modules/plugin-echo/plugin-echo-c.wasm\
196224
--allow-all
197225
```
198226

@@ -300,14 +328,23 @@ In [`.github/workflows/web-host.yml`](./.github/workflows/web-host.yml), after t
300328

301329
To be sure that the preview server is up and running before running the tests, we use the [`webServer.command` option](https://playwright.dev/docs/test-webserver) of [playwright.config.ts](./packages/web-host/playwright.config.ts) to run `WAIT_FOR_SERVER_AT_URL=http://localhost:4173/webassembly-component-model-experiments/ npm run test:e2e:all:preview`
302330

331+
### plugins
332+
333+
There are currently plugins implemented in 3 languages (most of them are in rust):
334+
335+
#### Rust
303336

304-
### plugins (TypeScript)
337+
You can write plugins in rust in [`crates/plugin-*`](./crates).
305338

306-
You can write plugins in rust in [`crates/plugin-*`](./crates), you can also write plugins in TypeScript in [`packages/plugin-*`](./packages), thanks to `jco componentize` (based on [componentize-js](https://github.com/bytecodealliance/componentize-js)).
339+
#### C
307340

308-
There is a [`packages/plugin-echo`](./packages/plugin-echo/) example plugin in TypeScript.
341+
You can write plugins in C in [`c_modules/plugin-*`](./c_modules), thanks to `wit-bindgen` (based on [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen)).
309342

310-
The downsides of writing plugins in TypeScript is mostly that your `.wasm` file will be **much larger** than the one compiled from rust:
343+
#### TypeScript
344+
345+
You can also write plugins in TypeScript in [`packages/plugin-*`](./packages), thanks to `jco componentize` (based on [componentize-js](https://github.com/bytecodealliance/componentize-js)).
346+
347+
The downsides of writing plugins in TypeScript is mostly that your `.wasm` file will be **much larger** than the one compiled from rust or C:
311348

312349
- ~100KB of wasm for the rust plugin
313350
- 11MB of wasm for the TypeScript plugin
@@ -316,9 +353,10 @@ The reason is that a JavaScript runtime needs to be embedded in the `.wasm` file
316353

317354
More about the [SpiderMonkey runtime embedding](https://github.com/bytecodealliance/ComponentizeJS?tab=readme-ov-file#explainer).
318355

319-
### plugins (Other languages)
356+
#### Other languages
357+
358+
Coming.
320359

321-
Coming soon.
322360

323361
## Developer experience
324362

@@ -346,6 +384,7 @@ Those are **optional** tools that are handy for WebAssembly development:
346384
- [cargo component 0.21.1+](https://github.com/bytecodealliance/cargo-component?tab=readme-ov-file#installation)
347385
- [wasm-tools 1.235.0](https://github.com/bytecodealliance/wasm-tools?tab=readme-ov-file#installation)
348386
- [wasm-opt 116](https://github.com/WebAssembly/binaryen?tab=readme-ov-file#installation)
387+
- [wit-bindgen-cli 0.43.0](https://github.com/bytecodealliance/wit-bindgen)
349388

350389
```bash
351390
# latest versions
@@ -356,3 +395,9 @@ cargo binstall cargo-component wasm-tools wasm-opt
356395
# specific versions I used for this project
357396
cargo binstall cargo-component@0.21.1 wasm-tools@1.235.0 wasm-opt@116
358397
```
398+
399+
### C tooling
400+
401+
- [From the WebAssembly Component Model section for C tooling](https://component-model.bytecodealliance.org/language-support/c.html)
402+
- [WASI SDK](https://github.com/WebAssembly/wasi-sdk)
403+
- [WIT Bindgen](https://github.com/bytecodealliance/wit-bindgen)

c_modules/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Plugins written in C
2+
3+
You can find here plugins written in C, some are re-implementations of the plugins written in Rust.
4+
5+
The `plugin_api.c`, `plugin_api.h` and `plugin_api.component_type.o` are generated with [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen), based on the [wit files](../crates/pluginlab/wit) of the project.
6+
7+
The wasm files are compiled with the [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) you downloaded with `just dl-wasi-sdk`, which contain the `clang` compiler.
8+
9+
All you have to do is run `just build` to build everything (including the C plugins) and `just test` to run the tests (including the C plugins).

c_modules/plugin-echo/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.wasm
2+
plugin_api.h
3+
plugin_api.c
4+
plugin_api_component_type.o

c_modules/plugin-echo/component.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include "plugin_api.h"
2+
#include <string.h>
3+
#include <stdlib.h>
4+
5+
/*
6+
* C implementation of the echo plugin
7+
*
8+
* IMPLEMENTATION SOURCE:
9+
* This implements the same interface as the Rust version in crates/plugin-echo/src/lib.rs
10+
* The function signatures are generated from the WIT interface by wit-bindgen:
11+
* - exports_repl_api_plugin_name() corresponds to fn name() -> String
12+
* - exports_repl_api_plugin_man() corresponds to fn man() -> String
13+
* - exports_repl_api_plugin_run() corresponds to fn run(payload: String) -> Result<PluginResponse, ()>
14+
*
15+
* MEMORY MANAGEMENT:
16+
* - Input parameters (like payload) are owned by the runtime - DO NOT free them
17+
* - Output parameters (like ret) are populated by us, freed by the runtime
18+
* - plugin_api_string_dup() allocates new memory for string copies
19+
* - The generated _free functions handle cleanup automatically
20+
* - No explicit free() calls needed in plugin code
21+
*/
22+
23+
void exports_repl_api_plugin_name(plugin_api_string_t *ret)
24+
{
25+
// Populate ret with "echo" as the plugin name
26+
// plugin_api_string_dup() allocates new memory and copies the string
27+
plugin_api_string_dup(ret, "echoc");
28+
}
29+
30+
void exports_repl_api_plugin_man(plugin_api_string_t *ret)
31+
{
32+
// Populate ret with the manual text for the echo command
33+
// plugin_api_string_dup() allocates new memory and copies the string
34+
const char *man_text =
35+
"\n"
36+
"NAME\n"
37+
" echoc - Echo a message (built with C)\n"
38+
"\n"
39+
"USAGE\n"
40+
" echoc <message>\n"
41+
"\n"
42+
"DESCRIPTION\n"
43+
" Echo a message.\n"
44+
"\n"
45+
" ";
46+
plugin_api_string_dup(ret, man_text);
47+
}
48+
49+
bool exports_repl_api_plugin_run(plugin_api_string_t *payload, exports_repl_api_plugin_plugin_response_t *ret)
50+
{
51+
// Set status to success (0 = success, 1 = error)
52+
ret->status = REPL_API_TRANSPORT_REPL_STATUS_SUCCESS;
53+
54+
// Set stdout to contain the payload
55+
// is_some = true means the optional string has a value
56+
ret->stdout.is_some = true;
57+
58+
// Create a properly null-terminated string from the payload
59+
// The payload has ptr and len, we need to ensure it's null-terminated
60+
char *temp_str = malloc(payload->len + 1);
61+
if (temp_str == NULL)
62+
{
63+
// Handle allocation failure
64+
ret->stdout.is_some = false;
65+
ret->stderr.is_some = false;
66+
return false;
67+
}
68+
69+
// Copy the payload data and null-terminate it
70+
memcpy(temp_str, payload->ptr, payload->len);
71+
temp_str[payload->len] = '\0';
72+
73+
// Use plugin_api_string_dup to create the output string
74+
plugin_api_string_dup(&ret->stdout.val, temp_str);
75+
76+
// Free our temporary string
77+
free(temp_str);
78+
79+
// Set stderr to none (no error output)
80+
ret->stderr.is_some = false;
81+
82+
// Return true for success (false would indicate an error)
83+
// This corresponds to Ok(response) in the Rust Result<T, ()> pattern
84+
return true;
85+
}

crates/pluginlab/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ There are two kinds of hosts:
1818
Those hosts then run the same codebase which is compiled to WebAssembly:
1919

2020
- the REPL logic
21-
- the plugins
21+
- the plugins (made a few in rust, C and TypeScript)
2222

2323
Security model: the REPL cli implements a security model inspired by [deno](https://docs.deno.com/runtime/fundamentals/security/#permissions):
2424

@@ -61,6 +61,7 @@ pluginlab\
6161
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_echo.wasm\
6262
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_weather.wasm\
6363
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_cat.wasm\
64+
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-c.wasm\
6465
--allow-all
6566
```
6667

@@ -85,6 +86,7 @@ pluginlab\
8586
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_echo.wasm\
8687
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_weather.wasm\
8788
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin_cat.wasm\
89+
--plugins https://topheman.github.io/webassembly-component-model-experiments/plugins/plugin-echo-c.wasm\
8890
--allow-all
8991
[Host] Starting REPL host...
9092
[Host] Loading REPL logic from: https://topheman.github.io/webassembly-component-model-experiments/plugins/repl_logic_guest.wasm

0 commit comments

Comments
 (0)