//! Instruction operand sub-components (aka "parts"): definitions and printing.

use super::regs::{self};
use super::EmitState;
use crate::ir::condcodes::{FloatCC, IntCC};
use crate::ir::types::*;
use crate::ir::{MemFlags, Type};
use crate::isa::x64::inst::regs::pretty_print_reg;
use crate::isa::x64::inst::Inst;
use crate::machinst::*;
use regalloc2::VReg;
use smallvec::{smallvec, SmallVec};
use std::fmt;
use std::string::String;

pub use crate::isa::x64::lower::isle::generated_code::DivSignedness;

/// An extenstion trait for converting `Writable{Xmm,Gpr}` to `Writable<Reg>`.
pub trait ToWritableReg {
    /// Convert `Writable{Xmm,Gpr}` to `Writable<Reg>`.
    fn to_writable_reg(&self) -> Writable<Reg>;
}

/// An extension trait for converting `Writable<Reg>` to `Writable{Xmm,Gpr}`.
pub trait FromWritableReg: Sized {
    /// Convert `Writable<Reg>` to `Writable{Xmm,Gpr}`.
    fn from_writable_reg(w: Writable<Reg>) -> Option<Self>;
}

/// A macro for defining a newtype of `Reg` that enforces some invariant about
/// the wrapped `Reg` (such as that it is of a particular register class).
macro_rules! newtype_of_reg {
    (
        $newtype_reg:ident,
        $newtype_writable_reg:ident,
        $newtype_option_writable_reg:ident,
        reg_mem: ($($newtype_reg_mem:ident $(aligned:$aligned:ident)?),*),
        reg_mem_imm: ($($newtype_reg_mem_imm:ident $(aligned:$aligned_imm:ident)?),*),
        $newtype_imm8_reg:ident,
        |$check_reg:ident| $check:expr
    ) => {
        /// A newtype wrapper around `Reg`.
        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub struct $newtype_reg(Reg);

        impl PartialEq<Reg> for $newtype_reg {
            fn eq(&self, other: &Reg) -> bool {
                self.0 == *other
            }
        }

        impl From<$newtype_reg> for Reg {
            fn from(r: $newtype_reg) -> Self {
                r.0
            }
        }

        impl $newtype_reg {
            /// Create this newtype from the given register, or return `None` if the register
            /// is not a valid instance of this newtype.
            pub fn new($check_reg: Reg) -> Option<Self> {
                if $check {
                    Some(Self($check_reg))
                } else {
                    None
                }
            }

            /// Get this newtype's underlying `Reg`.
            pub fn to_reg(self) -> Reg {
                self.0
            }
        }

        // Convenience impl so that people working with this newtype can use it
        // "just like" a plain `Reg`.
        //
        // NB: We cannot implement `DerefMut` because that would let people do
        // nasty stuff like `*my_gpr.deref_mut() = some_xmm_reg`, breaking the
        // invariants that `Gpr` provides.
        impl std::ops::Deref for $newtype_reg {
            type Target = Reg;

            fn deref(&self) -> &Reg {
                &self.0
            }
        }

        /// Writable Gpr.
        pub type $newtype_writable_reg = Writable<$newtype_reg>;

        #[allow(dead_code)] // Used by some newtypes and not others.
        /// Optional writable Gpr.
        pub type $newtype_option_writable_reg = Option<Writable<$newtype_reg>>;

        impl ToWritableReg for $newtype_writable_reg {
            fn to_writable_reg(&self) -> Writable<Reg> {
                Writable::from_reg(self.to_reg().to_reg())
            }
        }

        impl FromWritableReg for $newtype_writable_reg {
            fn from_writable_reg(w: Writable<Reg>) -> Option<Self> {
                Some(Writable::from_reg($newtype_reg::new(w.to_reg())?))
            }
        }

        $(
            /// A newtype wrapper around `RegMem` for general-purpose registers.
            #[derive(Clone, Debug)]
            pub struct $newtype_reg_mem(RegMem);

            impl From<$newtype_reg_mem> for RegMem {
                fn from(rm: $newtype_reg_mem) -> Self {
                    rm.0
                }
            }
            impl<'a> From<&'a $newtype_reg_mem> for &'a RegMem {
                fn from(rm: &'a $newtype_reg_mem) -> &'a RegMem {
                    &rm.0
                }
            }

            impl From<$newtype_reg> for $newtype_reg_mem {
                fn from(r: $newtype_reg) -> Self {
                    $newtype_reg_mem(RegMem::reg(r.into()))
                }
            }

            impl $newtype_reg_mem {
                /// Construct a `RegMem` newtype from the given `RegMem`, or return
                /// `None` if the `RegMem` is not a valid instance of this `RegMem`
                /// newtype.
                pub fn new(rm: RegMem) -> Option<Self> {
                    match rm {
                        RegMem::Mem { addr } => {
                            let mut _allow = true;
                            $(
                                if $aligned {
                                    _allow = addr.aligned();
                                }
                            )?
                            if _allow {
                                Some(Self(RegMem::Mem { addr }))
                            } else {
                                None
                            }
                        }
                        RegMem::Reg { reg: $check_reg } if $check => Some(Self(rm)),
                        RegMem::Reg { reg: _ } => None,
                    }
                }

                /// Convert this newtype into its underlying `RegMem`.
                pub fn to_reg_mem(self) -> RegMem {
                    self.0
                }

                #[allow(dead_code)] // Used by some newtypes and not others.
                pub(crate) fn get_operands<F: Fn(VReg) -> VReg>(
                    &self,
                    collector: &mut OperandCollector<'_, F>,
                ) {
                    self.0.get_operands(collector);
                }
            }
            impl PrettyPrint for $newtype_reg_mem {
                fn pretty_print(&self, size: u8, allocs: &mut AllocationConsumer<'_>) -> String {
                    self.0.pretty_print(size, allocs)
                }
            }
        )*

        $(
            /// A newtype wrapper around `RegMemImm`.
            #[derive(Clone, Debug)]
            pub struct $newtype_reg_mem_imm(RegMemImm);

            impl From<$newtype_reg_mem_imm> for RegMemImm {
                fn from(rmi: $newtype_reg_mem_imm) -> RegMemImm {
                    rmi.0
                }
            }
            impl<'a> From<&'a $newtype_reg_mem_imm> for &'a RegMemImm {
                fn from(rmi: &'a $newtype_reg_mem_imm) -> &'a RegMemImm {
                    &rmi.0
                }
            }

            impl From<$newtype_reg> for $newtype_reg_mem_imm {
                fn from(r: $newtype_reg) -> Self {
                    $newtype_reg_mem_imm(RegMemImm::reg(r.into()))
                }
            }

            impl $newtype_reg_mem_imm {
                /// Construct this newtype from the given `RegMemImm`, or return
                /// `None` if the `RegMemImm` is not a valid instance of this
                /// newtype.
                pub fn new(rmi: RegMemImm) -> Option<Self> {
                    match rmi {
                        RegMemImm::Imm { .. } => Some(Self(rmi)),
                        RegMemImm::Mem { addr } => {
                            let mut _allow = true;
                            $(
                                if $aligned_imm {
                                    _allow = addr.aligned();
                                }
                            )?
                            if _allow {
                                Some(Self(RegMemImm::Mem { addr }))
                            } else {
                                None
                            }
                        }
                        RegMemImm::Reg { reg: $check_reg } if $check => Some(Self(rmi)),
                        RegMemImm::Reg { reg: _ } => None,
                    }
                }

                /// Convert this newtype into its underlying `RegMemImm`.
                #[allow(dead_code)] // Used by some newtypes and not others.
                pub fn to_reg_mem_imm(self) -> RegMemImm {
                    self.0
                }

                #[allow(dead_code)] // Used by some newtypes and not others.
                pub(crate) fn get_operands<F: Fn(VReg) -> VReg>(
                    &self,
                    collector: &mut OperandCollector<'_, F>,
                ) {
                    self.0.get_operands(collector);
                }
            }

            impl PrettyPrint for $newtype_reg_mem_imm {
                fn pretty_print(&self, size: u8, allocs: &mut AllocationConsumer<'_>) -> String {
                    self.0.pretty_print(size, allocs)
                }
            }
        )*

        /// A newtype wrapper around `Imm8Reg`.
        #[derive(Clone, Debug)]
        #[allow(dead_code)] // Used by some newtypes and not others.
        pub struct $newtype_imm8_reg(Imm8Reg);

        impl From<$newtype_reg> for $newtype_imm8_reg {
            fn from(r: $newtype_reg) -> Self {
                Self(Imm8Reg::Reg { reg: r.to_reg() })
            }
        }

        impl $newtype_imm8_reg {
            /// Construct this newtype from the given `Imm8Reg`, or return
            /// `None` if the `Imm8Reg` is not a valid instance of this newtype.
            #[allow(dead_code)] // Used by some newtypes and not others.
            pub fn new(imm8_reg: Imm8Reg) -> Option<Self> {
                match imm8_reg {
                    Imm8Reg::Imm8 { .. } => Some(Self(imm8_reg)),
                    Imm8Reg::Reg { reg: $check_reg } if $check => Some(Self(imm8_reg)),
                    Imm8Reg::Reg { reg: _ } => None,
                }
            }

            /// Convert this newtype into its underlying `Imm8Reg`.
            #[allow(dead_code)] // Used by some newtypes and not others.
            pub fn to_imm8_reg(self) -> Imm8Reg {
                self.0
            }
        }
    };
}

