use super::{Module, RecGroupId, TypeAlloc};
use crate::{
types::{CoreTypeId, TypeIdentifier},
PackedIndex, RecGroup, Result, UnpackedIndex, WasmFeatures,
};
pub(crate) fn canonicalize_and_intern_rec_group(
features: &WasmFeatures,
types: &mut TypeAlloc,
module: &Module,
mut rec_group: RecGroup,
offset: usize,
) -> Result<(bool, RecGroupId)> {
TypeCanonicalizer::new(module, offset)
.with_features(features)
.canonicalize_rec_group(&mut rec_group)?;
Ok(types.intern_canonical_rec_group(rec_group))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CanonicalizationMode {
HashConsing,
OnlyIds,
}
pub(crate) struct TypeCanonicalizer<'a> {
module: &'a Module,
features: Option<&'a WasmFeatures>,
rec_group_start: u32,
rec_group_len: u32,
offset: usize,
mode: CanonicalizationMode,
within_rec_group: Option<core::ops::Range<CoreTypeId>>,
}
impl<'a> TypeCanonicalizer<'a> {
pub fn new(module: &'a Module, offset: usize) -> Self {
let rec_group_start = u32::MAX;
let rec_group_len = 0;
Self {
module,
features: None,
rec_group_start,
rec_group_len,
offset,
mode: CanonicalizationMode::HashConsing,
within_rec_group: None,
}
}
pub fn with_features(&mut self, features: &'a WasmFeatures) -> &mut Self {
debug_assert!(self.features.is_none());
self.features = Some(features);
self
}
fn allow_gc(&self) -> bool {
self.features.map_or(true, |f| f.gc())
}
fn canonicalize_rec_group(&mut self, rec_group: &mut RecGroup) -> Result<()> {
self.rec_group_start = u32::try_from(self.module.types.len()).unwrap();
self.rec_group_len = u32::try_from(rec_group.types().len()).unwrap();
for (rec_group_local_index, ty) in rec_group.types_mut().enumerate() {
let rec_group_local_index = u32::try_from(rec_group_local_index).unwrap();
let type_index = self.rec_group_start + rec_group_local_index;
if let Some(sup) = ty.supertype_idx.as_mut() {
if sup.as_module_index().map_or(false, |i| i >= type_index) {
bail!(self.offset, "supertypes must be defined before subtypes");
}
}
ty.remap_indices(&mut |idx| self.canonicalize_type_index(idx))?;
}
Ok(())
}
fn canonicalize_type_index(&self, ty: &mut PackedIndex) -> Result<()> {
match ty.unpack() {
UnpackedIndex::Id(_) => return Ok(()),
UnpackedIndex::Module(index) => {
if index < self.rec_group_start || self.mode == CanonicalizationMode::OnlyIds {
let id = self.module.type_id_at(index, self.offset)?;
if let Some(id) = PackedIndex::from_id(id) {
*ty = id;
return Ok(());
} else {
bail!(
self.offset,
"implementation limit: too many types in `TypeList`"
)
}
}
debug_assert!(self.allow_gc() || self.rec_group_len == 1);
let local = index - self.rec_group_start;
if self.allow_gc() && local < self.rec_group_len {
if let Some(id) = PackedIndex::from_rec_group_index(local) {
*ty = id;
return Ok(());
} else {
bail!(
self.offset,
"implementation limit: too many types in a recursion group"
)
}
}
bail!(
self.offset,
"unknown type {index}: type index out of bounds"
)
}
UnpackedIndex::RecGroup(local_index) => match self.mode {
CanonicalizationMode::HashConsing => Ok(()),
CanonicalizationMode::OnlyIds => {
let rec_group_elems = self.within_rec_group.as_ref().expect(
"configured to canonicalize all type reference indices to `CoreTypeId`s \
and found rec-group-local index, but missing `within_rec_group` context",
);
let rec_group_len = rec_group_elems.end.index() - rec_group_elems.start.index();
let rec_group_len = u32::try_from(rec_group_len).unwrap();
assert!(local_index < rec_group_len);
let rec_group_start = u32::try_from(rec_group_elems.start.index()).unwrap();
let id = CoreTypeId::from_index(rec_group_start + local_index);
*ty = PackedIndex::from_id(id).expect(
"should fit in impl limits since we already have the end of the rec group \
constructed successfully",
);
Ok(())
}
},
}
}
}