Skip to content

Commit ff92209

Browse files
committed
[component_util] Add Component Model elaboration/bindgen utilities
This commit adds a general implementation of WebAssembly Component Model type elaboration, closely derived from the formal specification. It also adds a number of functions which can generate Rust binding code from appropriately-structured (roughly: WIT-like) component types. Signed-off-by: Lucy Menon <[email protected]>
1 parent e2ff7ed commit ff92209

File tree

19 files changed

+6193
-2
lines changed

19 files changed

+6193
-2
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ members = [
1111
"src/hyperlight_host",
1212
"src/hyperlight_guest_capi",
1313
"src/hyperlight_testing",
14-
"fuzz",
14+
"fuzz",
1515
"src/hyperlight_guest_bin",
16+
"src/hyperlight_component_util",
1617
]
1718
# Guests have custom linker flags, so we need to exclude them from the workspace
1819
exclude = [
@@ -36,6 +37,7 @@ hyperlight-host = { path = "src/hyperlight_host", version = "0.5.1", default-fea
3637
hyperlight-guest = { path = "src/hyperlight_guest", version = "0.5.1", default-features = false }
3738
hyperlight-guest-bin = { path = "src/hyperlight_guest_bin", version = "0.5.1", default-features = false }
3839
hyperlight-testing = { path = "src/hyperlight_testing", default-features = false }
40+
hyperlight-component-util = { path = "src/hyperlight_component_util" }
3941

4042
[workspace.lints.rust]
4143
unsafe_op_in_unsafe_fn = "deny"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "hyperlight-component-util"
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+
Shared implementation for the procedural macros that generate Hyperlight host and guest bindings from component types
12+
"""
13+
14+
[lib]
15+
name = "hyperlight_component_util"
16+
17+
[dependencies]
18+
wasmparser = { version = "0.224.0" }
19+
quote = { version = "1.0.38" }
20+
proc-macro2 = { version = "1.0.93" }
21+
syn = { version = "2.0.96" }
22+
itertools = { version = "0.14.0" }
23+
prettyplease = { version = "0.2.31" }
24+
log = { version = "0.4" }
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
//! Just enough component parsing support to get at the actual types
18+
19+
use wasmparser::Payload::{
20+
ComponentAliasSection, ComponentExportSection, ComponentTypeSection, Version,
21+
};
22+
use wasmparser::{
23+
ComponentAlias, ComponentExternalKind, ComponentOuterAliasKind, ComponentType,
24+
ComponentTypeRef, Payload,
25+
};
26+
27+
use crate::etypes::{Component, Ctx, Defined};
28+
29+
/// From [`wasmparser::ComponentExport`], elaborate a deftype_e as per
30+
/// the specification.
31+
fn raw_type_export_type<'p, 'a, 'c>(
32+
ctx: &'c Ctx<'p, 'a>,
33+
ce: &'c wasmparser::ComponentExport<'a>,
34+
) -> &'c Defined<'a> {
35+
match ce.ty {
36+
Some(ComponentTypeRef::Component(n)) => match ctx.types.get(n as usize) {
37+
Some(t) => t,
38+
None => {
39+
panic!("malformed component type export: ascription does not refer to a type");
40+
}
41+
},
42+
Some(_) => {
43+
panic!(
44+
"malformed component type export: ascription does not refer to a component type"
45+
);
46+
}
47+
None => match ctx.types.get(ce.index as usize) {
48+
Some(t) => t,
49+
None => {
50+
panic!("malformed component type export: does not refer to a type");
51+
}
52+
},
53+
}
54+
}
55+
56+
/// Find the last exported type in a component, since in wasm-encoded
57+
/// WIT this is typically the main world to use. This is a very
58+
/// special case that just lets us pull a type out of a value-level
59+
///
60+
/// Precondition: The given iterator is
61+
/// - a component, whose
62+
/// - encoding version is 0xd exactly, and who
63+
/// - does not contain any value-level aliases, and whose
64+
/// - final export is a component type
65+
///
66+
/// Anything that is a "binary-encoded WIT" produced by a recent
67+
/// toolchain should satisfy this. On violation, this function will
68+
/// panic with an error message.
69+
///
70+
/// The reason we look for the last export is that the WIT binary
71+
/// encoding encodes any instance type imported/exported from the main
72+
/// component (a/k/a WIT world) as a type export, followed by a final
73+
/// type export for the type of the main component/world.
74+
///
75+
/// TODO: Allow the user to specify a specific export to use (or a WIT
76+
/// world name), since current WIT tooling can generate encoded
77+
/// packages with multiple component types in them.
78+
///
79+
/// TODO: Encode even more assumptions about WIT package structure
80+
/// (which are already there in rtypes/host/guest) and allow looking
81+
/// for a specific named world, instead of simply grabbing the last
82+
/// export.
83+
pub fn read_component_single_exported_type<'a>(
84+
items: impl Iterator<Item = wasmparser::Result<Payload<'a>>>,
85+
) -> Component<'a> {
86+
let mut ctx = Ctx::new(None, false);
87+
let mut last_idx = None;
88+
for x in items {
89+
match x {
90+
Ok(Version { num, encoding, .. }) => {
91+
if encoding != wasmparser::Encoding::Component {
92+
panic!("wasm file is not a component")
93+
}
94+
if num != 0xd {
95+
panic!("unknown component encoding version 0x{:x}\n", num);
96+
}
97+
}
98+
Ok(ComponentTypeSection(ts)) => {
99+
for t in ts {
100+
match t {
101+
Ok(ComponentType::Component(ct)) => {
102+
let ct_ = ctx.elab_component(&ct);
103+
ctx.types.push(Defined::Component(ct_.unwrap()));
104+
}
105+
_ => panic!("non-component type"),
106+
}
107+
}
108+
}
109+
Ok(ComponentExportSection(es)) => {
110+
for e in es {
111+
match e {
112+
Err(_) => panic!("invalid export section"),
113+
Ok(ce) => {
114+
if ce.kind == ComponentExternalKind::Type {
115+
last_idx = Some(ctx.types.len());
116+
ctx.types.push(raw_type_export_type(&ctx, &ce).clone());
117+
}
118+
}
119+
}
120+
}
121+
}
122+
Ok(ComponentAliasSection(r#as)) => {
123+
for a in r#as {
124+
match a {
125+
Ok(ComponentAlias::InstanceExport {
126+
kind: ComponentExternalKind::Type,
127+
..
128+
})
129+
| Ok(ComponentAlias::Outer {
130+
kind: ComponentOuterAliasKind::Type,
131+
..
132+
}) => {
133+
panic!("Component outer type aliases are not supported")
134+
}
135+
// Anything else doesn't affect the index
136+
// space that we are interested in, so we can
137+
// safely ignore
138+
_ => {}
139+
}
140+
}
141+
}
142+
143+
// No other component section should be terribly relevant
144+
// for us. We would not generally expect to find them in
145+
// a file that just represents a type like this, but it
146+
// seems like there are/may be a whole bunch of debugging
147+
// custom sections, etc that might show up, so for now
148+
// let's just ignore anything.
149+
_ => {}
150+
}
151+
}
152+
match last_idx {
153+
None => panic!("no exported type"),
154+
Some(n) => match ctx.types.into_iter().nth(n) {
155+
Some(Defined::Component(c)) => c,
156+
_ => panic!("final export is not component"),
157+
},
158+
}
159+
}

0 commit comments

Comments
 (0)