// Define a newtype of `Reg` for general-purpose registers.
newtype_of_reg!(
    Gpr,
    WritableGpr,
    OptionWritableGpr,
    reg_mem: (GprMem),
    reg_mem_imm: (GprMemImm),
    Imm8Gpr,
    |reg| reg.class() == RegClass::Int
);

// Define a newtype of `Reg` for XMM registers.
newtype_of_reg!(
    Xmm,
    WritableXmm,
    OptionWritableXmm,
    reg_mem: (XmmMem, XmmMemAligned aligned:true),
    reg_mem_imm: (XmmMemImm, XmmMemAlignedImm aligned:true),
    Imm8Xmm,
    |reg| reg.class() == RegClass::Float
);

// N.B.: `Amode` is defined in `inst.isle`. We add some convenience
// constructors here.

// Re-export the type from the ISLE generated code.
pub use crate::isa::x64::lower::isle::generated_code::Amode;

impl Amode {
    /// Create an immediate sign-extended and register addressing mode.
    pub fn imm_reg(simm32: i32, base: Reg) -> Self {
        debug_assert!(base.class() == RegClass::Int);
        Self::ImmReg {
            simm32,
            base,
            flags: MemFlags::trusted(),
        }
    }

    /// Create a sign-extended-32-to-64 with register and shift addressing mode.
    pub fn imm_reg_reg_shift(simm32: i32, base: Gpr, index: Gpr, shift: u8) -> Self {
        debug_assert!(base.class() == RegClass::Int);
        debug_assert!(index.class() == RegClass::Int);
        debug_assert!(shift <= 3);
        Self::ImmRegRegShift {
            simm32,
            base,
            index,
            shift,
            flags: MemFlags::trusted(),
        }
    }

    pub(crate) fn rip_relative(target: MachLabel) -> Self {
        Self::RipRelative { target }
    }

    pub(crate) fn with_flags(&self, flags: MemFlags) -> Self {
        match self {
            &Self::ImmReg { simm32, base, .. } => Self::ImmReg {
                simm32,
                base,
                flags,
            },
            &Self::ImmRegRegShift {
                simm32,
                base,
                index,
                shift,
                ..
            } => Self::ImmRegRegShift {
                simm32,
                base,
                index,
                shift,
                flags,
            },
            _ => panic!("Amode {:?} cannot take memflags", self),
        }
    }

    /// Add the registers mentioned by `self` to `collector`.
    pub(crate) fn get_operands<F: Fn(VReg) -> VReg>(
        &self,
        collector: &mut OperandCollector<'_, F>,
    ) {
        match self {
            Amode::ImmReg { base, .. } => {
                if *base != regs::rbp() && *base != regs::rsp() {
                    collector.reg_use(*base);
                }
            }
            Amode::ImmRegRegShift { base, index, .. } => {
                debug_assert_ne!(base.to_reg(), regs::rbp());
                debug_assert_ne!(base.to_reg(), regs::rsp());
                collector.reg_use(base.to_reg());
                debug_assert_ne!(index.to_reg(), regs::rbp());
                debug_assert_ne!(index.to_reg(), regs::rsp());
                collector.reg_use(index.to_reg());
            }
            Amode::RipRelative { .. } => {
                // RIP isn't involved in regalloc.
            }
        }
    }

    /// Same as `get_operands`, but add the registers in the "late" phase.
    pub(crate) fn get_operands_late<F: Fn(VReg) -> VReg>(
        &self,
        collector: &mut OperandCollector<'_, F>,
    ) {
        match self {
            Amode::ImmReg { base, .. } => {
                collector.reg_late_use(*base);
            }
            Amode::ImmRegRegShift { base, index, .. } => {
                collector.reg_late_use(base.to_reg());
                collector.reg_late_use(index.to_reg());
            }
            Amode::RipRelative { .. } => {
                // RIP isn't involved in regalloc.
            }
        }
    }

    pub(crate) fn get_flags(&self) -> MemFlags {
        match self {
            Amode::ImmReg { flags, .. } | Amode::ImmRegRegShift { flags, .. } => *flags,
            Amode::RipRelative { .. } => MemFlags::trusted(),
        }
    }

    pub(crate) fn can_trap(&self) -> bool {
        !self.get_flags().notrap()
    }

    pub(crate) fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self {
        // The order in which we consume allocs here must match the
        // order in which we produce operands in get_operands() above.
        match self {
            &Amode::ImmReg {
                simm32,
                base,
                flags,
            } => {
                let base = if base == regs::rsp() || base == regs::rbp() {
                    base
                } else {
                    allocs.next(base)
                };
                Amode::ImmReg {
                    simm32,
                    flags,
                    base,
                }
            }
            &Amode::ImmRegRegShift {
                simm32,
                base,
                index,
                shift,
                flags,
            } => Amode::ImmRegRegShift {
                simm32,
                shift,
                flags,
                base: Gpr::new(allocs.next(*base)).unwrap(),
                index: Gpr::new(allocs.next(*index)).unwrap(),
            },
            &Amode::RipRelative { target } => Amode::RipRelative { target },
        }
    }

    /// Offset the amode by a fixed offset.
    pub(crate) fn offset(&self, offset: i32) -> Self {
        let mut ret = self.clone();
        match &mut ret {
            &mut Amode::ImmReg { ref mut simm32, .. } => *simm32 += offset,
            &mut Amode::ImmRegRegShift { ref mut simm32, .. } => *simm32 += offset,
            _ => panic!("Cannot offset amode: {:?}", self),
        }
        ret
    }

    pub(crate) fn aligned(&self) -> bool {
        self.get_flags().aligned()
    }
}

impl PrettyPrint for Amode {
    fn pretty_print(&self, _size: u8, allocs: &mut AllocationConsumer<'_>) -> String {
        match self {
            Amode::ImmReg { simm32, base, .. } => {
                // Note: size is always 8; the address is 64 bits,
                // even if the addressed operand is smaller.
                format!("{}({})", *simm32, pretty_print_reg(*base, 8, allocs))
            }
            Amode::ImmRegRegShift {
                simm32,
                base,
                index,
                shift,
                ..
            } => format!(
                "{}({},{},{})",
                *simm32,
                pretty_print_reg(base.to_reg(), 8, allocs),
                pretty_print_reg(index.to_reg(), 8, allocs),
                1 << shift
            ),
            Amode::RipRelative { ref target } => format!("label{}(%rip)", target.get()),
        }
    }
}

/// A Memory Address. These denote a 64-bit value only.
/// Used for usual addressing modes as well as addressing modes used during compilation, when the
/// moving SP offset is not known.
#[derive(Clone, Debug)]
pub enum SyntheticAmode {
    /// A real amode.
    Real(Amode),

    /// A (virtual) offset to the "nominal SP" value, which will be recomputed as we push and pop
    /// within the function.
    NominalSPOffset {
        /// The nominal stack pointer value.
        simm32: i32,
    },

    /// A virtual offset to a constant that will be emitted in the constant section of the buffer.
    ConstantOffset(VCodeConstant),
}

impl SyntheticAmode {
    /// Create a real addressing mode.
    pub fn real(amode: Amode) -> Self {
        Self::Real(amode)
    }

    pub(crate) fn nominal_sp_offset(simm32: i32) -> Self {
        SyntheticAmode::NominalSPOffset { simm32 }
    }

    /// Add the registers mentioned by `self` to `collector`.
    pub(crate) fn get_operands<F: Fn(VReg) -> VReg>(
        &self,
        collector: &mut OperandCollector<'_, F>,
    ) {
        match self {
            SyntheticAmode::Real(addr) => addr.get_operands(collector),
            SyntheticAmode::NominalSPOffset { .. } => {
                // Nothing to do; the base is SP and isn't involved in regalloc.
            }
            SyntheticAmode::ConstantOffset(_) => {}
        }
    }

    /// Same as `get_operands`, but add the register in the "late" phase.
    pub(crate) fn get_operands_late<F: Fn(VReg) -> VReg>(
        &self,
        collector: &mut OperandCollector<'_, F>,
    ) {
        match self {
            SyntheticAmode::Real(addr) => addr.get_operands_late(collector),
            SyntheticAmode::NominalSPOffset { .. } => {
                // Nothing to do; the base is SP and isn't involved in regalloc.
            }
            SyntheticAmode::ConstantOffset(_) => {}
        }
    }

    pub(crate) fn finalize(&self, state: &mut EmitState, buffer: &mut MachBuffer<Inst>) -> Amode {
        match self {
            SyntheticAmode::Real(addr) => addr.clone(),
            SyntheticAmode::NominalSPOffset { simm32 } => {
                let off = *simm32 as i64 + state.virtual_sp_offset();
                Amode::imm_reg(off.try_into().expect("invalid sp offset"), regs::rsp())
            }
            SyntheticAmode::ConstantOffset(c) => {
                Amode::rip_relative(buffer.get_label_for_constant(*c))
            }
        }
    }

    pub(crate) fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self {
        match self {
            SyntheticAmode::Real(addr) => SyntheticAmode::Real(addr.with_allocs(allocs)),
            &SyntheticAmode::NominalSPOffset { .. } | &SyntheticAmode::ConstantOffset { .. } => {
                self.clone()
            }
        }
    }

    pub(crate) fn aligned(&self) -> bool {
        match self {
            SyntheticAmode::Real(addr) => addr.aligned(),
            SyntheticAmode::NominalSPOffset { .. } | SyntheticAmode::ConstantOffset { .. } => true,
        }
    }
}

impl Into<SyntheticAmode> for Amode {
    fn into(self) -> SyntheticAmode {
        SyntheticAmode::Real(self)
    }
}

