Skip to main content

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}