Skip to content

Commit 6e6617d

Browse files
committed
fix: add wasmtime flags macro support
Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent 1b1af85 commit 6e6617d

5 files changed

Lines changed: 165 additions & 39 deletions

File tree

Justfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,13 @@ test-isolated target=default-target features="" :
238238
{{ 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
239239

240240
# runs integration tests
241-
test-integration target=default-target features="":
241+
test-integration target=default-target features="": (witguest-wit)
242242
@# run execute_on_heap test with feature "executable_heap" on (runs with off during normal tests)
243243
{{ 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
244244

245+
@# run component-util integration tests that depend on generated WIT inputs
246+
{{ cargo-cmd }} test -p hyperlight-component-util --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test wasmtime_guest_codegen
247+
245248
@# run the rest of the integration tests
246249
{{ 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 '*'
247250

src/hyperlight_component_util/src/emit.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,21 @@ pub fn kebab_to_imports_name(trait_name: &str) -> Ident {
763763
pub fn kebab_to_exports_name(trait_name: &str) -> Ident {
764764
format_ident!("{}Exports", kebab_to_type(trait_name))
765765
}
766+
/// Convert a kebab name to a SCREAMING_SNAKE_CASE identifier suitable
767+
/// for use as a constant in a `wasmtime::component::flags!` invocation.
768+
pub fn kebab_to_flags_const(n: &str) -> Ident {
769+
let s: String = n
770+
.chars()
771+
.map(|c| {
772+
if c == '-' {
773+
'_'
774+
} else {
775+
c.to_ascii_uppercase()
776+
}
777+
})
778+
.collect();
779+
format_ident!("{}", s)
780+
}
766781

767782
/// The kinds of names that a function associated with a resource in
768783
/// WIT can have

src/hyperlight_component_util/src/hl.rs

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use itertools::Itertools;
1818
use proc_macro2::{Ident, TokenStream};
1919
use quote::{format_ident, quote};
2020

21-
use crate::emit::{ResolvedBoundVar, State, kebab_to_cons, kebab_to_var};
21+
use crate::emit::{ResolvedBoundVar, State, kebab_to_cons, kebab_to_flags_const, kebab_to_var};
2222
use crate::etypes::{self, Defined, Handleable, Tyvar, Value};
2323
use crate::rtypes;
2424

@@ -91,16 +91,37 @@ pub fn emit_hl_unmarshal_toplevel_value(
9191
}
9292
Value::Flags(ns) => {
9393
let bytes = usize::div_ceil(ns.len(), 8);
94-
let fields = ns.iter().enumerate().map(|(i, n)| {
95-
let byte_offset = i / 8;
96-
let bit_offset = i % 8;
97-
let fieldid = kebab_to_var(n.name);
94+
if s.is_wasmtime_guest {
95+
let result_var = format_ident!("{}_flags", id);
96+
let fields = ns.iter().enumerate().map(|(i, n)| {
97+
let byte_offset = i / 8;
98+
let bit_offset = i % 8;
99+
let const_name = kebab_to_flags_const(n.name);
100+
quote! {
101+
if (#id[#byte_offset] >> #bit_offset) & 0x1 == 1 {
102+
#result_var |= #tname::#const_name;
103+
}
104+
}
105+
});
98106
quote! {
99-
#fieldid: (#id[#byte_offset] >> #bit_offset) & 0x1 == 1,
107+
{
108+
let mut #result_var = #tname::empty();
109+
#(#fields)*
110+
(#result_var, #bytes)
111+
}
112+
}
113+
} else {
114+
let fields = ns.iter().enumerate().map(|(i, n)| {
115+
let byte_offset = i / 8;
116+
let bit_offset = i % 8;
117+
let fieldid = kebab_to_var(n.name);
118+
quote! {
119+
#fieldid: (#id[#byte_offset] >> #bit_offset) & 0x1 == 1,
120+
}
121+
});
122+
quote! {
123+
(#tname { #(#fields)* }, #bytes)
100124
}
101-
});
102-
quote! {
103-
(#tname { #(#fields)* }, #bytes)
104125
}
105126
}
106127
Value::Variant(vcs) => {
@@ -428,22 +449,42 @@ pub fn emit_hl_marshal_toplevel_value(
428449
}
429450
Value::Flags(ns) => {
430451
let bytes = usize::div_ceil(ns.len(), 8);
431-
let fields = ns
432-
.iter()
433-
.enumerate()
434-
.map(|(i, n)| {
435-
let byte_offset = i / 8;
436-
let bit_offset = i % 8;
437-
let fieldid = kebab_to_var(n.name);
438-
quote! {
439-
bytes[#byte_offset] |= (if #id.#fieldid { 1 } else { 0 }) << #bit_offset;
440-
}
441-
})
442-
.collect::<Vec<_>>();
443-
quote! {
444-
let mut bytes = [0; #bytes];
445-
#(#fields)*
446-
alloc::vec::Vec::from(bytes)
452+
if s.is_wasmtime_guest {
453+
let fields = ns
454+
.iter()
455+
.enumerate()
456+
.map(|(i, n)| {
457+
let byte_offset = i / 8;
458+
let bit_offset = i % 8;
459+
let const_name = kebab_to_flags_const(n.name);
460+
quote! {
461+
bytes[#byte_offset] |= (if #id.contains(#tname::#const_name) { 1 } else { 0 }) << #bit_offset;
462+
}
463+
})
464+
.collect::<Vec<_>>();
465+
quote! {
466+
let mut bytes = [0; #bytes];
467+
#(#fields)*
468+
alloc::vec::Vec::from(bytes)
469+
}
470+
} else {
471+
let fields = ns
472+
.iter()
473+
.enumerate()
474+
.map(|(i, n)| {
475+
let byte_offset = i / 8;
476+
let bit_offset = i % 8;
477+
let fieldid = kebab_to_var(n.name);
478+
quote! {
479+
bytes[#byte_offset] |= (if #id.#fieldid { 1 } else { 0 }) << #bit_offset;
480+
}
481+
})
482+
.collect::<Vec<_>>();
483+
quote! {
484+
let mut bytes = [0; #bytes];
485+
#(#fields)*
486+
alloc::vec::Vec::from(bytes)
487+
}
447488
}
448489
}
449490
Value::Variant(vcs) => {

src/hyperlight_component_util/src/rtypes.rs

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ use quote::{format_ident, quote};
2424
use syn::Ident;
2525

2626
use crate::emit::{
27-
FnName, ResourceItemName, State, WitName, kebab_to_cons, kebab_to_exports_name, kebab_to_fn,
28-
kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var,
29-
split_wit_name,
27+
FnName, ResourceItemName, State, WitName, kebab_to_cons, kebab_to_exports_name,
28+
kebab_to_flags_const, kebab_to_fn, kebab_to_getter, kebab_to_imports_name, kebab_to_namespace,
29+
kebab_to_type, kebab_to_var, split_wit_name,
3030
};
3131
use crate::etypes::{
3232
self, Component, Defined, ExternDecl, ExternDesc, Func, Handleable, ImportExport, Instance,
@@ -409,21 +409,41 @@ fn emit_value_toplevel(s: &mut State, v: Option<u32>, id: Ident, vt: &Value) ->
409409
}
410410
}
411411
Value::Flags(ns) => {
412-
let (vs, toks) = gather_needed_vars(s, v, |_| {
413-
let ns = ns
412+
if s.is_wasmtime_guest {
413+
let flags = ns
414414
.iter()
415415
.map(|n| {
416416
let orig_name = n.name;
417-
let id = kebab_to_var(orig_name);
418-
quote! { pub #id: bool }
417+
let const_name = kebab_to_flags_const(orig_name);
418+
quote! {
419+
#[component(name = #orig_name)]
420+
const #const_name;
421+
}
419422
})
420423
.collect::<Vec<_>>();
421-
quote! { #(#ns),* }
422-
});
423-
let vs = emit_type_defn_var_list(s, vs);
424-
quote! {
425-
#[derive(Debug, Clone, PartialEq)]
426-
pub struct #id #vs { #toks }
424+
quote! {
425+
::wasmtime::component::flags! {
426+
#id {
427+
#(#flags)*
428+
}
429+
}
430+
}
431+
} else {
432+
let (vs, toks) = gather_needed_vars(s, v, |_| {
433+
let ns = ns
434+
.iter()
435+
.map(|n| {
436+
let id = kebab_to_var(n.name);
437+
quote! { pub #id: bool }
438+
})
439+
.collect::<Vec<_>>();
440+
quote! { #(#ns),* }
441+
});
442+
let vs = emit_type_defn_var_list(s, vs);
443+
quote! {
444+
#[derive(Debug, Clone, PartialEq)]
445+
pub struct #id #vs { #toks }
446+
}
427447
}
428448
}
429449
Value::Variant(vcs) => {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright 2026 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
use hyperlight_component_util::{emit, guest, rtypes, util};
18+
19+
#[test]
20+
fn wasmtime_guest_codegen_emits_wasmtime_flags_macro() {
21+
let generated = util::read_wit_type_from_file(
22+
"../tests/rust_guests/witguest/interface.wasm",
23+
None,
24+
|kebab_name, ct| {
25+
// Mirrors the hyperlight-wasm guest-bindgen expansion path:
26+
// https://github.com/hyperlight-dev/hyperlight-wasm/blob/81e72f5920ebc23584097abfe24d05a40bf084cc/src/hyperlight_wasm_macro/src/lib.rs#L37-L38
27+
emit::run_state(true, true, |s| {
28+
rtypes::emit_toplevel(s, &kebab_name, ct);
29+
guest::emit_toplevel(s, &kebab_name, ct);
30+
})
31+
},
32+
);
33+
let generated: syn::File = syn::parse2(generated).expect("generated Rust should parse");
34+
let generated = prettyplease::unparse(&generated);
35+
36+
assert!(generated.contains("::wasmtime::component::flags! {"));
37+
assert!(generated.contains("Smallflags {"));
38+
assert!(generated.contains("\"flag-a\""));
39+
assert!(generated.contains("const FLAG_A;"));
40+
assert!(generated.contains("\"flag-b\""));
41+
assert!(generated.contains("const FLAG_B;"));
42+
assert!(generated.contains("\"flag-c\""));
43+
assert!(generated.contains("const FLAG_C;"));
44+
assert!(!generated.contains("pub flag_a: bool"));
45+
assert!(!generated.contains("pub flag_b: bool"));
46+
assert!(!generated.contains("pub flag_c: bool"));
47+
}

0 commit comments

Comments
 (0)