Skip to content

Commit 3cd035b

Browse files
committed
Add c Component example
Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent b60a313 commit 3cd035b

File tree

11 files changed

+231
-21
lines changed

11 files changed

+231
-21
lines changed

.devcontainer/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ RUN rustup default ${RUST_TOOLCHAIN} \
3535
&& cargo install --locked wasm-tools \
3636
&& cargo install wkg \
3737
&& cargo install wac-cli \
38-
&& cargo install cargo-component --locked
38+
&& cargo install cargo-component --locked \
39+
&& cargo install wit-bindgen-cli --locked

.github/workflows/dep_build_wasm_examples.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,14 @@ jobs:
7878
cache-to: ${{ env.CACHE_TO }}
7979
- name: Build Modules
8080
run: |
81-
for FILENAME in $(find . -name '*.c')
82-
do
83-
echo Building ${FILENAME}
84-
docker run --rm -i -v "${PWD}:/tmp/host" ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:latest /opt/wasi-sdk/bin/clang -flto -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/host/${FILENAME%.*}-wasi-libc.wasm /tmp/host/${FILENAME}
85-
cargo run -p hyperlight-wasm-aot compile ${FILENAME%.*}-wasi-libc.wasm ${FILENAME%.*}.aot
86-
cp ${FILENAME%.*}.aot ${FILENAME%.*}.wasm
87-
done
81+
just ensure-tools
82+
just build-wasm-examples release
8883
shell: bash
8984
working-directory: src/wasmsamples
9085
- name: Upload Wasm Modules
9186
uses: actions/upload-artifact@v4
9287
with:
9388
name: guest-modules
9489
path: |
95-
src/wasmsamples/*.wasm
96-
src/wasmsamples/*.aot
90+
x64/release/*.wasm
91+
x64/release/*.aot

.github/workflows/dep_rust.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ jobs:
8282
# this must be build before the formatting and other jobs run
8383
# because the component model example depends on the wasm component built here
8484
just ensure-tools
85+
just compile-wit
8586
just build-rust-component-examples ${{ matrix.config }}
8687
8788
- name: Fmt

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,4 @@ target/
479479
# MSVC Windows builds of rustc generate these, which store debugging information
480480
*.pdb
481481
src/component_sample/**/*.wasm
482+
src/wasmsamples/components/bindings/

