//! The [Value] trait describes what operations can be performed on interpreter values. The //! interpreter usually executes using [DataValue]s so an implementation is provided here. The fact //! that [Value] is a trait, however, allows interpretation of Cranelift IR on other kinds of //! values. use core::convert::TryFrom; use core::fmt::{self, Display, Formatter}; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::ir::immediates::{Ieee32, Ieee64}; use cranelift_codegen::ir::{types, Type}; use thiserror::Error; pub type ValueResult = Result; pub trait Value: Clone + From { // Identity. fn ty(&self) -> Type; fn int(n: i64, ty: Type) -> ValueResult; fn into_int(self) -> ValueResult; fn float(n: u64, ty: Type) -> ValueResult; fn into_float(self) -> ValueResult; fn is_nan(&self) -> ValueResult; fn bool(b: bool, ty: Type) -> ValueResult; fn into_bool(self) -> ValueResult; fn vector(v: [u8; 16], ty: Type) -> ValueResult; fn convert(self, kind: ValueConversionKind) -> ValueResult; // Comparison. fn eq(&self, other: &Self) -> ValueResult; fn gt(&self, other: &Self) -> ValueResult; fn ge(&self, other: &Self) -> ValueResult { Ok(self.eq(other)? || self.gt(other)?) } fn lt(&self, other: &Self) -> ValueResult { other.gt(self) } fn le(&self, other: &Self) -> ValueResult { Ok(other.eq(self)? || other.gt(self)?) } fn uno(&self, other: &Self) -> ValueResult; // Arithmetic. fn add(self, other: Self) -> ValueResult; fn sub(self, other: Self) -> ValueResult; fn mul(self, other: Self) -> ValueResult; fn div(self, other: Self) -> ValueResult; fn rem(self, other: Self) -> ValueResult; // Bitwise. fn shl(self, other: Self) -> ValueResult; fn ushr(self, other: Self) -> ValueResult; fn ishr(self, other: Self) -> ValueResult; fn rotl(self, other: Self) -> ValueResult; fn rotr(self, other: Self) -> ValueResult; fn and(self, other: Self) -> ValueResult; fn or(self, other: Self) -> ValueResult; fn xor(self, other: Self) -> ValueResult; fn not(self) -> ValueResult; } #[derive(Error, Debug)] pub enum ValueError { #[error("unable to convert type {1} into class {0}")] InvalidType(ValueTypeClass, Type), #[error("unable to convert value into type {0}")] InvalidValue(Type), #[error("unable to convert to primitive integer")] InvalidInteger(#[from] std::num::TryFromIntError), } #[derive(Debug)] pub enum ValueTypeClass { Integer, Boolean, Float, } impl Display for ValueTypeClass { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { ValueTypeClass::Integer => write!(f, "integer"), ValueTypeClass::Boolean => write!(f, "boolean"), ValueTypeClass::Float => write!(f, "float"), } } } #[derive(Debug)] pub enum ValueConversionKind { /// Throw a [ValueError] if an exact conversion to [Type] is not possible; e.g. in `i32` to /// `i16`, convert `0x00001234` to `0x1234`. Exact(Type), /// Truncate the value to fit into the specified [Type]; e.g. in `i16` to `i8`, `0x1234` becomes /// `0x34`. Truncate(Type), /// Convert to a larger integer type, extending the sign bit; e.g. in `i8` to `i16`, `0xff` /// becomes `0xffff`. SignExtend(Type), /// Convert to a larger integer type, extending with zeroes; e.g. in `i8` to `i16`, `0xff` /// becomes `0x00ff`. ZeroExtend(Type), /// Convert a signed integer to its unsigned value of the same size; e.g. in `i8` to `u8`, /// `0xff` (`-1`) becomes `0xff` (`255`). ToUnsigned, /// Convert an unsigned integer to its signed value of the same size; e.g. in `u8` to `i8`, /// `0xff` (`255`) becomes `0xff` (`-1`). ToSigned, /// Convert a floating point number by rounding to the nearest possible value with ties to even. /// See `fdemote`, e.g. RoundNearestEven(Type), } /// Helper for creating match expressions over [DataValue]. macro_rules! unary_match { ( $op:tt($arg1:expr); [ $( $data_value_ty:ident ),* ] ) => { match $arg1 { $( DataValue::$data_value_ty(a) => { Ok(DataValue::$data_value_ty($op a)) } )* _ => unimplemented!() } }; } macro_rules! binary_match { ( $op:tt($arg1:expr, $arg2:expr); [ $( $data_value_ty:ident ),* ] ) => { match ($arg1, $arg2) { $( (DataValue::$data_value_ty(a), DataValue::$data_value_ty(b)) => { Ok(DataValue::$data_value_ty(a $op b)) } )* _ => unimplemented!() } }; ( $op:tt($arg1:expr, $arg2:expr); unsigned integers ) => { match ($arg1, $arg2) { (DataValue::I8(a), DataValue::I8(b)) => { Ok(DataValue::I8((u8::try_from(*a)? $op u8::try_from(*b)?) as i8)) } (DataValue::I16(a), DataValue::I16(b)) => { Ok(DataValue::I16((u16::try_from(*a)? $op u16::try_from(*b)?) as i16)) } (DataValue::I32(a), DataValue::I32(b)) => { Ok(DataValue::I32((u32::try_from(*a)? $op u32::try_from(*b)?) as i32)) } (DataValue::I64(a), DataValue::I64(b)) => { Ok(DataValue::I64((u64::try_from(*a)? $op u64::try_from(*b)?) as i64)) } _ => { Err(ValueError::InvalidType(ValueTypeClass::Integer, if !($arg1).ty().is_int() { ($arg1).ty() } else { ($arg2).ty() })) } } }; } macro_rules! comparison_match { ( $op:path[$arg1:expr, $arg2:expr]; [ $( $data_value_ty:ident ),* ] ) => { match ($arg1, $arg2) { $( (DataValue::$data_value_ty(a), DataValue::$data_value_ty(b)) => { Ok($op(a, b)) } )* _ => unimplemented!("comparison: {:?}, {:?}", $arg1, $arg2) } }; } impl Value for DataValue { fn ty(&self) -> Type { self.ty() } fn int(n: i64, ty: Type) -> ValueResult { if ty.is_int() && !ty.is_vector() { DataValue::from_integer(n, ty).map_err(|_| ValueError::InvalidValue(ty)) } else { Err(ValueError::InvalidType(ValueTypeClass::Integer, ty)) } } fn into_int(self) -> ValueResult { match self { DataValue::I8(n) => Ok(n as i64), DataValue::I16(n) => Ok(n as i64), DataValue::I32(n) => Ok(n as i64), DataValue::I64(n) => Ok(n), _ => Err(ValueError::InvalidType(ValueTypeClass::Integer, self.ty())), } } fn float(bits: u64, ty: Type) -> ValueResult { match ty { types::F32 => Ok(DataValue::F32(Ieee32::with_bits(u32::try_from(bits)?))), types::F64 => Ok(DataValue::F64(Ieee64::with_bits(bits))), _ => Err(ValueError::InvalidType(ValueTypeClass::Float, ty)), } } fn into_float(self) -> ValueResult { unimplemented!() } fn is_nan(&self) -> ValueResult { match self { DataValue::F32(f) => Ok(f.is_nan()), DataValue::F64(f) => Ok(f.is_nan()), _ => Err(ValueError::InvalidType(ValueTypeClass::Float, self.ty())), } } fn bool(b: bool, ty: Type) -> ValueResult { assert!(ty.is_bool()); Ok(DataValue::B(b)) } fn into_bool(self) -> ValueResult { match self { DataValue::B(b) => Ok(b), _ => Err(ValueError::InvalidType(ValueTypeClass::Boolean, self.ty())), } } fn vector(_v: [u8; 16], _ty: Type) -> ValueResult { unimplemented!() } fn convert(self, kind: ValueConversionKind) -> ValueResult { Ok(match kind { ValueConversionKind::Exact(ty) => match (self, ty) { // TODO a lot to do here: from bmask to ireduce to raw_bitcast... (DataValue::I64(n), types::I32) => DataValue::I32(i32::try_from(n)?), (DataValue::B(b), t) if t.is_bool() => DataValue::B(b), (dv, _) => unimplemented!("conversion: {} -> {:?}", dv.ty(), kind), }, ValueConversionKind::Truncate(ty) => match (self.ty(), ty) { (types::I64, types::I32) => unimplemented!(), (types::I64, types::I16) => unimplemented!(), (types::I64, types::I8) => unimplemented!(), (types::I32, types::I16) => unimplemented!(), (types::I32, types::I8) => unimplemented!(), (types::I16, types::I8) => unimplemented!(), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), }, ValueConversionKind::SignExtend(ty) => match (self.ty(), ty) { (types::I8, types::I16) => unimplemented!(), (types::I8, types::I32) => unimplemented!(), (types::I8, types::I64) => unimplemented!(), (types::I16, types::I32) => unimplemented!(), (types::I16, types::I64) => unimplemented!(), (types::I32, types::I64) => unimplemented!(), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), }, ValueConversionKind::ZeroExtend(ty) => match (self.ty(), ty) { (types::I8, types::I16) => unimplemented!(), (types::I8, types::I32) => unimplemented!(), (types::I8, types::I64) => unimplemented!(), (types::I16, types::I32) => unimplemented!(), (types::I16, types::I64) => unimplemented!(), (types::I32, types::I64) => unimplemented!(), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), }, ValueConversionKind::ToUnsigned => match self { DataValue::I8(n) => DataValue::U8(n as u8), DataValue::I16(n) => DataValue::U16(n as u16), DataValue::I32(n) => DataValue::U32(n as u32), DataValue::I64(n) => DataValue::U64(n as u64), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), }, ValueConversionKind::ToSigned => match self { DataValue::U8(n) => DataValue::I8(n as i8), DataValue::U16(n) => DataValue::I16(n as i16), DataValue::U32(n) => DataValue::I32(n as i32), DataValue::U64(n) => DataValue::I64(n as i64), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), }, ValueConversionKind::RoundNearestEven(ty) => match (self.ty(), ty) { (types::F64, types::F32) => unimplemented!(), _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), }, }) } fn eq(&self, other: &Self) -> ValueResult { comparison_match!(PartialEq::eq[&self, &other]; [I8, I16, I32, I64, U8, U16, U32, U64, F32, F64]) } fn gt(&self, other: &Self) -> ValueResult { comparison_match!(PartialOrd::gt[&self, &other]; [I8, I16, I32, I64, U8, U16, U32, U64, F32, F64]) } fn uno(&self, other: &Self) -> ValueResult { Ok(self.is_nan()? || other.is_nan()?) } fn add(self, other: Self) -> ValueResult { binary_match!(+(&self, &other); [I8, I16, I32, I64]) // TODO: floats must handle NaNs, +/-0 } fn sub(self, other: Self) -> ValueResult { binary_match!(-(&self, &other); [I8, I16, I32, I64]) // TODO: floats must handle NaNs, +/-0 } fn mul(self, other: Self) -> ValueResult { binary_match!(*(&self, &other); [I8, I16, I32, I64]) } fn div(self, other: Self) -> ValueResult { binary_match!(/(&self, &other); [I8, I16, I32, I64]) } fn rem(self, other: Self) -> ValueResult { binary_match!(%(&self, &other); [I8, I16, I32, I64]) } fn shl(self, other: Self) -> ValueResult { binary_match!(<<(&self, &other); [I8, I16, I32, I64]) } fn ushr(self, other: Self) -> ValueResult { binary_match!(>>(&self, &other); unsigned integers) } fn ishr(self, other: Self) -> ValueResult { binary_match!(>>(&self, &other); [I8, I16, I32, I64]) } fn rotl(self, _other: Self) -> ValueResult { unimplemented!() } fn rotr(self, _other: Self) -> ValueResult { unimplemented!() } fn and(self, other: Self) -> ValueResult { binary_match!(&(&self, &other); [I8, I16, I32, I64]) } fn or(self, other: Self) -> ValueResult { binary_match!(|(&self, &other); [I8, I16, I32, I64]) } fn xor(self, other: Self) -> ValueResult { binary_match!(^(&self, &other); [I8, I16, I32, I64]) } fn not(self) -> ValueResult { unary_match!(!(&self); [I8, I16, I32, I64]) } }