diff --git a/.github/workflows/dep_rust.yml b/.github/workflows/dep_rust.yml index 56b641a..78c5fd0 100644 --- a/.github/workflows/dep_rust.yml +++ b/.github/workflows/dep_rust.yml @@ -76,6 +76,13 @@ jobs: with: name: guest-modules path: ./x64/${{ matrix.config }} + + - name: Build Rust component model examples + run: | + # this must be build before the formatting and other jobs run + # because the component model example depends on the wasm component built here + just ensure-tools + just build-rust-component-examples ${{ matrix.config }} - name: Fmt run: just fmt-check @@ -124,6 +131,10 @@ jobs: # required for gh cli when downloading GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Test Component Model Examples + run: just examples-components ${{ matrix.config }} ${{ matrix.hypervisor == 'mshv3' && 'mshv3' || ''}} + working-directory: ./src/hyperlight_wasm + ### Benchmarks ### - name: Download benchmarks from "latest" diff --git a/.gitignore b/.gitignore index 0825b42..5cb9465 100644 --- a/.gitignore +++ b/.gitignore @@ -478,3 +478,4 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb +src/component_sample/**/*.wasm diff --git a/Cargo.lock b/Cargo.lock index 1657a17..906e70a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -826,6 +826,29 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1202,6 +1225,37 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlight-component-macro" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d009bb99c6fbb8f6fec0230a834669ed304f20b9ebeddb5b3384315d97c2561d" +dependencies = [ + "env_logger", + "hyperlight-component-util", + "itertools 0.14.0", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wasmparser", +] + +[[package]] +name = "hyperlight-component-util" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be3a11b5c5967729ea7c5dedb017097e709da44eca3de3a2ee7681f5502eb751" +dependencies = [ + "itertools 0.14.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wasmparser", +] + [[package]] name = "hyperlight-host" version = "0.6.1" @@ -1259,6 +1313,7 @@ dependencies = [ "crossbeam-queue", "examples_common", "goblin", + "hyperlight-component-macro", "hyperlight-host", "libc", "log", @@ -1468,12 +1523,45 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "jobserver" version = "0.1.33" @@ -1967,6 +2055,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "postcard" version = "1.1.1" diff --git a/Cargo.toml b/Cargo.toml index 25b72fa..fd8b877 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = ["src/hyperlight_wasm", "src/examples_common", "src/hyperlight_wasm_aot" ] -exclude = [ "src/wasm_runtime", "src/rust_wasm_samples", "src/hyperlight_wasm_macro" ] +members = [ "src/hyperlight_wasm", "src/examples_common", "src/hyperlight_wasm_aot" ] +exclude = [ "src/wasm_runtime", "src/rust_wasm_samples", "src/hyperlight_wasm_macro", "src/component_sample" ] resolver = "2" [workspace.dependencies] diff --git a/Justfile b/Justfile index 17fcbd5..72790cd 100644 --- a/Justfile +++ b/Justfile @@ -3,10 +3,15 @@ default-tag:= "latest" build-wasm-examples-command := if os() == "windows" { "./src/hyperlight_wasm/scripts/build-wasm-examples.bat" } else { "./src/hyperlight_wasm/scripts/build-wasm-examples.sh" } 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" } set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"] -build-all target=default-target: (build target) (build-wasm-examples target) (build-rust-wasm-examples target) (build-wasm-runtime target) +ensure-tools: + cargo install --locked wasm-tools --version 1.235.0 + cargo install cargo-component --locked --version 0.21.1 + +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) build target=default-target features="": (build-wasm-runtime target) (fmt-check) cargo build {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --verbose --profile={{ if target == "debug" {"dev"} else { target } }} @@ -31,23 +36,37 @@ build-rust-wasm-examples target=default-target: (mkdir-redist target) 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 cp ./x64/{{ target }}/rust_wasm_samples.aot ./x64/{{ target }}/rust_wasm_samples.wasm +build-rust-component-examples target=default-target: + wasm-tools component wit ./src/component_sample/wit/example.wit -w -o ./src/component_sample/wit/component-world.wasm + # 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 + # 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 + rustup target add wasm32-unknown-unknown + cd ./src/component_sample && cargo component build --target wasm32-unknown-unknown --profile={{ if target == "debug" {"dev"} else { target } }} + cargo run -p hyperlight-wasm-aot compile --component ./src/component_sample/target/wasm32-unknown-unknown/{{ target }}/component_sample.wasm ./x64/{{ target }}/component_sample.aot + cp ./x64/{{ target }}/component_sample.aot ./x64/{{ target }}/component_sample.wasm + check target=default-target: cargo check --profile={{ if target == "debug" {"dev"} else { target } }} cd src/rust_wasm_samples && cargo check --profile={{ if target == "debug" {"dev"} else { target } }} + cd src/component_sample && cargo check --profile={{ if target == "debug" {"dev"} else { target } }} cd src/wasm_runtime && cargo check --profile={{ if target == "debug" {"dev"} else { target } }} fmt-check: rustup toolchain install nightly -c rustfmt && cargo +nightly fmt -v --all -- --check cd src/rust_wasm_samples && rustup toolchain install nightly -c rustfmt && cargo +nightly fmt -v --all -- --check + cd src/component_sample && rustup toolchain install nightly -c rustfmt && cargo +nightly fmt -v --all -- --check cd src/wasm_runtime && rustup toolchain install nightly -c rustfmt && cargo +nightly fmt -v --all -- --check -fmt: +fmt: + rustup toolchain install nightly -c rustfmt cargo +nightly fmt --all - cd src/rust_wasm_samples && cargo +nightly fmt - cd src/wasm_runtime && cargo +nightly fmt + cd src/rust_wasm_samples && cargo +nightly fmt -v --all + cd src/component_sample && cargo +nightly fmt -v --all + cd src/wasm_runtime && cargo +nightly fmt -v --all clippy target=default-target: (check target) cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings cd src/rust_wasm_samples && cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings + cd src/component_sample && cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings cd src/wasm_runtime && cargo clippy --profile={{ if target == "debug" {"dev"} else { target } }} --all-targets --all-features -- -D warnings # TESTING @@ -71,6 +90,9 @@ examples-ci target=default-target features="": (build-rust-wasm-examples target) cargo run {{ if features =="" {''} else {"--no-default-features -F function_call_metrics," + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example metrics cargo run {{ if features =="" {"--no-default-features --features kvm,mshv2"} else {"--no-default-features -F function_call_metrics," + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example metrics +examples-components target=default-target features="": (build-rust-component-examples target) + {{ 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}} diff --git a/README.md b/README.md index cbbf8f4..1d4fbe0 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Make sure the following are installed: 1. [pwsh](https://github.com/PowerShell/PowerShell) 1. [git](https://gitforwindows.org/) 1. [GitHub CLI](https://github.com/cli/cli#installation) +1. [wasm-tools](https://github.com/bytecodealliance/wasm-tools?tab=readme-ov-file#installation) +1. [cargo component](https://github.com/bytecodealliance/cargo-component?tab=readme-ov-file#installation) Ensure that Windows Hypervisor Platform is enabled: @@ -38,6 +40,8 @@ Make sure the following are installed: 1. [Rust](https://www.rust-lang.org/tools/install) `curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh` 1. [just](https://github.com/casey/just). is used as a command runner `cargo install just`. Do not install `just` through a package manager as it may install an older incompatible version, make sure to use at least version 1.5.0 if not installed through cargo. 1. [GitHub CLI](https://github.com/cli/cli#installation) +1. [wasm-tools](https://github.com/bytecodealliance/wasm-tools?tab=readme-ov-file#installation) +1. [cargo component](https://github.com/bytecodealliance/cargo-component?tab=readme-ov-file#installation) Ensure that KVM is enabled: On an Azure VM using a size that supports nested virtualisation: @@ -94,6 +98,17 @@ generate bindings from the same component type in the host. For a complete (albeit small) example of this, see [this example](https://aka.ms/hyperlight-wasm-sockets-sample). +### Debugging the macro + +You can get more detailed error messages by expanding the Macro locally: + +``` +cargo clean -p hyperlight-wasm +WIT_WORLD= HYPERLIGHT_COMPONENT_MACRO_DEBUG=/tmp/guest.rs cargo build -p hyperlight-wasm +HYPERLIGHT_COMPONENT_MACRO_DEBUG=/tmp/host.rs cargo build +``` + + ## Code of Conduct This project has adopted the [Microsoft Open Source Code of diff --git a/src/component_sample/Cargo.lock b/src/component_sample/Cargo.lock new file mode 100644 index 0000000..cf0aff8 --- /dev/null +++ b/src/component_sample/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "component_sample" +version = "0.1.0" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" +dependencies = [ + "bitflags", +] diff --git a/src/component_sample/Cargo.toml b/src/component_sample/Cargo.toml new file mode 100644 index 0000000..58d3ef6 --- /dev/null +++ b/src/component_sample/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "component_sample" +version = "0.1.0" +edition = "2024" + +[dependencies] +wit-bindgen-rt = { version = "0.41.0", features = ["bitflags"] } + +[lib] +crate-type = ["cdylib"] + +[package.metadata.component] +package = "component-sample:example" + +[package.metadata.component.target] +path = "wit/example.wit" +world = "example" + +[package.metadata.component.target.dependencies] + diff --git a/src/component_sample/src/bindings.rs b/src/component_sample/src/bindings.rs new file mode 100644 index 0000000..de01861 --- /dev/null +++ b/src/component_sample/src/bindings.rs @@ -0,0 +1,306 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +// * runtime_path: "wit_bindgen_rt" +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod component_sample { + pub mod example { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod host { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + pub fn print(message: &str) -> () { + unsafe { + let vec0 = message; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "component-sample:example/host")] + unsafe extern "C" { + #[link_name = "print"] + fn wit_import1(_: *mut u8, _: usize); + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn wit_import1(_: *mut u8, _: usize) { + unreachable!() + } + unsafe { wit_import1(ptr0.cast_mut(), len0) }; + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn host_function(input: &str) -> _rt::String { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit< + u8, + >; 2 * ::core::mem::size_of::<*const u8>()], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); 2 + * ::core::mem::size_of::<*const u8>()], + ); + let vec0 = input; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + let ptr1 = ret_area.0.as_mut_ptr().cast::(); + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "component-sample:example/host")] + unsafe extern "C" { + #[link_name = "host-function"] + fn wit_import2(_: *mut u8, _: usize, _: *mut u8); + } + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn wit_import2(_: *mut u8, _: usize, _: *mut u8) { + unreachable!() + } + unsafe { wit_import2(ptr0.cast_mut(), len0, ptr1) }; + let l3 = *ptr1.add(0).cast::<*mut u8>(); + let l4 = *ptr1 + .add(::core::mem::size_of::<*const u8>()) + .cast::(); + let len5 = l4; + let bytes5 = _rt::Vec::from_raw_parts(l3.cast(), len5, len5); + let result6 = _rt::string_lift(bytes5); + result6 + } + } + } + } +} +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod component_sample { + pub mod example { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod adder { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_add_cabi(arg0: i32, arg1: i32) -> i32 { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let result0 = T::add(arg0 as u32, arg1 as u32); + _rt::as_i32(result0) + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_call_host_cabi( + arg0: *mut u8, + arg1: usize, + ) -> *mut u8 { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let len0 = arg1; + let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); + let result1 = T::call_host(_rt::string_lift(bytes0)); + let ptr2 = (&raw mut _RET_AREA.0).cast::(); + let vec3 = (result1.into_bytes()).into_boxed_slice(); + let ptr3 = vec3.as_ptr().cast::(); + let len3 = vec3.len(); + ::core::mem::forget(vec3); + *ptr2.add(::core::mem::size_of::<*const u8>()).cast::() = len3; + *ptr2.add(0).cast::<*mut u8>() = ptr3.cast_mut(); + ptr2 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn __post_return_call_host(arg0: *mut u8) { + let l0 = *arg0.add(0).cast::<*mut u8>(); + let l1 = *arg0 + .add(::core::mem::size_of::<*const u8>()) + .cast::(); + _rt::cabi_dealloc(l0, l1, 1); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_do_something_cabi(arg0: i32) { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + T::do_something(arg0 as u32); + } + pub trait Guest { + fn add(left: u32, right: u32) -> u32; + fn call_host(input: _rt::String) -> _rt::String; + fn do_something(number: u32) -> (); + } + #[doc(hidden)] + macro_rules! __export_component_sample_example_adder_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[unsafe (export_name = + "component-sample:example/adder#add")] unsafe extern "C" fn + export_add(arg0 : i32, arg1 : i32,) -> i32 { unsafe { + $($path_to_types)*:: _export_add_cabi::<$ty > (arg0, arg1) } } + #[unsafe (export_name = + "component-sample:example/adder#call-host")] unsafe extern "C" fn + export_call_host(arg0 : * mut u8, arg1 : usize,) -> * mut u8 { + unsafe { $($path_to_types)*:: _export_call_host_cabi::<$ty > + (arg0, arg1) } } #[unsafe (export_name = + "cabi_post_component-sample:example/adder#call-host")] unsafe + extern "C" fn _post_return_call_host(arg0 : * mut u8,) { unsafe { + $($path_to_types)*:: __post_return_call_host::<$ty > (arg0) } } + #[unsafe (export_name = + "component-sample:example/adder#do-something")] unsafe extern "C" + fn export_do_something(arg0 : i32,) { unsafe { + $($path_to_types)*:: _export_do_something_cabi::<$ty > (arg0) } } + }; + }; + } + #[doc(hidden)] + pub(crate) use __export_component_sample_example_adder_cabi; + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct _RetArea( + [::core::mem::MaybeUninit< + u8, + >; 2 * ::core::mem::size_of::<*const u8>()], + ); + static mut _RET_AREA: _RetArea = _RetArea( + [::core::mem::MaybeUninit::uninit(); 2 + * ::core::mem::size_of::<*const u8>()], + ); + } + } + } +} +#[rustfmt::skip] +mod _rt { + #![allow(dead_code, clippy::all)] + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + String::from_utf8_unchecked(bytes) + } + } + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen_rt::run_ctors_once(); + } + pub fn as_i32(t: T) -> i32 { + t.as_i32() + } + pub trait AsI32 { + fn as_i32(self) -> i32; + } + impl<'a, T: Copy + AsI32> AsI32 for &'a T { + fn as_i32(self) -> i32 { + (*self).as_i32() + } + } + impl AsI32 for i32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for i16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for i8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for char { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for usize { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + extern crate alloc as alloc_crate; + pub use alloc_crate::alloc; +} +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! __export_example_impl { + ($ty:ident) => { + self::export!($ty with_types_in self); + }; + ($ty:ident with_types_in $($path_to_types_root:tt)*) => { + $($path_to_types_root)*:: + exports::component_sample::example::adder::__export_component_sample_example_adder_cabi!($ty + with_types_in $($path_to_types_root)*:: + exports::component_sample::example::adder); + }; +} +#[doc(inline)] +pub(crate) use __export_example_impl as export; +#[cfg(target_arch = "wasm32")] +#[unsafe( + link_section = "component-type:wit-bindgen:0.41.0:component-sample:example:example:encoded world" +)] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 380] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xfe\x01\x01A\x02\x01\ +A\x04\x01B\x04\x01@\x01\x07messages\x01\0\x04\0\x05print\x01\0\x01@\x01\x05input\ +s\0s\x04\0\x0dhost-function\x01\x01\x03\0\x1dcomponent-sample:example/host\x05\0\ +\x01B\x06\x01@\x02\x04lefty\x05righty\0y\x04\0\x03add\x01\0\x01@\x01\x05inputs\0\ +s\x04\0\x09call-host\x01\x01\x01@\x01\x06numbery\x01\0\x04\0\x0cdo-something\x01\ +\x02\x04\0\x1ecomponent-sample:example/adder\x05\x01\x04\0\x20component-sample:e\ +xample/example\x04\0\x0b\x0d\x01\0\x07example\x03\0\0\0G\x09producers\x01\x0cpro\ +cessed-by\x02\x0dwit-component\x070.227.1\x10wit-bindgen-rust\x060.41.0"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen_rt::maybe_link_cabi_realloc(); +} diff --git a/src/component_sample/src/lib.rs b/src/component_sample/src/lib.rs new file mode 100644 index 0000000..6d2a507 --- /dev/null +++ b/src/component_sample/src/lib.rs @@ -0,0 +1,25 @@ +#[allow(warnings)] +#[rustfmt::skip] +mod bindings; + +use bindings::component_sample::example::host::{host_function, print}; +use bindings::exports::component_sample::example::adder::Guest; + +struct Component {} + +impl Guest for Component { + fn add(left: u32, right: u32) -> u32 { + left + right + } + + fn call_host(input: String) -> String { + let host_result = host_function(&format!("{} from component", &input)); + host_result.to_string() + } + + fn do_something(number: u32) { + print(&format!("{number}")); + } +} + +bindings::export!(Component with_types_in bindings); diff --git a/src/component_sample/wit/example.wit b/src/component_sample/wit/example.wit new file mode 100644 index 0000000..0d05058 --- /dev/null +++ b/src/component_sample/wit/example.wit @@ -0,0 +1,17 @@ +package component-sample:example; + +world example { + import host; + export adder; +} + +interface adder { + add: func(left: u32, right: u32) -> u32; + call-host: func(input: string) -> string; + do-something: func(number: u32); +} + +interface host { + print: func(message: string); + host-function: func(input: string) -> string; +} \ No newline at end of file diff --git a/src/hyperlight_wasm/Cargo.toml b/src/hyperlight_wasm/Cargo.toml index c889ce3..561e4fe 100644 --- a/src/hyperlight_wasm/Cargo.toml +++ b/src/hyperlight_wasm/Cargo.toml @@ -44,6 +44,7 @@ windows = { version = "0.61", features = ["Win32_System_Threading"] } page_size = "0.6.0" [dev-dependencies] +hyperlight-component-macro = "0.6.1" examples_common = { path = "../examples_common" } criterion = { version = "0.6.0", features = ["html_reports"] } crossbeam-queue = "0.3" diff --git a/src/hyperlight_wasm/examples/component_example/main.rs b/src/hyperlight_wasm/examples/component_example/main.rs new file mode 100644 index 0000000..46e2839 --- /dev/null +++ b/src/hyperlight_wasm/examples/component_example/main.rs @@ -0,0 +1,75 @@ +#![allow(renamed_and_removed_lints)] +#![allow(unknown_lints)] +#![allow(unused_unit)] + +use bindings::component_sample::example::Adder; +use examples_common::get_wasm_module_path; + +extern crate alloc; +mod bindings { + hyperlight_component_macro::host_bindgen!("../component_sample/wit/component-world.wasm"); +} + +pub struct State {} +impl State { + pub fn new() -> Self { + State {} + } +} + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +impl bindings::component_sample::example::Host for State { + fn r#print(&mut self, message: alloc::string::String) { + assert_eq!("42", message); + println!("Logged from component: {message}"); + } + + fn r#host_function(&mut self, input: alloc::string::String) -> alloc::string::String { + format!("{input} and the host!") + } +} + +#[allow(refining_impl_trait)] +impl bindings::component_sample::example::ExampleImports for State { + type Host = State; + + fn r#host(&mut self) -> &mut Self { + self + } +} + +fn main() { + let state = State::new(); + let mut sb: hyperlight_wasm::ProtoWasmSandbox = hyperlight_wasm::SandboxBuilder::new() + .with_guest_input_buffer_size(70000000) + .with_guest_heap_size(200000000) + .with_guest_stack_size(100000000) + .build() + .unwrap(); + let rt = bindings::register_host_functions(&mut sb, state); + + let sb = sb.load_runtime().unwrap(); + + let mod_path = get_wasm_module_path("component_sample.wasm").unwrap(); + let sb = sb.load_module(mod_path).unwrap(); + + let mut wrapped = bindings::ExampleSandbox { sb, rt }; + + let instance = bindings::component_sample::example::ExampleExports::adder(&mut wrapped); + let result = instance.add(1, 2); + assert_eq!(3, result); + println!("Add result is {result}"); + let result = instance.add(4, 3); + assert_eq!(7, result); + println!("Add result is {result}"); + instance.do_something(42); + + let result = instance.call_host("Hello".to_string()); + assert_eq!("Hello from component and the host!", result); + print!("Host Component interaction: {result}") +} diff --git a/src/hyperlight_wasm_macro/src/wasmguest.rs b/src/hyperlight_wasm_macro/src/wasmguest.rs index a25eacf..89f6384 100644 --- a/src/hyperlight_wasm_macro/src/wasmguest.rs +++ b/src/hyperlight_wasm_macro/src/wasmguest.rs @@ -35,6 +35,7 @@ use hyperlight_component_util::hl::{ emit_hl_unmarshal_result, }; use hyperlight_component_util::{resource, rtypes}; +use proc_macro::Ident; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::ext::IdentExt; @@ -154,7 +155,6 @@ fn emit_export_extern_decl<'a, 'b, 'c>( .iter() .map(|p| rtypes::emit_value(s, &p.ty)) .collect::>(); - let rwt = rtypes::emit_func_result(s, &ft.result); let (pds, pus) = ft.params.iter().enumerate() .map(|(i, p)| { let id = kebab_to_var(p.name.name); @@ -166,7 +166,7 @@ fn emit_export_extern_decl<'a, 'b, 'c>( let get_instance = path.iter().map(|export| quote! { let instance_idx = Some(instance.get_export(&mut *store, instance_idx.as_ref(), #export).unwrap()); }).collect::>(); - let ret = format_ident!("ret"); + let (function_call, ret) = emit_wasm_function_call(s, &ft.result, pwts, pus); let marshal_result = emit_hl_marshal_result(s, ret.clone(), &ft.result); quote! { fn #n(fc: &::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec> { @@ -176,8 +176,7 @@ fn emit_export_extern_decl<'a, 'b, 'c>( let instance_idx = None; #(#get_instance;)* let func_idx = instance.get_export(&mut *store, instance_idx.as_ref(), #nlit).unwrap(); - let #ret = instance.get_typed_func::<(#(#pwts,)*), (#rwt,)>(&mut *store, func_idx)? - .call(&mut *store, (#(#pus,)*))?.0; + #function_call ::core::result::Result::Ok(::hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result::<&[u8]>(&#marshal_result)) } ::hyperlight_guest_bin::guest_function::register::register_function( @@ -206,6 +205,34 @@ fn emit_export_extern_decl<'a, 'b, 'c>( } } +fn emit_wasm_function_call( + s: &mut State, + result: &etypes::Result, + pwts: Vec, + pus: Vec, +) -> (TokenStream, proc_macro2::Ident) { + let ret = format_ident!("ret"); + + // if the result is empty we don't want a return result with `get_typed_func` + let rwt = match result { + etypes::Result::Named(rs) if rs.is_empty() => { + quote! { + instance.get_typed_func::<(#(#pwts,)*), ()>(&mut *store, func_idx)? + .call(&mut *store, (#(#pus,)*))?; + } + } + _ => { + let r = rtypes::emit_func_result(s, result); + quote! { + let #ret = instance.get_typed_func::<(#(#pwts,)*), ((#r,))>(&mut *store, func_idx)? + .call(&mut *store, (#(#pus,)*))?.0; + } + } + }; + + (rwt, ret) +} + // Emit code to register each export of the given instance with the // wasmtime linker, calling through Hyperlight. //