impl Into<SyntheticAmode> for VCodeConstant {
    fn into(self) -> SyntheticAmode {
        SyntheticAmode::ConstantOffset(self)
    }
}

impl PrettyPrint for SyntheticAmode {
    fn pretty_print(&self, _size: u8, allocs: &mut AllocationConsumer<'_>) -> String {
        match self {
            // See note in `Amode` regarding constant size of `8`.
            SyntheticAmode::Real(addr) => addr.pretty_print(8, allocs),
            SyntheticAmode::NominalSPOffset { simm32 } => {
                format!("rsp({} + virtual offset)", *simm32)
            }
            SyntheticAmode::ConstantOffset(c) => format!("const({})", c.as_u32()),
        }
    }
}

/// An operand which is either an integer Register, a value in Memory or an Immediate.  This can
/// denote an 8, 16, 32 or 64 bit value.  For the Immediate form, in the 8- and 16-bit case, only
/// the lower 8 or 16 bits of `simm32` is relevant.  In the 64-bit case, the value denoted by
/// `simm32` is its sign-extension out to 64 bits.
#[derive(Clone, Debug)]
pub enum RegMemImm {
    /// A register operand.
    Reg {
        /// The underlying register.
        reg: Reg,
    },
    /// A memory operand.
    Mem {
        /// The memory address.
        addr: SyntheticAmode,
    },
    /// An immediate operand.
    Imm {
        /// The immediate value.
        simm32: u32,
    },
}

impl RegMemImm {
    /// Create a register operand.
    pub fn reg(reg: Reg) -> Self {
        debug_assert!(reg.class() == RegClass::Int || reg.class() == RegClass::Float);
        Self::Reg { reg }
    }

    /// Create a memory operand.
    pub fn mem(addr: impl Into<SyntheticAmode>) -> Self {
        Self::Mem { addr: addr.into() }
    }

    /// Create an immediate operand.
    pub fn imm(simm32: u32) -> Self {
        Self::Imm { simm32 }
    }

    /// Asserts that in register mode, the reg class is the one that's expected.
    pub(crate) fn assert_regclass_is(&self, expected_reg_class: RegClass) {
        if let Self::Reg { reg } = self {
            debug_assert_eq!(reg.class(), expected_reg_class);
        }
    }

    /// Add the regs mentioned by `self` to `collector`.
    pub(crate) fn get_operands<F: Fn(VReg) -> VReg>(
        &self,
        collector: &mut OperandCollector<'_, F>,
    ) {
        match self {
            Self::Reg { reg } => collector.reg_use(*reg),
            Self::Mem { addr } => addr.get_operands(collector),
            Self::Imm { .. } => {}
        }
    }

    pub(crate) fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self {
        match self {
            Self::Reg { reg } => Self::Reg {
                reg: allocs.next(*reg),
            },
            Self::Mem { addr } => Self::Mem {
                addr: addr.with_allocs(allocs),
            },
            Self::Imm { .. } => self.clone(),
        }
    }
}

impl From<RegMem> for RegMemImm {
    fn from(rm: RegMem) -> RegMemImm {
        match rm {
            RegMem::Reg { reg } => RegMemImm::Reg { reg },
            RegMem::Mem { addr } => RegMemImm::Mem { addr },
        }
    }
}

impl From<Reg> for RegMemImm {
    fn from(reg: Reg) -> Self {
        RegMemImm::Reg { reg }
    }
}

impl PrettyPrint for RegMemImm {
    fn pretty_print(&self, size: u8, allocs: &mut AllocationConsumer<'_>) -> String {
        match self {
            Self::Reg { reg } => pretty_print_reg(*reg, size, allocs),
            Self::Mem { addr } => addr.pretty_print(size, allocs),
            Self::Imm { simm32 } => format!("${}", *simm32 as i32),
        }
    }
}

/// An operand which is either an 8-bit integer immediate or a register.
#[derive(Clone, Debug)]
pub enum Imm8Reg {
    /// 8-bit immediate operand.
    Imm8 {
        /// The 8-bit immediate value.
        imm: u8,
    },
    /// A register operand.
    Reg {
        /// The underlying register.
        reg: Reg,
    },
}

impl From<u8> for Imm8Reg {
    fn from(imm: u8) -> Self {
        Self::Imm8 { imm }
    }
}

impl From<Reg> for Imm8Reg {
    fn from(reg: Reg) -> Self {
        Self::Reg { reg }
    }
}

/// An operand which is either an integer Register or a value in Memory.  This can denote an 8, 16,
/// 32, 64, or 128 bit value.
#[derive(Clone, Debug)]
pub enum RegMem {
    /// A register operand.
    Reg {
        /// The underlying register.
        reg: Reg,
    },
    /// A memory operand.
    Mem {
        /// The memory address.
        addr: SyntheticAmode,
    },
}

impl RegMem {
    /// Create a register operand.
    pub fn reg(reg: Reg) -> Self {
        debug_assert!(reg.class() == RegClass::Int || reg.class() == RegClass::Float);
        Self::Reg { reg }
    }

    /// Create a memory operand.
    pub fn mem(addr: impl Into<SyntheticAmode>) -> Self {
        Self::Mem { addr: addr.into() }
    }
    /// Asserts that in register mode, the reg class is the one that's expected.
    pub(crate) fn assert_regclass_is(&self, expected_reg_class: RegClass) {
        if let Self::Reg { reg } = self {
            debug_assert_eq!(reg.class(), expected_reg_class);
        }
    }
    /// Add the regs mentioned by `self` to `collector`.
    pub(crate) fn get_operands<F: Fn(VReg) -> VReg>(
        &self,
        collector: &mut OperandCollector<'_, F>,
    ) {
        match self {
            RegMem::Reg { reg } => collector.reg_use(*reg),
            RegMem::Mem { addr, .. } => addr.get_operands(collector),
        }
    }

    pub(crate) fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self {
        match self {
            RegMem::Reg { reg } => RegMem::Reg {
                reg: allocs.next(*reg),
            },
            RegMem::Mem { addr } => RegMem::Mem {
                addr: addr.with_allocs(allocs),
            },
        }
    }
}

impl From<Reg> for RegMem {
    fn from(reg: Reg) -> RegMem {
        RegMem::Reg { reg }
    }
}

impl From<Writable<Reg>> for RegMem {
    fn from(r: Writable<Reg>) -> Self {
        RegMem::reg(r.to_reg())
    }
}

impl PrettyPrint for RegMem {
    fn pretty_print(&self, size: u8, allocs: &mut AllocationConsumer<'_>) -> String {
        match self {
            RegMem::Reg { reg } => pretty_print_reg(*reg, size, allocs),
            RegMem::Mem { addr, .. } => addr.pretty_print(size, allocs),
        }
    }
}

/// Some basic ALU operations.
#[derive(Copy, Clone, PartialEq)]
pub enum AluRmiROpcode {
    /// Add operation.
    Add,
    /// Add with carry.
    Adc,
    /// Integer subtraction.
    Sub,
    /// Integer subtraction with borrow.
    Sbb,
    /// Bitwise AND operation.
    And,
    /// Bitwise inclusive OR.
    Or,
    /// Bitwise exclusive OR.
    Xor,
    /// The signless, non-extending (N x N -> N, for N in {32,64}) variant.
    Mul,
}

impl fmt::Debug for AluRmiROpcode {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let name = match self {
            AluRmiROpcode::Add => "add",
            AluRmiROpcode::Adc => "adc",
            AluRmiROpcode::Sub => "sub",
            AluRmiROpcode::Sbb => "sbb",
            AluRmiROpcode::And => "and",
            AluRmiROpcode::Or => "or",
            AluRmiROpcode::Xor => "xor",
            AluRmiROpcode::Mul => "imul",
        };
        write!(fmt, "{}", name)
    }
}

impl fmt::Display for AluRmiROpcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

pub use crate::isa::x64::lower::isle::generated_code::AluRmROpcode;

impl AluRmROpcode {
    pub(crate) fn available_from(&self) -> SmallVec<[InstructionSet; 2]> {
        match self {
            AluRmROpcode::Andn => smallvec![InstructionSet::BMI1],
            AluRmROpcode::Sarx | AluRmROpcode::Shrx | AluRmROpcode::Shlx | AluRmROpcode::Bzhi => {
                smallvec![InstructionSet::BMI2]
            }
        }
    }
}

impl fmt::Display for AluRmROpcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&format!("{self:?}").to_lowercase())
    }
}

#[derive(Clone, PartialEq)]
/// Unary operations requiring register or memory and register operands.
pub enum UnaryRmROpcode {
    /// Bit-scan reverse.
    Bsr,
    /// Bit-scan forward.
    Bsf,
    /// Counts leading zeroes (Leading Zero CouNT).
    Lzcnt,
    /// Counts trailing zeroes (Trailing Zero CouNT).
    Tzcnt,
    /// Counts the number of ones (POPulation CouNT).
    Popcnt,
}

impl UnaryRmROpcode {
    pub(crate) fn available_from(&self) -> SmallVec<[InstructionSet; 2]> {
        match self {
            UnaryRmROpcode::Bsr | UnaryRmROpcode::Bsf => smallvec![],
            UnaryRmROpcode::Lzcnt => smallvec![InstructionSet::Lzcnt],
            UnaryRmROpcode::Tzcnt => smallvec![InstructionSet::BMI1],
            UnaryRmROpcode::Popcnt => smallvec![InstructionSet::Popcnt],
        }
    }
}

