From a3f7bd45c0ba12138f7b7a9adbdeb92c47271b6c Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Thu, 21 Aug 2025 19:26:25 +0000 Subject: [PATCH] Fixes FP rounding issue and adds tests The issue was caused becuase cranelift compilation assume SSE2 even for the where as rust compiler doesn't include these instructions by default. This means when transitioning through wasmtime libcalls the parameters are lost since wasm is using SSE2 instructions and wasmtime isn't. The more advance SSE intructions require a seperate pr in HL core that is needed to enable them. Signed-off-by: James Sturtevant --- Justfile | 3 +- .../examples/c-component/main.rs | 68 +++++++++++++++++++ .../examples/helloworld/main.rs | 32 +++++++++ src/wasm_runtime/.cargo/config.toml | 2 + src/wasmsamples/RunWasm.c | 10 +++ src/wasmsamples/components/runcomponent.c | 8 +++ src/wasmsamples/components/runcomponent.wit | 1 + 7 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/hyperlight_wasm/examples/c-component/main.rs diff --git a/Justfile b/Justfile index 9d3adf8..9a18c5b 100644 --- a/Justfile +++ b/Justfile @@ -101,7 +101,8 @@ examples-ci target=default-target features="": (build-rust-wasm-examples target) cargo run {{ if features =="" {"--no-default-features --features kvm,mshv3"} 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 + {{ wit-world }} cargo run {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example component_example + {{ wit-world-c }} cargo run {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example c-component # warning, compares to and then OVERWRITES the given baseline bench-ci baseline target="release" features="": diff --git a/src/hyperlight_wasm/examples/c-component/main.rs b/src/hyperlight_wasm/examples/c-component/main.rs new file mode 100644 index 0000000..1a6de5f --- /dev/null +++ b/src/hyperlight_wasm/examples/c-component/main.rs @@ -0,0 +1,68 @@ +use examples_common::get_wasm_module_path; +use hyperlight_wasm::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 main() { + let state = State::new(); + let mut sandbox = SandboxBuilder::new() + .with_guest_input_buffer_size(70000000) + .with_guest_heap_size(200000000) + .with_guest_stack_size(100000000) + //.with_debugging_enabled(8080) + .build() + .unwrap(); + let rt = bindings::register_host_functions(&mut sandbox, state); + + let sb = sandbox.load_runtime().unwrap(); + + let mod_path = get_wasm_module_path("runcomponent.aot").unwrap(); + let sb = sb.load_module(mod_path).unwrap(); + + let mut wrapped = bindings::RuncomponentSandbox { sb, rt }; + let instance = bindings::example::runcomponent::RuncomponentExports::guest(&mut wrapped); + let echo = instance.echo("Hello World!".to_string()); + println!("{}", echo); + + let result = instance.round_to_nearest_int(1.331, 24.0); + println!("rounded result {}", result); + assert_eq!(result, 32); +} diff --git a/src/hyperlight_wasm/examples/helloworld/main.rs b/src/hyperlight_wasm/examples/helloworld/main.rs index fe21a02..730844d 100644 --- a/src/hyperlight_wasm/examples/helloworld/main.rs +++ b/src/hyperlight_wasm/examples/helloworld/main.rs @@ -83,5 +83,37 @@ fn main() -> Result<()> { ); } } + + let tests = [ + (1.331, 24.0, 32), + (std::f32::consts::PI, std::f32::consts::E, 9), + (-5.7, 10.3, -59), + (0.0, 0.0, 0), + (99.999, 0.001, 0), + (-std::f32::consts::PI, -2.86, 9), + (1.5, 1.5, 2), + ]; + let mut sandbox = SandboxBuilder::new().build()?; + sandbox + .register( + "GetTimeSinceBootMicrosecond", + get_time_since_boot_microsecond, + ) + .unwrap(); + let wasm_sandbox = sandbox.load_runtime()?; + let mod_path = get_wasm_module_path("RunWasm.aot")?; + let mut loaded_wasm_sandbox = wasm_sandbox.load_module(mod_path)?; + let snapshot = loaded_wasm_sandbox.snapshot()?; + + for (idx, case) in tests.iter().enumerate() { + let (a, b, expected_result): (f32, f32, i32) = *case; + let result: i32 = loaded_wasm_sandbox.call_guest_function("RoundToNearestInt", (a, b))?; + assert_eq!( + result, expected_result, + "RoundToInt test case {idx} failed: got {}, expected {}", + result, expected_result + ); + loaded_wasm_sandbox.restore(&snapshot)? + } Ok(()) } diff --git a/src/wasm_runtime/.cargo/config.toml b/src/wasm_runtime/.cargo/config.toml index a8e6c8e..fc984db 100644 --- a/src/wasm_runtime/.cargo/config.toml +++ b/src/wasm_runtime/.cargo/config.toml @@ -7,6 +7,8 @@ rustflags = [ "code-model=small", "-C", "link-args=-e entrypoint", + "-C", + "target-feature=-soft-float,+sse,+sse2" ] linker = "rust-lld" diff --git a/src/wasmsamples/RunWasm.c b/src/wasmsamples/RunWasm.c index 3d40682..6736e5c 100644 --- a/src/wasmsamples/RunWasm.c +++ b/src/wasmsamples/RunWasm.c @@ -20,6 +20,7 @@ limitations under the License. #include #include #include +#include int HostPrint(char* msg); // Implementation of this will be available in the native host @@ -129,3 +130,12 @@ int KeepCPUBusy(int ms) printf("Kept CPU busy for %d ms using %d iterations of fib(10) %d|toreach max = %d|", ms, iter, INT_MAX, INT_MAX-iter); return ms; } + +__attribute__((export_name("RoundToNearestInt"))) +int RoundToNearestInt(float a, float b) +{ + float c = a*b; + float r = lrintf(c); + printf("rounded answer: %f\n", r); + return r; +} \ No newline at end of file diff --git a/src/wasmsamples/components/runcomponent.c b/src/wasmsamples/components/runcomponent.c index 0d3d43e..c958a4d 100644 --- a/src/wasmsamples/components/runcomponent.c +++ b/src/wasmsamples/components/runcomponent.c @@ -1,6 +1,7 @@ #include "bindings/runcomponent.h" #include #include +#include void exports_example_runcomponent_guest_echo(runcomponent_string_t *msg, runcomponent_string_t *ret) { @@ -8,4 +9,11 @@ void exports_example_runcomponent_guest_echo(runcomponent_string_t *msg, runcomp ret->ptr = (uint8_t *) malloc(ret->len); memcpy(ret->ptr, msg->ptr, ret->len); runcomponent_string_free(msg); +} + +int32_t exports_example_runcomponent_guest_round_to_nearest_int(float a, float b) +{ + float c = a*b; + float r = lrintf(c); + return r; } \ No newline at end of file diff --git a/src/wasmsamples/components/runcomponent.wit b/src/wasmsamples/components/runcomponent.wit index 941fcbf..876efdb 100644 --- a/src/wasmsamples/components/runcomponent.wit +++ b/src/wasmsamples/components/runcomponent.wit @@ -2,6 +2,7 @@ package example:runcomponent; interface guest { echo: func(msg: string) -> string; + round-to-nearest-int: func(a: f32, b: f32) -> s32; } interface host {