use crate::error::Result;
use crate::tombstone_arena::Tombstone;
use anyhow::bail;
use id_arena::Id;
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt;
use std::hash;
pub type TypeId = Id<Type>;
pub type RecGroupId = Id<RecGroup>;
#[derive(Debug, Clone)]
pub struct RecGroup {
pub types: Vec<TypeId>,
pub is_explicit: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum StorageType {
Val(ValType),
I8,
I16,
}
impl StorageType {
pub fn unpack(&self) -> ValType {
match self {
StorageType::Val(v) => *v,
StorageType::I8 | StorageType::I16 => ValType::I32,
}
}
pub(crate) fn to_wasmencoder_type(
&self,
indices: &crate::emit::IdsToIndices,
) -> wasm_encoder::StorageType {
match self {
StorageType::I8 => wasm_encoder::StorageType::I8,
StorageType::I16 => wasm_encoder::StorageType::I16,
StorageType::Val(vt) => wasm_encoder::StorageType::Val(vt.to_wasmencoder_type(indices)),
}
}
pub(crate) fn from_wasmparser(
st: wasmparser::StorageType,
ids: &crate::parse::IndicesToIds,
rec_group_start: u32,
) -> Result<StorageType> {
match st {
wasmparser::StorageType::I8 => Ok(StorageType::I8),
wasmparser::StorageType::I16 => Ok(StorageType::I16),
wasmparser::StorageType::Val(vt) => Ok(StorageType::Val(ValType::from_wasmparser(
&vt,
ids,
rec_group_start,
)?)),
}
}
}
impl fmt::Display for StorageType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StorageType::Val(v) => write!(f, "{v}"),
StorageType::I8 => write!(f, "i8"),
StorageType::I16 => write!(f, "i16"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FieldType {
pub element_type: StorageType,
pub mutable: bool,
}
impl FieldType {
pub(crate) fn to_wasmencoder_type(
&self,
indices: &crate::emit::IdsToIndices,
) -> wasm_encoder::FieldType {
wasm_encoder::FieldType {
element_type: self.element_type.to_wasmencoder_type(indices),
mutable: self.mutable,
}
}
pub(crate) fn from_wasmparser(
ft: wasmparser::FieldType,
ids: &crate::parse::IndicesToIds,
rec_group_start: u32,
) -> Result<FieldType> {
Ok(FieldType {
element_type: StorageType::from_wasmparser(ft.element_type, ids, rec_group_start)?,
mutable: ft.mutable,
})
}
}
impl fmt::Display for FieldType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.mutable {
write!(f, "(mut {})", self.element_type)
} else {
write!(f, "{}", self.element_type)
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FunctionType {
params: Box<[ValType]>,
results: Box<[ValType]>,
}
impl FunctionType {
pub fn new(params: Box<[ValType]>, results: Box<[ValType]>) -> Self {
FunctionType { params, results }
}
#[inline]
pub fn params(&self) -> &[ValType] {
&self.params
}
#[inline]
pub fn results(&self) -> &[ValType] {
&self.results
}
}
impl fmt::Display for FunctionType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(func")?;
if !self.params.is_empty() {
let params = self
.params
.iter()
.map(|p| format!("{p}"))
.collect::<Vec<_>>()
.join(" ");
write!(f, " (param {params})")?;
}
if !self.results.is_empty() {
let results = self
.results
.iter()
.map(|r| format!("{r}"))
.collect::<Vec<_>>()
.join(" ");
write!(f, " (result {results})")?;
}
write!(f, ")")
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StructType {
pub fields: Box<[FieldType]>,
}
impl fmt::Display for StructType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fields = self
.fields
.iter()
.map(|field| format!("(field {field})"))
.collect::<Vec<_>>()
.join(" ");
if fields.is_empty() {
write!(f, "(struct)")
} else {
write!(f, "(struct {fields})")
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ArrayType {
pub field: FieldType,
}
impl fmt::Display for ArrayType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(array {})", self.field)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CompositeType {
Function(FunctionType),
Struct(StructType),
Array(ArrayType),
}
impl CompositeType {
pub fn as_function(&self) -> Option<&FunctionType> {
match self {
CompositeType::Function(f) => Some(f),
_ => None,
}
}
pub fn as_function_mut(&mut self) -> Option<&mut FunctionType> {
match self {
CompositeType::Function(f) => Some(f),
_ => None,
}
}
pub fn as_struct(&self) -> Option<&StructType> {
match self {
CompositeType::Struct(s) => Some(s),
_ => None,
}
}
pub fn as_array(&self) -> Option<&ArrayType> {
match self {
CompositeType::Array(a) => Some(a),
_ => None,
}
}
pub fn unwrap_function(&self) -> &FunctionType {
self.as_function().expect("expected a function type")
}
pub fn unwrap_struct(&self) -> &StructType {
self.as_struct().expect("expected a struct type")
}
pub fn unwrap_array(&self) -> &ArrayType {
self.as_array().expect("expected an array type")
}
pub fn is_function(&self) -> bool {
matches!(self, CompositeType::Function(_))
}
pub fn is_struct(&self) -> bool {
matches!(self, CompositeType::Struct(_))
}
pub fn is_array(&self) -> bool {
matches!(self, CompositeType::Array(_))
}
}
impl fmt::Display for CompositeType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CompositeType::Function(ft) => write!(f, "{ft}"),
CompositeType::Struct(st) => write!(f, "{st}"),
CompositeType::Array(at) => write!(f, "{at}"),
}
}
}
#[derive(Debug, Clone)]
pub struct Type {
id: TypeId,
comp: CompositeType,
pub is_final: bool,
pub supertype: Option<TypeId>,
is_for_function_entry: bool,
pub name: Option<String>,
}
impl PartialEq for Type {
#[inline]
fn eq(&self, rhs: &Type) -> bool {
self.comp == rhs.comp
&& self.is_final == rhs.is_final
&& self.supertype == rhs.supertype
&& self.is_for_function_entry == rhs.is_for_function_entry
}
}
impl Eq for Type {}
impl PartialOrd for Type {
fn partial_cmp(&self, rhs: &Type) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
impl Ord for Type {
fn cmp(&self, rhs: &Type) -> Ordering {
self.comp
.cmp(&rhs.comp)
.then_with(|| self.is_final.cmp(&rhs.is_final))
.then_with(|| self.supertype.cmp(&rhs.supertype))
.then_with(|| self.is_for_function_entry.cmp(&rhs.is_for_function_entry))
}
}
impl hash::Hash for Type {
#[inline]
fn hash<H: hash::Hasher>(&self, h: &mut H) {
self.comp.hash(h);
self.is_final.hash(h);
self.supertype.hash(h);
self.is_for_function_entry.hash(h);
}
}
impl Tombstone for Type {
fn on_delete(&mut self) {
self.comp = CompositeType::Function(FunctionType {
params: Box::new([]),
results: Box::new([]),
});
}
}
impl Type {
#[inline]
pub(crate) fn placeholder(id: TypeId) -> Type {
Type {
id,
comp: CompositeType::Struct(Default::default()),
is_final: true,
supertype: None,
is_for_function_entry: false,
name: None,
}
}
#[inline]
pub(crate) fn new(id: TypeId, params: Box<[ValType]>, results: Box<[ValType]>) -> Type {
Type {
id,
comp: CompositeType::Function(FunctionType { params, results }),
is_final: true,
supertype: None,
is_for_function_entry: false,
name: None,
}
}
#[inline]
pub(crate) fn for_function_entry(id: TypeId, results: Box<[ValType]>) -> Type {
let params = vec![].into();
Type {
id,
comp: CompositeType::Function(FunctionType { params, results }),
is_final: true,
supertype: None,
is_for_function_entry: true,
name: None,
}
}
pub(crate) fn new_composite(
id: TypeId,
comp: CompositeType,
is_final: bool,
supertype: Option<TypeId>,
) -> Type {
Type {
id,
comp,
is_final,
supertype,
is_for_function_entry: false,
name: None,
}
}
#[inline]
pub fn id(&self) -> TypeId {
self.id
}
#[inline]
pub fn kind(&self) -> &CompositeType {
&self.comp
}
#[inline]
pub fn kind_mut(&mut self) -> &mut CompositeType {
&mut self.comp
}
#[inline]
pub fn params(&self) -> &[ValType] {
self.comp.unwrap_function().params()
}
#[inline]
pub fn results(&self) -> &[ValType] {
self.comp.unwrap_function().results()
}
#[inline]
pub fn as_function(&self) -> Option<&FunctionType> {
self.comp.as_function()
}
#[inline]
pub fn as_struct(&self) -> Option<&StructType> {
self.comp.as_struct()
}
#[inline]
pub fn as_array(&self) -> Option<&ArrayType> {
self.comp.as_array()
}
#[inline]
pub fn is_function(&self) -> bool {
self.comp.is_function()
}
#[inline]
pub fn is_struct(&self) -> bool {
self.comp.is_struct()
}
#[inline]
pub fn is_array(&self) -> bool {
self.comp.is_array()
}
pub(crate) fn is_for_function_entry(&self) -> bool {
self.is_for_function_entry
}
pub fn referenced_types(&self, out: &mut Vec<TypeId>) {
if let Some(sup) = self.supertype {
out.push(sup);
}
match &self.comp {
CompositeType::Function(ft) => {
collect_val_type_refs(ft.params(), out);
collect_val_type_refs(ft.results(), out);
}
CompositeType::Struct(st) => {
for field in st.fields.iter() {
collect_storage_type_refs(&field.element_type, out);
}
}
CompositeType::Array(at) => {
collect_storage_type_refs(&at.field.element_type, out);
}
}
}
}
fn collect_val_type_refs(val_types: &[ValType], out: &mut Vec<TypeId>) {
for vt in val_types {
if let ValType::Ref(rt) = vt {
if let HeapType::Concrete(id) | HeapType::Exact(id) = rt.heap_type {
out.push(id);
}
}
}
}
fn collect_storage_type_refs(st: &StorageType, out: &mut Vec<TypeId>) {
if let StorageType::Val(ValType::Ref(rt)) = st {
if let HeapType::Concrete(id) | HeapType::Exact(id) = rt.heap_type {
out.push(id);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ValType {
I32,
I64,
F32,
F64,
V128,
Ref(RefType),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum HeapType {
Abstract(AbstractHeapType),
Concrete(TypeId),
#[doc(hidden)]
Exact(TypeId),
}
impl HeapType {
pub fn to_wasmencoder_heap_type(
self,
indices: &crate::emit::IdsToIndices,
) -> wasm_encoder::HeapType {
match self {
HeapType::Abstract(ab_heap_type) => wasm_encoder::HeapType::Abstract {
shared: false,
ty: ab_heap_type.into(),
},
HeapType::Concrete(id) => wasm_encoder::HeapType::Concrete(indices.get_type_index(id)),
HeapType::Exact(id) => wasm_encoder::HeapType::Exact(indices.get_type_index(id)),
}
}
}
impl TryFrom<wasmparser::HeapType> for HeapType {
type Error = anyhow::Error;
fn try_from(heap_type: wasmparser::HeapType) -> Result<HeapType> {
match heap_type {
wasmparser::HeapType::Abstract { shared: _, ty } => {
Ok(HeapType::Abstract(ty.try_into()?))
}
wasmparser::HeapType::Concrete(_) | wasmparser::HeapType::Exact(_) => {
bail!("concrete/exact (indexed) heap types require IndicesToIds for resolution; use HeapType::from_wasmparser")
}
}
}
}
impl HeapType {
pub(crate) fn from_wasmparser(
heap_type: wasmparser::HeapType,
ids: &crate::parse::IndicesToIds,
rec_group_start: u32,
) -> Result<HeapType> {
match heap_type {
wasmparser::HeapType::Abstract { shared: _, ty } => {
Ok(HeapType::Abstract(ty.try_into()?))
}
wasmparser::HeapType::Concrete(unpacked) => {
let type_id = resolve_heap_type_index(unpacked, ids, rec_group_start)?;
Ok(HeapType::Concrete(type_id))
}
wasmparser::HeapType::Exact(unpacked) => {
let type_id = resolve_heap_type_index(unpacked, ids, rec_group_start)?;
Ok(HeapType::Exact(type_id))
}
}
}
}
fn resolve_heap_type_index(
unpacked: wasmparser::UnpackedIndex,
ids: &crate::parse::IndicesToIds,
rec_group_start: u32,
) -> Result<TypeId> {
match unpacked {
wasmparser::UnpackedIndex::Module(idx) => ids.get_type(idx),
wasmparser::UnpackedIndex::RecGroup(idx) => ids.get_type(rec_group_start + idx),
#[allow(unreachable_patterns)]
_ => bail!("unsupported type index variant"),
}
}
impl fmt::Display for HeapType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeapType::Abstract(ab_heap_type) => write!(
f,
"{}",
match ab_heap_type {
AbstractHeapType::Func => "func",
AbstractHeapType::Extern => "extern",
AbstractHeapType::Any => "any",
AbstractHeapType::None => "none",
AbstractHeapType::NoExtern => "noextern",
AbstractHeapType::NoFunc => "nofunc",
AbstractHeapType::Eq => "eq",
AbstractHeapType::Struct => "struct",
AbstractHeapType::Array => "array",
AbstractHeapType::I31 => "i31",
AbstractHeapType::Exn => "exn",
AbstractHeapType::NoExn => "noexn",
}
),
HeapType::Concrete(id) => write!(f, "{}", id.index()),
HeapType::Exact(id) => write!(f, "exact {}", id.index()),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum AbstractHeapType {
Func,
Extern,
Any,
None,
NoExtern,
NoFunc,
Eq,
Struct,
Array,
I31,
Exn,
NoExn,
}
#[allow(clippy::from_over_into)]
impl Into<wasm_encoder::AbstractHeapType> for AbstractHeapType {
fn into(self) -> wasm_encoder::AbstractHeapType {
match self {
AbstractHeapType::Func => wasm_encoder::AbstractHeapType::Func,
AbstractHeapType::Extern => wasm_encoder::AbstractHeapType::Extern,
AbstractHeapType::Any => wasm_encoder::AbstractHeapType::Any,
AbstractHeapType::None => wasm_encoder::AbstractHeapType::None,
AbstractHeapType::NoExtern => wasm_encoder::AbstractHeapType::NoExtern,
AbstractHeapType::NoFunc => wasm_encoder::AbstractHeapType::NoFunc,
AbstractHeapType::Eq => wasm_encoder::AbstractHeapType::Eq,
AbstractHeapType::Struct => wasm_encoder::AbstractHeapType::Struct,
AbstractHeapType::Array => wasm_encoder::AbstractHeapType::Array,
AbstractHeapType::I31 => wasm_encoder::AbstractHeapType::I31,
AbstractHeapType::Exn => wasm_encoder::AbstractHeapType::Exn,
AbstractHeapType::NoExn => wasm_encoder::AbstractHeapType::NoExn,
}
}
}
impl TryFrom<wasmparser::AbstractHeapType> for AbstractHeapType {
type Error = anyhow::Error;
fn try_from(
ab_heap_type: wasmparser::AbstractHeapType,
) -> std::result::Result<Self, Self::Error> {
Ok(match ab_heap_type {
wasmparser::AbstractHeapType::Func => AbstractHeapType::Func,
wasmparser::AbstractHeapType::Extern => AbstractHeapType::Extern,
wasmparser::AbstractHeapType::Any => AbstractHeapType::Any,
wasmparser::AbstractHeapType::None => AbstractHeapType::None,
wasmparser::AbstractHeapType::NoExtern => AbstractHeapType::NoExtern,
wasmparser::AbstractHeapType::NoFunc => AbstractHeapType::NoFunc,
wasmparser::AbstractHeapType::Eq => AbstractHeapType::Eq,
wasmparser::AbstractHeapType::Struct => AbstractHeapType::Struct,
wasmparser::AbstractHeapType::Array => AbstractHeapType::Array,
wasmparser::AbstractHeapType::I31 => AbstractHeapType::I31,
wasmparser::AbstractHeapType::Exn => AbstractHeapType::Exn,
wasmparser::AbstractHeapType::NoExn => AbstractHeapType::NoExn,
wasmparser::AbstractHeapType::Cont | wasmparser::AbstractHeapType::NoCont => {
bail!("Stack switching proposal is not supported")
}
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RefType {
pub nullable: bool,
pub heap_type: HeapType,
}
impl RefType {
pub const ANYREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::Any),
};
pub const EQREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::Eq),
};
pub const FUNCREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::Func),
};
pub const EXTERNREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::Extern),
};
pub const I31REF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::I31),
};
pub const ARRAYREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::Array),
};
pub const STRUCTREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::Struct),
};
pub const EXNREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::Exn),
};
pub const NULLREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::None),
};
pub const NULLEXTERNREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::NoExtern),
};
pub const NULLFUNCREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Abstract(AbstractHeapType::NoFunc),
};
pub fn is_nullable(&self) -> bool {
self.nullable
}
pub fn to_wasmencoder_ref_type(
self,
indices: &crate::emit::IdsToIndices,
) -> wasm_encoder::RefType {
wasm_encoder::RefType {
nullable: self.nullable,
heap_type: self.heap_type.to_wasmencoder_heap_type(indices),
}
}
}
impl TryFrom<wasmparser::RefType> for RefType {
type Error = anyhow::Error;
fn try_from(ref_type: wasmparser::RefType) -> Result<RefType> {
Ok(RefType {
nullable: ref_type.is_nullable(),
heap_type: ref_type.heap_type().try_into()?,
})
}
}
impl RefType {
pub(crate) fn from_wasmparser(
ref_type: wasmparser::RefType,
ids: &crate::parse::IndicesToIds,
rec_group_start: u32,
) -> Result<RefType> {
Ok(RefType {
nullable: ref_type.is_nullable(),
heap_type: HeapType::from_wasmparser(ref_type.heap_type(), ids, rec_group_start)?,
})
}
}
impl fmt::Display for RefType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.nullable {
match self.heap_type {
HeapType::Abstract(AbstractHeapType::Func) => write!(f, "funcref"),
HeapType::Abstract(AbstractHeapType::Extern) => write!(f, "externref"),
HeapType::Abstract(AbstractHeapType::Exn) => write!(f, "exnref"),
HeapType::Abstract(AbstractHeapType::Any) => write!(f, "anyref"),
HeapType::Abstract(AbstractHeapType::Eq) => write!(f, "eqref"),
HeapType::Abstract(AbstractHeapType::I31) => write!(f, "i31ref"),
HeapType::Abstract(AbstractHeapType::Struct) => write!(f, "structref"),
HeapType::Abstract(AbstractHeapType::Array) => write!(f, "arrayref"),
HeapType::Abstract(AbstractHeapType::None) => write!(f, "nullref"),
HeapType::Abstract(AbstractHeapType::NoExtern) => write!(f, "nullexternref"),
HeapType::Abstract(AbstractHeapType::NoFunc) => write!(f, "nullfuncref"),
HeapType::Abstract(AbstractHeapType::NoExn) => write!(f, "nullexnref"),
HeapType::Concrete(id) => write!(f, "(ref null {})", id.index()),
HeapType::Exact(id) => write!(f, "(ref null exact {})", id.index()),
}
} else {
write!(f, "(ref {})", self.heap_type)
}
}
}
impl ValType {
pub(crate) fn from_wasmparser_type(
ty: wasmparser::ValType,
ids: &crate::parse::IndicesToIds,
) -> Result<Box<[ValType]>> {
let v = vec![ValType::from_wasmparser(&ty, ids, 0)?];
Ok(v.into_boxed_slice())
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_wasmencoder_type(
&self,
indices: &crate::emit::IdsToIndices,
) -> wasm_encoder::ValType {
match self {
ValType::I32 => wasm_encoder::ValType::I32,
ValType::I64 => wasm_encoder::ValType::I64,
ValType::F32 => wasm_encoder::ValType::F32,
ValType::F64 => wasm_encoder::ValType::F64,
ValType::V128 => wasm_encoder::ValType::V128,
ValType::Ref(ref_type) => {
wasm_encoder::ValType::Ref(ref_type.to_wasmencoder_ref_type(indices))
}
}
}
pub(crate) fn from_wasmparser(
input: &wasmparser::ValType,
ids: &crate::parse::IndicesToIds,
rec_group_start: u32,
) -> Result<ValType> {
match input {
wasmparser::ValType::I32 => Ok(ValType::I32),
wasmparser::ValType::I64 => Ok(ValType::I64),
wasmparser::ValType::F32 => Ok(ValType::F32),
wasmparser::ValType::F64 => Ok(ValType::F64),
wasmparser::ValType::V128 => Ok(ValType::V128),
wasmparser::ValType::Ref(wasmparser::RefType::CONT)
| wasmparser::ValType::Ref(wasmparser::RefType::CONTREF)
| wasmparser::ValType::Ref(wasmparser::RefType::NULLCONTREF)
| wasmparser::ValType::Ref(wasmparser::RefType::NOCONT) => {
bail!("The stack switching proposal is not supported")
}
wasmparser::ValType::Ref(ref_type) => Ok(ValType::Ref(RefType::from_wasmparser(
*ref_type,
ids,
rec_group_start,
)?)),
}
}
}
impl fmt::Display for ValType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ValType::I32 => write!(f, "i32"),
ValType::I64 => write!(f, "i64"),
ValType::F32 => write!(f, "f32"),
ValType::F64 => write!(f, "f64"),
ValType::V128 => write!(f, "v128"),
ValType::Ref(r) => write!(f, "{}", r),
}
}
}