use crate::ir::component::idx_spaces::Space;
use crate::ir::component::refs::{Depth, GetCompRefs, GetItemRef, GetTypeRefs, IndexedRef};
use crate::ir::component::visitor::utils::{TypeBodyDecls, VisitCtxInner};
use crate::ir::component::visitor::{ResolvedItem, VisitCtx};
use crate::Component;
use std::collections::{HashMap, HashSet};
use wasmparser::{
ComponentAlias, ComponentDefinedType, ComponentExport, ComponentExternalKind,
ComponentFuncType, ComponentInstance, ComponentOuterAliasKind, ComponentType, ComponentTypeRef,
ComponentValType, InstanceTypeDeclaration, PrimitiveValType, TypeBounds,
};
#[derive(Debug, Clone)]
pub enum ConcreteType<'a> {
Instance {
funcs: Vec<(&'a str, ConcreteFuncType<'a>)>,
type_exports: Vec<(&'a str, ConcreteValType<'a>)>,
},
Func(ConcreteFuncType<'a>),
Resource,
}
#[derive(Debug, Clone)]
pub struct ConcreteFuncType<'a> {
pub is_async: bool,
pub params: Vec<(&'a str, ConcreteValType<'a>)>,
pub result: Option<ConcreteValType<'a>>,
}
#[derive(Debug, Clone)]
pub enum ConcreteValType<'a> {
Primitive(PrimitiveValType),
Record(Vec<(&'a str, Box<ConcreteValType<'a>>)>),
Variant(Vec<(&'a str, Option<Box<ConcreteValType<'a>>>)>),
List(Box<ConcreteValType<'a>>),
Tuple(Vec<ConcreteValType<'a>>),
Option(Box<ConcreteValType<'a>>),
Result {
ok: Option<Box<ConcreteValType<'a>>>,
err: Option<Box<ConcreteValType<'a>>>,
},
Flags(Vec<&'a str>),
Enum(Vec<&'a str>),
Map(Box<ConcreteValType<'a>>, Box<ConcreteValType<'a>>),
FixedLengthList(Box<ConcreteValType<'a>>, u32),
Resource,
NamedResource(&'a str),
AsyncHandle,
}
impl<'a> Component<'a> {
pub fn concretize_import(&'a self, name: &str) -> Option<ConcreteType<'a>> {
match self.resolve_named_import(name)? {
ResolvedItem::CompType(_, ty) => concretize_comp_type(self, ty),
_ => None,
}
}
pub fn concretize_export(&'a self, name: &str) -> Option<ConcreteType<'a>> {
let resolved = self.resolve_named_export(name)?;
match resolved {
ResolvedItem::CompType(_, ty) => concretize_comp_type(self, ty),
ResolvedItem::CompInst(_, ComponentInstance::FromExports(exports)) => {
concretize_from_exports_instance(self, exports)
}
ResolvedItem::CompInst(_, inst @ ComponentInstance::Instantiate { .. }) => {
let comp_ref = inst.get_comp_refs().into_iter().next();
let nested = comp_ref.and_then(|cr| match self.resolve(&cr.ref_) {
ResolvedItem::Component(_, nested) => Some(nested),
_ => None,
});
nested
.and_then(|n| n.concretize_export(name))
.or_else(|| nested.and_then(concretize_comp_func_exports))
.or_else(|| self.concretize_import(name))
}
ResolvedItem::Import(_, imp) => {
let type_ref = imp.get_type_refs().into_iter().next()?;
let ty = match self.resolve(&type_ref.ref_) {
ResolvedItem::CompType(_, ty) => ty,
_ => return None,
};
concretize_comp_type(self, ty)
}
_ => None,
}
}
fn enter_type_scope(&'a self, ty: &'a ComponentType<'a>) -> VisitCtx<'a> {
let mut inner = VisitCtxInner::new(self);
inner.push_component(self);
inner.maybe_enter_scope(ty);
match ty {
ComponentType::Instance(decls) => inner.push_type_body(TypeBodyDecls::Inst(decls)),
ComponentType::Component(decls) => inner.push_type_body(TypeBodyDecls::Comp(decls)),
_ => {}
}
VisitCtx { inner }
}
}
fn concretize_comp_type<'a>(
comp: &'a Component<'a>,
ty: &'a ComponentType<'a>,
) -> Option<ConcreteType<'a>> {
match ty {
ComponentType::Instance(decls) => {
let cx = comp.enter_type_scope(ty);
let d = concretize_instance_decls(comp, decls, &cx);
Some(ConcreteType::Instance {
funcs: d.funcs,
type_exports: d.type_exports,
})
}
ComponentType::Func(ft) => {
let cx = comp.enter_type_scope(ty);
Some(ConcreteType::Func(concretize_func_ty(
ft,
comp,
&cx,
&HashMap::new(),
)))
}
ComponentType::Resource { .. } => Some(ConcreteType::Resource),
_ => None,
}
}
fn build_instance_resource_map<'a>(
decls: &'a [InstanceTypeDeclaration<'a>],
cx: &VisitCtx<'a>,
) -> HashMap<u32, &'a str> {
let mut resource_by_idx: HashMap<u32, &'a str> = HashMap::new();
let mut type_count: u32 = 0;
for decl in decls {
match decl {
InstanceTypeDeclaration::Export {
name,
ty: ComponentTypeRef::Type(TypeBounds::SubResource),
} => {
resource_by_idx.insert(type_count, name.0);
type_count += 1;
}
InstanceTypeDeclaration::Export {
ty: ComponentTypeRef::Type(_),
..
} => {
type_count += 1;
}
InstanceTypeDeclaration::Export {
ty: ComponentTypeRef::Func(_),
..
} => {}
InstanceTypeDeclaration::Type(_) => {
type_count += 1;
}
InstanceTypeDeclaration::Alias(alias) => {
if matches!(
alias,
ComponentAlias::Outer {
kind: ComponentOuterAliasKind::Type,
..
}
) {
let resolved = cx.resolve(&alias.get_item_ref().ref_);
if let ResolvedItem::Alias(
_,
ComponentAlias::InstanceExport {
kind: ComponentExternalKind::Type,
name,
..
},
) = resolved
{
resource_by_idx.insert(type_count, name);
}
}
type_count += 1;
}
_ => {}
}
}
resource_by_idx
}
fn build_component_resource_map<'a>(
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
) -> HashMap<u32, &'a str> {
let mut map: HashMap<u32, &'a str> = HashMap::new();
for export in comp.exports.iter() {
if export.kind != ComponentExternalKind::Type {
continue;
}
let type_ref = export.get_item_ref().ref_;
if resolved_is_resource(cx.resolve(&type_ref), cx, &mut HashSet::new()) {
map.insert(export.index, export.name.0);
}
}
map
}
fn resolved_is_resource<'a>(
resolved: ResolvedItem<'a, '_>,
cx: &VisitCtx<'a>,
visited: &mut HashSet<IndexedRef>,
) -> bool {
match resolved {
ResolvedItem::CompType(_, ComponentType::Resource { .. }) => true,
ResolvedItem::Import(_, imp) => {
matches!(imp.ty, ComponentTypeRef::Type(TypeBounds::SubResource))
}
ResolvedItem::Alias(_, alias) => {
let next = alias.get_item_ref().ref_;
if !visited.insert(next) {
return false; }
resolved_is_resource(cx.resolve(&next), cx, visited)
}
_ => false,
}
}
struct ConcreteInstanceDecls<'a> {
funcs: Vec<(&'a str, ConcreteFuncType<'a>)>,
type_exports: Vec<(&'a str, ConcreteValType<'a>)>,
}
fn concretize_instance_decls<'a>(
comp: &'a Component<'a>,
decls: &'a [InstanceTypeDeclaration<'a>],
cx: &VisitCtx<'a>,
) -> ConcreteInstanceDecls<'a> {
let resource_map = build_instance_resource_map(decls, cx);
let mut funcs = vec![];
let mut type_exports = vec![];
for decl in decls {
if let InstanceTypeDeclaration::Export { name, ty, .. } = decl {
if let Some(type_ref) = decl.get_type_refs().first() {
let resolved = cx.resolve(&type_ref.ref_);
if let Some(ft) = resolve_and_concretize_func(resolved, comp, cx, &resource_map) {
funcs.push((name.0, ft));
} else {
match ty {
ComponentTypeRef::Type(TypeBounds::SubResource) => {
type_exports.push((name.0, ConcreteValType::NamedResource(name.0)));
}
ComponentTypeRef::Type(TypeBounds::Eq(_)) => {
let resolved2 = cx.resolve(&type_ref.ref_);
if let Some(cvt) =
concretize_from_resolved_to_val(resolved2, comp, cx, &resource_map)
{
type_exports.push((name.0, cvt));
}
}
_ => {}
}
}
}
}
}
ConcreteInstanceDecls {
funcs,
type_exports,
}
}
fn concretize_from_resolved_to_val<'a>(
resolved: ResolvedItem<'a, 'a>,
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
resource_map: &HashMap<u32, &'a str>,
) -> Option<ConcreteValType<'a>> {
match resolved {
ResolvedItem::CompType(_, ComponentType::Defined(dt)) => {
Some(concretize_defined_type(dt, comp, cx, resource_map))
}
ResolvedItem::CompType(_, ComponentType::Resource { .. }) => {
Some(ConcreteValType::Resource)
}
ResolvedItem::Alias(_, alias @ ComponentAlias::Outer { .. }) => {
concretize_from_resolved_to_val(
cx.resolve(&alias.get_item_ref().ref_),
comp,
cx,
resource_map,
)
}
ResolvedItem::Alias(
_,
ComponentAlias::InstanceExport {
instance_index,
name,
..
},
) => {
if let Some(nested_comp) = resolve_instantiated_comp(comp, *instance_index) {
match nested_comp.concretize_export(name) {
Some(ConcreteType::Resource) => Some(ConcreteValType::Resource),
_ => None,
}
} else {
Some(resolve_type_from_import_instance(
comp,
*instance_index,
name,
))
}
}
ResolvedItem::Import(_, imp) => {
for tr in imp.get_type_refs() {
let inner = comp.resolve(&tr.ref_);
if let Some(cvt) = concretize_from_resolved_to_val(inner, comp, cx, resource_map) {
return Some(cvt);
}
}
None
}
_ => None,
}
}
fn resolve_and_concretize_func<'a>(
resolved: ResolvedItem<'a, 'a>,
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
resource_map: &HashMap<u32, &'a str>,
) -> Option<ConcreteFuncType<'a>> {
match resolved {
ResolvedItem::CompType(_, ComponentType::Func(ft)) => {
Some(concretize_func_ty(ft, comp, cx, resource_map))
}
ResolvedItem::Alias(_, alias @ ComponentAlias::Outer { .. }) => {
resolve_and_concretize_func(
cx.resolve(&alias.get_item_ref().ref_),
comp,
cx,
resource_map,
)
}
ResolvedItem::Alias(
_,
ComponentAlias::InstanceExport {
instance_index,
name,
..
},
) => {
if let Some(nested_comp) = resolve_instantiated_comp(comp, *instance_index) {
match nested_comp.concretize_export(name) {
Some(ConcreteType::Func(ft)) => Some(ft),
_ => None,
}
} else {
resolve_func_from_import_instance(comp, *instance_index, name)
}
}
ResolvedItem::Import(_, imp) => {
let type_ref = imp.get_type_refs().into_iter().next()?;
match comp.resolve(&type_ref.ref_) {
ResolvedItem::CompType(_, ComponentType::Func(ft)) => {
Some(concretize_func_ty(ft, comp, cx, resource_map))
}
_ => None,
}
}
_ => None,
}
}
fn concretize_func_ty<'a>(
ft: &'a ComponentFuncType<'a>,
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
resource_map: &HashMap<u32, &'a str>,
) -> ConcreteFuncType<'a> {
ConcreteFuncType {
is_async: ft.async_,
params: ft
.params
.iter()
.map(|(name, ty)| (*name, concretize_val_type(ty, comp, cx, resource_map)))
.collect(),
result: ft
.result
.as_ref()
.map(|ty| concretize_val_type(ty, comp, cx, resource_map)),
}
}
fn concretize_val_type<'a>(
ty: &'a ComponentValType,
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
resource_map: &HashMap<u32, &'a str>,
) -> ConcreteValType<'a> {
match ty {
ComponentValType::Primitive(p) => ConcreteValType::Primitive(*p),
ComponentValType::Type(_) => {
if let Some(type_ref) = ty.get_type_refs().first() {
concretize_from_resolved(cx.resolve(&type_ref.ref_), comp, cx, resource_map)
} else {
unreachable!("`ComponentValType::Type(idx)` always carries exactly one type ref in a valid binary")
}
}
}
}
fn concretize_from_resolved<'a>(
resolved: ResolvedItem<'a, 'a>,
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
resource_map: &HashMap<u32, &'a str>,
) -> ConcreteValType<'a> {
match resolved {
ResolvedItem::CompType(_, ty) => concretize_comp_type_to_val(ty, comp, cx, resource_map),
ResolvedItem::Alias(_, alias @ ComponentAlias::Outer { .. }) => concretize_from_resolved(
cx.resolve(&alias.get_item_ref().ref_),
comp,
cx,
resource_map,
),
ResolvedItem::Alias(
_,
ComponentAlias::InstanceExport {
instance_index,
name,
..
},
) => {
let Some(nested_comp) = resolve_instantiated_comp(comp, *instance_index) else {
return resolve_type_from_import_instance(comp, *instance_index, name);
};
match nested_comp.concretize_export(name) {
Some(ConcreteType::Resource) => ConcreteValType::Resource,
None => panic!(
"invalid component: alias-export `{name}` used as a val type does not \
resolve to a known val-type kind"
),
Some(ConcreteType::Instance { .. }) | Some(ConcreteType::Func(_)) => panic!(
"invalid component: alias-export `{name}` used as a val type resolves \
to a non-val-type (Instance or Func)"
),
}
}
ResolvedItem::Import(_, import) => {
let type_ref = import
.get_type_refs()
.into_iter()
.next()
.expect("invalid component: Import used as a val type has no type ref");
concretize_from_resolved(cx.resolve(&type_ref.ref_), comp, cx, resource_map)
}
ResolvedItem::InstTyDeclExport(_, decl) => {
let type_ref =
decl.get_type_refs().into_iter().next().expect(
"invalid component: InstTyDeclExport used as a val type has no type ref",
);
concretize_from_resolved(cx.resolve(&type_ref.ref_), comp, cx, resource_map)
}
other => panic!("invalid component: {other:?} is not a val-type kind"),
}
}
fn concretize_comp_type_to_val<'a>(
ty: &'a ComponentType<'a>,
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
resource_map: &HashMap<u32, &'a str>,
) -> ConcreteValType<'a> {
match ty {
ComponentType::Defined(def) => concretize_defined_type(def, comp, cx, resource_map),
ComponentType::Resource { .. } => ConcreteValType::Resource,
ComponentType::Func(_) | ComponentType::Instance(_) | ComponentType::Component(_) => {
panic!("invalid component: {ty:?} is not a val-type kind")
}
}
}
fn concretize_defined_type<'a>(
ty: &'a ComponentDefinedType,
comp: &'a Component<'a>,
cx: &VisitCtx<'a>,
resource_map: &HashMap<u32, &'a str>,
) -> ConcreteValType<'a> {
match ty {
ComponentDefinedType::Primitive(p) => ConcreteValType::Primitive(*p),
ComponentDefinedType::Record(fields) => ConcreteValType::Record(
fields
.iter()
.map(|(name, ty)| {
(
*name,
Box::new(concretize_val_type(ty, comp, cx, resource_map)),
)
})
.collect(),
),
ComponentDefinedType::Variant(cases) => ConcreteValType::Variant(
cases
.iter()
.map(|c| {
(
c.name,
c.ty.as_ref()
.map(|t| Box::new(concretize_val_type(t, comp, cx, resource_map))),
)
})
.collect(),
),
ComponentDefinedType::List(ty) => {
ConcreteValType::List(Box::new(concretize_val_type(ty, comp, cx, resource_map)))
}
ComponentDefinedType::Tuple(types) => ConcreteValType::Tuple(
types
.iter()
.map(|t| concretize_val_type(t, comp, cx, resource_map))
.collect(),
),
ComponentDefinedType::Option(ty) => {
ConcreteValType::Option(Box::new(concretize_val_type(ty, comp, cx, resource_map)))
}
ComponentDefinedType::Result { ok, err } => ConcreteValType::Result {
ok: ok
.as_ref()
.map(|t| Box::new(concretize_val_type(t, comp, cx, resource_map))),
err: err
.as_ref()
.map(|t| Box::new(concretize_val_type(t, comp, cx, resource_map))),
},
ComponentDefinedType::Flags(names) => ConcreteValType::Flags(names.to_vec()),
ComponentDefinedType::Enum(names) => ConcreteValType::Enum(names.to_vec()),
ComponentDefinedType::Map(key, val) => ConcreteValType::Map(
Box::new(concretize_val_type(key, comp, cx, resource_map)),
Box::new(concretize_val_type(val, comp, cx, resource_map)),
),
ComponentDefinedType::FixedLengthList(elem, size) => ConcreteValType::FixedLengthList(
Box::new(concretize_val_type(elem, comp, cx, resource_map)),
*size,
),
ComponentDefinedType::Own(res_idx) => {
if let Some(&name) = resource_map.get(res_idx) {
ConcreteValType::NamedResource(name)
} else {
let type_ref = IndexedRef {
depth: Depth::default(),
space: Space::CompType,
index: *res_idx,
};
let resolved = cx.resolve(&type_ref);
let found_name = match resolved {
ResolvedItem::Import(_, imp) => {
let refs = imp.get_type_refs();
refs.iter()
.find_map(|tr| resource_map.get(&tr.ref_.index).copied())
}
_ => None,
};
if let Some(name) = found_name {
ConcreteValType::NamedResource(name)
} else {
ConcreteValType::Resource
}
}
}
ComponentDefinedType::Borrow(_) => ConcreteValType::Resource,
ComponentDefinedType::Future(_) | ComponentDefinedType::Stream(_) => {
ConcreteValType::AsyncHandle
}
}
}
fn concretize_from_exports_instance<'a>(
comp: &'a Component<'a>,
exports: &'a [ComponentExport<'a>],
) -> Option<ConcreteType<'a>> {
let cx = {
let mut inner = VisitCtxInner::new(comp);
inner.push_component(comp);
VisitCtx { inner }
};
let resource_map = build_component_resource_map(comp, &cx);
let mut funcs = vec![];
for export in exports.iter() {
if export.kind != ComponentExternalKind::Func {
continue; }
let resolved = comp.resolve(&export.get_item_ref().ref_);
if let Some(ft) = resolve_and_concretize_func(resolved, comp, &cx, &resource_map) {
funcs.push((export.name.0, ft));
}
}
Some(ConcreteType::Instance {
funcs,
type_exports: vec![],
})
}
fn concretize_comp_func_exports<'a>(comp: &'a Component<'a>) -> Option<ConcreteType<'a>> {
let cx = {
let mut inner = VisitCtxInner::new(comp);
inner.push_component(comp);
VisitCtx { inner }
};
let resource_map = build_component_resource_map(comp, &cx);
let mut funcs = vec![];
let mut type_exports = vec![];
for export in comp.exports.iter() {
match export.kind {
ComponentExternalKind::Func => {
let resolved = comp.resolve(&export.get_item_ref().ref_);
if let Some(ft) = resolve_and_concretize_func(resolved, comp, &cx, &resource_map) {
funcs.push((export.name.0, ft));
}
}
ComponentExternalKind::Type => {
let type_ref = export.get_item_ref().ref_;
let resolved = cx.resolve(&type_ref);
if resolved_is_resource(cx.resolve(&type_ref), &cx, &mut HashSet::new()) {
type_exports
.push((export.name.0, ConcreteValType::NamedResource(export.name.0)));
} else if let Some(cvt) =
concretize_from_resolved_to_val(resolved, comp, &cx, &resource_map)
{
type_exports.push((export.name.0, cvt));
}
}
_ => {}
}
}
if funcs.is_empty() {
return None;
}
Some(ConcreteType::Instance {
funcs,
type_exports,
})
}
fn resolve_type_from_import_instance<'a>(
comp: &'a Component<'a>,
instance_index: u32,
type_name: &str,
) -> ConcreteValType<'a> {
let inst_ref = IndexedRef {
depth: Depth::default(),
space: Space::CompInst,
index: instance_index,
};
let import = match comp.resolve(&inst_ref) {
ResolvedItem::Import(_, imp) => imp,
other => panic!(
"invalid component: instance {instance_index} is not an import \
(looking up type export `{type_name}`); resolved to {other:?}"
),
};
let type_ref = import
.get_type_refs()
.into_iter()
.next()
.unwrap_or_else(|| {
panic!(
"invalid component: import for instance {instance_index} carries no \
type ref (looking up type export `{type_name}`)"
)
});
let ty = match comp.resolve(&type_ref.ref_) {
ResolvedItem::CompType(_, ty) => ty,
other => panic!(
"invalid component: type ref for instance {instance_index} does not \
resolve to a component type (looking up type export `{type_name}`); \
resolved to {other:?}"
),
};
let decls = match ty {
ComponentType::Instance(decls) => decls,
other => panic!(
"invalid component: instance {instance_index}'s declared type is not an \
instance type (looking up type export `{type_name}`); got {other:?}"
),
};
let inner_cx = comp.enter_type_scope(ty);
let resource_map = build_instance_resource_map(decls, &inner_cx);
for decl in decls {
if let InstanceTypeDeclaration::Export { name, .. } = decl {
if name.0 != type_name {
continue;
}
if let Some(tr) = decl.get_type_refs().first() {
let resolved = inner_cx.resolve(&tr.ref_);
return concretize_from_resolved(resolved, comp, &inner_cx, &resource_map);
}
}
}
ConcreteValType::Resource
}
fn resolve_func_from_import_instance<'a>(
comp: &'a Component<'a>,
instance_index: u32,
func_name: &str,
) -> Option<ConcreteFuncType<'a>> {
let inst_ref = IndexedRef {
depth: Depth::default(),
space: Space::CompInst,
index: instance_index,
};
let import = match comp.resolve(&inst_ref) {
ResolvedItem::Import(_, imp) => imp,
_ => return None,
};
let type_ref = import.get_type_refs().into_iter().next()?;
let ty = match comp.resolve(&type_ref.ref_) {
ResolvedItem::CompType(_, ty) => ty,
_ => return None,
};
match concretize_comp_type(comp, ty)? {
ConcreteType::Instance { funcs, .. } => funcs
.into_iter()
.find(|(name, _)| *name == func_name)
.map(|(_, ft)| ft),
_ => None,
}
}
fn resolve_instantiated_comp<'a>(
comp: &'a Component<'a>,
instance_index: u32,
) -> Option<&'a Component<'a>> {
let inst_ref = IndexedRef {
depth: Depth::default(),
space: Space::CompInst,
index: instance_index,
};
let inst = match comp.resolve(&inst_ref) {
ResolvedItem::CompInst(_, inst) => inst,
_ => return None,
};
let comp_ref = inst.get_comp_refs().into_iter().next()?;
match comp.resolve(&comp_ref.ref_) {
ResolvedItem::Component(_, nested_comp) => Some(nested_comp),
_ => None,
}
}