impl fmt::Debug for UnaryRmROpcode {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self {
            UnaryRmROpcode::Bsr => write!(fmt, "bsr"),
            UnaryRmROpcode::Bsf => write!(fmt, "bsf"),
            UnaryRmROpcode::Lzcnt => write!(fmt, "lzcnt"),
            UnaryRmROpcode::Tzcnt => write!(fmt, "tzcnt"),
            UnaryRmROpcode::Popcnt => write!(fmt, "popcnt"),
        }
    }
}

impl fmt::Display for UnaryRmROpcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

pub use crate::isa::x64::lower::isle::generated_code::UnaryRmRVexOpcode;

impl UnaryRmRVexOpcode {
    pub(crate) fn available_from(&self) -> SmallVec<[InstructionSet; 2]> {
        match self {
            UnaryRmRVexOpcode::Blsi | UnaryRmRVexOpcode::Blsmsk | UnaryRmRVexOpcode::Blsr => {
                smallvec![InstructionSet::BMI1]
            }
        }
    }
}

impl fmt::Display for UnaryRmRVexOpcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&format!("{self:?}").to_lowercase())
    }
}

pub use crate::isa::x64::lower::isle::generated_code::UnaryRmRImmVexOpcode;

impl UnaryRmRImmVexOpcode {
    pub(crate) fn available_from(&self) -> SmallVec<[InstructionSet; 2]> {
        match self {
            UnaryRmRImmVexOpcode::Rorx => {
                smallvec![InstructionSet::BMI2]
            }
        }
    }
}

impl fmt::Display for UnaryRmRImmVexOpcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&format!("{self:?}").to_lowercase())
    }
}

#[derive(Clone, Copy, PartialEq)]
/// Comparison operations.
pub enum CmpOpcode {
    /// CMP instruction: compute `a - b` and set flags from result.
    Cmp,
    /// TEST instruction: compute `a & b` and set flags from result.
    Test,
}

#[derive(Debug)]
pub(crate) enum InstructionSet {
    SSE,
    SSE2,
    SSSE3,
    SSE41,
    SSE42,
    Popcnt,
    Lzcnt,
    BMI1,
    #[allow(dead_code)] // never constructed (yet).
    BMI2,
    FMA,
    AVX,
    AVX2,
    AVX512BITALG,
    AVX512DQ,
    AVX512F,
    AVX512VBMI,
    AVX512VL,
}

/// Some SSE operations requiring 2 operands r/m and r.
#[derive(Clone, Copy, PartialEq)]
#[allow(dead_code)] // some variants here aren't used just yet
#[allow(missing_docs)]
pub enum SseOpcode {
    Addps,
    Addpd,
    Addss,
    Addsd,
    Andps,
    Andpd,
    Andnps,
    Andnpd,
    Blendvpd,
    Blendvps,
    Comiss,
    Comisd,
    Cmpps,
    Cmppd,
    Cmpss,
    Cmpsd,
    Cvtdq2ps,
    Cvtdq2pd,
    Cvtpd2ps,
    Cvtps2pd,
    Cvtsd2ss,
    Cvtsd2si,
    Cvtsi2ss,
    Cvtsi2sd,
    Cvtss2si,
    Cvtss2sd,
    Cvttpd2dq,
    Cvttps2dq,
    Cvttss2si,
    Cvttsd2si,
    Divps,
    Divpd,
    Divss,
    Divsd,
    Insertps,
    Maxps,
    Maxpd,
    Maxss,
    Maxsd,
    Minps,
    Minpd,
    Minss,
    Minsd,
    Movaps,
    Movapd,
    Movd,
    Movdqa,
    Movdqu,
    Movlhps,
    Movmskps,
    Movmskpd,
    Movq,
    Movss,
    Movsd,
    Movups,
    Movupd,
    Mulps,
    Mulpd,
    Mulss,
    Mulsd,
    Orps,
    Orpd,
    Pabsb,
    Pabsw,
    Pabsd,
    Packssdw,
    Packsswb,
    Packusdw,
    Packuswb,
    Paddb,
    Paddd,
    Paddq,
    Paddw,
    Paddsb,
    Paddsw,
    Paddusb,
    Paddusw,
    Palignr,
    Pand,
    Pandn,
    Pavgb,
    Pavgw,
    Pblendvb,
    Pcmpeqb,
    Pcmpeqw,
    Pcmpeqd,
    Pcmpeqq,
    Pcmpgtb,
    Pcmpgtw,
    Pcmpgtd,
    Pcmpgtq,
    Pextrb,
    Pextrw,
    Pextrd,
    Pextrq,
    Pinsrb,
    Pinsrw,
    Pinsrd,
    Pmaddubsw,
    Pmaddwd,
    Pmaxsb,
    Pmaxsw,
    Pmaxsd,
    Pmaxub,
    Pmaxuw,
    Pmaxud,
    Pminsb,
    Pminsw,
    Pminsd,
    Pminub,
    Pminuw,
    Pminud,
    Pmovmskb,
    Pmovsxbd,
    Pmovsxbw,
    Pmovsxbq,
    Pmovsxwd,
    Pmovsxwq,
    Pmovsxdq,
    Pmovzxbd,
    Pmovzxbw,
    Pmovzxbq,
    Pmovzxwd,
    Pmovzxwq,
    Pmovzxdq,
    Pmuldq,
    Pmulhw,
    Pmulhuw,
    Pmulhrsw,
    Pmulld,
    Pmullw,
    Pmuludq,
    Por,
    Pshufb,
    Pshufd,
    Psllw,
    Pslld,
    Psllq,
    Psraw,
    Psrad,
    Psrlw,
    Psrld,
    Psrlq,
    Psubb,
    Psubd,
    Psubq,
    Psubw,
    Psubsb,
    Psubsw,
    Psubusb,
    Psubusw,
    Ptest,
    Punpckhbw,
    Punpckhwd,
    Punpcklbw,
    Punpcklwd,
    Pxor,
    Rcpss,
    Roundps,
    Roundpd,
    Roundss,
    Roundsd,
    Rsqrtss,
    Shufps,
    Sqrtps,
    Sqrtpd,
    Sqrtss,
    Sqrtsd,
    Subps,
    Subpd,
    Subss,
    Subsd,
    Ucomiss,
    Ucomisd,
    Unpcklps,
    Unpckhps,
    Xorps,
    Xorpd,
    Phaddw,
    Phaddd,
    Punpckhdq,
    Punpckldq,
    Punpckhqdq,
    Punpcklqdq,
    Pshuflw,
    Pshufhw,
    Pblendw,
    Movddup,
}

