//! 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<Value>;

/// 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<Value> {
        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 { .. })
    }
}