Justfile

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ build-wasm-examples-command := if os() == "windows" { "./src/hyperlight_wasm/scr
44
mkdir-arg := if os() == "windows" { "-Force" } else { "-p" }
55
latest-release:= if os() == "windows" {"$(git tag -l --sort=v:refname | select -last 2 | select -first 1)"} else {`git tag -l --sort=v:refname | tail -n 2 | head -n 1`}
66
wit-world := if os() == "windows" { "$env:WIT_WORLD=\"" + justfile_directory() + "\\src\\component_sample\\wit\\component-world.wasm" + "\";" } else { "WIT_WORLD=" + justfile_directory() + "/src/component_sample/wit/component-world.wasm" }
7+
wit-world-c := if os() == "windows" { "$env:WIT_WORLD=\"" + justfile_directory() + "\\src\\wasmsamples\\components\\runcomponent-world.wasm" + "\";" } else { "WIT_WORLD=" + justfile_directory() + "/src/wasmsamples/components/runcomponent-world.wasm" }
78

89
set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"]
910

@@ -14,8 +15,9 @@ make-vendor-tar:
1415
-C ./src wasm_runtime hyperlight_wasm_macro
1516

1617
ensure-tools:
17-
cargo install --locked wasm-tools --version 1.235.0
18+
cargo install wasm-tools --locked --version 1.235.0
1819
cargo install cargo-component --locked --version 0.21.1
20+
cargo install wit-bindgen-cli --locked --version 0.43.0
1921

2022
build-all target=default-target: (build target) (build-wasm-examples target) (build-rust-wasm-examples target) (build-wasm-runtime target) (build-rust-component-examples target)
2123

@@ -26,10 +28,14 @@ mkdir-redist target=default-target:
2628
mkdir {{ mkdir-arg }} x64
2729
mkdir {{ mkdir-arg }} x64/{{ target }}
2830

31+
compile-wit:
32+
wasm-tools component wit ./src/wasmsamples/components/runcomponent.wit -w -o ./src/wasmsamples/components/runcomponent-world.wasm
33+
wasm-tools component wit ./src/component_sample/wit/example.wit -w -o ./src/component_sample/wit/component-world.wasm
34+
2935
build-wasm-runtime target=default-target:
3036
cd ./src/wasm_runtime && cargo build --verbose --profile={{ if target == "debug" {"dev"} else { target } }} && rm -R target
3137

32-
build-wasm-examples target=default-target:
38+
build-wasm-examples target=default-target: (compile-wit)
3339
{{ build-wasm-examples-command }} {{target}}
3440

3541
build-rust-wasm-examples target=default-target: (mkdir-redist target)
@@ -38,8 +44,7 @@ build-rust-wasm-examples target=default-target: (mkdir-redist target)
3844
cargo run -p hyperlight-wasm-aot compile ./src/rust_wasm_samples/target/wasm32-unknown-unknown/{{ target }}/rust_wasm_samples.wasm ./x64/{{ target }}/rust_wasm_samples.aot
3945
cp ./x64/{{ target }}/rust_wasm_samples.aot ./x64/{{ target }}/rust_wasm_samples.wasm
4046

41-
build-rust-component-examples target=default-target:
42-
wasm-tools component wit ./src/component_sample/wit/example.wit -w -o ./src/component_sample/wit/component-world.wasm
47+
build-rust-component-examples target=default-target: (compile-wit)
4348
# use cargo component so we don't get all the wasi imports https://github.com/bytecodealliance/cargo-component?tab=readme-ov-file#relationship-with-wasm32-wasip2
4449
# we also explicitly target wasm32-unknown-unknown since cargo component might try to pull in wasi imports https://github.com/bytecodealliance/cargo-component/issues/290
4550
rustup target add wasm32-unknown-unknown
@@ -101,10 +106,14 @@ examples-components target=default-target features="": (build-rust-component-exa
101106
{{ wit-world }} cargo run {{ if features =="" {''} else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example component_example
102107

103108
# warning, compares to and then OVERWRITES the given baseline
104-
bench-ci baseline target=default-target features="":
105-
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} -- --verbose --save-baseline {{baseline}}
106-
bench target=default-target features="":
107-
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} -- --verbose
109+
bench-ci baseline target="release" features="":
110+
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks -- --verbose --save-baseline {{baseline}}
111+
cd src/hyperlight_wasm; {{wit-world-c}} cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks_components -- --verbose --save-baseline {{baseline}}-components
112+
bench target="release" features="": (bench-wasm target features) (bench-components target features)
113+
bench-wasm target="release" features="":
114+
cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks -- --verbose
115+
bench-components target="release" features="":
116+
cd src/hyperlight_wasm; {{wit-world-c}} cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks_components -- --verbose
108117
bench-download os hypervisor cpu tag="":
109118
gh release download {{ tag }} -D ./src/hyperlight_wasm/target/ -p benchmarks_{{ os }}_{{ hypervisor }}_{{ cpu }}.tar.gz
110119
mkdir {{ mkdir-arg }} ./src/hyperlight_wasm/target/criterion

src/hyperlight_wasm/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,8 @@ mshv3 = ["hyperlight-host/mshv3"]
8888
[[bench]]
8989
name = "benchmarks"
9090
harness = false
91+
92+
[[bench]]
93+
name = "benchmarks_components"
94+
harness = false
95+
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use std::sync::{Arc, Mutex};
2+
3+
use criterion::{Bencher, Criterion, criterion_group, criterion_main};
4+
use hyperlight_wasm::{LoadedWasmSandbox, SandboxBuilder};
5+
6+
use crate::bindings::example::runcomponent::Guest;
7+
8+
extern crate alloc;
9+
mod bindings {
10+
hyperlight_component_macro::host_bindgen!(
11+
"../../src/wasmsamples/components/runcomponent-world.wasm"
12+
);
13+
}
14+
15+
pub struct State {}
16+
impl State {
17+
pub fn new() -> Self {
18+
State {}
19+
}
20+
}
21+
22+
impl Default for State {
23+
fn default() -> Self {
24+
Self::new()
25+
}
26+
}
27+
28+
impl bindings::example::runcomponent::Host for State {
29+
fn r#get_time_since_boot_microsecond(&mut self) -> i64 {
30+
let res = std::time::SystemTime::now()
31+
.duration_since(std::time::SystemTime::UNIX_EPOCH)
32+
.unwrap()
33+
.as_micros();
34+
i64::try_from(res).unwrap()
35+
}
36+
}
37+
38+
impl bindings::example::runcomponent::RuncomponentImports for State {
39+
type Host = State;
40+
41+
fn r#host(&mut self) -> impl ::core::borrow::BorrowMut<Self::Host> {
42+
self
43+
}
44+
}
45+
46+
fn wasm_component_guest_call_benchmark(c: &mut Criterion) {
47+
let mut group = c.benchmark_group("wasm_component_guest_functions");
48+
49+
let bench_guest_function = |b: &mut Bencher<'_>, ext| {
50+
let (sb, rt) = get_loaded_wasm_sandbox(ext);
51+
let mut wrapped = bindings::RuncomponentSandbox { sb, rt };
52+
let instance = bindings::example::runcomponent::RuncomponentExports::guest(&mut wrapped);
53+
54+
b.iter(|| {
55+
instance.echo("Hello World!".to_string());
56+
});
57+
};
58+
59+
group.bench_function("wasm_guest_call", |b: &mut Bencher<'_>| {
60+
bench_guest_function(b, "wasm");
61+
});
62+
63+
group.bench_function("wasm_guest_call_aot", |b: &mut Bencher<'_>| {
64+
bench_guest_function(b, "aot");
65+
});
66+
67+
group.finish();
68+
}
69+
70+
fn wasm_component_sandbox_benchmark(c: &mut Criterion) {
71+
let mut group = c.benchmark_group("wasm_component_sandboxes");
72+
let create_wasm_sandbox = || {
73+
get_loaded_wasm_sandbox("wasm");
74+
};
75+
76+
group.bench_function("create_sandbox", |b| {
77+
b.iter_with_large_drop(create_wasm_sandbox);
78+
});
79+
80+
group.bench_function("create_sandbox_and_drop", |b| {
81+
b.iter(create_wasm_sandbox);
82+
});
83+
84+
group.finish();
85+
}
86+
87+
fn get_loaded_wasm_sandbox(
88+
ext: &str,
89+
) -> (
90+
LoadedWasmSandbox,
91+
Arc<Mutex<bindings::RuncomponentResources<State>>>,
92+
) {
93+
let state = State::new();
94+
let mut sandbox = SandboxBuilder::new().build().unwrap();
95+
let rt = bindings::register_host_functions(&mut sandbox, state);
96+
97+
let sb = sandbox.load_runtime().unwrap();
98+
99+
let sb = sb
100+
.load_module(format!("../../x64/release/runcomponent.{ext}",))
101+
.unwrap();
102+
(sb, rt)
103+
}
104+
105+
criterion_group! {
106+
name = benches_components;
107+
config = Criterion::default();//.warm_up_time(Duration::from_millis(50)); // If warm_up_time is default 3s warmup, the benchmark will fail due memory error
108+
targets = wasm_component_guest_call_benchmark, wasm_component_sandbox_benchmark
109+
}
110+
criterion_main!(benches_components);

