use crate::prelude::*;
use anyhow::{bail, ensure, Result};
use core::fmt::{self, Display, Write};
use wasmtime_environ::{
EngineOrModuleTypeIndex, EntityType, Global, Memory, ModuleTypes, Table, TypeTrace,
VMSharedTypeIndex, WasmArrayType, WasmCompositeType, WasmFieldType, WasmFuncType, WasmHeapType,
WasmRefType, WasmStorageType, WasmStructType, WasmSubType, WasmValType,
};
use crate::{type_registry::RegisteredType, Engine};
pub(crate) mod matching;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum Mutability {
Const,
Var,
}
impl Mutability {
#[inline]
pub fn is_const(&self) -> bool {
*self == Self::Const
}
#[inline]
pub fn is_var(&self) -> bool {
*self == Self::Var
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum Finality {
Final,
NonFinal,
}
impl Finality {
#[inline]
pub fn is_final(&self) -> bool {
*self == Self::Final
}
#[inline]
pub fn is_non_final(&self) -> bool {
*self == Self::NonFinal
}
}
#[derive(Clone, Hash)]
pub enum ValType {
I32,
I64,
F32,
F64,
V128,
Ref(RefType),
}
impl fmt::Debug for ValType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl 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) => Display::fmt(r, f),
}
}
}
impl From<RefType> for ValType {
#[inline]
fn from(r: RefType) -> Self {
ValType::Ref(r)
}
}
impl ValType {
pub const EXTERNREF: Self = ValType::Ref(RefType::EXTERNREF);
pub const FUNCREF: Self = ValType::Ref(RefType::FUNCREF);
pub const NULLFUNCREF: Self = ValType::Ref(RefType::NULLFUNCREF);
pub const ANYREF: Self = ValType::Ref(RefType::ANYREF);
pub const I31REF: Self = ValType::Ref(RefType::I31REF);
pub const NULLREF: Self = ValType::Ref(RefType::NULLREF);
#[inline]
pub fn is_num(&self) -> bool {
match self {
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 => true,
_ => false,
}
}
#[inline]
pub fn is_i32(&self) -> bool {
matches!(self, ValType::I32)
}
#[inline]
pub fn is_i64(&self) -> bool {
matches!(self, ValType::I64)
}
#[inline]
pub fn is_f32(&self) -> bool {
matches!(self, ValType::F32)
}
#[inline]
pub fn is_f64(&self) -> bool {
matches!(self, ValType::F64)
}
#[inline]
pub fn is_v128(&self) -> bool {
matches!(self, ValType::V128)
}
#[inline]
pub fn is_ref(&self) -> bool {
matches!(self, ValType::Ref(_))
}
#[inline]
pub fn is_funcref(&self) -> bool {
matches!(
self,
ValType::Ref(RefType {
is_nullable: true,
heap_type: HeapType::Func
})
)
}
#[inline]
pub fn is_externref(&self) -> bool {
matches!(
self,
ValType::Ref(RefType {
is_nullable: true,
heap_type: HeapType::Extern
})
)
}
#[inline]
pub fn is_anyref(&self) -> bool {
matches!(
self,
ValType::Ref(RefType {
is_nullable: true,
heap_type: HeapType::Any
})
)
}
#[inline]
pub fn as_ref(&self) -> Option<&RefType> {
match self {
ValType::Ref(r) => Some(r),
_ => None,
}
}
#[inline]
pub fn unwrap_ref(&self) -> &RefType {
self.as_ref()
.expect("ValType::unwrap_ref on a non-reference type")
}
pub fn matches(&self, other: &ValType) -> bool {
match (self, other) {
(Self::I32, Self::I32) => true,
(Self::I64, Self::I64) => true,
(Self::F32, Self::F32) => true,
(Self::F64, Self::F64) => true,
(Self::V128, Self::V128) => true,
(Self::Ref(a), Self::Ref(b)) => a.matches(b),
(Self::I32, _)
| (Self::I64, _)
| (Self::F32, _)
| (Self::F64, _)
| (Self::V128, _)
| (Self::Ref(_), _) => false,
}
}
pub fn eq(a: &Self, b: &Self) -> bool {
a.matches(b) && b.matches(a)
}
#[inline]
pub(crate) fn is_vmgcref_type_and_points_to_object(&self) -> bool {
match self {
ValType::Ref(r) => r.is_vmgcref_type_and_points_to_object(),
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => false,
}
}
pub(crate) fn ensure_matches(&self, engine: &Engine, other: &ValType) -> Result<()> {
if !self.comes_from_same_engine(engine) || !other.comes_from_same_engine(engine) {
bail!("type used with wrong engine");
}
if self.matches(other) {
Ok(())
} else {
bail!("type mismatch: expected {other}, found {self}")
}
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
match self {
Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true,
Self::Ref(r) => r.comes_from_same_engine(engine),
}
}
pub(crate) fn to_wasm_type(&self) -> WasmValType {
match self {
Self::I32 => WasmValType::I32,
Self::I64 => WasmValType::I64,
Self::F32 => WasmValType::F32,
Self::F64 => WasmValType::F64,
Self::V128 => WasmValType::V128,
Self::Ref(r) => WasmValType::Ref(r.to_wasm_type()),
}
}
#[inline]
pub(crate) fn from_wasm_type(engine: &Engine, ty: &WasmValType) -> Self {
match ty {
WasmValType::I32 => Self::I32,
WasmValType::I64 => Self::I64,
WasmValType::F32 => Self::F32,
WasmValType::F64 => Self::F64,
WasmValType::V128 => Self::V128,
WasmValType::Ref(r) => Self::Ref(RefType::from_wasm_type(engine, r)),
}
}
}
#[derive(Clone, Hash)]
pub struct RefType {
is_nullable: bool,
heap_type: HeapType,
}
impl fmt::Debug for RefType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
impl fmt::Display for RefType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(ref ")?;
if self.is_nullable() {
write!(f, "null ")?;
}
write!(f, "{})", self.heap_type())
}
}
impl RefType {
pub const EXTERNREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::Extern,
};
pub const FUNCREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::Func,
};
pub const NULLFUNCREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::NoFunc,
};
pub const ANYREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::Any,
};
pub const I31REF: Self = RefType {
is_nullable: true,
heap_type: HeapType::I31,
};
pub const NULLREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::None,
};
pub fn new(is_nullable: bool, heap_type: HeapType) -> RefType {
RefType {
is_nullable,
heap_type,
}
}
pub fn is_nullable(&self) -> bool {
self.is_nullable
}
#[inline]
pub fn heap_type(&self) -> &HeapType {
&self.heap_type
}
pub fn matches(&self, other: &RefType) -> bool {
if self.is_nullable() && !other.is_nullable() {
return false;
}
self.heap_type().matches(other.heap_type())
}
pub fn eq(a: &RefType, b: &RefType) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn ensure_matches(&self, engine: &Engine, other: &RefType) -> Result<()> {
if !self.comes_from_same_engine(engine) || !other.comes_from_same_engine(engine) {
bail!("type used with wrong engine");
}
if self.matches(other) {
Ok(())
} else {
bail!("type mismatch: expected {other}, found {self}")
}
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
self.heap_type().comes_from_same_engine(engine)
}
pub(crate) fn to_wasm_type(&self) -> WasmRefType {
WasmRefType {
nullable: self.is_nullable(),
heap_type: self.heap_type().to_wasm_type(),
}
}
pub(crate) fn from_wasm_type(engine: &Engine, ty: &WasmRefType) -> RefType {
RefType {
is_nullable: ty.nullable,
heap_type: HeapType::from_wasm_type(engine, &ty.heap_type),
}
}
pub(crate) fn is_vmgcref_type_and_points_to_object(&self) -> bool {
self.heap_type().is_vmgcref_type_and_points_to_object()
}
}
#[derive(Debug, Clone, Hash)]
pub enum HeapType {
Extern,
NoExtern,
Func,
ConcreteFunc(FuncType),
NoFunc,
Any,
Eq,
I31,
Array,
ConcreteArray(ArrayType),
Struct,
ConcreteStruct(StructType),
None,
}
impl Display for HeapType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeapType::Extern => write!(f, "extern"),
HeapType::NoExtern => write!(f, "noextern"),
HeapType::Func => write!(f, "func"),
HeapType::NoFunc => write!(f, "nofunc"),
HeapType::Any => write!(f, "any"),
HeapType::Eq => write!(f, "eq"),
HeapType::I31 => write!(f, "i31"),
HeapType::Array => write!(f, "array"),
HeapType::Struct => write!(f, "struct"),
HeapType::None => write!(f, "none"),
HeapType::ConcreteFunc(ty) => write!(f, "(concrete func {:?})", ty.type_index()),
HeapType::ConcreteArray(ty) => write!(f, "(concrete array {:?})", ty.type_index()),
HeapType::ConcreteStruct(ty) => write!(f, "(concrete struct {:?})", ty.type_index()),
}
}
}
impl From<FuncType> for HeapType {
#[inline]
fn from(f: FuncType) -> Self {
HeapType::ConcreteFunc(f)
}
}
impl From<ArrayType> for HeapType {
#[inline]
fn from(a: ArrayType) -> Self {
HeapType::ConcreteArray(a)
}
}
impl From<StructType> for HeapType {
#[inline]
fn from(s: StructType) -> Self {
HeapType::ConcreteStruct(s)
}
}
impl HeapType {
pub fn is_extern(&self) -> bool {
matches!(self, HeapType::Extern)
}
pub fn is_func(&self) -> bool {
matches!(self, HeapType::Func)
}
pub fn is_no_func(&self) -> bool {
matches!(self, HeapType::NoFunc)
}
pub fn is_any(&self) -> bool {
matches!(self, HeapType::Any)
}
pub fn is_i31(&self) -> bool {
matches!(self, HeapType::I31)
}
pub fn is_none(&self) -> bool {
matches!(self, HeapType::None)
}
pub fn is_abstract(&self) -> bool {
!self.is_concrete()
}
#[inline]
pub fn is_concrete(&self) -> bool {
matches!(self, HeapType::ConcreteFunc(_) | HeapType::ConcreteArray(_))
}
pub fn is_concrete_func(&self) -> bool {
matches!(self, HeapType::ConcreteFunc(_))
}
pub fn as_concrete_func(&self) -> Option<&FuncType> {
match self {
HeapType::ConcreteFunc(f) => Some(f),
_ => None,
}
}
pub fn unwrap_concrete_func(&self) -> &FuncType {
self.as_concrete_func().unwrap()
}
pub fn is_concrete_array(&self) -> bool {
matches!(self, HeapType::ConcreteArray(_))
}
pub fn as_concrete_array(&self) -> Option<&ArrayType> {
match self {
HeapType::ConcreteArray(f) => Some(f),
_ => None,
}
}
pub fn unwrap_concrete_array(&self) -> &ArrayType {
self.as_concrete_array().unwrap()
}
#[inline]
pub fn top(&self) -> HeapType {
match self {
HeapType::Func | HeapType::ConcreteFunc(_) | HeapType::NoFunc => HeapType::Func,
HeapType::Extern | HeapType::NoExtern => HeapType::Extern,
HeapType::Any
| HeapType::Eq
| HeapType::I31
| HeapType::Array
| HeapType::ConcreteArray(_)
| HeapType::Struct
| HeapType::ConcreteStruct(_)
| HeapType::None => HeapType::Any,
}
}
#[inline]
pub fn is_top(&self) -> bool {
match self {
HeapType::Any | HeapType::Extern | HeapType::Func => true,
_ => false,
}
}
#[inline]
pub fn bottom(&self) -> HeapType {
match self {
HeapType::Extern | HeapType::NoExtern => HeapType::NoExtern,
HeapType::Func | HeapType::ConcreteFunc(_) | HeapType::NoFunc => HeapType::NoFunc,
HeapType::Any
| HeapType::Eq
| HeapType::I31
| HeapType::Array
| HeapType::ConcreteArray(_)
| HeapType::Struct
| HeapType::ConcreteStruct(_)
| HeapType::None => HeapType::None,
}
}
#[inline]
pub fn is_bottom(&self) -> bool {
match self {
HeapType::None | HeapType::NoExtern | HeapType::NoFunc => true,
_ => false,
}
}
pub fn matches(&self, other: &HeapType) -> bool {
match (self, other) {
(HeapType::Extern, HeapType::Extern) => true,
(HeapType::Extern, _) => false,
(HeapType::NoExtern, HeapType::NoExtern | HeapType::Extern) => true,
(HeapType::NoExtern, _) => false,
(HeapType::NoFunc, HeapType::NoFunc | HeapType::ConcreteFunc(_) | HeapType::Func) => {
true
}
(HeapType::NoFunc, _) => false,
(HeapType::ConcreteFunc(_), HeapType::Func) => true,
(HeapType::ConcreteFunc(a), HeapType::ConcreteFunc(b)) => {
assert!(a.comes_from_same_engine(b.engine()));
a.engine()
.signatures()
.is_subtype(a.type_index(), b.type_index())
}
(HeapType::ConcreteFunc(_), _) => false,
(HeapType::Func, HeapType::Func) => true,
(HeapType::Func, _) => false,
(
HeapType::None,
HeapType::None
| HeapType::ConcreteArray(_)
| HeapType::Array
| HeapType::ConcreteStruct(_)
| HeapType::Struct
| HeapType::I31
| HeapType::Eq
| HeapType::Any,
) => true,
(HeapType::None, _) => false,
(HeapType::ConcreteArray(_), HeapType::Array | HeapType::Eq | HeapType::Any) => true,
(HeapType::ConcreteArray(a), HeapType::ConcreteArray(b)) => {
assert!(a.comes_from_same_engine(b.engine()));
a.engine()
.signatures()
.is_subtype(a.type_index(), b.type_index())
}
(HeapType::ConcreteArray(_), _) => false,
(HeapType::Array, HeapType::Array | HeapType::Eq | HeapType::Any) => true,
(HeapType::Array, _) => false,
(HeapType::ConcreteStruct(_), HeapType::Struct | HeapType::Eq | HeapType::Any) => true,
(HeapType::ConcreteStruct(a), HeapType::ConcreteStruct(b)) => {
assert!(a.comes_from_same_engine(b.engine()));
a.engine()
.signatures()
.is_subtype(a.type_index(), b.type_index())
}
(HeapType::ConcreteStruct(_), _) => false,
(HeapType::Struct, HeapType::Struct | HeapType::Eq | HeapType::Any) => true,
(HeapType::Struct, _) => false,
(HeapType::I31, HeapType::I31 | HeapType::Eq | HeapType::Any) => true,
(HeapType::I31, _) => false,
(HeapType::Eq, HeapType::Eq | HeapType::Any) => true,
(HeapType::Eq, _) => false,
(HeapType::Any, HeapType::Any) => true,
(HeapType::Any, _) => false,
}
}
pub fn eq(a: &HeapType, b: &HeapType) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn ensure_matches(&self, engine: &Engine, other: &HeapType) -> Result<()> {
if !self.comes_from_same_engine(engine) || !other.comes_from_same_engine(engine) {
bail!("type used with wrong engine");
}
if self.matches(other) {
Ok(())
} else {
bail!("type mismatch: expected {other}, found {self}");
}
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
match self {
HeapType::Extern
| HeapType::NoExtern
| HeapType::Func
| HeapType::NoFunc
| HeapType::Any
| HeapType::Eq
| HeapType::I31
| HeapType::Array
| HeapType::Struct
| HeapType::None => true,
HeapType::ConcreteFunc(ty) => ty.comes_from_same_engine(engine),
HeapType::ConcreteArray(ty) => ty.comes_from_same_engine(engine),
HeapType::ConcreteStruct(ty) => ty.comes_from_same_engine(engine),
}
}
pub(crate) fn to_wasm_type(&self) -> WasmHeapType {
match self {
HeapType::Extern => WasmHeapType::Extern,
HeapType::NoExtern => WasmHeapType::NoExtern,
HeapType::Func => WasmHeapType::Func,
HeapType::NoFunc => WasmHeapType::NoFunc,
HeapType::Any => WasmHeapType::Any,
HeapType::Eq => WasmHeapType::Eq,
HeapType::I31 => WasmHeapType::I31,
HeapType::Array => WasmHeapType::Array,
HeapType::Struct => WasmHeapType::Struct,
HeapType::None => WasmHeapType::None,
HeapType::ConcreteFunc(f) => {
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Engine(f.type_index()))
}
HeapType::ConcreteArray(a) => {
WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::Engine(a.type_index()))
}
HeapType::ConcreteStruct(a) => {
WasmHeapType::ConcreteStruct(EngineOrModuleTypeIndex::Engine(a.type_index()))
}
}
}
pub(crate) fn from_wasm_type(engine: &Engine, ty: &WasmHeapType) -> HeapType {
match ty {
WasmHeapType::Extern => HeapType::Extern,
WasmHeapType::NoExtern => HeapType::NoExtern,
WasmHeapType::Func => HeapType::Func,
WasmHeapType::NoFunc => HeapType::NoFunc,
WasmHeapType::Any => HeapType::Any,
WasmHeapType::Eq => HeapType::Eq,
WasmHeapType::I31 => HeapType::I31,
WasmHeapType::Array => HeapType::Array,
WasmHeapType::Struct => HeapType::Struct,
WasmHeapType::None => HeapType::None,
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Engine(idx)) => {
HeapType::ConcreteFunc(FuncType::from_shared_type_index(engine, *idx))
}
WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::Engine(idx)) => {
HeapType::ConcreteArray(ArrayType::from_shared_type_index(engine, *idx))
}
WasmHeapType::ConcreteStruct(EngineOrModuleTypeIndex::Engine(idx)) => {
HeapType::ConcreteStruct(StructType::from_shared_type_index(engine, *idx))
}
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(_))
| WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::RecGroup(_))
| WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::Module(_))
| WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::RecGroup(_))
| WasmHeapType::ConcreteStruct(EngineOrModuleTypeIndex::Module(_))
| WasmHeapType::ConcreteStruct(EngineOrModuleTypeIndex::RecGroup(_)) => {
panic!("HeapType::from_wasm_type on non-canonicalized-for-runtime-usage heap type")
}
}
}
pub(crate) fn as_registered_type(&self) -> Option<&RegisteredType> {
match self {
HeapType::ConcreteFunc(f) => Some(&f.registered_type),
HeapType::ConcreteArray(a) => Some(&a.registered_type),
HeapType::ConcreteStruct(a) => Some(&a.registered_type),
HeapType::Extern
| HeapType::NoExtern
| HeapType::Func
| HeapType::NoFunc
| HeapType::Any
| HeapType::Eq
| HeapType::I31
| HeapType::Array
| HeapType::Struct
| HeapType::None => None,
}
}
#[inline]
pub(crate) fn is_vmgcref_type(&self) -> bool {
match self.top() {
Self::Any | Self::Extern => true,
Self::Func => false,
ty => unreachable!("not a top type: {ty:?}"),
}
}
#[inline]
pub(crate) fn is_vmgcref_type_and_points_to_object(&self) -> bool {
self.is_vmgcref_type()
&& !matches!(
self,
HeapType::I31 | HeapType::NoExtern | HeapType::NoFunc | HeapType::None
)
}
}
#[derive(Debug, Clone)]
pub enum ExternType {
Func(FuncType),
Global(GlobalType),
Table(TableType),
Memory(MemoryType),
}
macro_rules! extern_type_accessors {
($(($variant:ident($ty:ty) $get:ident $unwrap:ident))*) => ($(
/// Attempt to return the underlying type of this external type,
/// returning `None` if it is a different type.
pub fn $get(&self) -> Option<&$ty> {
if let ExternType::$variant(e) = self {
Some(e)
} else {
None
}
}
pub fn $unwrap(&self) -> &$ty {
self.$get().expect(concat!("expected ", stringify!($ty)))
}
)*)
}
impl ExternType {
extern_type_accessors! {
(Func(FuncType) func unwrap_func)
(Global(GlobalType) global unwrap_global)
(Table(TableType) table unwrap_table)
(Memory(MemoryType) memory unwrap_memory)
}
pub(crate) fn from_wasmtime(
engine: &Engine,
types: &ModuleTypes,
ty: &EntityType,
) -> ExternType {
match ty {
EntityType::Function(idx) => match idx {
EngineOrModuleTypeIndex::Engine(e) => {
FuncType::from_shared_type_index(engine, *e).into()
}
EngineOrModuleTypeIndex::Module(m) => {
let subty = &types[*m];
FuncType::from_wasm_func_type(
engine,
subty.is_final,
subty.supertype,
subty.unwrap_func().clone(),
)
.into()
}
EngineOrModuleTypeIndex::RecGroup(_) => unreachable!(),
},
EntityType::Global(ty) => GlobalType::from_wasmtime_global(engine, ty).into(),
EntityType::Memory(ty) => MemoryType::from_wasmtime_memory(ty).into(),
EntityType::Table(ty) => TableType::from_wasmtime_table(engine, ty).into(),
EntityType::Tag(_) => unimplemented!("wasm tag support"),
}
}
}
impl From<FuncType> for ExternType {
fn from(ty: FuncType) -> ExternType {
ExternType::Func(ty)
}
}
impl From<GlobalType> for ExternType {
fn from(ty: GlobalType) -> ExternType {
ExternType::Global(ty)
}
}
impl From<MemoryType> for ExternType {
fn from(ty: MemoryType) -> ExternType {
ExternType::Memory(ty)
}
}
impl From<TableType> for ExternType {
fn from(ty: TableType) -> ExternType {
ExternType::Table(ty)
}
}
#[derive(Clone, Hash)]
pub enum StorageType {
I8,
I16,
ValType(ValType),
}
impl From<ValType> for StorageType {
#[inline]
fn from(v: ValType) -> Self {
StorageType::ValType(v)
}
}
impl StorageType {
#[inline]
pub fn is_i8(&self) -> bool {
matches!(self, Self::I8)
}
#[inline]
pub fn is_i16(&self) -> bool {
matches!(self, Self::I16)
}
#[inline]
pub fn is_val_type(&self) -> bool {
matches!(self, Self::I16)
}
#[inline]
pub fn as_val_type(&self) -> Option<&ValType> {
match self {
Self::ValType(v) => Some(v),
_ => None,
}
}
pub fn unwrap_val_type(&self) -> &ValType {
self.as_val_type().unwrap()
}
pub fn matches(&self, other: &Self) -> bool {
match (self, other) {
(StorageType::I8, StorageType::I8) => true,
(StorageType::I8, _) => false,
(StorageType::I16, StorageType::I16) => true,
(StorageType::I16, _) => false,
(StorageType::ValType(a), StorageType::ValType(b)) => a.matches(b),
(StorageType::ValType(_), _) => false,
}
}
pub fn eq(a: &Self, b: &Self) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
match self {
StorageType::I8 | StorageType::I16 => true,
StorageType::ValType(v) => v.comes_from_same_engine(engine),
}
}
pub(crate) fn from_wasm_storage_type(engine: &Engine, ty: &WasmStorageType) -> Self {
match ty {
WasmStorageType::I8 => Self::I8,
WasmStorageType::I16 => Self::I16,
WasmStorageType::Val(v) => ValType::from_wasm_type(engine, &v).into(),
}
}
pub(crate) fn to_wasm_storage_type(&self) -> WasmStorageType {
match self {
Self::I8 => WasmStorageType::I8,
Self::I16 => WasmStorageType::I16,
Self::ValType(v) => WasmStorageType::Val(v.to_wasm_type()),
}
}
}
#[derive(Clone, Hash)]
pub struct FieldType {
mutability: Mutability,
element_type: StorageType,
}
impl FieldType {
#[inline]
pub fn new(mutability: Mutability, element_type: StorageType) -> Self {
Self {
mutability,
element_type,
}
}
#[inline]
pub fn mutability(&self) -> Mutability {
self.mutability
}
#[inline]
pub fn element_type(&self) -> &StorageType {
&self.element_type
}
pub fn matches(&self, other: &Self) -> bool {
(other.mutability == Mutability::Var || self.mutability == Mutability::Const)
&& self.element_type.matches(&other.element_type)
}
pub fn eq(a: &Self, b: &Self) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
self.element_type.comes_from_same_engine(engine)
}
pub(crate) fn from_wasm_field_type(engine: &Engine, ty: &WasmFieldType) -> Self {
Self {
mutability: if ty.mutable {
Mutability::Var
} else {
Mutability::Const
},
element_type: StorageType::from_wasm_storage_type(engine, &ty.element_type),
}
}
pub(crate) fn to_wasm_field_type(&self) -> WasmFieldType {
WasmFieldType {
element_type: self.element_type.to_wasm_storage_type(),
mutable: matches!(self.mutability, Mutability::Var),
}
}
}
#[derive(Debug, Clone, Hash)]
pub struct StructType {
registered_type: RegisteredType,
}
impl StructType {
pub fn new(engine: &Engine, fields: impl IntoIterator<Item = FieldType>) -> Result<Self> {
Self::with_finality_and_supertype(engine, Finality::Final, None, fields)
}
pub fn with_finality_and_supertype(
engine: &Engine,
finality: Finality,
supertype: Option<&Self>,
fields: impl IntoIterator<Item = FieldType>,
) -> Result<Self> {
let fields = fields.into_iter();
let mut wasmtime_fields = Vec::with_capacity({
let size_hint = fields.size_hint();
let cap = size_hint.1.unwrap_or(size_hint.0);
supertype.is_some() as usize * cap
});
let mut registrations = smallvec::SmallVec::<[_; 4]>::new();
let fields = fields
.map(|ty: FieldType| {
assert!(ty.comes_from_same_engine(engine));
if supertype.is_some() {
wasmtime_fields.push(ty.clone());
}
if let Some(r) = ty.element_type.as_val_type().and_then(|v| v.as_ref()) {
if let Some(r) = r.heap_type().as_registered_type() {
registrations.push(r.clone());
}
}
ty.to_wasm_field_type()
})
.collect();
if let Some(supertype) = supertype {
ensure!(
supertype.finality().is_non_final(),
"cannot create a subtype of a final supertype"
);
ensure!(
Self::fields_match(wasmtime_fields.into_iter(), supertype.fields()),
"struct fields must match their supertype's fields"
);
}
Self::from_wasm_struct_type(
engine,
finality.is_final(),
supertype.map(|ty| ty.type_index().into()),
WasmStructType { fields },
)
}
pub fn engine(&self) -> &Engine {
self.registered_type.engine()
}
pub fn finality(&self) -> Finality {
match self.registered_type.is_final {
true => Finality::Final,
false => Finality::NonFinal,
}
}
pub fn supertype(&self) -> Option<Self> {
self.registered_type
.supertype
.map(|ty| Self::from_shared_type_index(self.engine(), ty.unwrap_engine_type_index()))
}
pub fn field(&self, i: usize) -> Option<FieldType> {
let engine = self.engine();
self.as_wasm_struct_type()
.fields
.get(i)
.map(|ty| FieldType::from_wasm_field_type(engine, ty))
}
#[inline]
pub fn fields(&self) -> impl ExactSizeIterator<Item = FieldType> + '_ {
let engine = self.engine();
self.as_wasm_struct_type()
.fields
.iter()
.map(|ty| FieldType::from_wasm_field_type(engine, ty))
}
pub fn matches(&self, other: &StructType) -> bool {
assert!(self.comes_from_same_engine(other.engine()));
if self.type_index() == other.type_index() {
return true;
}
Self::fields_match(self.fields(), other.fields())
}
fn fields_match(
a: impl ExactSizeIterator<Item = FieldType>,
b: impl ExactSizeIterator<Item = FieldType>,
) -> bool {
a.len() >= b.len() && a.zip(b).all(|(a, b)| a.matches(&b))
}
pub fn eq(a: &StructType, b: &StructType) -> bool {
assert!(a.comes_from_same_engine(b.engine()));
a.type_index() == b.type_index()
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
Engine::same(self.registered_type.engine(), engine)
}
pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
self.registered_type.index()
}
pub(crate) fn as_wasm_struct_type(&self) -> &WasmStructType {
self.registered_type.unwrap_struct()
}
pub(crate) fn from_wasm_struct_type(
engine: &Engine,
is_final: bool,
supertype: Option<EngineOrModuleTypeIndex>,
ty: WasmStructType,
) -> Result<StructType> {
const MAX_FIELDS: usize = 10_000;
let fields_len = ty.fields.len();
ensure!(
fields_len <= MAX_FIELDS,
"attempted to define a struct type with {fields_len} fields, but \
that is more than the maximum supported number of fields \
({MAX_FIELDS})",
);
let ty = RegisteredType::new(
engine,
WasmSubType {
is_final,
supertype,
composite_type: WasmCompositeType::Struct(ty),
},
);
Ok(Self {
registered_type: ty,
})
}
pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> StructType {
let ty = RegisteredType::root(engine, index).expect(
"VMSharedTypeIndex is not registered in the Engine! Wrong \
engine? Didn't root the index somewhere?",
);
assert!(ty.is_struct());
Self {
registered_type: ty,
}
}
}
#[derive(Debug, Clone, Hash)]
pub struct ArrayType {
registered_type: RegisteredType,
}
impl ArrayType {
pub fn new(engine: &Engine, field_type: FieldType) -> Self {
Self::with_finality_and_supertype(engine, Finality::Final, None, field_type)
.expect("cannot fail without a supertype")
}
pub fn with_finality_and_supertype(
engine: &Engine,
finality: Finality,
supertype: Option<&Self>,
field_type: FieldType,
) -> Result<Self> {
if let Some(supertype) = supertype {
assert!(supertype.comes_from_same_engine(engine));
ensure!(
supertype.finality().is_non_final(),
"cannot create a subtype of a final supertype"
);
ensure!(
field_type.matches(&supertype.field_type()),
"array field type must match its supertype's field type"
);
}
let _registration = field_type
.element_type
.as_val_type()
.and_then(|v| v.as_ref())
.and_then(|r| r.heap_type().as_registered_type());
assert!(field_type.comes_from_same_engine(engine));
let wasm_ty = WasmArrayType(field_type.to_wasm_field_type());
Ok(Self::from_wasm_array_type(
engine,
finality.is_final(),
supertype.map(|ty| ty.type_index().into()),
wasm_ty,
))
}
pub fn engine(&self) -> &Engine {
self.registered_type.engine()
}
pub fn finality(&self) -> Finality {
match self.registered_type.is_final {
true => Finality::Final,
false => Finality::NonFinal,
}
}
pub fn supertype(&self) -> Option<Self> {
self.registered_type
.supertype
.map(|ty| Self::from_shared_type_index(self.engine(), ty.unwrap_engine_type_index()))
}
pub fn field_type(&self) -> FieldType {
FieldType::from_wasm_field_type(self.engine(), &self.as_wasm_array_type().0)
}
pub fn mutability(&self) -> Mutability {
if self.as_wasm_array_type().0.mutable {
Mutability::Var
} else {
Mutability::Const
}
}
pub fn element_type(&self) -> StorageType {
StorageType::from_wasm_storage_type(
self.engine(),
&self.registered_type.unwrap_array().0.element_type,
)
}
pub fn matches(&self, other: &ArrayType) -> bool {
assert!(self.comes_from_same_engine(other.engine()));
if self.type_index() == other.type_index() {
return true;
}
self.field_type().matches(&other.field_type())
}
pub fn eq(a: &ArrayType, b: &ArrayType) -> bool {
assert!(a.comes_from_same_engine(b.engine()));
a.type_index() == b.type_index()
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
Engine::same(self.registered_type.engine(), engine)
}
pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
self.registered_type.index()
}
pub(crate) fn as_wasm_array_type(&self) -> &WasmArrayType {
self.registered_type.unwrap_array()
}
pub(crate) fn from_wasm_array_type(
engine: &Engine,
is_final: bool,
supertype: Option<EngineOrModuleTypeIndex>,
ty: WasmArrayType,
) -> ArrayType {
let ty = RegisteredType::new(
engine,
WasmSubType {
is_final,
supertype,
composite_type: WasmCompositeType::Array(ty),
},
);
Self {
registered_type: ty,
}
}
pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> ArrayType {
let ty = RegisteredType::root(engine, index).expect(
"VMSharedTypeIndex is not registered in the Engine! Wrong \
engine? Didn't root the index somewhere?",
);
assert!(ty.is_array());
Self {
registered_type: ty,
}
}
}
#[derive(Debug, Clone, Hash)]
pub struct FuncType {
registered_type: RegisteredType,
}
impl Display for FuncType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(type (func")?;
if self.params().len() > 0 {
write!(f, " (param")?;
for p in self.params() {
write!(f, " {p}")?;
}
write!(f, ")")?;
}
if self.results().len() > 0 {
write!(f, " (result")?;
for r in self.results() {
write!(f, " {r}")?;
}
write!(f, ")")?;
}
write!(f, "))")
}
}
impl FuncType {
pub fn new(
engine: &Engine,
params: impl IntoIterator<Item = ValType>,
results: impl IntoIterator<Item = ValType>,
) -> FuncType {
Self::with_finality_and_supertype(engine, Finality::Final, None, params, results)
.expect("cannot fail without a supertype")
}
pub fn with_finality_and_supertype(
engine: &Engine,
finality: Finality,
supertype: Option<&Self>,
params: impl IntoIterator<Item = ValType>,
results: impl IntoIterator<Item = ValType>,
) -> Result<Self> {
let params = params.into_iter();
let results = results.into_iter();
let mut wasmtime_params = Vec::with_capacity({
let size_hint = params.size_hint();
let cap = size_hint.1.unwrap_or(size_hint.0);
supertype.is_some() as usize * cap
});
let mut wasmtime_results = Vec::with_capacity({
let size_hint = results.size_hint();
let cap = size_hint.1.unwrap_or(size_hint.0);
supertype.is_some() as usize * cap
});
let mut registrations = smallvec::SmallVec::<[_; 4]>::new();
let mut to_wasm_type = |ty: ValType, vec: &mut Vec<_>| {
assert!(ty.comes_from_same_engine(engine));
if supertype.is_some() {
vec.push(ty.clone());
}
if let Some(r) = ty.as_ref() {
if let Some(r) = r.heap_type().as_registered_type() {
registrations.push(r.clone());
}
}
ty.to_wasm_type()
};
let wasm_func_ty = WasmFuncType::new(
params
.map(|p| to_wasm_type(p, &mut wasmtime_params))
.collect(),
results
.map(|r| to_wasm_type(r, &mut wasmtime_results))
.collect(),
);
if let Some(supertype) = supertype {
assert!(supertype.comes_from_same_engine(engine));
ensure!(
supertype.finality().is_non_final(),
"cannot create a subtype of a final supertype"
);
ensure!(
Self::matches_impl(
wasmtime_params.iter().cloned(),
supertype.params(),
wasmtime_results.iter().cloned(),
supertype.results()
),
"function type must match its supertype: found (func{params}{results}), expected \
{supertype}",
params = if wasmtime_params.is_empty() {
String::new()
} else {
let mut s = format!(" (params");
for p in &wasmtime_params {
write!(&mut s, " {p}").unwrap();
}
s.push(')');
s
},
results = if wasmtime_results.is_empty() {
String::new()
} else {
let mut s = format!(" (results");
for r in &wasmtime_results {
write!(&mut s, " {r}").unwrap();
}
s.push(')');
s
},
);
}
Ok(Self::from_wasm_func_type(
engine,
finality.is_final(),
supertype.map(|ty| ty.type_index().into()),
wasm_func_ty,
))
}
pub fn engine(&self) -> &Engine {
self.registered_type.engine()
}
pub fn finality(&self) -> Finality {
match self.registered_type.is_final {
true => Finality::Final,
false => Finality::NonFinal,
}
}
pub fn supertype(&self) -> Option<Self> {
self.registered_type
.supertype
.map(|ty| Self::from_shared_type_index(self.engine(), ty.unwrap_engine_type_index()))
}
pub fn param(&self, i: usize) -> Option<ValType> {
let engine = self.engine();
self.registered_type
.unwrap_func()
.params()
.get(i)
.map(|ty| ValType::from_wasm_type(engine, ty))
}
#[inline]
pub fn params(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
let engine = self.engine();
self.registered_type
.unwrap_func()
.params()
.iter()
.map(|ty| ValType::from_wasm_type(engine, ty))
}
pub fn result(&self, i: usize) -> Option<ValType> {
let engine = self.engine();
self.registered_type
.unwrap_func()
.returns()
.get(i)
.map(|ty| ValType::from_wasm_type(engine, ty))
}
#[inline]
pub fn results(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
let engine = self.engine();
self.registered_type
.unwrap_func()
.returns()
.iter()
.map(|ty| ValType::from_wasm_type(engine, ty))
}
pub fn matches(&self, other: &FuncType) -> bool {
assert!(self.comes_from_same_engine(other.engine()));
if self.type_index() == other.type_index() {
return true;
}
Self::matches_impl(
self.params(),
other.params(),
self.results(),
other.results(),
)
}
fn matches_impl(
a_params: impl ExactSizeIterator<Item = ValType>,
b_params: impl ExactSizeIterator<Item = ValType>,
a_results: impl ExactSizeIterator<Item = ValType>,
b_results: impl ExactSizeIterator<Item = ValType>,
) -> bool {
a_params.len() == b_params.len()
&& a_results.len() == b_results.len()
&& a_params
.zip(b_params)
.all(|(a, b)| b.matches(&a))
&& a_results
.zip(b_results)
.all(|(a, b)| a.matches(&b))
}
pub fn eq(a: &FuncType, b: &FuncType) -> bool {
assert!(a.comes_from_same_engine(b.engine()));
a.type_index() == b.type_index()
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
Engine::same(self.registered_type.engine(), engine)
}
pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
self.registered_type.index()
}
pub(crate) fn as_wasm_func_type(&self) -> &WasmFuncType {
self.registered_type.unwrap_func()
}
pub(crate) fn into_registered_type(self) -> RegisteredType {
self.registered_type
}
pub(crate) fn from_wasm_func_type(
engine: &Engine,
is_final: bool,
supertype: Option<EngineOrModuleTypeIndex>,
ty: WasmFuncType,
) -> FuncType {
let ty = RegisteredType::new(
engine,
WasmSubType {
is_final,
supertype,
composite_type: WasmCompositeType::Func(ty),
},
);
Self {
registered_type: ty,
}
}
pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> FuncType {
let ty = RegisteredType::root(engine, index).expect(
"VMSharedTypeIndex is not registered in the Engine! Wrong \
engine? Didn't root the index somewhere?",
);
assert!(ty.is_func());
Self {
registered_type: ty,
}
}
}
#[derive(Debug, Clone, Hash)]
pub struct GlobalType {
content: ValType,
mutability: Mutability,
}
impl GlobalType {
pub fn new(content: ValType, mutability: Mutability) -> GlobalType {
GlobalType {
content,
mutability,
}
}
pub fn content(&self) -> &ValType {
&self.content
}
pub fn mutability(&self) -> Mutability {
self.mutability
}
pub(crate) fn to_wasm_type(&self) -> Global {
let wasm_ty = self.content().to_wasm_type();
let mutability = matches!(self.mutability(), Mutability::Var);
Global {
wasm_ty,
mutability,
}
}
pub(crate) fn from_wasmtime_global(engine: &Engine, global: &Global) -> GlobalType {
let ty = ValType::from_wasm_type(engine, &global.wasm_ty);
let mutability = if global.mutability {
Mutability::Var
} else {
Mutability::Const
};
GlobalType::new(ty, mutability)
}
}
#[derive(Debug, Clone, Hash)]
pub struct TableType {
element: RefType,
ty: Table,
}
impl TableType {
pub fn new(element: RefType, min: u32, max: Option<u32>) -> TableType {
let wasm_ty = element.to_wasm_type();
debug_assert!(
wasm_ty.is_canonicalized_for_runtime_usage(),
"should be canonicalized for runtime usage: {wasm_ty:?}"
);
TableType {
element,
ty: Table {
wasm_ty,
minimum: min,
maximum: max,
},
}
}
pub fn element(&self) -> &RefType {
&self.element
}
pub fn minimum(&self) -> u32 {
self.ty.minimum
}
pub fn maximum(&self) -> Option<u32> {
self.ty.maximum
}
pub(crate) fn from_wasmtime_table(engine: &Engine, table: &Table) -> TableType {
let element = RefType::from_wasm_type(engine, &table.wasm_ty);
TableType {
element,
ty: table.clone(),
}
}
pub(crate) fn wasmtime_table(&self) -> &Table {
&self.ty
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct MemoryType {
ty: Memory,
}
impl MemoryType {
pub fn new(minimum: u32, maximum: Option<u32>) -> MemoryType {
MemoryType {
ty: Memory {
memory64: false,
shared: false,
minimum: minimum.into(),
maximum: maximum.map(|i| i.into()),
},
}
}
pub fn new64(minimum: u64, maximum: Option<u64>) -> MemoryType {
MemoryType {
ty: Memory {
memory64: true,
shared: false,
minimum,
maximum,
},
}
}
pub fn shared(minimum: u32, maximum: u32) -> MemoryType {
MemoryType {
ty: Memory {
memory64: false,
shared: true,
minimum: minimum.into(),
maximum: Some(maximum.into()),
},
}
}
pub fn is_64(&self) -> bool {
self.ty.memory64
}
pub fn is_shared(&self) -> bool {
self.ty.shared
}
pub fn minimum(&self) -> u64 {
self.ty.minimum
}
pub fn maximum(&self) -> Option<u64> {
self.ty.maximum
}
pub(crate) fn from_wasmtime_memory(memory: &Memory) -> MemoryType {
MemoryType { ty: memory.clone() }
}
pub(crate) fn wasmtime_memory(&self) -> &Memory {
&self.ty
}
}
#[derive(Clone)]
pub struct ImportType<'module> {
module: &'module str,
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
}
impl<'module> ImportType<'module> {
pub(crate) fn new(
module: &'module str,
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
) -> ImportType<'module> {
assert!(ty.is_canonicalized_for_runtime_usage());
ImportType {
module,
name,
ty,
types,
engine,
}
}
pub fn module(&self) -> &'module str {
self.module
}
pub fn name(&self) -> &'module str {
self.name
}
pub fn ty(&self) -> ExternType {
ExternType::from_wasmtime(self.engine, self.types, &self.ty)
}
}
impl<'module> fmt::Debug for ImportType<'module> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ImportType")
.field("module", &self.module())
.field("name", &self.name())
.field("ty", &self.ty())
.finish()
}
}
#[derive(Clone)]
pub struct ExportType<'module> {
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
}
impl<'module> ExportType<'module> {
pub(crate) fn new(
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
) -> ExportType<'module> {
ExportType {
name,
ty,
types,
engine,
}
}
pub fn name(&self) -> &'module str {
self.name
}
pub fn ty(&self) -> ExternType {
ExternType::from_wasmtime(self.engine, self.types, &self.ty)
}
}
impl<'module> fmt::Debug for ExportType<'module> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExportType")
.field("name", &self.name().to_owned())
.field("ty", &self.ty())
.finish()
}
}