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