impl SseOpcode {
    /// Which `InstructionSet` is the first supporting this opcode?
    pub(crate) fn available_from(&self) -> InstructionSet {
        use InstructionSet::*;
        match self {
            SseOpcode::Addps
            | SseOpcode::Addss
            | SseOpcode::Andps
            | SseOpcode::Andnps
            | SseOpcode::Comiss
            | SseOpcode::Cmpps
            | SseOpcode::Cmpss
            | SseOpcode::Cvtsi2ss
            | SseOpcode::Cvtss2si
            | SseOpcode::Cvttss2si
            | SseOpcode::Divps
            | SseOpcode::Divss
            | SseOpcode::Maxps
            | SseOpcode::Maxss
            | SseOpcode::Minps
            | SseOpcode::Minss
            | SseOpcode::Movaps
            | SseOpcode::Movlhps
            | SseOpcode::Movmskps
            | SseOpcode::Movss
            | SseOpcode::Movups
            | SseOpcode::Mulps
            | SseOpcode::Mulss
            | SseOpcode::Orps
            | SseOpcode::Rcpss
            | SseOpcode::Rsqrtss
            | SseOpcode::Shufps
            | SseOpcode::Sqrtps
            | SseOpcode::Sqrtss
            | SseOpcode::Subps
            | SseOpcode::Subss
            | SseOpcode::Ucomiss
            | SseOpcode::Unpcklps
            | SseOpcode::Unpckhps
            | SseOpcode::Xorps => SSE,

            SseOpcode::Addpd
            | SseOpcode::Addsd
            | SseOpcode::Andpd
            | SseOpcode::Andnpd
            | SseOpcode::Cmppd
            | SseOpcode::Cmpsd
            | SseOpcode::Comisd
            | SseOpcode::Cvtdq2ps
            | SseOpcode::Cvtdq2pd
            | SseOpcode::Cvtpd2ps
            | SseOpcode::Cvtps2pd
            | SseOpcode::Cvtsd2ss
            | SseOpcode::Cvtsd2si
            | SseOpcode::Cvtsi2sd
            | SseOpcode::Cvtss2sd
            | SseOpcode::Cvttpd2dq
            | SseOpcode::Cvttps2dq
            | SseOpcode::Cvttsd2si
            | SseOpcode::Divpd
            | SseOpcode::Divsd
            | SseOpcode::Maxpd
            | SseOpcode::Maxsd
            | SseOpcode::Minpd
            | SseOpcode::Minsd
            | SseOpcode::Movapd
            | SseOpcode::Movd
            | SseOpcode::Movmskpd
            | SseOpcode::Movq
            | SseOpcode::Movsd
            | SseOpcode::Movupd
            | SseOpcode::Movdqa
            | SseOpcode::Movdqu
            | SseOpcode::Mulpd
            | SseOpcode::Mulsd
            | SseOpcode::Orpd
            | SseOpcode::Packssdw
            | SseOpcode::Packsswb
            | SseOpcode::Packuswb
            | SseOpcode::Paddb
            | SseOpcode::Paddd
            | SseOpcode::Paddq
            | SseOpcode::Paddw
            | SseOpcode::Paddsb
            | SseOpcode::Paddsw
            | SseOpcode::Paddusb
            | SseOpcode::Paddusw
            | SseOpcode::Pand
            | SseOpcode::Pandn
            | SseOpcode::Pavgb
            | SseOpcode::Pavgw
            | SseOpcode::Pcmpeqb
            | SseOpcode::Pcmpeqw
            | SseOpcode::Pcmpeqd
            | SseOpcode::Pcmpgtb
            | SseOpcode::Pcmpgtw
            | SseOpcode::Pcmpgtd
            | SseOpcode::Pextrw
            | SseOpcode::Pinsrw
            | SseOpcode::Pmaddwd
            | SseOpcode::Pmaxsw
            | SseOpcode::Pmaxub
            | SseOpcode::Pminsw
            | SseOpcode::Pminub
            | SseOpcode::Pmovmskb
            | SseOpcode::Pmulhw
            | SseOpcode::Pmulhuw
            | SseOpcode::Pmullw
            | SseOpcode::Pmuludq
            | SseOpcode::Por
            | SseOpcode::Pshufd
            | SseOpcode::Psllw
            | SseOpcode::Pslld
            | SseOpcode::Psllq
            | SseOpcode::Psraw
            | SseOpcode::Psrad
            | SseOpcode::Psrlw
            | SseOpcode::Psrld
            | SseOpcode::Psrlq
            | SseOpcode::Psubb
            | SseOpcode::Psubd
            | SseOpcode::Psubq
            | SseOpcode::Psubw
            | SseOpcode::Psubsb
            | SseOpcode::Psubsw
            | SseOpcode::Psubusb
            | SseOpcode::Psubusw
            | SseOpcode::Punpckhbw
            | SseOpcode::Punpckhwd
            | SseOpcode::Punpcklbw
            | SseOpcode::Punpcklwd
            | SseOpcode::Pxor
            | SseOpcode::Sqrtpd
            | SseOpcode::Sqrtsd
            | SseOpcode::Subpd
            | SseOpcode::Subsd
            | SseOpcode::Ucomisd
            | SseOpcode::Xorpd
            | SseOpcode::Punpckldq
            | SseOpcode::Punpckhdq
            | SseOpcode::Punpcklqdq
            | SseOpcode::Punpckhqdq
            | SseOpcode::Pshuflw
            | SseOpcode::Pshufhw => SSE2,

            SseOpcode::Pabsb
            | SseOpcode::Pabsw
            | SseOpcode::Pabsd
            | SseOpcode::Palignr
            | SseOpcode::Pmulhrsw
            | SseOpcode::Pshufb
            | SseOpcode::Phaddw
            | SseOpcode::Phaddd
            | SseOpcode::Pmaddubsw
            | SseOpcode::Movddup => SSSE3,

            SseOpcode::Blendvpd
            | SseOpcode::Blendvps
            | SseOpcode::Insertps
            | SseOpcode::Packusdw
            | SseOpcode::Pblendvb
            | SseOpcode::Pcmpeqq
            | SseOpcode::Pextrb
            | SseOpcode::Pextrd
            | SseOpcode::Pextrq
            | SseOpcode::Pinsrb
            | SseOpcode::Pinsrd
            | SseOpcode::Pmaxsb
            | SseOpcode::Pmaxsd
            | SseOpcode::Pmaxuw
            | SseOpcode::Pmaxud
            | SseOpcode::Pminsb
            | SseOpcode::Pminsd
            | SseOpcode::Pminuw
            | SseOpcode::Pminud
            | SseOpcode::Pmovsxbd
            | SseOpcode::Pmovsxbw
            | SseOpcode::Pmovsxbq
            | SseOpcode::Pmovsxwd
            | SseOpcode::Pmovsxwq
            | SseOpcode::Pmovsxdq
            | SseOpcode::Pmovzxbd
            | SseOpcode::Pmovzxbw
            | SseOpcode::Pmovzxbq
            | SseOpcode::Pmovzxwd
            | SseOpcode::Pmovzxwq
            | SseOpcode::Pmovzxdq
            | SseOpcode::Pmuldq
            | SseOpcode::Pmulld
            | SseOpcode::Ptest
            | SseOpcode::Roundps
            | SseOpcode::Roundpd
            | SseOpcode::Roundss
            | SseOpcode::Roundsd
            | SseOpcode::Pblendw => SSE41,

            SseOpcode::Pcmpgtq => SSE42,
        }
    }

    /// Returns the src operand size for an instruction.
    pub(crate) fn src_size(&self) -> u8 {
        match self {
            SseOpcode::Movd => 4,
            _ => 8,
        }
    }
}

