diff --git a/.github/workflows/dep_build_wasm_examples.yml b/.github/workflows/dep_build_wasm_examples.yml index 5b99316..7cf7c58 100644 --- a/.github/workflows/dep_build_wasm_examples.yml +++ b/.github/workflows/dep_build_wasm_examples.yml @@ -77,14 +77,7 @@ jobs: cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/wasm-clang-builder:buildcache cache-to: ${{ env.CACHE_TO }} - name: Build Modules - run: | - for FILENAME in $(find . -name '*.c') - do - echo Building ${FILENAME} - 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} - cargo run -p hyperlight-wasm-aot compile ${FILENAME%.*}-wasi-libc.wasm ${FILENAME%.*}.aot - cp ${FILENAME%.*}.aot ${FILENAME%.*}.wasm - done + run: just build-wasm-examples release shell: bash working-directory: src/wasmsamples - name: Upload Wasm Modules @@ -92,5 +85,5 @@ jobs: with: name: guest-modules path: | - src/wasmsamples/*.wasm - src/wasmsamples/*.aot + src/x64/release/*.wasm + src/x64/release/*.aot diff --git a/.gitignore b/.gitignore index 5cb9465..b99517d 100644 --- a/.gitignore +++ b/.gitignore @@ -479,3 +479,4 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb src/component_sample/**/*.wasm +src/wasmsamples/components/bindings/ diff --git a/Justfile b/Justfile index 209b2c0..92b473d 100644 --- a/Justfile +++ b/Justfile @@ -4,6 +4,7 @@ build-wasm-examples-command := if os() == "windows" { "./src/hyperlight_wasm/scr mkdir-arg := if os() == "windows" { "-Force" } else { "-p" } 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`} 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" } +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" } set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"] @@ -29,7 +30,8 @@ mkdir-redist target=default-target: build-wasm-runtime target=default-target: cd ./src/wasm_runtime && cargo build --verbose --profile={{ if target == "debug" {"dev"} else { target } }} && rm -R target -build-wasm-examples target=default-target: +build-wasm-examples target=default-target: (ensure-tools) + wasm-tools component wit ./src/wasmsamples/components/runcomponent.wit -w -o ./src/wasmsamples/components/runcomponent-world.wasm {{ build-wasm-examples-command }} {{target}} build-rust-wasm-examples target=default-target: (mkdir-redist target) @@ -101,10 +103,14 @@ examples-components target=default-target features="": (build-rust-component-exa {{ wit-world }} cargo run {{ if features =="" {''} else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example component_example # warning, compares to and then OVERWRITES the given baseline -bench-ci baseline target=default-target features="": - cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} -- --verbose --save-baseline {{baseline}} -bench target=default-target features="": - cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} -- --verbose +bench-ci baseline target="release" features="": + cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks -- --verbose --save-baseline {{baseline}} + 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 +bench target="release" features="": (bench-wasm target features) (bench-components target features) +bench-wasm target="release" features="": + cd src/hyperlight_wasm && cargo bench --profile={{ if target == "debug" {"dev"} else { target } }} {{ if features =="" {''} else { "--features " + features } }} --bench benchmarks -- --verbose +bench-components target="release" features="": + 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 bench-download os hypervisor cpu tag="": gh release download {{ tag }} -D ./src/hyperlight_wasm/target/ -p benchmarks_{{ os }}_{{ hypervisor }}_{{ cpu }}.tar.gz mkdir {{ mkdir-arg }} ./src/hyperlight_wasm/target/criterion diff --git a/src/hyperlight_wasm/Cargo.toml b/src/hyperlight_wasm/Cargo.toml index 7e3280b..7c2ae4d 100644 --- a/src/hyperlight_wasm/Cargo.toml +++ b/src/hyperlight_wasm/Cargo.toml @@ -88,3 +88,8 @@ mshv3 = ["hyperlight-host/mshv3"] [[bench]] name = "benchmarks" harness = false + +[[bench]] +name = "benchmarks_components" +harness = false + diff --git a/src/hyperlight_wasm/benches/benchmarks_components.rs b/src/hyperlight_wasm/benches/benchmarks_components.rs new file mode 100644 index 0000000..34d7e8c --- /dev/null +++ b/src/hyperlight_wasm/benches/benchmarks_components.rs @@ -0,0 +1,110 @@ +use std::sync::{Arc, Mutex}; + +use criterion::{Bencher, Criterion, criterion_group, criterion_main}; +use hyperlight_wasm::{LoadedWasmSandbox, SandboxBuilder}; + +use crate::bindings::example::runcomponent::Guest; + +extern crate alloc; +mod bindings { + hyperlight_component_macro::host_bindgen!( + "../../src/wasmsamples/components/runcomponent-world.wasm" + ); +} + +pub struct State {} +impl State { + pub fn new() -> Self { + State {} + } +} + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +impl bindings::example::runcomponent::Host for State { + fn r#get_time_since_boot_microsecond(&mut self) -> i64 { + let res = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_micros(); + i64::try_from(res).unwrap() + } +} + +impl bindings::example::runcomponent::RuncomponentImports for State { + type Host = State; + + fn r#host(&mut self) -> impl ::core::borrow::BorrowMut { + self + } +} + +fn wasm_component_guest_call_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("wasm_component_guest_functions"); + + let bench_guest_function = |b: &mut Bencher<'_>, ext| { + let (sb, rt) = get_loaded_wasm_sandbox(ext); + let mut wrapped = bindings::RuncomponentSandbox { sb, rt }; + let instance = bindings::example::runcomponent::RuncomponentExports::guest(&mut wrapped); + + b.iter(|| { + instance.echo("Hello World!".to_string()); + }); + }; + + group.bench_function("wasm_guest_call", |b: &mut Bencher<'_>| { + bench_guest_function(b, "wasm"); + }); + + group.bench_function("wasm_guest_call_aot", |b: &mut Bencher<'_>| { + bench_guest_function(b, "aot"); + }); + + group.finish(); +} + +fn wasm_component_sandbox_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("wasm_component_sandboxes"); + let create_wasm_sandbox = || { + get_loaded_wasm_sandbox("wasm"); + }; + + group.bench_function("create_sandbox", |b| { + b.iter_with_large_drop(create_wasm_sandbox); + }); + + group.bench_function("create_sandbox_and_drop", |b| { + b.iter(create_wasm_sandbox); + }); + + group.finish(); +} + +fn get_loaded_wasm_sandbox( + ext: &str, +) -> ( + LoadedWasmSandbox, + Arc>>, +) { + let state = State::new(); + let mut sandbox = SandboxBuilder::new().build().unwrap(); + let rt = bindings::register_host_functions(&mut sandbox, state); + + let sb = sandbox.load_runtime().unwrap(); + + let sb = sb + .load_module(format!("../../x64/release/runcomponent.{ext}",)) + .unwrap(); + (sb, rt) +} + +criterion_group! { + name = benches_components; + 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 + targets = wasm_component_guest_call_benchmark, wasm_component_sandbox_benchmark +} +criterion_main!(benches_components); diff --git a/src/hyperlight_wasm/scripts/build-wasm-examples.sh b/src/hyperlight_wasm/scripts/build-wasm-examples.sh index a14a5d2..e926358 100755 --- a/src/hyperlight_wasm/scripts/build-wasm-examples.sh +++ b/src/hyperlight_wasm/scripts/build-wasm-examples.sh @@ -12,7 +12,7 @@ OUTPUT_DIR=$(realpath $OUTPUT_DIR) if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then # running in a container so use the installed wasi-sdk as the devcontainer has this installed - for FILENAME in $(find . -name '*.c') + for FILENAME in $(find . -name '*.c' -not -path './components/*') do echo Building ${FILENAME} # Build the wasm file with wasi-libc for wasmtime @@ -23,6 +23,29 @@ if [ -f "/.dockerenv" ] || grep -q docker /proc/1/cgroup; then cargo run -p hyperlight-wasm-aot compile ${OUTPUT_DIR}/${FILENAME%.*}-wasi-libc.wasm ${OUTPUT_DIR}/${FILENAME%.*}.aot cp ${OUTPUT_DIR}/${FILENAME%.*}.aot ${OUTPUT_DIR}/${FILENAME%.*}.wasm done + + for WIT_FILE in ${PWD}/components/*.wit; do + COMPONENT_NAME=$(basename ${WIT_FILE} .wit) + echo Building component: ${COMPONENT_NAME} + + # Generate bindings for the component + wit-bindgen c ${WIT_FILE} --out-dir ${PWD}/components/bindings + + # Build the wasm file with wasi-libc for wasmtime + /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 ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm \ + ${PWD}/components/${COMPONENT_NAME}.c \ + ${PWD}/components/bindings/${COMPONENT_NAME}.c \ + ${PWD}/components/bindings/${COMPONENT_NAME}_component_type.o + + # Build AOT for Wasmtime + cargo run -p hyperlight-wasm-aot compile --component ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm ${OUTPUT_DIR}/${COMPONENT_NAME}.aot + cp ${OUTPUT_DIR}/${COMPONENT_NAME}.aot ${OUTPUT_DIR}/${COMPONENT_NAME}.wasm + done + else # not running in a container so use the docker image to build the wasm files echo Building docker image that has Wasm sdk. Should be quick if preivoulsy built and no changes to dockerfile. @@ -33,17 +56,41 @@ else 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 - for FILENAME in $(find . -name '*.c') + for FILENAME in $(find . -name '*.c' -not -path './components/*') do echo Building ${FILENAME} # Build the wasm file with wasi-libc for wasmtime - 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} + 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} # Build AOT for Wasmtime; note that Wasmtime does not support # interpreting, so its wasm binary is secretly an AOT binary. cargo run -p hyperlight-wasm-aot compile ${OUTPUT_DIR}/${FILENAME%.*}-wasi-libc.wasm ${OUTPUT_DIR}/${FILENAME%.*}.aot cp ${OUTPUT_DIR}/${FILENAME%.*}.aot ${OUTPUT_DIR}/${FILENAME%.*}.wasm done + + echo Building components + # Iterate over all .wit files in the components folder + for WIT_FILE in ${PWD}/components/*.wit; do + COMPONENT_NAME=$(basename ${WIT_FILE} .wit) + echo Building component: ${COMPONENT_NAME} + + # Generate bindings for the component + wit-bindgen c ${WIT_FILE} --out-dir ${PWD}/components/bindings + + # Build the wasm file with wasi-libc for wasmtime + docker run --rm -i -v "${PWD}:/tmp/host" -v "${OUTPUT_DIR}:/tmp/output/" wasm-clang-builder:latest /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/output/${COMPONENT_NAME}-p2.wasm \ + /tmp/host/components/${COMPONENT_NAME}.c \ + /tmp/host/components/bindings/${COMPONENT_NAME}.c \ + /tmp/host/components/bindings/${COMPONENT_NAME}_component_type.o + + # Build AOT for Wasmtime + cargo run -p hyperlight-wasm-aot compile --component ${OUTPUT_DIR}/${COMPONENT_NAME}-p2.wasm ${OUTPUT_DIR}/${COMPONENT_NAME}.aot + cp ${OUTPUT_DIR}/${COMPONENT_NAME}.aot ${OUTPUT_DIR}/${COMPONENT_NAME}.wasm + done fi popd diff --git a/src/wasmsamples/compile-wasm.bat b/src/wasmsamples/compile-wasm.bat index e4c7d10..8b9fd44 100644 --- a/src/wasmsamples/compile-wasm.bat +++ b/src/wasmsamples/compile-wasm.bat @@ -31,6 +31,22 @@ for /R "%1" %%i in (*.c) do ( copy %2\%%~ni.aot %2\%%~ni.wasm ) +echo Building components +for %%j in (%~1\components\*.wit) do ( + set "COMPONENT_NAME=%%~nj" + echo Building component: !COMPONENT_NAME! + + rem Generate bindings for the component + wit-bindgen c %%j --out-dir %~1\components\bindings + + rem Build the wasm file with wasi-libc for wasmtime + %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 + + rem Build AOT for Wasmtime + cargo run -p hyperlight-wasm-aot compile --component %2\!COMPONENT_NAME!-p2.wasm %2\!COMPONENT_NAME!.aot + copy %2\!COMPONENT_NAME!.aot %2\!COMPONENT_NAME!.wasm +) + goto :EOF :Error echo Usage - compile-wasm ^ ^ diff --git a/src/wasmsamples/components/runcomponent.c b/src/wasmsamples/components/runcomponent.c new file mode 100644 index 0000000..0d3d43e --- /dev/null +++ b/src/wasmsamples/components/runcomponent.c @@ -0,0 +1,11 @@ +#include "bindings/runcomponent.h" +#include +#include + +void exports_example_runcomponent_guest_echo(runcomponent_string_t *msg, runcomponent_string_t *ret) +{ + ret->len = msg->len; + ret->ptr = (uint8_t *) malloc(ret->len); + memcpy(ret->ptr, msg->ptr, ret->len); + runcomponent_string_free(msg); +} \ No newline at end of file diff --git a/src/wasmsamples/components/runcomponent.wit b/src/wasmsamples/components/runcomponent.wit new file mode 100644 index 0000000..941fcbf --- /dev/null +++ b/src/wasmsamples/components/runcomponent.wit @@ -0,0 +1,14 @@ +package example:runcomponent; + +interface guest { + echo: func(msg: string) -> string; +} + +interface host { + get-time-since-boot-microsecond: func() -> s64; +} + +world runcomponent { + export guest; + import host; +} \ No newline at end of file