//! Pure expression nodes and SSA value definitions for Slonik IR. use core::fmt; use crate::ir::{Block, Expr, Type, Value}; /// Memory access width in bytes. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum MemSize { S1, S2, S4, S8, S16, } impl MemSize { /// Returns the access width in bytes. pub const fn bytes(self) -> u8 { match self { Self::S1 => 1, Self::S2 => 2, Self::S4 => 4, Self::S8 => 8, Self::S16 => 16, } } /// Returns the access width in bits. pub const fn bits(self) -> u16 { (self.bytes() as u16) * 8 } } impl fmt::Display for MemSize { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}B", self.bytes()) } } /// Integer comparison condition codes. /// /// These are used by [`ExprData::Icmp`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum IntCC { Eq, Ne, Ult, Ule, Ugt, Uge, Slt, Sle, Sgt, Sge, } impl IntCC { /// Returns the logical inverse of this condition code. pub const fn invert(self) -> Self { match self { Self::Eq => Self::Ne, Self::Ne => Self::Eq, Self::Ult => Self::Uge, Self::Ule => Self::Ugt, Self::Ugt => Self::Ule, Self::Uge => Self::Ult, Self::Slt => Self::Sge, Self::Sle => Self::Sgt, Self::Sgt => Self::Sle, Self::Sge => Self::Slt, } } /// Returns the condition code obtained by swapping the operands. pub const fn swap_args(self) -> Self { match self { Self::Eq => Self::Eq, Self::Ne => Self::Ne, Self::Ult => Self::Ugt, Self::Ule => Self::Uge, Self::Ugt => Self::Ult, Self::Uge => Self::Ule, Self::Slt => Self::Sgt, Self::Sle => Self::Sge, Self::Sgt => Self::Slt, Self::Sge => Self::Sle, } } } impl fmt::Display for IntCC { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Eq => "eq", Self::Ne => "ne", Self::Ult => "ult", Self::Ule => "ule", Self::Ugt => "ugt", Self::Uge => "uge", Self::Slt => "slt", Self::Sle => "sle", Self::Sgt => "sgt", Self::Sge => "sge", }; f.write_str(s) } } /// Floating-point comparison condition codes. /// /// These are used by [`ExprData::Fcmp`]. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum FloatCC { Eq, Ne, Lt, Le, Gt, Ge, Ordered, Unordered, } impl FloatCC { /// Returns the logical inverse of this condition code. pub const fn invert(self) -> Self { match self { Self::Eq => Self::Ne, Self::Ne => Self::Eq, Self::Lt => Self::Ge, Self::Le => Self::Gt, Self::Gt => Self::Le, Self::Ge => Self::Lt, Self::Ordered => Self::Unordered, Self::Unordered => Self::Ordered, } } /// Returns the condition code obtained by swapping the operands. pub const fn swap_args(self) -> Self { match self { Self::Eq => Self::Eq, Self::Ne => Self::Ne, Self::Lt => Self::Gt, Self::Le => Self::Ge, Self::Gt => Self::Lt, Self::Ge => Self::Le, Self::Ordered => Self::Ordered, Self::Unordered => Self::Unordered, } } } impl fmt::Display for FloatCC { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Eq => "eq", Self::Ne => "ne", Self::Lt => "lt", Self::Le => "le", Self::Gt => "gt", Self::Ge => "ge", Self::Ordered => "ord", Self::Unordered => "uno", }; f.write_str(s) } } /// Unary expression operators. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum UnaryOp { /// Integer or bitvector negation. Neg, /// Bitwise not. Not, /// Floating-point negation. FNeg, } impl fmt::Display for UnaryOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Neg => "neg", Self::Not => "not", Self::FNeg => "fneg", }; f.write_str(s) } } /// Binary expression operators. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum BinaryOp { IAdd, ISub, IMul, UDiv, SDiv, URem, SRem, And, Or, Xor, Shl, LShr, AShr, FAdd, FSub, FMul, FDiv, } impl BinaryOp { /// Returns whether this binary operator is commutative. pub const fn is_commutative(self) -> bool { matches!( self, Self::IAdd | Self::IMul | Self::And | Self::Or | Self::Xor | Self::FAdd | Self::FMul ) } } impl fmt::Display for BinaryOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::IAdd => "iadd", Self::ISub => "isub", Self::IMul => "imul", Self::UDiv => "udiv", Self::SDiv => "sdiv", Self::URem => "urem", Self::SRem => "srem", Self::And => "and", Self::Or => "or", Self::Xor => "xor", Self::Shl => "shl", Self::LShr => "lshr", Self::AShr => "ashr", Self::FAdd => "fadd", Self::FSub => "fsub", Self::FMul => "fmul", Self::FDiv => "fdiv", }; f.write_str(s) } } /// Cast and conversion operators. /// /// The source type is taken from the operand value. /// The destination type is taken from the result value. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum CastOp { /// Zero-extension. Zext, /// Sign-extension. Sext, /// Truncation. Trunc, /// Bit-preserving reinterpretation. Bitcast, /// Integer-to-pointer conversion. IntToPtr, /// Pointer-to-integer conversion. PtrToInt, } impl fmt::Display for CastOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Self::Zext => "zext", Self::Sext => "sext", Self::Trunc => "trunc", Self::Bitcast => "bitcast", Self::IntToPtr => "inttoptr", Self::PtrToInt => "ptrtoint", }; f.write_str(s) } } /// A pure value-producing expression node. /// /// Every expression defines exactly one SSA value. /// The type of that value is stored in the corresponding [`ValueData`]. #[derive(Clone, Debug, PartialEq)] pub enum ExprData { /// A signed integer literal. /// /// The result type determines the intended width. IConst { imm: i64 }, /// A 32-bit floating-point literal stored as raw IEEE-754 bits. F32Const { bits: u32 }, /// A 64-bit floating-point literal stored as raw IEEE-754 bits. F64Const { bits: u64 }, /// A boolean literal. BConst { value: bool }, /// A unary operation. Unary { op: UnaryOp, arg: Value }, /// A binary operation. Binary { op: BinaryOp, lhs: Value, rhs: Value, }, /// A cast or conversion. Cast { op: CastOp, arg: Value }, /// An integer comparison. /// /// The result type is expected to be `bool`. Icmp { cc: IntCC, lhs: Value, rhs: Value }, /// A floating-point comparison. /// /// The result type is expected to be `bool`. Fcmp { cc: FloatCC, lhs: Value, rhs: Value }, /// A conditional select. /// /// `cond` must be a boolean value. Select { cond: Value, if_true: Value, if_false: Value, }, /// A memory load. Load { addr: Value, size: MemSize }, } impl ExprData { /// Returns whether this expression may observe memory. pub const fn may_read_memory(&self) -> bool { matches!(self, Self::Load { .. }) } /// Returns whether this expression may trap. pub const fn may_trap(&self) -> bool { matches!(self, Self::Load { .. }) } } /// The definition site of an SSA value. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ValueDef { /// A value defined by an expression. Expr(Expr), /// A block parameter at position `index`. Param(Block, u16), } /// Metadata attached to an SSA value. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ValueData { /// The semantic type of the value. pub ty: Type, /// The definition site of the value. pub def: ValueDef, }