use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Opcode {
IConst,
FConst,
Splat,
GlobalAddr,
Add,
Sub,
Mul,
SDiv,
UDiv,
SRem,
URem,
And,
Or,
Xor,
Shl,
LShr,
AShr,
FAdd,
FSub,
FMul,
FDiv,
FRem,
FNeg,
Fma,
ICmp,
FCmp,
Trunc,
SExt,
ZExt,
FPTrunc,
FPExt,
FPToSI,
FPToUI,
SIToFP,
UIToFP,
PtrToInt,
IntToPtr,
Bitcast,
Alloca,
Load,
Store,
PtrAdd,
Memcpy,
Memmove,
Memset,
AtomicLoad,
AtomicStore,
AtomicRmw,
Cmpxchg,
Fence,
Jump,
BrIf,
Switch,
Return,
Unreachable,
Call,
CallIndirect,
TailCall,
Ctlz,
Cttz,
Ctpop,
Bswap,
Bitreverse,
SAddOverflow,
UAddOverflow,
SSubOverflow,
USubOverflow,
SMulOverflow,
UMulOverflow,
Expect,
UnreachableHint,
Prefetch,
FrameAddress,
ReturnAddress,
VaStart,
VaArg,
VaEnd,
VaCopy,
StackSave,
StackRestore,
SetjmpMarker,
LongjmpMarker,
TargetIntrinsic,
InlineAsm,
}
impl Opcode {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::IConst => "iconst",
Self::FConst => "fconst",
Self::Splat => "splat",
Self::GlobalAddr => "global_addr",
Self::Add => "add",
Self::Sub => "sub",
Self::Mul => "mul",
Self::SDiv => "sdiv",
Self::UDiv => "udiv",
Self::SRem => "srem",
Self::URem => "urem",
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",
Self::FRem => "frem",
Self::FNeg => "fneg",
Self::Fma => "fma",
Self::ICmp => "icmp",
Self::FCmp => "fcmp",
Self::Trunc => "trunc",
Self::SExt => "sext",
Self::ZExt => "zext",
Self::FPTrunc => "fptrunc",
Self::FPExt => "fpext",
Self::FPToSI => "fptosi",
Self::FPToUI => "fptoui",
Self::SIToFP => "sitofp",
Self::UIToFP => "uitofp",
Self::PtrToInt => "ptrtoint",
Self::IntToPtr => "inttoptr",
Self::Bitcast => "bitcast",
Self::Alloca => "alloca",
Self::Load => "load",
Self::Store => "store",
Self::PtrAdd => "ptr_add",
Self::Memcpy => "memcpy",
Self::Memmove => "memmove",
Self::Memset => "memset",
Self::AtomicLoad => "atomic_load",
Self::AtomicStore => "atomic_store",
Self::AtomicRmw => "atomic_rmw",
Self::Cmpxchg => "cmpxchg",
Self::Fence => "fence",
Self::Jump => "jump",
Self::BrIf => "br_if",
Self::Switch => "switch",
Self::Return => "return",
Self::Unreachable => "unreachable",
Self::Call => "call",
Self::CallIndirect => "call_indirect",
Self::TailCall => "tail_call",
Self::Ctlz => "ctlz",
Self::Cttz => "cttz",
Self::Ctpop => "ctpop",
Self::Bswap => "bswap",
Self::Bitreverse => "bitreverse",
Self::SAddOverflow => "sadd_overflow",
Self::UAddOverflow => "uadd_overflow",
Self::SSubOverflow => "ssub_overflow",
Self::USubOverflow => "usub_overflow",
Self::SMulOverflow => "smul_overflow",
Self::UMulOverflow => "umul_overflow",
Self::Expect => "expect",
Self::UnreachableHint => "unreachable_hint",
Self::Prefetch => "prefetch",
Self::FrameAddress => "frame_address",
Self::ReturnAddress => "return_address",
Self::VaStart => "va_start",
Self::VaArg => "va_arg",
Self::VaEnd => "va_end",
Self::VaCopy => "va_copy",
Self::StackSave => "stacksave",
Self::StackRestore => "stackrestore",
Self::SetjmpMarker => "setjmp_marker",
Self::LongjmpMarker => "longjmp_marker",
Self::TargetIntrinsic => "target_intrinsic",
Self::InlineAsm => "inline_asm",
}
}
pub fn all() -> impl Iterator<Item = Self> {
ALL.iter().copied()
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
ALL.iter().copied().find(|op| op.name() == name)
}
#[must_use]
pub const fn is_terminator(self) -> bool {
matches!(
self,
Self::Jump
| Self::BrIf
| Self::Switch
| Self::Return
| Self::Unreachable
| Self::TailCall
)
}
#[must_use]
pub const fn is_commutative(self) -> bool {
matches!(
self,
Self::Add
| Self::Mul
| Self::And
| Self::Or
| Self::Xor
| Self::FAdd
| Self::FMul
| Self::SAddOverflow
| Self::UAddOverflow
| Self::SMulOverflow
| Self::UMulOverflow
)
}
#[must_use]
pub const fn has_effects(self) -> bool {
!matches!(
self,
Self::IConst
| Self::FConst
| Self::Splat
| Self::GlobalAddr
| Self::Add
| Self::Sub
| Self::Mul
| Self::SDiv
| Self::UDiv
| Self::SRem
| Self::URem
| Self::And
| Self::Or
| Self::Xor
| Self::Shl
| Self::LShr
| Self::AShr
| Self::FAdd
| Self::FSub
| Self::FMul
| Self::FDiv
| Self::FRem
| Self::FNeg
| Self::Fma
| Self::ICmp
| Self::FCmp
| Self::Trunc
| Self::SExt
| Self::ZExt
| Self::FPTrunc
| Self::FPExt
| Self::FPToSI
| Self::FPToUI
| Self::SIToFP
| Self::UIToFP
| Self::PtrToInt
| Self::IntToPtr
| Self::Bitcast
| Self::PtrAdd
| Self::Ctlz
| Self::Cttz
| Self::Ctpop
| Self::Bswap
| Self::Bitreverse
| Self::SAddOverflow
| Self::UAddOverflow
| Self::SSubOverflow
| Self::USubOverflow
| Self::SMulOverflow
| Self::UMulOverflow
| Self::Expect
| Self::FrameAddress
| Self::ReturnAddress
)
}
#[must_use]
pub const fn results(self) -> Option<u8> {
match self {
Self::Call | Self::CallIndirect | Self::InlineAsm => None,
Self::Cmpxchg
| Self::SAddOverflow
| Self::UAddOverflow
| Self::SSubOverflow
| Self::USubOverflow
| Self::SMulOverflow
| Self::UMulOverflow => Some(2),
Self::Store
| Self::Memcpy
| Self::Memmove
| Self::Memset
| Self::AtomicStore
| Self::Fence
| Self::Prefetch
| Self::VaStart
| Self::VaEnd
| Self::VaCopy
| Self::StackRestore
| Self::UnreachableHint
| Self::SetjmpMarker
| Self::LongjmpMarker => Some(0),
_ if self.is_terminator() => Some(0),
_ => Some(1),
}
}
#[must_use]
pub const fn extra_kind(self) -> ExtraKind {
match self {
Self::IConst | Self::FConst | Self::Splat => ExtraKind::Imm,
Self::GlobalAddr | Self::TargetIntrinsic => ExtraKind::Symbol,
Self::ICmp => ExtraKind::IntPred,
Self::FCmp => ExtraKind::FloatPred,
Self::Alloca
| Self::Load
| Self::Store
| Self::Memcpy
| Self::Memmove
| Self::Memset
| Self::AtomicLoad
| Self::AtomicStore
| Self::Cmpxchg => ExtraKind::Mem,
Self::AtomicRmw => ExtraKind::Rmw,
Self::Fence => ExtraKind::Order,
Self::Jump | Self::BrIf => ExtraKind::Targets,
Self::Switch => ExtraKind::Switch,
Self::Call | Self::CallIndirect | Self::TailCall => ExtraKind::Call,
Self::InlineAsm => ExtraKind::Asm,
_ => ExtraKind::None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ExtraKind {
None,
Imm,
Symbol,
IntPred,
FloatPred,
Mem,
Rmw,
Order,
Targets,
Call,
Switch,
Asm,
}
impl ExtraKind {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::None => "nothing",
Self::Imm => "a constant",
Self::Symbol => "a name",
Self::IntPred => "an integer comparison",
Self::FloatPred => "a floating point comparison",
Self::Mem => "an access",
Self::Rmw => "a read-modify-write",
Self::Order => "an ordering",
Self::Targets => "branch targets",
Self::Call => "a call",
Self::Switch => "a switch",
Self::Asm => "inline assembly",
}
}
}
impl fmt::Display for Opcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
static ALL: &[Opcode] = &[
Opcode::IConst,
Opcode::FConst,
Opcode::Splat,
Opcode::GlobalAddr,
Opcode::Add,
Opcode::Sub,
Opcode::Mul,
Opcode::SDiv,
Opcode::UDiv,
Opcode::SRem,
Opcode::URem,
Opcode::And,
Opcode::Or,
Opcode::Xor,
Opcode::Shl,
Opcode::LShr,
Opcode::AShr,
Opcode::FAdd,
Opcode::FSub,
Opcode::FMul,
Opcode::FDiv,
Opcode::FRem,
Opcode::FNeg,
Opcode::Fma,
Opcode::ICmp,
Opcode::FCmp,
Opcode::Trunc,
Opcode::SExt,
Opcode::ZExt,
Opcode::FPTrunc,
Opcode::FPExt,
Opcode::FPToSI,
Opcode::FPToUI,
Opcode::SIToFP,
Opcode::UIToFP,
Opcode::PtrToInt,
Opcode::IntToPtr,
Opcode::Bitcast,
Opcode::Alloca,
Opcode::Load,
Opcode::Store,
Opcode::PtrAdd,
Opcode::Memcpy,
Opcode::Memmove,
Opcode::Memset,
Opcode::AtomicLoad,
Opcode::AtomicStore,
Opcode::AtomicRmw,
Opcode::Cmpxchg,
Opcode::Fence,
Opcode::Jump,
Opcode::BrIf,
Opcode::Switch,
Opcode::Return,
Opcode::Unreachable,
Opcode::Call,
Opcode::CallIndirect,
Opcode::TailCall,
Opcode::Ctlz,
Opcode::Cttz,
Opcode::Ctpop,
Opcode::Bswap,
Opcode::Bitreverse,
Opcode::SAddOverflow,
Opcode::UAddOverflow,
Opcode::SSubOverflow,
Opcode::USubOverflow,
Opcode::SMulOverflow,
Opcode::UMulOverflow,
Opcode::Expect,
Opcode::UnreachableHint,
Opcode::Prefetch,
Opcode::FrameAddress,
Opcode::ReturnAddress,
Opcode::VaStart,
Opcode::VaArg,
Opcode::VaEnd,
Opcode::VaCopy,
Opcode::StackSave,
Opcode::StackRestore,
Opcode::SetjmpMarker,
Opcode::LongjmpMarker,
Opcode::TargetIntrinsic,
Opcode::InlineAsm,
];
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IntPred {
Eq,
Ne,
Slt,
Sle,
Sgt,
Sge,
Ult,
Ule,
Ugt,
Uge,
}
impl IntPred {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Eq => "eq",
Self::Ne => "ne",
Self::Slt => "slt",
Self::Sle => "sle",
Self::Sgt => "sgt",
Self::Sge => "sge",
Self::Ult => "ult",
Self::Ule => "ule",
Self::Ugt => "ugt",
Self::Uge => "uge",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::all().find(|pred| pred.name() == name)
}
pub fn all() -> impl Iterator<Item = Self> {
[
Self::Eq,
Self::Ne,
Self::Slt,
Self::Sle,
Self::Sgt,
Self::Sge,
Self::Ult,
Self::Ule,
Self::Ugt,
Self::Uge,
]
.into_iter()
}
#[must_use]
pub const fn inverse(self) -> Self {
match self {
Self::Eq => Self::Ne,
Self::Ne => Self::Eq,
Self::Slt => Self::Sge,
Self::Sge => Self::Slt,
Self::Sle => Self::Sgt,
Self::Sgt => Self::Sle,
Self::Ult => Self::Uge,
Self::Uge => Self::Ult,
Self::Ule => Self::Ugt,
Self::Ugt => Self::Ule,
}
}
#[must_use]
pub const fn swapped(self) -> Self {
match self {
Self::Eq => Self::Eq,
Self::Ne => Self::Ne,
Self::Slt => Self::Sgt,
Self::Sgt => Self::Slt,
Self::Sle => Self::Sge,
Self::Sge => Self::Sle,
Self::Ult => Self::Ugt,
Self::Ugt => Self::Ult,
Self::Ule => Self::Uge,
Self::Uge => Self::Ule,
}
}
#[must_use]
pub const fn is_signed(self) -> bool {
matches!(self, Self::Slt | Self::Sle | Self::Sgt | Self::Sge)
}
}
impl fmt::Display for IntPred {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FloatPred {
False,
Oeq,
Ogt,
Oge,
Olt,
Ole,
One,
Ord,
Uno,
Ueq,
Ugt,
Uge,
Ult,
Ule,
Une,
True,
}
impl FloatPred {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::False => "false",
Self::Oeq => "oeq",
Self::Ogt => "ogt",
Self::Oge => "oge",
Self::Olt => "olt",
Self::Ole => "ole",
Self::One => "one",
Self::Ord => "ord",
Self::Uno => "uno",
Self::Ueq => "ueq",
Self::Ugt => "ugt",
Self::Uge => "uge",
Self::Ult => "ult",
Self::Ule => "ule",
Self::Une => "une",
Self::True => "true",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::all().find(|pred| pred.name() == name)
}
pub fn all() -> impl Iterator<Item = Self> {
[
Self::False,
Self::Oeq,
Self::Ogt,
Self::Oge,
Self::Olt,
Self::Ole,
Self::One,
Self::Ord,
Self::Uno,
Self::Ueq,
Self::Ugt,
Self::Uge,
Self::Ult,
Self::Ule,
Self::Une,
Self::True,
]
.into_iter()
}
#[must_use]
pub const fn inverse(self) -> Self {
match self {
Self::False => Self::True,
Self::Oeq => Self::Une,
Self::Ogt => Self::Ule,
Self::Oge => Self::Ult,
Self::Olt => Self::Uge,
Self::Ole => Self::Ugt,
Self::One => Self::Ueq,
Self::Ord => Self::Uno,
Self::Uno => Self::Ord,
Self::Ueq => Self::One,
Self::Ugt => Self::Ole,
Self::Uge => Self::Olt,
Self::Ult => Self::Oge,
Self::Ule => Self::Ogt,
Self::Une => Self::Oeq,
Self::True => Self::False,
}
}
#[must_use]
pub const fn swapped(self) -> Self {
match self {
Self::Ogt => Self::Olt,
Self::Olt => Self::Ogt,
Self::Oge => Self::Ole,
Self::Ole => Self::Oge,
Self::Ugt => Self::Ult,
Self::Ult => Self::Ugt,
Self::Uge => Self::Ule,
Self::Ule => Self::Uge,
same => same,
}
}
#[must_use]
pub const fn is_ordered(self) -> bool {
matches!(
self,
Self::Oeq | Self::Ogt | Self::Oge | Self::Olt | Self::Ole | Self::One | Self::Ord
)
}
}
impl fmt::Display for FloatPred {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_opcode_is_in_the_table() {
assert_eq!(ALL.len(), Opcode::InlineAsm as usize + 1);
for (position, &op) in ALL.iter().enumerate() {
assert_eq!(op as usize, position, "{op} is out of order in ALL");
}
}
#[test]
fn every_opcode_has_its_own_name_and_finds_it_again() {
let mut names: Vec<&str> = Opcode::all().map(Opcode::name).collect();
let total = names.len();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), total, "two opcodes share a name");
for op in Opcode::all() {
assert_eq!(Opcode::from_name(op.name()), Some(op));
}
assert_eq!(Opcode::from_name("phi"), None);
assert_eq!(Opcode::from_name("getelementptr"), None);
assert_eq!(Opcode::from_name(""), None);
}
#[test]
fn the_terminators_are_the_ones_control_leaves_by() {
let terminators: Vec<&str> =
Opcode::all().filter(|op| op.is_terminator()).map(Opcode::name).collect();
assert_eq!(terminators, ["jump", "br_if", "switch", "return", "unreachable", "tail_call"]);
}
#[test]
fn a_terminator_produces_nothing() {
for op in Opcode::all().filter(|op| op.is_terminator()) {
assert_eq!(op.results(), Some(0), "{op}");
}
}
#[test]
fn the_pair_producing_opcodes_are_the_ones_with_a_flag_beside_the_value() {
let pairs: Vec<&str> =
Opcode::all().filter(|op| op.results() == Some(2)).map(Opcode::name).collect();
assert_eq!(
pairs,
[
"cmpxchg",
"sadd_overflow",
"uadd_overflow",
"ssub_overflow",
"usub_overflow",
"smul_overflow",
"umul_overflow"
]
);
}
#[test]
fn memory_has_effects_and_arithmetic_does_not() {
for op in [Opcode::Load, Opcode::Store, Opcode::Call, Opcode::Alloca, Opcode::Fence] {
assert!(op.has_effects(), "{op}");
}
for op in [Opcode::Add, Opcode::FDiv, Opcode::ICmp, Opcode::PtrAdd, Opcode::IConst] {
assert!(!op.has_effects(), "{op}");
}
}
#[test]
fn commuting_is_only_claimed_where_it_holds() {
assert!(Opcode::Add.is_commutative());
assert!(Opcode::FAdd.is_commutative());
assert!(!Opcode::Sub.is_commutative());
assert!(!Opcode::FDiv.is_commutative());
assert!(!Opcode::Shl.is_commutative());
}
#[test]
fn an_integer_predicate_inverts_and_swaps_back_to_itself() {
for pred in IntPred::all() {
assert_eq!(pred.inverse().inverse(), pred);
assert_eq!(pred.swapped().swapped(), pred);
assert_eq!(IntPred::from_name(pred.name()), Some(pred));
}
assert_eq!(IntPred::Slt.inverse(), IntPred::Sge);
assert_eq!(IntPred::Slt.swapped(), IntPred::Sgt);
assert_eq!(IntPred::from_name("lt"), None);
}
#[test]
fn a_floating_predicate_inverts_across_the_ordered_line() {
for pred in FloatPred::all() {
assert_eq!(pred.inverse().inverse(), pred);
assert_eq!(pred.swapped().swapped(), pred);
assert_eq!(FloatPred::from_name(pred.name()), Some(pred));
}
for pred in FloatPred::all().filter(|p| !matches!(p, FloatPred::False | FloatPred::True)) {
assert_ne!(pred.is_ordered(), pred.inverse().is_ordered(), "{pred}");
}
assert_eq!(FloatPred::Olt.inverse(), FloatPred::Uge);
assert_eq!(FloatPred::Olt.swapped(), FloatPred::Ogt);
}
#[test]
fn swapping_a_predicate_keeps_it_ordered_or_unordered() {
for pred in FloatPred::all() {
assert_eq!(pred.is_ordered(), pred.swapped().is_ordered(), "{pred}");
}
for pred in IntPred::all() {
assert_eq!(pred.is_signed(), pred.swapped().is_signed(), "{pred}");
}
}
#[test]
fn no_two_predicates_share_a_name_within_their_family() {
for names in [
IntPred::all().map(IntPred::name).collect::<Vec<_>>(),
FloatPred::all().map(FloatPred::name).collect::<Vec<_>>(),
] {
let total = names.len();
let mut names = names;
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), total);
}
}
}