use std::collections::HashMap;
use cviz::model::{TypeArena, ValueType, ValueTypeId};
use wasm_encoder::{
ComponentTypeRef, ComponentTypeSection, ComponentValType, InstanceType, TypeBounds,
};
use super::ty::prim_cv;
pub(crate) struct InstTypeCtx {
pub cache: HashMap<ValueTypeId, u32>,
pub resource_exports: Vec<(ValueTypeId, String, u32, u32)>,
pub outer_resources: HashMap<ValueTypeId, u32>,
pub alias_locals: HashMap<ValueTypeId, u32>,
pub compound_exports: HashMap<ValueTypeId, String>,
}
impl InstTypeCtx {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
resource_exports: Vec::new(),
outer_resources: HashMap::new(),
alias_locals: HashMap::new(),
compound_exports: HashMap::new(),
}
}
pub fn with_outer_resources(outer: HashMap<ValueTypeId, u32>) -> Self {
Self {
cache: HashMap::new(),
resource_exports: Vec::new(),
outer_resources: outer,
alias_locals: HashMap::new(),
compound_exports: HashMap::new(),
}
}
pub fn encode_cv(
&mut self,
id: ValueTypeId,
inst: &mut InstanceType,
arena: &TypeArena,
) -> anyhow::Result<ComponentValType> {
if let Some(cv) = prim_cv(arena.lookup_val(id)) {
return Ok(cv);
}
if let Some(&local_idx) = self.cache.get(&id) {
return Ok(ComponentValType::Type(local_idx));
}
if let Some(&alias_local) = self.alias_locals.get(&id) {
let is_resource = matches!(
arena.lookup_val(id),
ValueType::Resource(_) | ValueType::AsyncHandle
);
if !is_resource {
self.cache.insert(id, alias_local);
return Ok(ComponentValType::Type(alias_local));
}
}
let vt = arena.lookup_val(id).clone();
let local_idx = match vt {
ValueType::Resource(ref name) => {
if self.outer_resources.contains_key(&id) {
let alias_local = self.alias_locals.get(&id).copied().ok_or_else(|| {
anyhow::anyhow!(
"outer_resources entry for {:?} but no alias_locals entry; \
alias outer should have been emitted before encode_cv",
id
)
})?;
let own_local = inst.type_count();
inst.ty().defined_type().own(alias_local);
let export_name = if name.is_empty() {
format!("res-{}", self.resource_exports.len())
} else {
name.clone()
};
self.resource_exports
.push((id, export_name, alias_local, own_local));
own_local
} else {
let export_name = if name.is_empty() {
format!("res-{}", self.resource_exports.len())
} else {
name.clone()
};
let res_local = inst.type_count();
inst.export(
&export_name,
ComponentTypeRef::Type(TypeBounds::SubResource),
);
let own_local = inst.type_count();
inst.ty().defined_type().own(res_local);
self.resource_exports
.push((id, export_name, res_local, own_local));
own_local
}
}
ValueType::AsyncHandle => {
if self.outer_resources.contains_key(&id) {
let alias_local = self.alias_locals.get(&id).copied().ok_or_else(|| {
anyhow::anyhow!(
"outer_resources entry for AsyncHandle but no alias_locals entry"
)
})?;
let own_local = inst.type_count();
inst.ty().defined_type().own(alias_local);
let export_name = format!("res-{}", self.resource_exports.len());
self.resource_exports
.push((id, export_name, alias_local, own_local));
own_local
} else {
let export_name = format!("res-{}", self.resource_exports.len());
let res_local = inst.type_count();
inst.export(
&export_name,
ComponentTypeRef::Type(TypeBounds::SubResource),
);
let own_local = inst.type_count();
inst.ty().defined_type().own(res_local);
self.resource_exports
.push((id, export_name, res_local, own_local));
own_local
}
}
ValueType::Option(inner_id) => {
let inner_cv = self.encode_cv(inner_id, inst, arena)?;
let idx = inst.type_count();
inst.ty().defined_type().option(inner_cv);
idx
}
ValueType::Result { ok, err } => {
let ok_cv = ok.map(|id| self.encode_cv(id, inst, arena)).transpose()?;
let err_cv = err.map(|id| self.encode_cv(id, inst, arena)).transpose()?;
let idx = inst.type_count();
inst.ty().defined_type().result(ok_cv, err_cv);
idx
}
ValueType::Variant(cases) => {
let mut encoded: Vec<(String, Option<ComponentValType>)> = Vec::new();
for (name, opt_id) in &cases {
let opt_cv = opt_id
.map(|id| self.encode_cv(id, inst, arena))
.transpose()?;
encoded.push((name.clone(), opt_cv));
}
let idx = inst.type_count();
inst.ty()
.defined_type()
.variant(encoded.iter().map(|(n, cv)| (n.as_str(), *cv)));
idx
}
ValueType::Record(fields) => {
let mut encoded: Vec<(String, ComponentValType)> = Vec::new();
for (name, id) in &fields {
encoded.push((name.clone(), self.encode_cv(*id, inst, arena)?));
}
let idx = inst.type_count();
inst.ty()
.defined_type()
.record(encoded.iter().map(|(n, cv)| (n.as_str(), *cv)));
idx
}
ValueType::Tuple(ids) => {
let mut encoded: Vec<ComponentValType> = Vec::new();
for id in &ids {
encoded.push(self.encode_cv(*id, inst, arena)?);
}
let idx = inst.type_count();
inst.ty().defined_type().tuple(encoded);
idx
}
ValueType::List(inner_id) => {
let inner_cv = self.encode_cv(inner_id, inst, arena)?;
let idx = inst.type_count();
inst.ty().defined_type().list(inner_cv);
idx
}
ValueType::FixedSizeList(inner_id, n) => {
let inner_cv = self.encode_cv(inner_id, inst, arena)?;
let idx = inst.type_count();
inst.ty().defined_type().fixed_length_list(inner_cv, n);
idx
}
ValueType::Enum(tags) => {
let idx = inst.type_count();
inst.ty()
.defined_type()
.enum_type(tags.iter().map(|s| s.as_str()));
idx
}
ValueType::Flags(names) => {
let idx = inst.type_count();
inst.ty()
.defined_type()
.flags(names.iter().map(|s| s.as_str()));
idx
}
other => anyhow::bail!(
"Unsupported type {:?} in tier-1 adapter instance-type encoding. \
If you need support for this type, \
please open an issue with a repro at https://github.com/ejrgilbert/splicer/issues",
other
),
};
if let Some(name) = self.compound_exports.get(&id).cloned() {
let export_idx = inst.type_count();
inst.export(&name, ComponentTypeRef::Type(TypeBounds::Eq(local_idx)));
self.cache.insert(id, export_idx);
return Ok(ComponentValType::Type(export_idx));
}
self.cache.insert(id, local_idx);
Ok(ComponentValType::Type(local_idx))
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_comp_cv(
id: ValueTypeId,
arena: &TypeArena,
comp_types: &mut ComponentTypeSection,
comp_type_count: &mut u32,
comp_own_by_vid: &HashMap<ValueTypeId, u32>,
comp_cache: &mut HashMap<ValueTypeId, u32>,
) -> anyhow::Result<ComponentValType> {
if let Some(cv) = prim_cv(arena.lookup_val(id)) {
return Ok(cv);
}
if let Some(&idx) = comp_cache.get(&id) {
return Ok(ComponentValType::Type(idx));
}
let vt = arena.lookup_val(id).clone();
match vt {
ValueType::Resource(_) | ValueType::AsyncHandle => {
let own_idx = comp_own_by_vid.get(&id).copied().ok_or_else(|| {
anyhow::anyhow!(
"internal error: resource/async-handle {id:?} not registered in \
comp_own_by_vid — the handler-import phase must declare every \
resource before encode_comp_cv references it"
)
})?;
Ok(ComponentValType::Type(own_idx))
}
ValueType::Result { ok, err } => {
let ok_cv = ok
.map(|id| {
encode_comp_cv(
id,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)
})
.transpose()?;
let err_cv = err
.map(|id| {
encode_comp_cv(
id,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)
})
.transpose()?;
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types.defined_type().result(ok_cv, err_cv);
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::Option(inner_id) => {
let inner_cv = encode_comp_cv(
inner_id,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)?;
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types.defined_type().option(inner_cv);
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::Variant(cases) => {
let mut encoded: Vec<(String, Option<ComponentValType>)> = Vec::new();
for (name, opt_id) in &cases {
let opt_cv = opt_id
.map(|id| {
encode_comp_cv(
id,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)
})
.transpose()?;
encoded.push((name.clone(), opt_cv));
}
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types
.defined_type()
.variant(encoded.iter().map(|(n, cv)| (n.as_str(), *cv)));
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::Record(fields) => {
let mut encoded: Vec<(String, ComponentValType)> = Vec::new();
for (name, fid) in &fields {
encoded.push((
name.clone(),
encode_comp_cv(
*fid,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)?,
));
}
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types
.defined_type()
.record(encoded.iter().map(|(n, cv)| (n.as_str(), *cv)));
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::Tuple(ids) => {
let mut encoded: Vec<ComponentValType> = Vec::new();
for fid in &ids {
encoded.push(encode_comp_cv(
*fid,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)?);
}
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types.defined_type().tuple(encoded);
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::List(inner_id) => {
let inner_cv = encode_comp_cv(
inner_id,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)?;
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types.defined_type().list(inner_cv);
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::FixedSizeList(inner_id, n) => {
let inner_cv = encode_comp_cv(
inner_id,
arena,
comp_types,
comp_type_count,
comp_own_by_vid,
comp_cache,
)?;
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types.defined_type().fixed_length_list(inner_cv, n);
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::Enum(tags) => {
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types
.defined_type()
.enum_type(tags.iter().map(|s| s.as_str()));
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
ValueType::Flags(names) => {
let idx = *comp_type_count;
*comp_type_count += 1;
comp_types
.defined_type()
.flags(names.iter().map(|s| s.as_str()));
comp_cache.insert(id, idx);
Ok(ComponentValType::Type(idx))
}
other => anyhow::bail!(
"Unsupported type {:?} in tier-1 adapter component-type encoding. \
If you need support for this type, \
please open an issue with a repro at https://github.com/ejrgilbert/splicer/issues",
other
),
}
}