//! Ordered block-local statements for Slonik IR. //! //! In the lift IR every value-producing operation is a statement anchored in a //! block. This ensures memory order is always explicit in the statement stream //! rather than floating in a sea of pure expressions. use crate::ir::{BinaryOp, Block, CastOp, FloatCC, IntCC, MemSize, UnaryOp, Value}; use cranelift_entity::EntityList; /// A compact list of SSA values. pub type ValueList = EntityList; /// A target block together with the SSA arguments passed to that block. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct BlockCall { /// The destination block. pub block: Block, /// The arguments passed to the destination block. pub args: ValueList, } /// An ordered statement inside a basic block. /// /// Variants that carry a `result` field produce exactly one SSA value. /// The `result` value's [`ValueDef`] points back to this statement. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum StmtData { // ------------------------------------------------------------------ // // Value-producing statements // // ------------------------------------------------------------------ // /// Signed integer literal. Result type determines the width. IConst { imm: i64, result: Value }, /// 32-bit float literal stored as raw IEEE-754 bits. F32Const { bits: u32, result: Value }, /// 64-bit float literal stored as raw IEEE-754 bits. F64Const { bits: u64, result: Value }, /// Boolean literal. BConst { value: bool, result: Value }, /// Unary operation. Unary { op: UnaryOp, arg: Value, result: Value }, /// Binary operation. Binary { op: BinaryOp, lhs: Value, rhs: Value, result: Value }, /// Cast or conversion. Cast { op: CastOp, arg: Value, result: Value }, /// Integer comparison. Result type is always `bool`. Icmp { cc: IntCC, lhs: Value, rhs: Value, result: Value }, /// Floating-point comparison. Result type is always `bool`. Fcmp { cc: FloatCC, lhs: Value, rhs: Value, result: Value }, /// Conditional select. `cond` must be `bool`. Select { cond: Value, if_true: Value, if_false: Value, result: Value, }, /// Memory load. Anchored in the statement stream to preserve memory order. Load { addr: Value, size: MemSize, result: Value }, // ------------------------------------------------------------------ // // Side-effecting statements (no result value) // // ------------------------------------------------------------------ // /// Memory store. Store { addr: Value, value: Value, size: MemSize }, /// Effectful call. Call { callee: Value, args: ValueList }, // ------------------------------------------------------------------ // // Terminators // // ------------------------------------------------------------------ // /// Unconditional jump. Jump { dst: BlockCall }, /// Conditional branch. BrIf { cond: Value, then_dst: BlockCall, else_dst: BlockCall, }, /// Return from the current body. Return { values: ValueList }, } impl StmtData { /// Returns the result value produced by this statement, if any. pub const fn result(self) -> Option { match self { Self::IConst { result, .. } | Self::F32Const { result, .. } | Self::F64Const { result, .. } | Self::BConst { result, .. } | Self::Unary { result, .. } | Self::Binary { result, .. } | Self::Cast { result, .. } | Self::Icmp { result, .. } | Self::Fcmp { result, .. } | Self::Select { result, .. } | Self::Load { result, .. } => Some(result), Self::Store { .. } | Self::Call { .. } | Self::Jump { .. } | Self::BrIf { .. } | Self::Return { .. } => None, } } /// Returns whether this statement is a block terminator. pub const fn is_terminator(self) -> bool { matches!(self, Self::Jump { .. } | Self::BrIf { .. } | Self::Return { .. }) } /// Returns whether this statement may read memory. pub const fn may_read_memory(self) -> bool { matches!(self, Self::Load { .. } | Self::Call { .. }) } /// Returns whether this statement may write memory. pub const fn may_write_memory(self) -> bool { matches!(self, Self::Store { .. } | Self::Call { .. }) } }