hyperlight-component-util 0.19.0

Shared implementation for the procedural macros that generate Hyperlight host and guest bindings from component types
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 The Hyperlight Authors.

//! Just enough component parsing support to get at the actual types

use wasmparser::Payload::{
    ComponentAliasSection, ComponentExportSection, ComponentTypeSection, Version,
};
use wasmparser::{
    ComponentAlias, ComponentExternName, ComponentExternalKind, ComponentOuterAliasKind,
    ComponentType, ComponentTypeRef, Payload,
};

use crate::etypes::{Component, Ctx, Defined};

/// From [`wasmparser::ComponentExport`], elaborate a deftype_e as per
/// the specification.
fn raw_type_export_type<'p, 'a, 'c>(
    ctx: &'c Ctx<'p, 'a>,
    ce: &'c wasmparser::ComponentExport<'a>,
) -> &'c Defined<'a> {
    match ce.ty {
        Some(ComponentTypeRef::Component(n)) => match ctx.types.get(n as usize) {
            Some(t) => t,
            None => {
                panic!("malformed component type export: ascription does not refer to a type");
            }
        },
        Some(_) => {
            panic!(
                "malformed component type export: ascription does not refer to a component type"
            );
        }
        None => match ctx.types.get(ce.index as usize) {
            Some(t) => t,
            None => {
                panic!("malformed component type export: does not refer to a type");
            }
        },
    }
}

/// Find the last exported type in a component, since in wasm-encoded
/// WIT this is typically the main world to use.  This is a very
/// special case that just lets us pull a type out of a value-level
///
/// Precondition: The given iterator is
/// - a component, whose
/// - encoding version is 0xd exactly, and who
/// - does not contain any value-level aliases, and whose
/// - final export is a component type
///
/// Anything that is a "binary-encoded WIT" produced by a recent
/// toolchain should satisfy this. On violation, this function will
/// panic with an error message.
///
/// The reason we look for the last export is that the WIT binary
/// encoding encodes any instance type imported/exported from the main
/// component (a/k/a WIT world) as a type export, followed by a final
/// type export for the type of the main component/world.
///
/// TODO: Allow the user to specify a specific export to use (or a WIT
/// world name), since current WIT tooling can generate encoded
/// packages with multiple component types in them.
///
/// TODO: Encode even more assumptions about WIT package structure
/// (which are already there in rtypes/host/guest) and allow looking
/// for a specific named world, instead of simply grabbing the last
/// export.
pub fn read_component_single_exported_type<'a>(
    items: impl Iterator<Item = wasmparser::Result<Payload<'a>>>,
    world_name: Option<String>,
) -> Component<'a> {
    let mut ctx = Ctx::new(None, false);
    let mut selected_type_idx = None;
    for x in items {
        match x {
            Ok(Version { num, encoding, .. }) => {
                if encoding != wasmparser::Encoding::Component {
                    panic!("wasm file is not a component")
                }
                if num != 0xd {
                    panic!("unknown component encoding version 0x{:x}\n", num);
                }
            }
            Ok(ComponentTypeSection(ts)) => {
                for t in ts {
                    match t {
                        Ok(ComponentType::Component(ct)) => {
                            let ct_ = ctx.elab_component(&ct);
                            ctx.types.push(Defined::Component(ct_.unwrap()));
                        }
                        _ => panic!("non-component type"),
                    }
                }
            }
            Ok(ComponentExportSection(es)) => {
                for e in es {
                    match e {
                        Err(_) => panic!("invalid export section"),
                        Ok(ce) => {
                            if ce.kind == ComponentExternalKind::Type {
                                ctx.types.push(raw_type_export_type(&ctx, &ce).clone());

                                // picks the world index if world_name is passed in the proc_macro
                                // else picks the index of last type, exported by core module
                                if let Some(world) = world_name.as_ref() {
                                    let ComponentExternName { name, .. } = ce.name;
                                    if name.eq_ignore_ascii_case(world) {
                                        selected_type_idx = Some(ctx.types.len() - 1);
                                    }
                                } else {
                                    selected_type_idx = Some(ctx.types.len() - 1);
                                }
                            }
                        }
                    }
                }
            }
            Ok(ComponentAliasSection(r#as)) => {
                for a in r#as {
                    match a {
                        Ok(ComponentAlias::InstanceExport {
                            kind: ComponentExternalKind::Type,
                            ..
                        })
                        | Ok(ComponentAlias::Outer {
                            kind: ComponentOuterAliasKind::Type,
                            ..
                        }) => {
                            panic!("Component outer type aliases are not supported")
                        }
                        // Anything else doesn't affect the index
                        // space that we are interested in, so we can
                        // safely ignore
                        _ => {}
                    }
                }
            }

            // No other component section should be terribly relevant
            // for us.  We would not generally expect to find them in
            // a file that just represents a type like this, but it
            // seems like there are/may be a whole bunch of debugging
            // custom sections, etc that might show up, so for now
            // let's just ignore anything.
            _ => {}
        }
    }

    match selected_type_idx {
        Some(n) => match ctx.types.into_iter().nth(n) {
            Some(Defined::Component(c)) => c,
            _ => panic!("final export is not component"),
        },
        None => match &world_name {
            Some(name) => panic!("world '{}' not found in component", name),
            None => panic!("no exported type"),
        },
    }
}