impl fmt::Debug for SseOpcode {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let name = match self {
            SseOpcode::Addps => "addps",
            SseOpcode::Addpd => "addpd",
            SseOpcode::Addss => "addss",
            SseOpcode::Addsd => "addsd",
            SseOpcode::Andpd => "andpd",
            SseOpcode::Andps => "andps",
            SseOpcode::Andnps => "andnps",
            SseOpcode::Andnpd => "andnpd",
            SseOpcode::Blendvpd => "blendvpd",
            SseOpcode::Blendvps => "blendvps",
            SseOpcode::Cmpps => "cmpps",
            SseOpcode::Cmppd => "cmppd",
            SseOpcode::Cmpss => "cmpss",
            SseOpcode::Cmpsd => "cmpsd",
            SseOpcode::Comiss => "comiss",
            SseOpcode::Comisd => "comisd",
            SseOpcode::Cvtdq2ps => "cvtdq2ps",
            SseOpcode::Cvtdq2pd => "cvtdq2pd",
            SseOpcode::Cvtpd2ps => "cvtpd2ps",
            SseOpcode::Cvtps2pd => "cvtps2pd",
            SseOpcode::Cvtsd2ss => "cvtsd2ss",
            SseOpcode::Cvtsd2si => "cvtsd2si",
            SseOpcode::Cvtsi2ss => "cvtsi2ss",
            SseOpcode::Cvtsi2sd => "cvtsi2sd",
            SseOpcode::Cvtss2si => "cvtss2si",
            SseOpcode::Cvtss2sd => "cvtss2sd",
            SseOpcode::Cvttpd2dq => "cvttpd2dq",
            SseOpcode::Cvttps2dq => "cvttps2dq",
            SseOpcode::Cvttss2si => "cvttss2si",
            SseOpcode::Cvttsd2si => "cvttsd2si",
            SseOpcode::Divps => "divps",
            SseOpcode::Divpd => "divpd",
            SseOpcode::Divss => "divss",
            SseOpcode::Divsd => "divsd",
            SseOpcode::Insertps => "insertps",
            SseOpcode::Maxps => "maxps",
            SseOpcode::Maxpd => "maxpd",
            SseOpcode::Maxss => "maxss",
            SseOpcode::Maxsd => "maxsd",
            SseOpcode::Minps => "minps",
            SseOpcode::Minpd => "minpd",
            SseOpcode::Minss => "minss",
            SseOpcode::Minsd => "minsd",
            SseOpcode::Movaps => "movaps",
            SseOpcode::Movapd => "movapd",
            SseOpcode::Movd => "movd",
            SseOpcode::Movdqa => "movdqa",
            SseOpcode::Movdqu => "movdqu",
            SseOpcode::Movlhps => "movlhps",
            SseOpcode::Movmskps => "movmskps",
            SseOpcode::Movmskpd => "movmskpd",
            SseOpcode::Movq => "movq",
            SseOpcode::Movss => "movss",
            SseOpcode::Movsd => "movsd",
            SseOpcode::Movups => "movups",
            SseOpcode::Movupd => "movupd",
            SseOpcode::Mulps => "mulps",
            SseOpcode::Mulpd => "mulpd",
            SseOpcode::Mulss => "mulss",
            SseOpcode::Mulsd => "mulsd",
            SseOpcode::Orpd => "orpd",
            SseOpcode::Orps => "orps",
            SseOpcode::Pabsb => "pabsb",
            SseOpcode::Pabsw => "pabsw",
            SseOpcode::Pabsd => "pabsd",
            SseOpcode::Packssdw => "packssdw",
            SseOpcode::Packsswb => "packsswb",
            SseOpcode::Packusdw => "packusdw",
            SseOpcode::Packuswb => "packuswb",
            SseOpcode::Paddb => "paddb",
            SseOpcode::Paddd => "paddd",
            SseOpcode::Paddq => "paddq",
            SseOpcode::Paddw => "paddw",
            SseOpcode::Paddsb => "paddsb",
            SseOpcode::Paddsw => "paddsw",
            SseOpcode::Paddusb => "paddusb",
            SseOpcode::Paddusw => "paddusw",
            SseOpcode::Palignr => "palignr",
            SseOpcode::Pand => "pand",
            SseOpcode::Pandn => "pandn",
            SseOpcode::Pavgb => "pavgb",
            SseOpcode::Pavgw => "pavgw",
            SseOpcode::Pblendvb => "pblendvb",
            SseOpcode::Pcmpeqb => "pcmpeqb",
            SseOpcode::Pcmpeqw => "pcmpeqw",
            SseOpcode::Pcmpeqd => "pcmpeqd",
            SseOpcode::Pcmpeqq => "pcmpeqq",
            SseOpcode::Pcmpgtb => "pcmpgtb",
            SseOpcode::Pcmpgtw => "pcmpgtw",
            SseOpcode::Pcmpgtd => "pcmpgtd",
            SseOpcode::Pcmpgtq => "pcmpgtq",
            SseOpcode::Pextrb => "pextrb",
            SseOpcode::Pextrw => "pextrw",
            SseOpcode::Pextrd => "pextrd",
            SseOpcode::Pextrq => "pextrq",
            SseOpcode::Pinsrb => "pinsrb",
            SseOpcode::Pinsrw => "pinsrw",
            SseOpcode::Pinsrd => "pinsrd",
            SseOpcode::Pmaddubsw => "pmaddubsw",
            SseOpcode::Pmaddwd => "pmaddwd",
            SseOpcode::Pmaxsb => "pmaxsb",
            SseOpcode::Pmaxsw => "pmaxsw",
            SseOpcode::Pmaxsd => "pmaxsd",
            SseOpcode::Pmaxub => "pmaxub",
            SseOpcode::Pmaxuw => "pmaxuw",
            SseOpcode::Pmaxud => "pmaxud",
            SseOpcode::Pminsb => "pminsb",
            SseOpcode::Pminsw => "pminsw",
            SseOpcode::Pminsd => "pminsd",
            SseOpcode::Pminub => "pminub",
            SseOpcode::Pminuw => "pminuw",
            SseOpcode::Pminud => "pminud",
            SseOpcode::Pmovmskb => "pmovmskb",
            SseOpcode::Pmovsxbd => "pmovsxbd",
            SseOpcode::Pmovsxbw => "pmovsxbw",
            SseOpcode::Pmovsxbq => "pmovsxbq",
            SseOpcode::Pmovsxwd => "pmovsxwd",
            SseOpcode::Pmovsxwq => "pmovsxwq",
            SseOpcode::Pmovsxdq => "pmovsxdq",
            SseOpcode::Pmovzxbd => "pmovzxbd",
            SseOpcode::Pmovzxbw => "pmovzxbw",
            SseOpcode::Pmovzxbq => "pmovzxbq",
            SseOpcode::Pmovzxwd => "pmovzxwd",
            SseOpcode::Pmovzxwq => "pmovzxwq",
            SseOpcode::Pmovzxdq => "pmovzxdq",
            SseOpcode::Pmuldq => "pmuldq",
            SseOpcode::Pmulhw => "pmulhw",
            SseOpcode::Pmulhuw => "pmulhuw",
            SseOpcode::Pmulhrsw => "pmulhrsw",
            SseOpcode::Pmulld => "pmulld",
            SseOpcode::Pmullw => "pmullw",
            SseOpcode::Pmuludq => "pmuludq",
            SseOpcode::Por => "por",
            SseOpcode::Pshufb => "pshufb",
            SseOpcode::Pshufd => "pshufd",
            SseOpcode::Psllw => "psllw",
            SseOpcode::Pslld => "pslld",
            SseOpcode::Psllq => "psllq",
            SseOpcode::Psraw => "psraw",
            SseOpcode::Psrad => "psrad",
            SseOpcode::Psrlw => "psrlw",
            SseOpcode::Psrld => "psrld",
            SseOpcode::Psrlq => "psrlq",
            SseOpcode::Psubb => "psubb",
            SseOpcode::Psubd => "psubd",
            SseOpcode::Psubq => "psubq",
            SseOpcode::Psubw => "psubw",
            SseOpcode::Psubsb => "psubsb",
            SseOpcode::Psubsw => "psubsw",
            SseOpcode::Psubusb => "psubusb",
            SseOpcode::Psubusw => "psubusw",
            SseOpcode::Ptest => "ptest",
            SseOpcode::Punpckhbw => "punpckhbw",
            SseOpcode::Punpckhwd => "punpckhwd",
            SseOpcode::Punpcklbw => "punpcklbw",
            SseOpcode::Punpcklwd => "punpcklwd",
            SseOpcode::Pxor => "pxor",
            SseOpcode::Rcpss => "rcpss",
            SseOpcode::Roundps => "roundps",
            SseOpcode::Roundpd => "roundpd",
            SseOpcode::Roundss => "roundss",
            SseOpcode::Roundsd => "roundsd",
            SseOpcode::Rsqrtss => "rsqrtss",
            SseOpcode::Shufps => "shufps",
            SseOpcode::Sqrtps => "sqrtps",
            SseOpcode::Sqrtpd => "sqrtpd",
            SseOpcode::Sqrtss => "sqrtss",
            SseOpcode::Sqrtsd => "sqrtsd",
            SseOpcode::Subps => "subps",
            SseOpcode::Subpd => "subpd",
            SseOpcode::Subss => "subss",
            SseOpcode::Subsd => "subsd",
            SseOpcode::Ucomiss => "ucomiss",
            SseOpcode::Ucomisd => "ucomisd",
            SseOpcode::Unpcklps => "unpcklps",
            SseOpcode::Unpckhps => "unpckhps",
            SseOpcode::Xorps => "xorps",
            SseOpcode::Xorpd => "xorpd",
            SseOpcode::Phaddw => "phaddw",
            SseOpcode::Phaddd => "phaddd",
            SseOpcode::Punpckldq => "punpckldq",
            SseOpcode::Punpckhdq => "punpckhdq",
            SseOpcode::Punpcklqdq => "punpcklqdq",
            SseOpcode::Punpckhqdq => "punpckhqdq",
            SseOpcode::Pshuflw => "pshuflw",
            SseOpcode::Pshufhw => "pshufhw",
            SseOpcode::Pblendw => "pblendw",
            SseOpcode::Movddup => "movddup",
        };
        write!(fmt, "{}", name)
    }
}

impl fmt::Display for SseOpcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

pub use crate::isa::x64::lower::isle::generated_code::AvxOpcode;

