Skip to content

Commit e23332d

Browse files
committed
[component_macro] Add host_bindgen!() and guest_bindgen!()
This adds a proc-macro crate that exposes the bindings code generation from hyperlight_component_util as macros suitable for using WIT to define a hyperlight host<->guest interface. Signed-off-by: Lucy Menon <[email protected]>
1 parent ff92209 commit e23332d

File tree

4 files changed

+138
-0
lines changed

4 files changed

+138
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ members = [
1414
"fuzz",
1515
"src/hyperlight_guest_bin",
1616
"src/hyperlight_component_util",
17+
"src/hyperlight_component_macro",
1718
]
1819
# Guests have custom linker flags, so we need to exclude them from the workspace
1920
exclude = [
@@ -38,6 +39,7 @@ hyperlight-guest = { path = "src/hyperlight_guest", version = "0.5.1", default-f
3839
hyperlight-guest-bin = { path = "src/hyperlight_guest_bin", version = "0.5.1", default-features = false }
3940
hyperlight-testing = { path = "src/hyperlight_testing", default-features = false }
4041
hyperlight-component-util = { path = "src/hyperlight_component_util" }
42+
hyperlight-component-macro = { path = "src/hyperlight_component_macro" }
4143

4244
[workspace.lints.rust]
4345
unsafe_op_in_unsafe_fn = "deny"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "hyperlight-component-macro"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
license.workspace = true
7+
homepage.workspace = true
8+
repository.workspace = true
9+
readme.workspace = true
10+
description = """
11+
Procedural macros to generate Hyperlight host and guest bindings from component types
12+
"""
13+
14+
[lib]
15+
name = "hyperlight_component_macro"
16+
proc-macro = true
17+
18+
[dependencies]
19+
wasmparser = { version = "0.224.0" }
20+
quote = { version = "1.0.38" }
21+
proc-macro2 = { version = "1.0.93" }
22+
syn = { version = "2.0.96" }
23+
itertools = { version = "0.14.0" }
24+
prettyplease = { version = "0.2.31" }
25+
hyperlight-component-util = { workspace = true }
26+
env_logger = { version = "0.11.8" }
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
Copyright 2025 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+
//! # Component-model bindgen macros
18+
//!
19+
//! These macros make it easy to use Wasm Component Model types
20+
//! (e.g. those described by WIT) to describe the interface between a
21+
//! Hyperlight host and guest.
22+
//!
23+
//! For both host and guest bindings, bindings generation takes in a
24+
//! binary-encoded wasm component, which should have roughly the
25+
//! structure of a binary-encoded WIT (in particular, component
26+
//! import/export kebab-names should have `wit:package/name` namespace
27+
//! structure, and the same two-level convention for wrapping a
28+
//! component type into an actual component should be adhered to). If
29+
//! you are using WIT as the input, it is easy to build such a file
30+
//! via `wasm-tools component wit -w -o file.wasm file.wit`.
31+
//!
32+
//! Both macros can take the path to such a file as a parameter, or,
33+
//! if one is not provided, will fall back to using the path in the
34+
//! environment variable `$WIT_WORLD`. A relative path provided either way
35+
//! will be resolved relative to `$CARGO_MANIFEST_DIR`.
36+
//!
37+
//! ## Debugging
38+
//!
39+
//! The generated code can be examined by setting the environment
40+
//! variable `$HYPERLIGHT_COMPONENT_MACRO_DEBUG=/path/to/file.rs`,
41+
//! which will result in the generated code being written to that
42+
//! file, which is then included back into the Rust source.
43+
//!
44+
//! The macros also can be configured to output a great deal of debug
45+
//! information about the internal elaboration and codegen
46+
//! phases. This is logged via the `log` and `env_logger` crates, so
47+
//! setting `RUST_LOG=debug` before running the compiler should
48+
//! suffice to produce this output.
49+
50+
extern crate proc_macro;
51+
52+
use hyperlight_component_util::*;
53+
54+
/// Create host bindings for the wasm component type in the file
55+
/// passed in (or `$WIT_WORLD`, if nothing is passed in). This will
56+
/// produce all relevant types and trait implementations for the
57+
/// component type, as well as functions allowing the component to be
58+
/// instantiated inside a sandbox.
59+
///
60+
/// This includes both a primitive `register_host_functions`, which can
61+
/// be used to directly register the host functions on any sandbox
62+
/// (and which can easily be used with Hyperlight-Wasm), as well as an
63+
/// `instantiate()` method on the component trait that makes
64+
/// instantiating the sandbox particularly ergonomic in core
65+
/// Hyperlight.
66+
#[proc_macro]
67+
pub fn host_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
68+
env_logger::init();
69+
let path: Option<syn::LitStr> = syn::parse_macro_input!(input as Option<syn::LitStr>);
70+
let path = path
71+
.map(|x| x.value().into())
72+
.unwrap_or_else(|| std::env::var_os("WIT_WORLD").unwrap());
73+
util::read_wit_type_from_file(path, |kebab_name, ct| {
74+
let decls = emit::run_state(false, false, |s| {
75+
rtypes::emit_toplevel(s, &kebab_name, ct);
76+
host::emit_toplevel(s, &kebab_name, ct);
77+
});
78+
util::emit_decls(decls).into()
79+
})
80+
}
81+
82+
/// Create the hyperlight_guest_init() function (which should be
83+
/// called in hyperlight_main()) for the wasm component type in the
84+
/// file passed in (or `$WIT_WORLD`, if nothing is passed in). This
85+
/// function registers Hyperlight functions for component exports
86+
/// (which are implemented by calling into the trait provided) and
87+
/// implements the relevant traits for a trivial Host type (by calling
88+
/// into the Hyperlight host).
89+
#[proc_macro]
90+
pub fn guest_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
91+
env_logger::init();
92+
let path: Option<syn::LitStr> = syn::parse_macro_input!(input as Option<syn::LitStr>);
93+
let path = path
94+
.map(|x| x.value().into())
95+
.unwrap_or_else(|| std::env::var_os("WIT_WORLD").unwrap());
96+
util::read_wit_type_from_file(path, |kebab_name, ct| {
97+
let decls = emit::run_state(true, false, |s| {
98+
// Emit type/trait definitions for all instances in the world
99+
rtypes::emit_toplevel(s, &kebab_name, ct);
100+
// Emit the host/guest function registrations
101+
guest::emit_toplevel(s, &kebab_name, ct);
102+
});
103+
// Use util::emit_decls() to choose between emitting the token
104+
// stream directly and emitting an include!() pointing at a
105+
// temporary file, depending on whether the user has requested
106+
// a debug temporary file be created.
107+
util::emit_decls(decls).into()
108+
})
109+
}

src/hyperlight_host/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ metrics-util = "0.19.1"
102102
metrics-exporter-prometheus = "0.17.0"
103103
tracing-tracy = "0.11.4"
104104
serde_json = "1.0"
105+
hyperlight-component-macro = { workspace = true }
105106

106107
[target.'cfg(windows)'.dev-dependencies]
107108
windows = { version = "0.61", features = [

0 commit comments

Comments
 (0)