use crate::{memory::MemoryType, module::ModuleInfo, structures::TypedIndex, units::Pages};
use std::{borrow::Cow, convert::TryFrom};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Type {
I32,
I64,
F32,
F64,
V128,
}
impl std::fmt::Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum Value {
I32(i32),
I64(i64),
F32(f32),
F64(f64),
V128(u128),
}
impl Value {
pub fn ty(&self) -> Type {
match self {
Value::I32(_) => Type::I32,
Value::I64(_) => Type::I64,
Value::F32(_) => Type::F32,
Value::F64(_) => Type::F64,
Value::V128(_) => Type::V128,
}
}
pub fn to_u128(&self) -> u128 {
match *self {
Value::I32(x) => x as u128,
Value::I64(x) => x as u128,
Value::F32(x) => f32::to_bits(x) as u128,
Value::F64(x) => f64::to_bits(x) as u128,
Value::V128(x) => x,
}
}
}
macro_rules! value_conversions {
($native_type:ty, $value_variant:ident) => {
impl From<$native_type> for Value {
fn from(n: $native_type) -> Self {
Self::$value_variant(n)
}
}
impl TryFrom<&Value> for $native_type {
type Error = &'static str;
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::$value_variant(value) => Ok(*value),
_ => Err("Invalid cast."),
}
}
}
};
}
value_conversions!(i32, I32);
value_conversions!(i64, I64);
value_conversions!(f32, F32);
value_conversions!(f64, F64);
value_conversions!(u128, V128);
pub unsafe trait NativeWasmType: Copy + Into<Value>
where
Self: Sized,
{
const TYPE: Type;
fn from_binary(bits: u64) -> Self;
fn to_binary(self) -> u64;
}
unsafe impl NativeWasmType for i32 {
const TYPE: Type = Type::I32;
fn from_binary(bits: u64) -> Self {
bits as _
}
fn to_binary(self) -> u64 {
self as _
}
}
unsafe impl NativeWasmType for i64 {
const TYPE: Type = Type::I64;
fn from_binary(bits: u64) -> Self {
bits as _
}
fn to_binary(self) -> u64 {
self as _
}
}
unsafe impl NativeWasmType for f32 {
const TYPE: Type = Type::F32;
fn from_binary(bits: u64) -> Self {
f32::from_bits(bits as u32)
}
fn to_binary(self) -> u64 {
self.to_bits() as _
}
}
unsafe impl NativeWasmType for f64 {
const TYPE: Type = Type::F64;
fn from_binary(bits: u64) -> Self {
f64::from_bits(bits)
}
fn to_binary(self) -> u64 {
self.to_bits()
}
}
pub unsafe trait WasmExternType: Copy
where
Self: Sized,
{
type Native: NativeWasmType;
fn from_native(native: Self::Native) -> Self;
fn to_native(self) -> Self::Native;
}
macro_rules! wasm_extern_type {
($type:ty => $native_type:ty) => {
unsafe impl WasmExternType for $type {
type Native = $native_type;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
};
}
wasm_extern_type!(i8 => i32);
wasm_extern_type!(u8 => i32);
wasm_extern_type!(i16 => i32);
wasm_extern_type!(u16 => i32);
wasm_extern_type!(i32 => i32);
wasm_extern_type!(u32 => i32);
wasm_extern_type!(i64 => i64);
wasm_extern_type!(u64 => i64);
wasm_extern_type!(f32 => f32);
wasm_extern_type!(f64 => f64);
pub unsafe trait ValueType: Copy
where
Self: Sized,
{
}
macro_rules! convert_value_impl {
($t:ty) => {
unsafe impl ValueType for $t {}
};
( $($t:ty),* ) => {
$(
convert_value_impl!($t);
)*
};
}
convert_value_impl!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64);
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementType {
Anyfunc,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct TableDescriptor {
pub element: ElementType,
pub minimum: u32,
pub maximum: Option<u32>,
}
impl TableDescriptor {
pub(crate) fn fits_in_imported(&self, imported: TableDescriptor) -> bool {
let imported_max = imported.maximum.unwrap_or(u32::max_value());
let self_max = self.maximum.unwrap_or(u32::max_value());
self.element == imported.element
&& imported_max <= self_max
&& self.minimum <= imported.minimum
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum Initializer {
Const(Value),
GetGlobal(ImportedGlobalIndex),
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub struct GlobalDescriptor {
pub mutable: bool,
pub ty: Type,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GlobalInit {
pub desc: GlobalDescriptor,
pub init: Initializer,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemoryDescriptor {
pub minimum: Pages,
pub maximum: Option<Pages>,
pub shared: bool,
pub memory_type: MemoryType,
}
impl MemoryDescriptor {
pub fn new(minimum: Pages, maximum: Option<Pages>, shared: bool) -> Result<Self, String> {
let memory_type = match (maximum.is_some(), shared) {
(true, true) => MemoryType::SharedStatic,
(true, false) => MemoryType::Static,
(false, false) => MemoryType::Dynamic,
(false, true) => {
return Err("Max number of pages is required for shared memory".to_string());
}
};
Ok(MemoryDescriptor {
minimum,
maximum,
shared,
memory_type,
})
}
pub fn memory_type(&self) -> MemoryType {
self.memory_type
}
pub(crate) fn fits_in_imported(&self, imported: MemoryDescriptor) -> bool {
let imported_max = imported.maximum.unwrap_or(Pages(65_536));
let self_max = self.maximum.unwrap_or(Pages(65_536));
self.shared == imported.shared
&& imported_max <= self_max
&& self.minimum <= imported.minimum
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct FuncSig {
params: Cow<'static, [Type]>,
returns: Cow<'static, [Type]>,
}
impl FuncSig {
pub fn new<Params, Returns>(params: Params, returns: Returns) -> Self
where
Params: Into<Cow<'static, [Type]>>,
Returns: Into<Cow<'static, [Type]>>,
{
Self {
params: params.into(),
returns: returns.into(),
}
}
pub fn params(&self) -> &[Type] {
&self.params
}
pub fn returns(&self) -> &[Type] {
&self.returns
}
pub fn check_param_value_types(&self, params: &[Value]) -> bool {
self.params.len() == params.len()
&& self
.params
.iter()
.zip(params.iter().map(|val| val.ty()))
.all(|(t0, ref t1)| t0 == t1)
}
}
impl std::fmt::Display for FuncSig {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let params = self
.params
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(", ");
let returns = self
.returns
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(", ");
write!(f, "[{}] -> [{}]", params, returns)
}
}
pub trait LocalImport {
type Local: TypedIndex;
type Import: TypedIndex;
}
#[rustfmt::skip]
macro_rules! define_map_index {
($ty:ident) => {
#[derive(Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $ty (u32);
impl TypedIndex for $ty {
#[doc(hidden)]
fn new(index: usize) -> Self {
$ty (index as _)
}
#[doc(hidden)]
fn index(&self) -> usize {
self.0 as usize
}
}
};
($($normal_ty:ident,)* | local: $($local_ty:ident,)* | imported: $($imported_ty:ident,)*) => {
$(
define_map_index!($normal_ty);
define_map_index!($local_ty);
define_map_index!($imported_ty);
impl LocalImport for $normal_ty {
type Local = $local_ty;
type Import = $imported_ty;
}
)*
};
}
#[rustfmt::skip]
define_map_index![
FuncIndex, MemoryIndex, TableIndex, GlobalIndex,
| local: LocalFuncIndex, LocalMemoryIndex, LocalTableIndex, LocalGlobalIndex,
| imported: ImportedFuncIndex, ImportedMemoryIndex, ImportedTableIndex, ImportedGlobalIndex,
];
#[rustfmt::skip]
macro_rules! define_local_or_import {
($ty:ident, $local_ty:ident, $imported_ty:ident, $imports:ident) => {
impl $ty {
pub fn local_or_import(self, info: &ModuleInfo) -> LocalOrImport<$ty> {
if self.index() < info.$imports.len() {
LocalOrImport::Import(<Self as LocalImport>::Import::new(self.index()))
} else {
LocalOrImport::Local(<Self as LocalImport>::Local::new(self.index() - info.$imports.len()))
}
}
}
impl $local_ty {
pub fn convert_up(self, info: &ModuleInfo) -> $ty {
$ty ((self.index() + info.$imports.len()) as u32)
}
}
impl $imported_ty {
pub fn convert_up(self, _info: &ModuleInfo) -> $ty {
$ty (self.index() as u32)
}
}
};
($(($ty:ident | ($local_ty:ident, $imported_ty:ident): $imports:ident),)*) => {
$(
define_local_or_import!($ty, $local_ty, $imported_ty, $imports);
)*
};
}
#[rustfmt::skip]
define_local_or_import![
(FuncIndex | (LocalFuncIndex, ImportedFuncIndex): imported_functions),
(MemoryIndex | (LocalMemoryIndex, ImportedMemoryIndex): imported_memories),
(TableIndex | (LocalTableIndex, ImportedTableIndex): imported_tables),
(GlobalIndex | (LocalGlobalIndex, ImportedGlobalIndex): imported_globals),
];
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct SigIndex(u32);
impl TypedIndex for SigIndex {
#[doc(hidden)]
fn new(index: usize) -> Self {
SigIndex(index as _)
}
#[doc(hidden)]
fn index(&self) -> usize {
self.0 as usize
}
}
pub enum LocalOrImport<T>
where
T: LocalImport,
{
Local(T::Local),
Import(T::Import),
}
impl<T> LocalOrImport<T>
where
T: LocalImport,
{
pub fn local(self) -> Option<T::Local> {
match self {
LocalOrImport::Local(local) => Some(local),
LocalOrImport::Import(_) => None,
}
}
pub fn import(self) -> Option<T::Import> {
match self {
LocalOrImport::Import(import) => Some(import),
LocalOrImport::Local(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::types::NativeWasmType;
use crate::types::WasmExternType;
#[test]
fn test_native_types_round_trip() {
assert_eq!(
42i32,
i32::from_native(i32::from_binary((42i32).to_native().to_binary()))
);
assert_eq!(
-42i32,
i32::from_native(i32::from_binary((-42i32).to_native().to_binary()))
);
use std::i64;
let xi64 = i64::MAX;
assert_eq!(
xi64,
i64::from_native(i64::from_binary((xi64).to_native().to_binary()))
);
let yi64 = i64::MIN;
assert_eq!(
yi64,
i64::from_native(i64::from_binary((yi64).to_native().to_binary()))
);
assert_eq!(
16.5f32,
f32::from_native(f32::from_binary((16.5f32).to_native().to_binary()))
);
assert_eq!(
-16.5f32,
f32::from_native(f32::from_binary((-16.5f32).to_native().to_binary()))
);
use std::f64;
let xf64: f64 = f64::MAX;
assert_eq!(
xf64,
f64::from_native(f64::from_binary((xf64).to_native().to_binary()))
);
let yf64: f64 = f64::MIN;
assert_eq!(
yf64,
f64::from_native(f64::from_binary((yf64).to_native().to_binary()))
);
}
}