src/hyperlight_wasm/scripts/build-wasm-examples.sh

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ OUTPUT_DIR=$(realpath $OUTPUT_DIR)
1212

1313
if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then
1414
# running in a container so use the installed wasi-sdk as the devcontainer has this installed
15-
for FILENAME in $(find . -name '*.c')
15+
for FILENAME in $(find . -name '*.c' -not -path './components/*')
1616
do
1717
echo Building ${FILENAME}
1818
# Build the wasm file with wasi-libc for wasmtime
@@ -23,6 +23,29 @@ if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then
2323
cargo run -p hyperlight-wasm-aot compile ${OUTPUT_DIR}/${FILENAME%.*}-wasi-libc.wasm ${OUTPUT_DIR}/${FILENAME%.*}.aot
2424
cp ${OUTPUT_DIR}/${FILENAME%.*}.aot ${OUTPUT_DIR}/${FILENAME%.*}.wasm
2525
done
26+
27+
for WIT_FILE in ${PWD}/components/*.wit; do
28+
COMPONENT_NAME=$(basename ${WIT_FILE} .wit)
29+
echo Building component: ${COMPONENT_NAME}
30+
31+
# Generate bindings for the component
32+
wit-bindgen c ${WIT_FILE} --out-dir ${PWD}/components/bindings
33+
34+
# Build the wasm file with wasi-libc for wasmtime
35+
/opt/wasi-sdk/bin/wasm32-wasip2-clang \
36+
-ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 \
37+
-Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors \
38+
-Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections \
39+
-o ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm \
40+
${PWD}/components/${COMPONENT_NAME}.c \
41+
${PWD}/components/bindings/${COMPONENT_NAME}.c \
42+
${PWD}/components/bindings/${COMPONENT_NAME}_component_type.o
43+
44+
# Build AOT for Wasmtime
45+
cargo run -p hyperlight-wasm-aot compile --component ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm ${OUTPUT_DIR}/${COMPONENT_NAME}.aot
46+
cp ${OUTPUT_DIR}/${COMPONENT_NAME}.aot ${OUTPUT_DIR}/${COMPONENT_NAME}.wasm
47+
done
48+
2649
else
2750
# not running in a container so use the docker image to build the wasm files
2851
echo Building docker image that has Wasm sdk. Should be quick if preivoulsy built and no changes to dockerfile.
@@ -33,17 +56,41 @@ else
3356

3457
docker build --build-arg GCC_VERSION=12 --build-arg WASI_SDK_VERSION_FULL=25.0 --cache-from ghcr.io/hyperlight-dev/wasm-clang-builder:latest -t wasm-clang-builder:latest . 2> ${OUTPUT_DIR}/dockerbuild.log
3558

36-
for FILENAME in $(find . -name '*.c')
59+
for FILENAME in $(find . -name '*.c' -not -path './components/*')
3760
do
3861
echo Building ${FILENAME}
3962
# Build the wasm file with wasi-libc for wasmtime
40-
docker run --rm -i -v "${PWD}:/tmp/host" -v "${OUTPUT_DIR}:/tmp/output" wasm-clang-builder:latest /opt/wasi-sdk/bin/clang -flto -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/output/${FILENAME%.*}-wasi-libc.wasm /tmp/host/${FILENAME}
63+
docker run --rm -i -v "${PWD}:/tmp/host" -v "${OUTPUT_DIR}:/tmp/output/" wasm-clang-builder:latest /opt/wasi-sdk/bin/clang -flto -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/output/${FILENAME%.*}-wasi-libc.wasm /tmp/host/${FILENAME}
4164

4265
# Build AOT for Wasmtime; note that Wasmtime does not support
4366
# interpreting, so its wasm binary is secretly an AOT binary.
4467
cargo run -p hyperlight-wasm-aot compile ${OUTPUT_DIR}/${FILENAME%.*}-wasi-libc.wasm ${OUTPUT_DIR}/${FILENAME%.*}.aot
4568
cp ${OUTPUT_DIR}/${FILENAME%.*}.aot ${OUTPUT_DIR}/${FILENAME%.*}.wasm
4669
done
70+
71+
echo Building components
72+
# Iterate over all .wit files in the components folder
73+
for WIT_FILE in ${PWD}/components/*.wit; do
74+
COMPONENT_NAME=$(basename ${WIT_FILE} .wit)
75+
echo Building component: ${COMPONENT_NAME}
76+
77+
# Generate bindings for the component
78+
wit-bindgen c ${WIT_FILE} --out-dir ${PWD}/components/bindings
79+
80+
# Build the wasm file with wasi-libc for wasmtime
81+
docker run --rm -i -v "${PWD}:/tmp/host" -v "${OUTPUT_DIR}:/tmp/output/" wasm-clang-builder:latest /opt/wasi-sdk/bin/wasm32-wasip2-clang \
82+
-ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 \
83+
-Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors \
84+
-Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections \
85+
-o /tmp/output/${COMPONENT_NAME}-p2.wasm \
86+
/tmp/host/components/${COMPONENT_NAME}.c \
87+
/tmp/host/components/bindings/${COMPONENT_NAME}.c \
88+
/tmp/host/components/bindings/${COMPONENT_NAME}_component_type.o
89+
90+
# Build AOT for Wasmtime
91+
cargo run -p hyperlight-wasm-aot compile --component ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm ${OUTPUT_DIR}/${COMPONENT_NAME}.aot
92+
cp ${OUTPUT_DIR}/${COMPONENT_NAME}.aot ${OUTPUT_DIR}/${COMPONENT_NAME}.wasm
93+
done
4794
fi
4895

4996
popd

src/wasmsamples/compile-wasm.bat

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ for /R "%1" %%i in (*.c) do (
3131
copy %2\%%~ni.aot %2\%%~ni.wasm
3232
)
3333

34+
echo Building components
35+
for %%j in (%~1\components\*.wit) do (
36+
set "COMPONENT_NAME=%%~nj"
37+
echo Building component: !COMPONENT_NAME!
38+
39+
rem Generate bindings for the component
40+
wit-bindgen c %%j --out-dir %~1\components\bindings
41+
42+
rem Build the wasm file with wasi-libc for wasmtime
43+
%dockercmd% run --rm -i -v !dockerinput!:/tmp/host1 -v !dockeroutput!:/tmp/host2 wasm-clang-builder /opt/wasi-sdk/bin/wasm32-wasip2-clang -ffunction-sections -mexec-model=reactor -O3 -z stack-size=4096 -Wl,--initial-memory=65536 -Wl,--export=__data_end -Wl,--export=__heap_base,--export=malloc,--export=free,--export=__wasm_call_ctors -Wl,--strip-all,--no-entry -Wl,--allow-undefined -Wl,--gc-sections -o /tmp/host2/!COMPONENT_NAME!-p2.wasm /tmp/host1/components/!COMPONENT_NAME!.c /tmp/host1/components/bindings/!COMPONENT_NAME!.c /tmp/host1/components/bindings/!COMPONENT_NAME!_component_type.o
44+
45+
rem Build AOT for Wasmtime
46+
cargo run -p hyperlight-wasm-aot compile --component %2\!COMPONENT_NAME!-p2.wasm %2\!COMPONENT_NAME!.aot
47+
copy %2\!COMPONENT_NAME!.aot %2\!COMPONENT_NAME!.wasm
48+
)
49+
3450
goto :EOF
3551
:Error
3652
echo Usage - compile-wasm ^<source directory^> ^<output directory^>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include "bindings/runcomponent.h"
2+
#include <stdlib.h>
3+
#include <string.h>
4+
5+
void exports_example_runcomponent_guest_echo(runcomponent_string_t *msg, runcomponent_string_t *ret)
6+
{
7+
ret->len = msg->len;
8+
ret->ptr = (uint8_t *) malloc(ret->len);
9+
memcpy(ret->ptr, msg->ptr, ret->len);
10+
runcomponent_string_free(msg);
11+
}

0 commit comments

Comments
 (0)