impl AvxOpcode {
    /// Which `InstructionSet`s support the opcode?
    pub(crate) fn available_from(&self) -> SmallVec<[InstructionSet; 2]> {
        match self {
            AvxOpcode::Vfmadd213ss
            | AvxOpcode::Vfmadd213sd
            | AvxOpcode::Vfmadd213ps
            | AvxOpcode::Vfmadd213pd
            | AvxOpcode::Vfmadd132ss
            | AvxOpcode::Vfmadd132sd
            | AvxOpcode::Vfmadd132ps
            | AvxOpcode::Vfmadd132pd
            | AvxOpcode::Vfnmadd213ss
            | AvxOpcode::Vfnmadd213sd
            | AvxOpcode::Vfnmadd213ps
            | AvxOpcode::Vfnmadd213pd
            | AvxOpcode::Vfnmadd132ss
            | AvxOpcode::Vfnmadd132sd
            | AvxOpcode::Vfnmadd132ps
            | AvxOpcode::Vfnmadd132pd => smallvec![InstructionSet::FMA],
            AvxOpcode::Vminps
            | AvxOpcode::Vminpd
            | AvxOpcode::Vmaxps
            | AvxOpcode::Vmaxpd
            | AvxOpcode::Vandnps
            | AvxOpcode::Vandnpd
            | AvxOpcode::Vpandn
            | AvxOpcode::Vcmpps
            | AvxOpcode::Vcmppd
            | AvxOpcode::Vpsrlw
            | AvxOpcode::Vpsrld
            | AvxOpcode::Vpsrlq
            | AvxOpcode::Vpaddb
            | AvxOpcode::Vpaddw
            | AvxOpcode::Vpaddd
            | AvxOpcode::Vpaddq
            | AvxOpcode::Vpaddsb
            | AvxOpcode::Vpaddsw
            | AvxOpcode::Vpaddusb
            | AvxOpcode::Vpaddusw
            | AvxOpcode::Vpsubb
            | AvxOpcode::Vpsubw
            | AvxOpcode::Vpsubd
            | AvxOpcode::Vpsubq
            | AvxOpcode::Vpsubsb
            | AvxOpcode::Vpsubsw
            | AvxOpcode::Vpsubusb
            | AvxOpcode::Vpsubusw
            | AvxOpcode::Vpavgb
            | AvxOpcode::Vpavgw
            | AvxOpcode::Vpand
            | AvxOpcode::Vandps
            | AvxOpcode::Vandpd
            | AvxOpcode::Vpor
            | AvxOpcode::Vorps
            | AvxOpcode::Vorpd
            | AvxOpcode::Vpxor
            | AvxOpcode::Vxorps
            | AvxOpcode::Vxorpd
            | AvxOpcode::Vpmullw
            | AvxOpcode::Vpmulld
            | AvxOpcode::Vpmulhw
            | AvxOpcode::Vpmulhd
            | AvxOpcode::Vpmulhrsw
            | AvxOpcode::Vpmulhuw
            | AvxOpcode::Vpmuldq
            | AvxOpcode::Vpmuludq
            | AvxOpcode::Vpunpckhwd
            | AvxOpcode::Vpunpcklwd
            | AvxOpcode::Vunpcklps
            | AvxOpcode::Vunpckhps
            | AvxOpcode::Vaddps
            | AvxOpcode::Vaddpd
            | AvxOpcode::Vsubps
            | AvxOpcode::Vsubpd
            | AvxOpcode::Vmulps
            | AvxOpcode::Vmulpd
            | AvxOpcode::Vdivps
            | AvxOpcode::Vdivpd
            | AvxOpcode::Vpcmpeqb
            | AvxOpcode::Vpcmpeqw
            | AvxOpcode::Vpcmpeqd
            | AvxOpcode::Vpcmpeqq
            | AvxOpcode::Vpcmpgtb
            | AvxOpcode::Vpcmpgtw
            | AvxOpcode::Vpcmpgtd
            | AvxOpcode::Vpcmpgtq
            | AvxOpcode::Vblendvps
            | AvxOpcode::Vblendvpd
            | AvxOpcode::Vpblendvb
            | AvxOpcode::Vmovlhps
            | AvxOpcode::Vpminsb
            | AvxOpcode::Vpminsw
            | AvxOpcode::Vpminsd
            | AvxOpcode::Vpminub
            | AvxOpcode::Vpminuw
            | AvxOpcode::Vpminud
            | AvxOpcode::Vpmaxsb
            | AvxOpcode::Vpmaxsw
            | AvxOpcode::Vpmaxsd
            | AvxOpcode::Vpmaxub
            | AvxOpcode::Vpmaxuw
            | AvxOpcode::Vpmaxud
            | AvxOpcode::Vpunpcklbw
            | AvxOpcode::Vpunpckhbw
            | AvxOpcode::Vpacksswb
            | AvxOpcode::Vpackssdw
            | AvxOpcode::Vpackuswb
            | AvxOpcode::Vpackusdw
            | AvxOpcode::Vpalignr
            | AvxOpcode::Vpinsrb
            | AvxOpcode::Vpinsrw
            | AvxOpcode::Vpinsrd
            | AvxOpcode::Vpinsrq
            | AvxOpcode::Vpmaddwd
            | AvxOpcode::Vpmaddubsw
            | AvxOpcode::Vinsertps
            | AvxOpcode::Vpshufb
            | AvxOpcode::Vshufps
            | AvxOpcode::Vpsllw
            | AvxOpcode::Vpslld
            | AvxOpcode::Vpsllq
            | AvxOpcode::Vpsraw
            | AvxOpcode::Vpsrad
            | AvxOpcode::Vpmovsxbw
            | AvxOpcode::Vpmovzxbw
            | AvxOpcode::Vpmovsxwd
            | AvxOpcode::Vpmovzxwd
            | AvxOpcode::Vpmovsxdq
            | AvxOpcode::Vpmovzxdq
            | AvxOpcode::Vaddss
            | AvxOpcode::Vaddsd
            | AvxOpcode::Vmulss
            | AvxOpcode::Vmulsd
            | AvxOpcode::Vsubss
            | AvxOpcode::Vsubsd
            | AvxOpcode::Vdivss
            | AvxOpcode::Vdivsd
            | AvxOpcode::Vpabsb
            | AvxOpcode::Vpabsw
            | AvxOpcode::Vpabsd
            | AvxOpcode::Vminss
            | AvxOpcode::Vminsd
            | AvxOpcode::Vmaxss
            | AvxOpcode::Vmaxsd
            | AvxOpcode::Vsqrtps
            | AvxOpcode::Vsqrtpd
            | AvxOpcode::Vroundpd
            | AvxOpcode::Vroundps
            | AvxOpcode::Vcvtdq2pd
            | AvxOpcode::Vcvtdq2ps
            | AvxOpcode::Vcvtpd2ps
            | AvxOpcode::Vcvtps2pd
            | AvxOpcode::Vcvttpd2dq
            | AvxOpcode::Vcvttps2dq
            | AvxOpcode::Vphaddw
            | AvxOpcode::Vphaddd
            | AvxOpcode::Vpunpckldq
            | AvxOpcode::Vpunpckhdq
            | AvxOpcode::Vpunpcklqdq
            | AvxOpcode::Vpunpckhqdq
            | AvxOpcode::Vpshuflw
            | AvxOpcode::Vpshufhw
            | AvxOpcode::Vpshufd
            | AvxOpcode::Vmovss
            | AvxOpcode::Vmovsd
            | AvxOpcode::Vmovups
            | AvxOpcode::Vmovupd
            | AvxOpcode::Vmovdqu
            | AvxOpcode::Vpextrb
            | AvxOpcode::Vpextrw
            | AvxOpcode::Vpextrd
            | AvxOpcode::Vpextrq
            | AvxOpcode::Vpblendw
            | AvxOpcode::Vmovddup
            | AvxOpcode::Vbroadcastss
            | AvxOpcode::Vmovd
            | AvxOpcode::Vmovq
            | AvxOpcode::Vmovmskps
            | AvxOpcode::Vmovmskpd
            | AvxOpcode::Vpmovmskb
            | AvxOpcode::Vcvtsi2ss
            | AvxOpcode::Vcvtsi2sd
            | AvxOpcode::Vcvtss2sd
            | AvxOpcode::Vcvtsd2ss
            | AvxOpcode::Vsqrtss
            | AvxOpcode::Vsqrtsd
            | AvxOpcode::Vroundss
            | AvxOpcode::Vroundsd => {
                smallvec![InstructionSet::AVX]
            }

            AvxOpcode::Vpbroadcastb | AvxOpcode::Vpbroadcastw | AvxOpcode::Vpbroadcastd => {
                smallvec![InstructionSet::AVX2]
            }
        }
    }

    /// Is the opcode known to be commutative?
    ///
    /// Note that this method is not exhaustive, and there may be commutative
    /// opcodes that we don't recognize as commutative.
    pub(crate) fn is_commutative(&self) -> bool {
        match *self {
            AvxOpcode::Vpaddb
            | AvxOpcode::Vpaddw
            | AvxOpcode::Vpaddd
            | AvxOpcode::Vpaddq
            | AvxOpcode::Vpaddsb
            | AvxOpcode::Vpaddsw
            | AvxOpcode::Vpaddusb
            | AvxOpcode::Vpaddusw
            | AvxOpcode::Vpand
            | AvxOpcode::Vandps
            | AvxOpcode::Vandpd
            | AvxOpcode::Vpor
            | AvxOpcode::Vorps
            | AvxOpcode::Vorpd
            | AvxOpcode::Vpxor
            | AvxOpcode::Vxorps
            | AvxOpcode::Vxorpd
            | AvxOpcode::Vpmuldq
            | AvxOpcode::Vpmuludq
            | AvxOpcode::Vaddps
            | AvxOpcode::Vaddpd
            | AvxOpcode::Vmulps
            | AvxOpcode::Vmulpd
            | AvxOpcode::Vpcmpeqb
            | AvxOpcode::Vpcmpeqw
            | AvxOpcode::Vpcmpeqd
            | AvxOpcode::Vpcmpeqq
            | AvxOpcode::Vaddss
            | AvxOpcode::Vaddsd
            | AvxOpcode::Vmulss
            | AvxOpcode::Vmulsd => true,
            _ => false,
        }
    }
}

impl fmt::Display for AvxOpcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        format!("{self:?}").to_lowercase().fmt(f)
    }
}

#[derive(Copy, Clone, PartialEq)]
#[allow(missing_docs)]
pub enum Avx512TupleType {
    Full,
    FullMem,
    Mem128,
}

pub use crate::isa::x64::lower::isle::generated_code::Avx512Opcode;

impl Avx512Opcode {
    /// Which `InstructionSet`s support the opcode?
    pub(crate) fn available_from(&self) -> SmallVec<[InstructionSet; 2]> {
        match self {
            Avx512Opcode::Vcvtudq2ps
            | Avx512Opcode::Vpabsq
            | Avx512Opcode::Vpsraq
            | Avx512Opcode::VpsraqImm => {
                smallvec![InstructionSet::AVX512F, InstructionSet::AVX512VL]
            }
            Avx512Opcode::Vpermi2b => {
                smallvec![InstructionSet::AVX512VL, InstructionSet::AVX512VBMI]
            }
            Avx512Opcode::Vpmullq => smallvec![InstructionSet::AVX512VL, InstructionSet::AVX512DQ],
            Avx512Opcode::Vpopcntb => {
                smallvec![InstructionSet::AVX512VL, InstructionSet::AVX512BITALG]
            }
        }
    }

    /// What is the "TupleType" of this opcode, which affects the scaling factor
    /// for 8-bit displacements when this instruction uses memory operands.
    ///
    /// This can be found in the encoding table for each instruction and is
    /// interpreted according to Table 2-34 and 2-35 in the Intel instruction
    /// manual.
    pub fn tuple_type(&self) -> Avx512TupleType {
        use Avx512Opcode::*;
        use Avx512TupleType::*;

        match self {
            Vcvtudq2ps | Vpabsq | Vpmullq | VpsraqImm => Full,
            Vpermi2b | Vpopcntb => FullMem,
            Vpsraq => Mem128,
        }
    }
}

impl fmt::Display for Avx512Opcode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = format!("{self:?}");
        f.write_str(&s.to_lowercase())
    }
}

/// This defines the ways a value can be extended: either signed- or zero-extension, or none for
/// types that are not extended. Contrast with [ExtMode], which defines the widths from and to which
/// values can be extended.
#[allow(dead_code)]
#[derive(Clone, PartialEq)]
pub enum ExtKind {
    /// No extension.
    None,
    /// Sign-extend.
    SignExtend,
    /// Zero-extend.
    ZeroExtend,
}

/// These indicate ways of extending (widening) a value, using the Intel
/// naming: B(yte) = u8, W(ord) = u16, L(ong)word = u32, Q(uad)word = u64
#[derive(Clone, PartialEq)]
pub enum ExtMode {
    /// Byte -> Longword.
    BL,
    /// Byte -> Quadword.
    BQ,
    /// Word -> Longword.
    WL,
    /// Word -> Quadword.
    WQ,
    /// Longword -> Quadword.
    LQ,
}

impl ExtMode {
    /// Calculate the `ExtMode` from passed bit lengths of the from/to types.
    pub(crate) fn new(from_bits: u16, to_bits: u16) -> Option<ExtMode> {
        match (from_bits, to_bits) {
            (1, 8) | (1, 16) | (1, 32) | (8, 16) | (8, 32) => Some(ExtMode::BL),
            (1, 64) | (8, 64) => Some(ExtMode::BQ),
            (16, 32) => Some(ExtMode::WL),
            (16, 64) => Some(ExtMode::WQ),
            (32, 64) => Some(ExtMode::LQ),
            _ => None,
        }
    }

