//! Instruction-level control-flow facts.

/// A statically described branch or call target.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FlowTarget {
    /// A statically known target address.
    Direct(u64),

    /// A dynamically computed target.
    Indirect,
}

/// The control-flow behavior of one decoded instruction.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FlowKind {
    /// Execution continues at the next instruction.
    FallThrough,

    /// Unconditional jump.
    Jump { target: FlowTarget },

    /// Conditional jump.
    ///
    /// A conditional jump always has a taken edge and a fallthrough path.
    CondJump { target: FlowTarget },

    /// Call instruction.
    ///
    /// `returns` tells later analyses whether the architecture/lifter believes
    /// execution may continue at the next instruction after the call.
    Call { target: FlowTarget, returns: bool },

    /// Return from the current routine.
    Return,

    /// Trap, breakpoint, or other terminal fault-like instruction.
    Trap,
}

/// Flow facts for one decoded instruction.
///
/// This structure is the architecture-facing summary used by later CFG-building
/// or region-building code.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FlowInfo {
    /// How many bytes does this instruction occupy
    len: u8,
    /// What kind of control-flow behaviour does it have?
    kind: FlowKind,
}

impl FlowInfo {
    /// Creates a new flow summary.
    pub const fn new(len: u8, kind: FlowKind) -> Self {
        Self { len, kind }
    }

    /// Returns the instruction length in bytes.
    pub const fn len(self) -> u8 {
        self.len
    }

    /// Returns the control-flow kind.
    pub const fn kind(self) -> FlowKind {
        self.kind
    }

    /// Returns whether this instruction ends the current basic block.
    ///
    /// This is still an instruction-level fact, not whole-program CFG recovery.
    pub const fn terminates_block(self) -> bool {
        match self.kind {
            FlowKind::FallThrough => false,
            FlowKind::Jump { .. } => true,
            FlowKind::CondJump { .. } => true,
            FlowKind::Call { returns, .. } => !returns,
            FlowKind::Return => true,
            FlowKind::Trap => true,
        }
    }

    /// Returns whether execution may continue at the next instruction.
    pub const fn has_fallthrough(self) -> bool {
        match self.kind {
            FlowKind::FallThrough => true,
            FlowKind::Jump { .. } => false,
            FlowKind::CondJump { .. } => true,
            FlowKind::Call { returns, .. } => returns,
            FlowKind::Return => false,
            FlowKind::Trap => false,
        }
    }

    /// Returns the explicit non-fallthrough target carried by the instruction,
    /// if any.
    pub const fn target(self) -> Option<FlowTarget> {
        match self.kind {
            FlowKind::Jump { target } => Some(target),
            FlowKind::CondJump { target } => Some(target),
            FlowKind::Call { target, .. } => Some(target),
            FlowKind::FallThrough | FlowKind::Return | FlowKind::Trap => None,
        }
    }
}
