Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,13 @@ test-isolated target=default-target features="" :
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F function_call_metrics," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- metrics::tests::test_metrics_are_emitted --exact

# runs integration tests
test-integration target=default-target features="":
test-integration target=default-target features="": (witguest-wit)
@# run execute_on_heap test with feature "executable_heap" on (runs with off during normal tests)
{{ cargo-cmd }} test {{ if features =="" {"--features executable_heap"} else if features=="no-default-features" {"--no-default-features --features executable_heap"} else {"--no-default-features -F executable_heap," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test integration_test execute_on_heap

@# run component-util integration tests that depend on generated WIT inputs
{{ cargo-cmd }} test -p hyperlight-component-util --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test wasmtime_guest_codegen

@# run the rest of the integration tests
{{ cargo-cmd }} test -p hyperlight-host {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F " + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test '*'

Expand Down
15 changes: 15 additions & 0 deletions src/hyperlight_component_util/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,21 @@ pub fn kebab_to_imports_name(trait_name: &str) -> Ident {
pub fn kebab_to_exports_name(trait_name: &str) -> Ident {
format_ident!("{}Exports", kebab_to_type(trait_name))
}
/// Convert a kebab name to a SCREAMING_SNAKE_CASE identifier suitable
/// for use as a constant in a `wasmtime::component::flags!` invocation.
pub fn kebab_to_flags_const(n: &str) -> Ident {
let s: String = n
.chars()
.map(|c| {
if c == '-' {
'_'
} else {
c.to_ascii_uppercase()
}
})
.collect();
format_ident!("{}", s)
}

/// The kinds of names that a function associated with a resource in
/// WIT can have
Expand Down
43 changes: 35 additions & 8 deletions src/hyperlight_component_util/src/hl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use itertools::Itertools;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};

use crate::emit::{ResolvedBoundVar, State, kebab_to_cons, kebab_to_var};
use crate::emit::{ResolvedBoundVar, State, kebab_to_cons, kebab_to_flags_const, kebab_to_var};
use crate::etypes::{self, Defined, Handleable, Tyvar, Value};
use crate::rtypes;

Expand Down Expand Up @@ -91,16 +91,37 @@ pub fn emit_hl_unmarshal_toplevel_value(
}
Value::Flags(ns) => {
let bytes = usize::div_ceil(ns.len(), 8);
let result_var = format_ident!("{}_flags", id);
let fields = ns.iter().enumerate().map(|(i, n)| {
let byte_offset = i / 8;
let bit_offset = i % 8;
let fieldid = kebab_to_var(n.name);
quote! {
#fieldid: (#id[#byte_offset] >> #bit_offset) & 0x1 == 1,
let is_set = quote! { (#id[#byte_offset] >> #bit_offset) & 0x1 == 1 };
if s.is_wasmtime_guest {
let const_name = kebab_to_flags_const(n.name);
quote! {
if #is_set {
#result_var |= #tname::#const_name;
}
}
} else {
let fieldid = kebab_to_var(n.name);
quote! {
#fieldid: #is_set,
}
}
});
quote! {
(#tname { #(#fields)* }, #bytes)
if s.is_wasmtime_guest {
quote! {
{
let mut #result_var = #tname::empty();
#(#fields)*
(#result_var, #bytes)
}
}
} else {
quote! {
(#tname { #(#fields)* }, #bytes)
}
}
}
Value::Variant(vcs) => {
Expand Down Expand Up @@ -434,9 +455,15 @@ pub fn emit_hl_marshal_toplevel_value(
.map(|(i, n)| {
let byte_offset = i / 8;
let bit_offset = i % 8;
let fieldid = kebab_to_var(n.name);
let is_set = if s.is_wasmtime_guest {
let const_name = kebab_to_flags_const(n.name);
quote! { #id.contains(#tname::#const_name) }
} else {
let fieldid = kebab_to_var(n.name);
quote! { #id.#fieldid }
};
quote! {
bytes[#byte_offset] |= (if #id.#fieldid { 1 } else { 0 }) << #bit_offset;
bytes[#byte_offset] |= (if #is_set { 1 } else { 0 }) << #bit_offset;
}
})
.collect::<Vec<_>>();
Expand Down
46 changes: 33 additions & 13 deletions src/hyperlight_component_util/src/rtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ use quote::{format_ident, quote};
use syn::Ident;

use crate::emit::{
FnName, ResourceItemName, State, WitName, kebab_to_cons, kebab_to_exports_name, kebab_to_fn,
kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var,
split_wit_name,
FnName, ResourceItemName, State, WitName, kebab_to_cons, kebab_to_exports_name,
kebab_to_flags_const, kebab_to_fn, kebab_to_getter, kebab_to_imports_name, kebab_to_namespace,
kebab_to_type, kebab_to_var, split_wit_name,
};
use crate::etypes::{
self, Component, Defined, ExternDecl, ExternDesc, Func, Handleable, ImportExport, Instance,
Expand Down Expand Up @@ -409,21 +409,41 @@ fn emit_value_toplevel(s: &mut State, v: Option<u32>, id: Ident, vt: &Value) ->
}
}
Value::Flags(ns) => {
let (vs, toks) = gather_needed_vars(s, v, |_| {
let ns = ns
if s.is_wasmtime_guest {
let flags = ns
.iter()
.map(|n| {
let orig_name = n.name;
let id = kebab_to_var(orig_name);
quote! { pub #id: bool }
let const_name = kebab_to_flags_const(orig_name);
quote! {
#[component(name = #orig_name)]
const #const_name;
}
})
.collect::<Vec<_>>();
quote! { #(#ns),* }
});
let vs = emit_type_defn_var_list(s, vs);
quote! {
#[derive(Debug, Clone, PartialEq)]
pub struct #id #vs { #toks }
quote! {
::wasmtime::component::flags! {
#id {
#(#flags)*
}
}
}
} else {
let (vs, toks) = gather_needed_vars(s, v, |_| {
let ns = ns
.iter()
.map(|n| {
let id = kebab_to_var(n.name);
quote! { pub #id: bool }
})
.collect::<Vec<_>>();
quote! { #(#ns),* }
});
let vs = emit_type_defn_var_list(s, vs);
quote! {
#[derive(Debug, Clone, PartialEq)]
pub struct #id #vs { #toks }
}
}
}
Value::Variant(vcs) => {
Expand Down
47 changes: 47 additions & 0 deletions src/hyperlight_component_util/tests/wasmtime_guest_codegen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2026 The Hyperlight Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

use hyperlight_component_util::{emit, guest, rtypes, util};

#[test]
fn wasmtime_guest_codegen_emits_wasmtime_flags_macro() {
let generated = util::read_wit_type_from_file(
"../tests/rust_guests/witguest/interface.wasm",
None,
|kebab_name, ct| {
// Mirrors the hyperlight-wasm guest-bindgen expansion path:
// https://github.com/hyperlight-dev/hyperlight-wasm/blob/81e72f5920ebc23584097abfe24d05a40bf084cc/src/hyperlight_wasm_macro/src/lib.rs#L37-L38
emit::run_state(true, true, |s| {
rtypes::emit_toplevel(s, &kebab_name, ct);
guest::emit_toplevel(s, &kebab_name, ct);
})
},
);
let generated: syn::File = syn::parse2(generated).expect("generated Rust should parse");
let generated = prettyplease::unparse(&generated);

assert!(generated.contains("::wasmtime::component::flags! {"));
assert!(generated.contains("Smallflags {"));
assert!(generated.contains("\"flag-a\""));
assert!(generated.contains("const FLAG_A;"));
assert!(generated.contains("\"flag-b\""));
assert!(generated.contains("const FLAG_B;"));
assert!(generated.contains("\"flag-c\""));
assert!(generated.contains("const FLAG_C;"));
assert!(!generated.contains("pub flag_a: bool"));
assert!(!generated.contains("pub flag_b: bool"));
assert!(!generated.contains("pub flag_c: bool"));
Comment thread
jsturtevant marked this conversation as resolved.
}
Loading