    /// Return the source register size in bytes.
    pub(crate) fn src_size(&self) -> u8 {
        match self {
            ExtMode::BL | ExtMode::BQ => 1,
            ExtMode::WL | ExtMode::WQ => 2,
            ExtMode::LQ => 4,
        }
    }

    /// Return the destination register size in bytes.
    pub(crate) fn dst_size(&self) -> u8 {
        match self {
            ExtMode::BL | ExtMode::WL => 4,
            ExtMode::BQ | ExtMode::WQ | ExtMode::LQ => 8,
        }
    }

    /// Source size, as an integer type.
    pub(crate) fn src_type(&self) -> Type {
        match self {
            ExtMode::BL | ExtMode::BQ => I8,
            ExtMode::WL | ExtMode::WQ => I16,
            ExtMode::LQ => I32,
        }
    }
}

impl fmt::Debug for ExtMode {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let name = match self {
            ExtMode::BL => "bl",
            ExtMode::BQ => "bq",
            ExtMode::WL => "wl",
            ExtMode::WQ => "wq",
            ExtMode::LQ => "lq",
        };
        write!(fmt, "{}", name)
    }
}

impl fmt::Display for ExtMode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

/// These indicate the form of a scalar shift/rotate: left, signed right, unsigned right.
#[derive(Clone, Copy)]
pub enum ShiftKind {
    /// Left shift.
    ShiftLeft,
    /// Inserts zeros in the most significant bits.
    ShiftRightLogical,
    /// Replicates the sign bit in the most significant bits.
    ShiftRightArithmetic,
    /// Left rotation.
    RotateLeft,
    /// Right rotation.
    RotateRight,
}

impl fmt::Debug for ShiftKind {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let name = match self {
            ShiftKind::ShiftLeft => "shl",
            ShiftKind::ShiftRightLogical => "shr",
            ShiftKind::ShiftRightArithmetic => "sar",
            ShiftKind::RotateLeft => "rol",
            ShiftKind::RotateRight => "ror",
        };
        write!(fmt, "{}", name)
    }
}

impl fmt::Display for ShiftKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

/// These indicate condition code tests.  Not all are represented since not all are useful in
/// compiler-generated code.
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum CC {
    ///  overflow
    O = 0,
    /// no overflow
    NO = 1,

    /// < unsigned
    B = 2,
    /// >= unsigned
    NB = 3,

    /// zero
    Z = 4,
    /// not-zero
    NZ = 5,

    /// <= unsigned
    BE = 6,
    /// > unsigned
    NBE = 7,

    /// negative
    S = 8,
    /// not-negative
    NS = 9,

    /// < signed
    L = 12,
    /// >= signed
    NL = 13,

    /// <= signed
    LE = 14,
    /// > signed
    NLE = 15,

    /// parity
    P = 10,

    /// not parity
    NP = 11,
}

impl CC {
    pub(crate) fn from_intcc(intcc: IntCC) -> Self {
        match intcc {
            IntCC::Equal => CC::Z,
            IntCC::NotEqual => CC::NZ,
            IntCC::SignedGreaterThanOrEqual => CC::NL,
            IntCC::SignedGreaterThan => CC::NLE,
            IntCC::SignedLessThanOrEqual => CC::LE,
            IntCC::SignedLessThan => CC::L,
            IntCC::UnsignedGreaterThanOrEqual => CC::NB,
            IntCC::UnsignedGreaterThan => CC::NBE,
            IntCC::UnsignedLessThanOrEqual => CC::BE,
            IntCC::UnsignedLessThan => CC::B,
        }
    }

    pub(crate) fn invert(&self) -> Self {
        match self {
            CC::O => CC::NO,
            CC::NO => CC::O,

            CC::B => CC::NB,
            CC::NB => CC::B,

            CC::Z => CC::NZ,
            CC::NZ => CC::Z,

            CC::BE => CC::NBE,
            CC::NBE => CC::BE,

            CC::S => CC::NS,
            CC::NS => CC::S,

            CC::L => CC::NL,
            CC::NL => CC::L,

            CC::LE => CC::NLE,
            CC::NLE => CC::LE,

            CC::P => CC::NP,
            CC::NP => CC::P,
        }
    }

    pub(crate) fn get_enc(self) -> u8 {
        self as u8
    }
}

impl fmt::Debug for CC {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let name = match self {
            CC::O => "o",
            CC::NO => "no",
            CC::B => "b",
            CC::NB => "nb",
            CC::Z => "z",
            CC::NZ => "nz",
            CC::BE => "be",
            CC::NBE => "nbe",
            CC::S => "s",
            CC::NS => "ns",
            CC::L => "l",
            CC::NL => "nl",
            CC::LE => "le",
            CC::NLE => "nle",
            CC::P => "p",
            CC::NP => "np",
        };
        write!(fmt, "{}", name)
    }
}

impl fmt::Display for CC {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

/// Encode the ways that floats can be compared. This is used in float comparisons such as `cmpps`,
/// e.g.; it is distinguished from other float comparisons (e.g. `ucomiss`) in that those use EFLAGS
/// whereas [FcmpImm] is used as an immediate.
#[derive(Clone, Copy)]
pub enum FcmpImm {
    /// Equal comparison.
    Equal = 0x00,
    /// Less than comparison.
    LessThan = 0x01,
    /// Less than or equal comparison.
    LessThanOrEqual = 0x02,
    /// Unordered.
    Unordered = 0x03,
    /// Not equal comparison.
    NotEqual = 0x04,
    /// Unordered of greater than or equal comparison.
    UnorderedOrGreaterThanOrEqual = 0x05,
    /// Unordered or greater than comparison.
    UnorderedOrGreaterThan = 0x06,
    /// Ordered.
    Ordered = 0x07,
}

impl FcmpImm {
    pub(crate) fn encode(self) -> u8 {
        self as u8
    }
}

impl From<FloatCC> for FcmpImm {
    fn from(cond: FloatCC) -> Self {
        match cond {
            FloatCC::Equal => FcmpImm::Equal,
            FloatCC::LessThan => FcmpImm::LessThan,
            FloatCC::LessThanOrEqual => FcmpImm::LessThanOrEqual,
            FloatCC::Unordered => FcmpImm::Unordered,
            FloatCC::NotEqual => FcmpImm::NotEqual,
            FloatCC::UnorderedOrGreaterThanOrEqual => FcmpImm::UnorderedOrGreaterThanOrEqual,
            FloatCC::UnorderedOrGreaterThan => FcmpImm::UnorderedOrGreaterThan,
            FloatCC::Ordered => FcmpImm::Ordered,
            _ => panic!("unable to create comparison predicate for {}", cond),
        }
    }
}

/// Encode the rounding modes used as part of the Rounding Control field.
/// Note, these rounding immediates only consider the rounding control field
/// (i.e. the rounding mode) which only take up the first two bits when encoded.
/// However the rounding immediate which this field helps make up, also includes
/// bits 3 and 4 which define the rounding select and precision mask respectively.
/// These two bits are not defined here and are implictly set to zero when encoded.
#[derive(Clone, Copy)]
pub enum RoundImm {
    /// Round to nearest mode.
    RoundNearest = 0x00,
    /// Round down mode.
    RoundDown = 0x01,
    /// Round up mode.
    RoundUp = 0x02,
    /// Round to zero mode.
    RoundZero = 0x03,
}

impl RoundImm {
    pub(crate) fn encode(self) -> u8 {
        self as u8
    }
}

/// An operand's size in bits.
#[derive(Clone, Copy, PartialEq)]
pub enum OperandSize {
    /// 8-bit.
    Size8,
    /// 16-bit.
    Size16,
    /// 32-bit.
    Size32,
    /// 64-bit.
    Size64,
}

impl OperandSize {
    pub(crate) fn from_bytes(num_bytes: u32) -> Self {
        match num_bytes {
            1 => OperandSize::Size8,
            2 => OperandSize::Size16,
            4 => OperandSize::Size32,
            8 => OperandSize::Size64,
            _ => unreachable!("Invalid OperandSize: {}", num_bytes),
        }
    }

    // Computes the OperandSize for a given type.
    // For vectors, the OperandSize of the lanes is returned.
    pub(crate) fn from_ty(ty: Type) -> Self {
        Self::from_bytes(ty.lane_type().bytes())
    }

    // Check that the value of self is one of the allowed sizes.
    pub(crate) fn is_one_of(&self, sizes: &[Self]) -> bool {
        sizes.iter().any(|val| *self == *val)
    }

    pub(crate) fn to_bytes(&self) -> u8 {
        match self {
            Self::Size8 => 1,
            Self::Size16 => 2,
            Self::Size32 => 4,
            Self::Size64 => 8,
        }
    }

    pub(crate) fn to_bits(&self) -> u8 {
        self.to_bytes() * 8
    }

    pub(crate) fn to_type(&self) -> Type {
        match self {
            Self::Size8 => I8,
            Self::Size16 => I16,
            Self::Size32 => I32,
            Self::Size64 => I64,
        }
    }
}

/// An x64 memory fence kind.
#[derive(Clone)]
#[allow(dead_code)]
pub enum FenceKind {
    /// `mfence` instruction ("Memory Fence")
    MFence,
    /// `lfence` instruction ("Load Fence")
    LFence,
    /// `sfence` instruction ("Store Fence")
    SFence,
}
