hyperlight_component_util/component.rs
1/*
2Copyright 2025 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15 */
16
17//! Just enough component parsing support to get at the actual types
18
19use wasmparser::Payload::{
20 ComponentAliasSection, ComponentExportSection, ComponentTypeSection, Version,
21};
22use wasmparser::{
23 ComponentAlias, ComponentExternalKind, ComponentOuterAliasKind, ComponentType,
24 ComponentTypeRef, Payload,
25};
26
27use crate::etypes::{Component, Ctx, Defined};
28
29/// From [`wasmparser::ComponentExport`], elaborate a deftype_e as per
30/// the specification.
31fn 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.
83pub fn read_component_single_exported_type<'a>(
84 items: impl Iterator<Item = wasmparser::Result<Payload<'a>>>,
85 world_name: Option<String>,
86) -> Component<'a> {
87 let mut ctx = Ctx::new(None, false);
88 let mut selected_type_idx = None;
89 for x in items {
90 match x {
91 Ok(Version { num, encoding, .. }) => {
92 if encoding != wasmparser::Encoding::Component {
93 panic!("wasm file is not a component")
94 }
95 if num != 0xd {
96 panic!("unknown component encoding version 0x{:x}\n", num);
97 }
98 }
99 Ok(ComponentTypeSection(ts)) => {
100 for t in ts {
101 match t {
102 Ok(ComponentType::Component(ct)) => {
103 let ct_ = ctx.elab_component(&ct);
104 ctx.types.push(Defined::Component(ct_.unwrap()));
105 }
106 _ => panic!("non-component type"),
107 }
108 }
109 }
110 Ok(ComponentExportSection(es)) => {
111 for e in es {
112 match e {
113 Err(_) => panic!("invalid export section"),
114 Ok(ce) => {
115 if ce.kind == ComponentExternalKind::Type {
116 ctx.types.push(raw_type_export_type(&ctx, &ce).clone());
117
118 // picks the world index if world_name is passed in the proc_macro
119 // else picks the index of last type, exported by core module
120 if let Some(world) = world_name.as_ref() {
121 let name = ce.name.0;
122 if name.eq_ignore_ascii_case(world) {
123 selected_type_idx = Some(ctx.types.len() - 1);
124 }
125 } else {
126 selected_type_idx = Some(ctx.types.len() - 1);
127 }
128 }
129 }
130 }
131 }
132 }
133 Ok(ComponentAliasSection(r#as)) => {
134 for a in r#as {
135 match a {
136 Ok(ComponentAlias::InstanceExport {
137 kind: ComponentExternalKind::Type,
138 ..
139 })
140 | Ok(ComponentAlias::Outer {
141 kind: ComponentOuterAliasKind::Type,
142 ..
143 }) => {
144 panic!("Component outer type aliases are not supported")
145 }
146 // Anything else doesn't affect the index
147 // space that we are interested in, so we can
148 // safely ignore
149 _ => {}
150 }
151 }
152 }
153
154 // No other component section should be terribly relevant
155 // for us. We would not generally expect to find them in
156 // a file that just represents a type like this, but it
157 // seems like there are/may be a whole bunch of debugging
158 // custom sections, etc that might show up, so for now
159 // let's just ignore anything.
160 _ => {}
161 }
162 }
163
164 match selected_type_idx {
165 Some(n) => match ctx.types.into_iter().nth(n) {
166 Some(Defined::Component(c)) => c,
167 _ => panic!("final export is not component"),
168 },
169 None => match &world_name {
170 Some(name) => panic!("world '{}' not found in component", name),
171 None => panic!("no exported type"),
172 },
173 }
174}