#![allow(non_camel_case_types)]
use crate::codegen::*;
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct X86PhysReg(pub u16);
impl X86PhysReg {
pub const NO_REG: Self = X86PhysReg(0);
pub const RAX: Self = X86PhysReg(1);
pub const RCX: Self = X86PhysReg(2);
pub const RDX: Self = X86PhysReg(3);
pub const RBX: Self = X86PhysReg(4);
pub const RSP: Self = X86PhysReg(5);
pub const RBP: Self = X86PhysReg(6);
pub const RSI: Self = X86PhysReg(7);
pub const RDI: Self = X86PhysReg(8);
pub const R8: Self = X86PhysReg(9);
pub const R9: Self = X86PhysReg(10);
pub const R10: Self = X86PhysReg(11);
pub const R11: Self = X86PhysReg(12);
pub const R12: Self = X86PhysReg(13);
pub const R13: Self = X86PhysReg(14);
pub const R14: Self = X86PhysReg(15);
pub const R15: Self = X86PhysReg(16);
pub const XMM0: Self = X86PhysReg(17);
pub const XMM1: Self = X86PhysReg(18);
pub const XMM2: Self = X86PhysReg(19);
pub const XMM3: Self = X86PhysReg(20);
pub const XMM4: Self = X86PhysReg(21);
pub const XMM5: Self = X86PhysReg(22);
pub const XMM6: Self = X86PhysReg(23);
pub const XMM7: Self = X86PhysReg(24);
pub const XMM8: Self = X86PhysReg(25);
pub const XMM9: Self = X86PhysReg(26);
pub const XMM10: Self = X86PhysReg(27);
pub const XMM11: Self = X86PhysReg(28);
pub const XMM12: Self = X86PhysReg(29);
pub const XMM13: Self = X86PhysReg(30);
pub const XMM14: Self = X86PhysReg(31);
pub const XMM15: Self = X86PhysReg(32);
pub fn is_gpr(self) -> bool {
matches!(self.0, 1..=16)
}
pub fn is_xmm(self) -> bool {
matches!(self.0, 17..=32)
}
pub fn is_valid(self) -> bool {
self.0 != 0
}
pub fn reg_class(&self) -> X86RegClass {
if self.is_gpr() {
X86RegClass::GPR
} else if self.is_xmm() {
X86RegClass::XMM
} else {
X86RegClass::None
}
}
pub fn as_str_64(&self) -> &'static str {
match *self {
Self::RAX => "rax",
Self::RCX => "rcx",
Self::RDX => "rdx",
Self::RBX => "rbx",
Self::RSP => "rsp",
Self::RBP => "rbp",
Self::RSI => "rsi",
Self::RDI => "rdi",
Self::R8 => "r8",
Self::R9 => "r9",
Self::R10 => "r10",
Self::R11 => "r11",
Self::R12 => "r12",
Self::R13 => "r13",
Self::R14 => "r14",
Self::R15 => "r15",
_ => "???",
}
}
pub fn encoding(self) -> u8 {
match self {
Self::RAX => 0,
Self::RCX => 1,
Self::RDX => 2,
Self::RBX => 3,
Self::RSP => 4,
Self::RBP => 5,
Self::RSI => 6,
Self::RDI => 7,
Self::R8 => 8,
Self::R9 => 9,
Self::R10 => 10,
Self::R11 => 11,
Self::R12 => 12,
Self::R13 => 13,
Self::R14 => 14,
Self::R15 => 15,
Self::XMM0 => 0,
Self::XMM1 => 1,
Self::XMM2 => 2,
Self::XMM3 => 3,
Self::XMM4 => 4,
Self::XMM5 => 5,
Self::XMM6 => 6,
Self::XMM7 => 7,
Self::XMM8 => 8,
Self::XMM9 => 9,
Self::XMM10 => 10,
Self::XMM11 => 11,
Self::XMM12 => 12,
Self::XMM13 => 13,
Self::XMM14 => 14,
Self::XMM15 => 15,
_ => 0,
}
}
pub fn from_encoding_gpr(enc: u8, is_64bit: bool) -> Self {
if is_64bit {
match enc {
0 => Self::RAX,
1 => Self::RCX,
2 => Self::RDX,
3 => Self::RBX,
4 => Self::RSP,
5 => Self::RBP,
6 => Self::RSI,
7 => Self::RDI,
_ => Self::NO_REG,
}
} else {
match enc {
0 => Self::RAX,
1 => Self::RCX,
2 => Self::RDX,
3 => Self::RBX,
4 => Self::RSP,
5 => Self::RBP,
6 => Self::RSI,
7 => Self::RDI,
_ => Self::NO_REG,
}
}
}
pub fn from_encoding_xmm(enc: u8) -> Self {
match enc {
0 => Self::XMM0,
1 => Self::XMM1,
2 => Self::XMM2,
3 => Self::XMM3,
4 => Self::XMM4,
5 => Self::XMM5,
6 => Self::XMM6,
7 => Self::XMM7,
8 => Self::XMM8,
9 => Self::XMM9,
10 => Self::XMM10,
11 => Self::XMM11,
12 => Self::XMM12,
13 => Self::XMM13,
14 => Self::XMM14,
15 => Self::XMM15,
_ => Self::XMM0,
}
}
}
impl fmt::Display for X86PhysReg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str_64())
}
}
pub type VReg = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86RegClass {
None,
GPR,
XMM,
YMM,
ZMM,
Mask,
Segment,
}
impl X86RegClass {
pub fn size_bytes(&self) -> u32 {
match self {
Self::GPR => 8,
Self::XMM => 16,
Self::YMM => 32,
Self::ZMM => 64,
Self::Mask => 8,
Self::Segment => 2,
Self::None => 0,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::GPR => "GPR",
Self::XMM => "XMM",
Self::YMM => "YMM",
Self::ZMM => "ZMM",
Self::Mask => "MASK",
Self::Segment => "SEG",
Self::None => "NONE",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DeepX86Opcode {
MOVrr,
MOVri,
MOVri64,
MOVmr,
MOVrm,
MOVms,
MOVsm,
MOVSXrr,
MOVZXrr,
MOVAPSrr,
MOVAPSmr,
MOVAPSrm,
MOVUPSrr,
MOVUPSmr,
MOVUPSrm,
MOVDQArr,
MOVDQAmr,
MOVDQArm,
VMOVAPSrr,
VMOVAPSmr,
VMOVAPSrm,
VMOVDQArr,
VMOVDQAmr,
VMOVDQArm,
ADDrr,
ADDri,
ADDri8,
ADDmr,
ADDrm,
SUBrr,
SUBri,
SUBri8,
SUBmr,
SUBrm,
MULrr,
IMULrr,
IMULrri,
IMULrri8,
DIVr,
IDIVr,
INCr,
DECr,
NEGr,
NOTr,
ANDrr,
ANDri,
ANDri8,
ANDmr,
ANDrm,
ORrr,
ORri,
ORri8,
ORmr,
ORrm,
XORrr,
XORri,
XORri8,
XORmr,
XORrm,
SHLrCL,
SHLri,
SHRrCL,
SHRri,
SARrCL,
SARri,
ROLrCL,
ROLri,
RORrCL,
RORri,
ADDSSrr,
ADDSDrr,
SUBSSrr,
SUBSDrr,
MULSSrr,
MULSDrr,
DIVSSrr,
DIVSDrr,
SQRTSSr,
SQRTSDr,
CVTSI2SSrr,
CVTSI2SDrr,
CVTSS2SIrr,
CVTSD2SIrr,
VADDSSrr,
VADDSDrr,
VADDPSrr,
VADDPDrr,
VSUBSSrr,
VSUBSDrr,
VSUBPSrr,
VSUBPDrr,
VMULSSrr,
VMULSDrr,
VMULPSrr,
VMULPDrr,
VDIVSSrr,
VDIVSDrr,
VDIVPSrr,
VDIVPDrr,
VFMADD132SSr,
VFMADD213SSr,
VFMADD231SSr,
VFMADD132SDr,
VFMADD213SDr,
VFMADD231SDr,
CMPrr,
CMPri,
CMPri8,
CMPmr,
CMPrm,
TESTrr,
TESTri,
UCOMISSrr,
UCOMISDrr,
SETOr,
SETNOr,
SETBr,
SETAEr,
SETEr,
SETNEr,
SETBEr,
SETAr,
SETSr,
SETNSr,
SETPr,
SETNPr,
SETLr,
SETGEr,
SETLEr,
SETGr,
CMOVOrr,
CMOVNOrr,
CMOVBrr,
CMOVAErr,
CMOVErr,
CMOVNErr,
CMOVBErr,
CMOVArr,
CMOVSrr,
CMOVNSrr,
CMOVPrr,
CMOVNPrr,
CMOVLrr,
CMOVGErr,
CMOVLErr,
CMOVGrr,
JMP1,
JMP4,
JO1,
JNO1,
JB1,
JAE1,
JE1,
JNE1,
JBE1,
JA1,
JS1,
JNS1,
JP1,
JNP1,
JL1,
JGE1,
JLE1,
JG1,
JO4,
JNO4,
JB4,
JAE4,
JE4,
JNE4,
JBE4,
JA4,
JS4,
JNS4,
JP4,
JNP4,
JL4,
JGE4,
JLE4,
JG4,
CALLpcrel32,
CALLr,
CALLm,
RET,
RETI,
RETIW,
PUSHr,
PUSHri,
PUSHri32,
PUSHrm,
POPr,
POPrm,
PUSH64r,
POP64r,
LEAVE,
ENTER,
LEA64r,
LEA32r,
LEA16r,
NOP,
NOPW,
NOPL,
INT3,
UD2,
HLT,
PAUSE,
CDQ,
CDQE,
CQO,
CBW,
CWDE,
CWD,
LAHF,
SAHF,
CLC,
STC,
CMC,
CLD,
STD,
CPUID,
RDTSC,
RDTSCP,
MFENCE,
LFENCE,
SFENCE,
XCHGrr,
BSWAPr,
BSFrr,
BSRrr,
POPCNTrr,
LZCNTrr,
TZCNTrr,
ANDNrr,
BEXTRrr,
BLSIr,
BLSMSKr,
BLSRr,
MULXrr,
PDEPrr,
PEXTrr,
MOVSB,
MOVSW,
MOVSD,
MOVSQ,
STOSB,
STOSW,
STOSD,
STOSQ,
PHI,
COPY,
COPY_TO_REGCLASS,
LOAD_STACK_GUARD,
FRAME_ALLOC,
IMPLICIT_DEF,
IMPLICIT_USE,
KILL,
INLINEASM,
LIFETIME_START,
LIFETIME_END,
CFI_INSTRUCTION,
GC_LABEL,
STATEPOINT,
PATCHPOINT,
X86_TAIL_CALL,
X86_TAIL_RET,
X86_SEG_ALLOCA,
DBG_VALUE,
DBG_LABEL,
DBG_INSTR_REF,
UNKNOWN,
}
impl DeepX86Opcode {
pub fn is_terminator(self) -> bool {
matches!(
self,
Self::RET
| Self::RETI
| Self::RETIW
| Self::JMP1
| Self::JMP4
| Self::JO1
| Self::JNO1
| Self::JB1
| Self::JAE1
| Self::JE1
| Self::JNE1
| Self::JBE1
| Self::JA1
| Self::JS1
| Self::JNS1
| Self::JP1
| Self::JNP1
| Self::JL1
| Self::JGE1
| Self::JLE1
| Self::JG1
| Self::JO4
| Self::JNO4
| Self::JB4
| Self::JAE4
| Self::JE4
| Self::JNE4
| Self::JBE4
| Self::JA4
| Self::JS4
| Self::JNS4
| Self::JP4
| Self::JNP4
| Self::JL4
| Self::JGE4
| Self::JLE4
| Self::JG4
| Self::X86_TAIL_RET
| Self::UNKNOWN
)
}
pub fn is_branch(self) -> bool {
self.is_terminator()
&& !matches!(
self,
Self::RET | Self::RETI | Self::RETIW | Self::X86_TAIL_RET
)
}
pub fn is_call(self) -> bool {
matches!(self, Self::CALLpcrel32 | Self::CALLr | Self::CALLm)
}
pub fn is_return(self) -> bool {
matches!(
self,
Self::RET | Self::RETI | Self::RETIW | Self::X86_TAIL_RET
)
}
pub fn may_load(self) -> bool {
matches!(
self,
Self::MOVrm
| Self::MOVsm
| Self::MOVAPSrm
| Self::MOVUPSrm
| Self::MOVDQArm
| Self::VMOVAPSrm
| Self::VMOVDQArm
| Self::ADDrm
| Self::SUBrm
| Self::ANDrm
| Self::ORrm
| Self::XORrm
| Self::CMPrm
| Self::POPrm
| Self::PUSHrm
| Self::CALLm
)
}
pub fn may_store(self) -> bool {
matches!(
self,
Self::MOVmr
| Self::MOVms
| Self::MOVAPSmr
| Self::MOVUPSmr
| Self::MOVDQAmr
| Self::VMOVAPSmr
| Self::VMOVDQAmr
| Self::ADDmr
| Self::SUBmr
| Self::ANDmr
| Self::ORmr
| Self::XORmr
)
}
pub fn has_side_effects(self) -> bool {
self.is_call()
|| self.is_return()
|| matches!(
self,
Self::INT3
| Self::UD2
| Self::HLT
| Self::CPUID
| Self::RDTSC
| Self::RDTSCP
| Self::MFENCE
| Self::LFENCE
| Self::SFENCE
| Self::INLINEASM
| Self::STATEPOINT
)
}
pub fn is_speculatable(self) -> bool {
!self.has_side_effects() && !self.may_load() && !self.may_store()
}
pub fn mnemonic(self) -> &'static str {
match self {
Self::MOVrr
| Self::MOVri
| Self::MOVri64
| Self::MOVmr
| Self::MOVrm
| Self::MOVms
| Self::MOVsm => "mov",
Self::MOVSXrr => "movsx",
Self::MOVZXrr => "movzx",
Self::MOVAPSrr | Self::MOVAPSmr | Self::MOVAPSrm => "movaps",
Self::MOVUPSrr | Self::MOVUPSmr | Self::MOVUPSrm => "movups",
Self::MOVDQArr | Self::MOVDQAmr | Self::MOVDQArm => "movdqa",
Self::VMOVAPSrr | Self::VMOVAPSmr | Self::VMOVAPSrm => "vmovaps",
Self::VMOVDQArr | Self::VMOVDQAmr | Self::VMOVDQArm => "vmovdqa",
Self::ADDrr | Self::ADDri | Self::ADDri8 | Self::ADDmr | Self::ADDrm => "add",
Self::SUBrr | Self::SUBri | Self::SUBri8 | Self::SUBmr | Self::SUBrm => "sub",
Self::MULrr => "mul",
Self::IMULrr | Self::IMULrri | Self::IMULrri8 => "imul",
Self::DIVr => "div",
Self::IDIVr => "idiv",
Self::INCr => "inc",
Self::DECr => "dec",
Self::NEGr => "neg",
Self::NOTr => "not",
Self::ANDrr | Self::ANDri | Self::ANDri8 | Self::ANDmr | Self::ANDrm => "and",
Self::ORrr | Self::ORri | Self::ORri8 | Self::ORmr | Self::ORrm => "or",
Self::XORrr | Self::XORri | Self::XORri8 | Self::XORmr | Self::XORrm => "xor",
Self::SHLrCL | Self::SHLri => "shl",
Self::SHRrCL | Self::SHRri => "shr",
Self::SARrCL | Self::SARri => "sar",
Self::ROLrCL | Self::ROLri => "rol",
Self::RORrCL | Self::RORri => "ror",
Self::ADDSSrr | Self::ADDSDrr => "adds",
Self::SUBSSrr | Self::SUBSDrr => "subs",
Self::MULSSrr | Self::MULSDrr => "muls",
Self::DIVSSrr | Self::DIVSDrr => "divs",
Self::SQRTSSr | Self::SQRTSDr => "sqrt",
Self::CVTSI2SSrr | Self::CVTSI2SDrr => "cvtsi2s",
Self::CVTSS2SIrr | Self::CVTSD2SIrr => "cvt2si",
Self::VADDSSrr | Self::VADDSDrr | Self::VADDPSrr | Self::VADDPDrr => "vadd",
Self::VSUBSSrr | Self::VSUBSDrr | Self::VSUBPSrr | Self::VSUBPDrr => "vsub",
Self::VMULSSrr | Self::VMULSDrr | Self::VMULPSrr | Self::VMULPDrr => "vmul",
Self::VDIVSSrr | Self::VDIVSDrr | Self::VDIVPSrr | Self::VDIVPDrr => "vdiv",
Self::VFMADD132SSr
| Self::VFMADD213SSr
| Self::VFMADD231SSr
| Self::VFMADD132SDr
| Self::VFMADD213SDr
| Self::VFMADD231SDr => "vfmadd",
Self::CMPrr | Self::CMPri | Self::CMPri8 | Self::CMPmr | Self::CMPrm => "cmp",
Self::TESTrr | Self::TESTri => "test",
Self::UCOMISSrr | Self::UCOMISDrr => "ucomis",
Self::SETOr => "seto",
Self::SETNOr => "setno",
Self::SETBr => "setb",
Self::SETAEr => "setae",
Self::SETEr => "sete",
Self::SETNEr => "setne",
Self::SETBEr => "setbe",
Self::SETAr => "seta",
Self::SETSr => "sets",
Self::SETNSr => "setns",
Self::SETPr => "setp",
Self::SETNPr => "setnp",
Self::SETLr => "setl",
Self::SETGEr => "setge",
Self::SETLEr => "setle",
Self::SETGr => "setg",
Self::CMOVOrr => "cmovo",
Self::CMOVNOrr => "cmovno",
Self::CMOVBrr => "cmovb",
Self::CMOVAErr => "cmovae",
Self::CMOVErr => "cmove",
Self::CMOVNErr => "cmovne",
Self::CMOVBErr => "cmovbe",
Self::CMOVArr => "cmova",
Self::CMOVSrr => "cmovs",
Self::CMOVNSrr => "cmovns",
Self::CMOVPrr => "cmovp",
Self::CMOVNPrr => "cmovnp",
Self::CMOVLrr => "cmovl",
Self::CMOVGErr => "cmovge",
Self::CMOVLErr => "cmovle",
Self::CMOVGrr => "cmovg",
Self::JMP1 | Self::JMP4 => "jmp",
Self::JO1 | Self::JO4 => "jo",
Self::JNO1 | Self::JNO4 => "jno",
Self::JB1 | Self::JB4 => "jb",
Self::JAE1 | Self::JAE4 => "jae",
Self::JE1 | Self::JE4 => "je",
Self::JNE1 | Self::JNE4 => "jne",
Self::JBE1 | Self::JBE4 => "jbe",
Self::JA1 | Self::JA4 => "ja",
Self::JS1 | Self::JS4 => "js",
Self::JNS1 | Self::JNS4 => "jns",
Self::JP1 | Self::JP4 => "jp",
Self::JNP1 | Self::JNP4 => "jnp",
Self::JL1 | Self::JL4 => "jl",
Self::JGE1 | Self::JGE4 => "jge",
Self::JLE1 | Self::JLE4 => "jle",
Self::JG1 | Self::JG4 => "jg",
Self::CALLpcrel32 | Self::CALLr | Self::CALLm => "call",
Self::RET | Self::RETI | Self::RETIW => "ret",
Self::PUSHr | Self::PUSHri | Self::PUSHri32 | Self::PUSHrm => "push",
Self::POPr | Self::POPrm => "pop",
Self::PUSH64r => "push",
Self::POP64r => "pop",
Self::LEAVE => "leave",
Self::ENTER => "enter",
Self::LEA64r | Self::LEA32r | Self::LEA16r => "lea",
Self::NOP => "nop",
Self::NOPW => "nopw",
Self::NOPL => "nopl",
Self::INT3 => "int3",
Self::UD2 => "ud2",
Self::HLT => "hlt",
Self::PAUSE => "pause",
Self::CDQ | Self::CDQE | Self::CQO => "cdo",
Self::CBW | Self::CWDE => "cbw",
Self::CWD => "cwd",
Self::LAHF => "lahf",
Self::SAHF => "sahf",
Self::CLC => "clc",
Self::STC => "stc",
Self::CMC => "cmc",
Self::CLD => "cld",
Self::STD => "std",
Self::CPUID => "cpuid",
Self::RDTSC => "rdtsc",
Self::RDTSCP => "rdtscp",
Self::MFENCE => "mfence",
Self::LFENCE => "lfence",
Self::SFENCE => "sfence",
Self::XCHGrr => "xchg",
Self::BSWAPr => "bswap",
Self::BSFrr => "bsf",
Self::BSRrr => "bsr",
Self::POPCNTrr => "popcnt",
Self::LZCNTrr => "lzcnt",
Self::TZCNTrr => "tzcnt",
Self::ANDNrr => "andn",
Self::BEXTRrr => "bextr",
Self::BLSIr => "blsi",
Self::BLSMSKr => "blsmsk",
Self::BLSRr => "blsr",
Self::MULXrr => "mulx",
Self::PDEPrr => "pdep",
Self::PEXTrr => "pext",
Self::MOVSB => "movsb",
Self::MOVSW => "movsw",
Self::MOVSD => "movsd",
Self::MOVSQ => "movsq",
Self::STOSB => "stosb",
Self::STOSW => "stosw",
Self::STOSD => "stosd",
Self::STOSQ => "stosq",
Self::PHI => "PHI",
Self::COPY => "COPY",
Self::COPY_TO_REGCLASS => "COPY_TO_REGCLASS",
Self::LOAD_STACK_GUARD => "LOAD_STACK_GUARD",
Self::FRAME_ALLOC => "FRAME_ALLOC",
Self::IMPLICIT_DEF => "IMPLICIT_DEF",
Self::IMPLICIT_USE => "IMPLICIT_USE",
Self::KILL => "KILL",
Self::INLINEASM => "INLINEASM",
Self::LIFETIME_START => "LIFETIME_START",
Self::LIFETIME_END => "LIFETIME_END",
Self::CFI_INSTRUCTION => "CFI_INSTRUCTION",
Self::GC_LABEL => "GC_LABEL",
Self::STATEPOINT => "STATEPOINT",
Self::PATCHPOINT => "PATCHPOINT",
Self::X86_TAIL_CALL => "TAILCALL",
Self::X86_TAIL_RET => "TAILRET",
Self::X86_SEG_ALLOCA => "SEGALLOCA",
Self::DBG_VALUE => "DBG_VALUE",
Self::DBG_LABEL => "DBG_LABEL",
Self::DBG_INSTR_REF => "DBG_INSTR_REF",
Self::UNKNOWN => "unknown",
}
}
pub fn is_pseudo(self) -> bool {
matches!(
self,
Self::PHI
| Self::COPY
| Self::COPY_TO_REGCLASS
| Self::LOAD_STACK_GUARD
| Self::FRAME_ALLOC
| Self::IMPLICIT_DEF
| Self::IMPLICIT_USE
| Self::KILL
| Self::INLINEASM
| Self::LIFETIME_START
| Self::LIFETIME_END
| Self::CFI_INSTRUCTION
| Self::GC_LABEL
| Self::STATEPOINT
| Self::PATCHPOINT
| Self::X86_TAIL_CALL
| Self::X86_SEG_ALLOCA
| Self::DBG_VALUE
| Self::DBG_LABEL
| Self::DBG_INSTR_REF
)
}
pub fn num_implicit_operands(self) -> u32 {
match self {
Self::DIVr | Self::IDIVr => 3, Self::MULrr | Self::IMULrr => 2,
Self::RET => 0,
_ => 0,
}
}
}
impl fmt::Display for DeepX86Opcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.mnemonic())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86CC {
O,
NO,
B,
AE,
E,
NE,
BE,
A,
S,
NS,
P,
NP,
L,
GE,
LE,
G,
}
impl X86CC {
pub fn invert(self) -> Self {
match self {
Self::O => Self::NO,
Self::NO => Self::O,
Self::B => Self::AE,
Self::AE => Self::B,
Self::E => Self::NE,
Self::NE => Self::E,
Self::BE => Self::A,
Self::A => Self::BE,
Self::S => Self::NS,
Self::NS => Self::S,
Self::P => Self::NP,
Self::NP => Self::P,
Self::L => Self::GE,
Self::GE => Self::L,
Self::LE => Self::G,
Self::G => Self::LE,
}
}
pub fn suffix(self) -> &'static str {
match self {
Self::O => "o",
Self::NO => "no",
Self::B => "b",
Self::AE => "ae",
Self::E => "e",
Self::NE => "ne",
Self::BE => "be",
Self::A => "a",
Self::S => "s",
Self::NS => "ns",
Self::P => "p",
Self::NP => "np",
Self::L => "l",
Self::GE => "ge",
Self::LE => "le",
Self::G => "g",
}
}
pub fn setcc_opcode(self) -> DeepX86Opcode {
match self {
Self::O => DeepX86Opcode::SETOr,
Self::NO => DeepX86Opcode::SETNOr,
Self::B => DeepX86Opcode::SETBr,
Self::AE => DeepX86Opcode::SETAEr,
Self::E => DeepX86Opcode::SETEr,
Self::NE => DeepX86Opcode::SETNEr,
Self::BE => DeepX86Opcode::SETBEr,
Self::A => DeepX86Opcode::SETAr,
Self::S => DeepX86Opcode::SETSr,
Self::NS => DeepX86Opcode::SETNSr,
Self::P => DeepX86Opcode::SETPr,
Self::NP => DeepX86Opcode::SETNPr,
Self::L => DeepX86Opcode::SETLr,
Self::GE => DeepX86Opcode::SETGEr,
Self::LE => DeepX86Opcode::SETLEr,
Self::G => DeepX86Opcode::SETGr,
}
}
pub fn cmov_opcode(self) -> DeepX86Opcode {
match self {
Self::O => DeepX86Opcode::CMOVOrr,
Self::NO => DeepX86Opcode::CMOVNOrr,
Self::B => DeepX86Opcode::CMOVBrr,
Self::AE => DeepX86Opcode::CMOVAErr,
Self::E => DeepX86Opcode::CMOVErr,
Self::NE => DeepX86Opcode::CMOVNErr,
Self::BE => DeepX86Opcode::CMOVBErr,
Self::A => DeepX86Opcode::CMOVArr,
Self::S => DeepX86Opcode::CMOVSrr,
Self::NS => DeepX86Opcode::CMOVNSrr,
Self::P => DeepX86Opcode::CMOVPrr,
Self::NP => DeepX86Opcode::CMOVNPrr,
Self::L => DeepX86Opcode::CMOVLrr,
Self::GE => DeepX86Opcode::CMOVGErr,
Self::LE => DeepX86Opcode::CMOVLErr,
Self::G => DeepX86Opcode::CMOVGrr,
}
}
pub fn jcc_opcode_short(self) -> DeepX86Opcode {
match self {
Self::O => DeepX86Opcode::JO1,
Self::NO => DeepX86Opcode::JNO1,
Self::B => DeepX86Opcode::JB1,
Self::AE => DeepX86Opcode::JAE1,
Self::E => DeepX86Opcode::JE1,
Self::NE => DeepX86Opcode::JNE1,
Self::BE => DeepX86Opcode::JBE1,
Self::A => DeepX86Opcode::JA1,
Self::S => DeepX86Opcode::JS1,
Self::NS => DeepX86Opcode::JNS1,
Self::P => DeepX86Opcode::JP1,
Self::NP => DeepX86Opcode::JNP1,
Self::L => DeepX86Opcode::JL1,
Self::GE => DeepX86Opcode::JGE1,
Self::LE => DeepX86Opcode::JLE1,
Self::G => DeepX86Opcode::JG1,
}
}
pub fn jcc_opcode_near(self) -> DeepX86Opcode {
match self {
Self::O => DeepX86Opcode::JO4,
Self::NO => DeepX86Opcode::JNO4,
Self::B => DeepX86Opcode::JB4,
Self::AE => DeepX86Opcode::JAE4,
Self::E => DeepX86Opcode::JE4,
Self::NE => DeepX86Opcode::JNE4,
Self::BE => DeepX86Opcode::JBE4,
Self::A => DeepX86Opcode::JA4,
Self::S => DeepX86Opcode::JS4,
Self::NS => DeepX86Opcode::JNS4,
Self::P => DeepX86Opcode::JP4,
Self::NP => DeepX86Opcode::JNP4,
Self::L => DeepX86Opcode::JL4,
Self::GE => DeepX86Opcode::JGE4,
Self::LE => DeepX86Opcode::JLE4,
Self::G => DeepX86Opcode::JG4,
}
}
pub fn from_icmp_pred(pred: &str, is_signed: bool) -> Self {
match (pred, is_signed) {
("eq", _) => Self::E,
("ne", _) => Self::NE,
("ugt", _) | ("sgt", true) => Self::G,
("uge", _) | ("sge", true) => Self::GE,
("ult", _) | ("slt", true) => Self::L,
("ule", _) | ("sle", true) => Self::LE,
("sgt", false) => Self::A,
("sge", false) => Self::AE,
("slt", false) => Self::B,
("sle", false) => Self::BE,
_ => Self::E,
}
}
}
impl fmt::Display for X86CC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.suffix())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum X86MO {
VReg(u32),
PReg(X86PhysReg),
Imm(i64),
Imm8(u8),
FpImm(f64),
Mem {
base: Option<u32>,
index: Option<u32>,
scale: u8,
displacement: i32,
segment: Option<u16>,
size: u8,
},
RIPRel(i32),
BlockLabel(usize),
GlobalSymbol(String),
CondCode(X86CC),
JumpTable(usize),
FrameIndex(u32),
ConstantPoolIndex(u32),
TargetIndex(u32),
ExternalSymbol(String),
MCSymbol(String),
CFIIndex(u32),
InlineAsm(String),
Metadata,
}
impl X86MO {
pub fn vreg(id: u32) -> Self {
Self::VReg(id)
}
pub fn preg(reg: X86PhysReg) -> Self {
Self::PReg(reg)
}
pub fn imm(val: i64) -> Self {
Self::Imm(val)
}
pub fn imm8(val: u8) -> Self {
Self::Imm8(val)
}
pub fn mem(base: Option<u32>, index: Option<u32>, scale: u8, disp: i32, size: u8) -> Self {
Self::Mem {
base,
index,
scale,
displacement: disp,
segment: None,
size,
}
}
pub fn mem_base_disp(base: u32, disp: i32, size: u8) -> Self {
Self::Mem {
base: Some(base),
index: None,
scale: 1,
displacement: disp,
segment: None,
size,
}
}
pub fn frame_index(idx: u32) -> Self {
Self::FrameIndex(idx)
}
pub fn block_label(id: usize) -> Self {
Self::BlockLabel(id)
}
pub fn cond(cc: X86CC) -> Self {
Self::CondCode(cc)
}
pub fn global(name: &str) -> Self {
Self::GlobalSymbol(name.to_string())
}
pub fn external(name: &str) -> Self {
Self::ExternalSymbol(name.to_string())
}
pub fn cp_index(idx: u32) -> Self {
Self::ConstantPoolIndex(idx)
}
pub fn jt_index(idx: usize) -> Self {
Self::JumpTable(idx)
}
pub fn is_reg(&self) -> bool {
matches!(self, Self::VReg(_) | Self::PReg(_))
}
pub fn is_vreg(&self) -> bool {
matches!(self, Self::VReg(_))
}
pub fn is_preg(&self) -> bool {
matches!(self, Self::PReg(_))
}
pub fn is_imm(&self) -> bool {
matches!(self, Self::Imm(_) | Self::Imm8(_) | Self::FpImm(_))
}
pub fn is_mem(&self) -> bool {
matches!(self, Self::Mem { .. } | Self::RIPRel(_))
}
pub fn is_label(&self) -> bool {
matches!(self, Self::BlockLabel(_))
}
pub fn vreg_id(&self) -> Option<u32> {
if let Self::VReg(id) = self {
Some(*id)
} else {
None
}
}
pub fn preg_reg(&self) -> Option<X86PhysReg> {
if let Self::PReg(r) = self {
Some(*r)
} else {
None
}
}
pub fn as_imm(&self) -> Option<i64> {
match self {
Self::Imm(v) => Some(*v),
Self::Imm8(v) => Some(*v as i64),
_ => None,
}
}
}
impl fmt::Display for X86MO {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::VReg(id) => write!(f, "%v{}", id),
Self::PReg(r) => write!(f, "%{}", r),
Self::Imm(v) => write!(f, "{}", v),
Self::Imm8(v) => write!(f, "{}", v),
Self::FpImm(v) => write!(f, "{}", v),
Self::Mem {
base: Some(b),
index: None,
displacement: d,
size: s,
..
} => {
write!(f, "{} ptr [%v{}+{}]", size_bytes_name(*s), b, d)
}
Self::Mem {
base: Some(b),
index: Some(i),
scale: sc,
displacement: d,
size: s,
..
} => {
write!(
f,
"{} ptr [%v{}+%v{}*{}+{}]",
size_bytes_name(*s),
b,
i,
sc,
d
)
}
Self::Mem {
base: None,
index: Some(i),
scale: sc,
displacement: d,
size: s,
..
} => {
write!(f, "{} ptr [%v{}*{}+{}]", size_bytes_name(*s), i, sc, d)
}
Self::Mem {
base: None,
index: None,
displacement: d,
size: s,
..
} => {
write!(f, "{} ptr [{}]", size_bytes_name(*s), d)
}
Self::RIPRel(d) => write!(f, "[rip+{}]", d),
Self::BlockLabel(id) => write!(f, ".LBB{}", id),
Self::GlobalSymbol(s) => write!(f, "{}", s),
Self::CondCode(cc) => write!(f, "{}", cc),
Self::JumpTable(id) => write!(f, "JTI{}", id),
Self::FrameIndex(id) => write!(f, "<fi#{}>", id),
Self::ConstantPoolIndex(id) => write!(f, "<cp#{}>", id),
Self::TargetIndex(id) => write!(f, "<ti#{}>", id),
Self::ExternalSymbol(s) => write!(f, "{}", s),
Self::MCSymbol(s) => write!(f, "{}", s),
Self::CFIIndex(id) => write!(f, "<cfi#{}>", id),
Self::InlineAsm(s) => write!(f, "{}", s),
Self::Metadata => write!(f, "<metadata>"),
}
}
}
fn size_bytes_name(s: u8) -> &'static str {
match s {
1 => "byte",
2 => "word",
4 => "dword",
8 => "qword",
16 => "xmmword",
32 => "ymmword",
64 => "zmmword",
_ => "?",
}
}
#[derive(Debug, Clone)]
pub struct X86MI {
pub opcode: DeepX86Opcode,
pub operands: Vec<X86MO>,
pub flags: X86MIFlags,
pub id: u32,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct X86MIFlags {
pub is_terminator: bool,
pub is_branch: bool,
pub is_call: bool,
pub is_return: bool,
pub is_compare: bool,
pub may_load: bool,
pub may_store: bool,
pub has_side_effects: bool,
pub is_barrier: bool,
pub is_indirect_branch: bool,
pub is_cond_branch: bool,
pub is_uncond_branch: bool,
pub is_trap: bool,
pub has_opt_size_def: bool,
pub has_opt_size_use: bool,
pub is_convergent: bool,
pub is_move_imm: bool,
pub is_move_reg: bool,
pub uses_custom_insertion_hook: bool,
pub has_post_isel_hook: bool,
}
impl X86MI {
pub fn new(opcode: DeepX86Opcode) -> Self {
let flags = X86MIFlags {
is_terminator: opcode.is_terminator(),
is_branch: opcode.is_branch(),
is_call: opcode.is_call(),
is_return: opcode.is_return(),
is_compare: matches!(
opcode,
DeepX86Opcode::CMPrr
| DeepX86Opcode::CMPri
| DeepX86Opcode::CMPri8
| DeepX86Opcode::CMPmr
| DeepX86Opcode::CMPrm
| DeepX86Opcode::TESTrr
| DeepX86Opcode::TESTri
),
may_load: opcode.may_load(),
may_store: opcode.may_store(),
has_side_effects: opcode.has_side_effects(),
..Default::default()
};
Self {
opcode,
operands: Vec::new(),
flags,
id: 0,
}
}
pub fn with_operands(mut self, ops: Vec<X86MO>) -> Self {
self.operands = ops;
self
}
pub fn with_id(mut self, id: u32) -> Self {
self.id = id;
self
}
pub fn push_op(mut self, op: X86MO) -> Self {
self.operands.push(op);
self
}
pub fn operand(&self, idx: usize) -> Option<&X86MO> {
self.operands.get(idx)
}
pub fn num_operands(&self) -> usize {
self.operands.len()
}
pub fn defs(&self) -> Vec<&X86MO> {
self.operands.iter().filter(|op| op.is_reg()).collect()
}
pub fn uses(&self) -> Vec<&X86MO> {
self.operands
.iter()
.filter(|op| op.is_reg() || op.is_mem())
.collect()
}
pub fn is_terminator(&self) -> bool {
self.flags.is_terminator
}
pub fn set_terminator(mut self) -> Self {
self.flags.is_terminator = true;
self
}
pub fn to_asm_string(&self) -> String {
let mut s = format!("\t{}", self.opcode.mnemonic());
if !self.operands.is_empty() {
s.push('\t');
let parts: Vec<String> = self.operands.iter().map(|op| format!("{}", op)).collect();
s.push_str(&parts.join(", "));
}
s
}
}
impl fmt::Display for X86MI {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_asm_string())
}
}
#[derive(Debug, Clone)]
pub struct X86MBB {
pub label: usize,
pub name: String,
pub instructions: Vec<X86MI>,
pub successors: Vec<usize>,
pub predecessors: Vec<usize>,
pub is_entry: bool,
pub alignment: u32,
pub live_in: BTreeSet<u32>,
pub live_out: BTreeSet<u32>,
pub frequency: f64,
pub is_landing_pad: bool,
pub is_eh_pad: bool,
}
impl X86MBB {
pub fn new(label: usize) -> Self {
Self {
label,
name: format!(".LBB{}", label),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
alignment: 16,
live_in: BTreeSet::new(),
live_out: BTreeSet::new(),
frequency: 1.0,
is_landing_pad: false,
is_eh_pad: false,
}
}
pub fn push_instr(&mut self, inst: X86MI) {
self.instructions.push(inst);
}
pub fn add_successor(&mut self, block_label: usize) {
if !self.successors.contains(&block_label) {
self.successors.push(block_label);
}
}
pub fn add_predecessor(&mut self, block_label: usize) {
if !self.predecessors.contains(&block_label) {
self.predecessors.push(block_label);
}
}
pub fn last_instr(&self) -> Option<&X86MI> {
self.instructions.last()
}
pub fn last_instr_mut(&mut self) -> Option<&mut X86MI> {
self.instructions.last_mut()
}
pub fn has_terminator(&self) -> bool {
self.last_instr()
.map(|i| i.flags.is_terminator)
.unwrap_or(false)
}
pub fn insert_before_terminator(&mut self, inst: X86MI) {
if let Some(last) = self.instructions.last() {
if last.flags.is_terminator {
let pos = self.instructions.len() - 1;
self.instructions.insert(pos, inst);
return;
}
}
self.instructions.push(inst);
}
pub fn estimated_size(&self) -> usize {
self.instructions.iter().map(|_| 4).sum()
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn len(&self) -> usize {
self.instructions.len()
}
}
impl fmt::Display for X86MBB {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}:", self.name)?;
for inst in &self.instructions {
writeln!(f, " {}", inst)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86Frame {
pub total_size: u32,
pub local_size: u32,
pub saved_reg_size: u32,
pub return_address_offset: u32,
pub has_frame_pointer: bool,
pub needs_realignment: bool,
pub local_area_offset: u32,
pub uses_red_zone: bool,
pub max_call_frame_size: u32,
pub has_var_sized_objects: bool,
pub spill_slot_size: u32,
pub callee_saved_info: Vec<(X86PhysReg, u32)>, }
impl Default for X86Frame {
fn default() -> Self {
Self {
total_size: 0,
local_size: 0,
saved_reg_size: 0,
return_address_offset: 8,
has_frame_pointer: false,
needs_realignment: false,
local_area_offset: 0,
uses_red_zone: true,
max_call_frame_size: 0,
has_var_sized_objects: false,
spill_slot_size: 0,
callee_saved_info: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86MF {
pub name: String,
pub blocks: Vec<X86MBB>,
pub vreg_counter: u32,
pub frame: X86Frame,
pub is_64bit: bool,
pub call_conv: X86Conv,
pub reg_alloc_assignments: HashMap<u32, X86PhysReg>,
pub spill_slots: HashMap<u32, u32>,
pub encoded_bytes: Vec<u8>,
pub symbols: Vec<(String, u64)>,
pub relocations: Vec<(u64, String, String)>,
pub constant_pool: Vec<u8>,
pub jump_tables: Vec<Vec<usize>>,
pub eh_info: Option<EHPadInfo>,
pub debug_info: Option<DebugVarInfo>,
pub patchpoints: Vec<PatchPoint>,
pub stackmaps: Vec<StackMapLocation>,
pub frame_indices: HashMap<u32, FrameIndexInfo>,
}
#[derive(Debug, Clone)]
pub struct EHPadInfo {
pub landing_pads: HashMap<usize, LandingPad>,
}
#[derive(Debug, Clone)]
pub struct LandingPad {
pub clauses: Vec<LandingPadClause>,
pub is_cleanup: bool,
}
#[derive(Debug, Clone)]
pub struct LandingPadClause {
pub catch_type: Option<String>,
pub filter: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub struct DebugVarInfo {
pub variables: Vec<(String, u32)>, pub locations: Vec<DebugLocation>,
}
#[derive(Debug, Clone)]
pub struct DebugLocation {
pub vreg: u32,
pub offset: u32,
pub expression: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct PatchPoint {
pub id: u32,
pub offset: u64,
}
#[derive(Debug, Clone)]
pub struct StackMapLocation {
pub id: u64,
pub offset: u64,
pub locations: Vec<u64>,
}
#[derive(Debug, Clone)]
pub struct FrameIndexInfo {
pub offset: i32,
pub size: u32,
pub alignment: u32,
pub is_fixed: bool,
pub is_spill: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86Conv {
SystemV64,
Win64,
CDecl32,
StdCall32,
FastCall32,
}
impl Default for X86Conv {
fn default() -> Self {
Self::SystemV64
}
}
impl X86Conv {
pub fn is_64bit(&self) -> bool {
matches!(self, Self::SystemV64 | Self::Win64)
}
pub fn uses_red_zone(&self) -> bool {
matches!(self, Self::SystemV64)
}
pub fn callee_saved_gprs(&self) -> Vec<X86PhysReg> {
match self {
Self::SystemV64 => vec![
X86PhysReg::RBX,
X86PhysReg::RBP,
X86PhysReg::R12,
X86PhysReg::R13,
X86PhysReg::R14,
X86PhysReg::R15,
],
Self::Win64 => vec![
X86PhysReg::RBX,
X86PhysReg::RBP,
X86PhysReg::RDI,
X86PhysReg::RSI,
X86PhysReg::R12,
X86PhysReg::R13,
X86PhysReg::R14,
X86PhysReg::R15,
],
Self::CDecl32 | Self::StdCall32 => vec![
X86PhysReg::RBX,
X86PhysReg::RBP,
X86PhysReg::RSI,
X86PhysReg::RDI,
],
Self::FastCall32 => vec![
X86PhysReg::RBX,
X86PhysReg::RBP,
X86PhysReg::RSI,
X86PhysReg::RDI,
],
}
}
pub fn callee_saved_xmms(&self) -> Vec<X86PhysReg> {
match self {
Self::SystemV64 => vec![],
Self::Win64 => vec![
X86PhysReg::XMM6,
X86PhysReg::XMM7,
X86PhysReg::XMM8,
X86PhysReg::XMM9,
X86PhysReg::XMM10,
X86PhysReg::XMM11,
X86PhysReg::XMM12,
X86PhysReg::XMM13,
X86PhysReg::XMM14,
X86PhysReg::XMM15,
],
_ => vec![],
}
}
pub fn int_arg_regs(&self) -> Vec<X86PhysReg> {
match self {
Self::SystemV64 => vec![
X86PhysReg::RDI,
X86PhysReg::RSI,
X86PhysReg::RDX,
X86PhysReg::RCX,
X86PhysReg::R8,
X86PhysReg::R9,
],
Self::Win64 => vec![
X86PhysReg::RCX,
X86PhysReg::RDX,
X86PhysReg::R8,
X86PhysReg::R9,
],
_ => vec![],
}
}
pub fn sse_arg_regs(&self) -> Vec<X86PhysReg> {
match self {
Self::SystemV64 => vec![
X86PhysReg::XMM0,
X86PhysReg::XMM1,
X86PhysReg::XMM2,
X86PhysReg::XMM3,
X86PhysReg::XMM4,
X86PhysReg::XMM5,
X86PhysReg::XMM6,
X86PhysReg::XMM7,
],
Self::Win64 => vec![
X86PhysReg::XMM0,
X86PhysReg::XMM1,
X86PhysReg::XMM2,
X86PhysReg::XMM3,
],
_ => vec![],
}
}
pub fn return_gpr(&self) -> X86PhysReg {
X86PhysReg::RAX
}
pub fn return_xmm(&self) -> X86PhysReg {
X86PhysReg::XMM0
}
pub fn stack_alignment(&self) -> u32 {
if self.is_64bit() {
16
} else {
4
}
}
}
impl X86MF {
pub fn new(name: &str, is_64bit: bool, call_conv: X86Conv) -> Self {
Self {
name: name.to_string(),
blocks: Vec::new(),
vreg_counter: 1,
frame: X86Frame::default(),
is_64bit,
call_conv,
reg_alloc_assignments: HashMap::new(),
spill_slots: HashMap::new(),
encoded_bytes: Vec::new(),
symbols: Vec::new(),
relocations: Vec::new(),
constant_pool: Vec::new(),
jump_tables: Vec::new(),
eh_info: None,
debug_info: None,
patchpoints: Vec::new(),
stackmaps: Vec::new(),
frame_indices: HashMap::new(),
}
}
pub fn new_vreg(&mut self) -> u32 {
let v = self.vreg_counter;
self.vreg_counter += 1;
v
}
pub fn new_block(&mut self) -> usize {
let label = self.blocks.len();
self.blocks.push(X86MBB::new(label));
label
}
pub fn push_block(&mut self, block: X86MBB) -> usize {
let label = block.label;
self.blocks.push(block);
label
}
pub fn entry_block(&self) -> Option<&X86MBB> {
self.blocks.first()
}
pub fn entry_block_mut(&mut self) -> Option<&mut X86MBB> {
self.blocks.first_mut()
}
pub fn get_block(&self, label: usize) -> Option<&X86MBB> {
self.blocks.get(label)
}
pub fn get_block_mut(&mut self, label: usize) -> Option<&mut X86MBB> {
self.blocks.get_mut(label)
}
pub fn total_instructions(&self) -> usize {
self.blocks.iter().map(|b| b.instructions.len()).sum()
}
pub fn assign_instr_ids(&mut self) {
let mut next_id = 0u32;
for block in &mut self.blocks {
for inst in &mut block.instructions {
inst.id = next_id;
next_id += 1;
}
}
}
pub fn compute_predecessors(&mut self) {
for block in &mut self.blocks {
block.predecessors.clear();
}
let labels: Vec<usize> = self.blocks.iter().map(|b| b.label).collect();
for i in 0..self.blocks.len() {
let succs: Vec<usize> = self.blocks[i].successors.clone();
for s in succs {
if let Some(b) = self.blocks.get_mut(s) {
b.add_predecessor(labels[i]);
}
}
}
}
pub fn estimate_code_size(&self) -> usize {
self.blocks.iter().map(|b| b.estimated_size()).sum()
}
pub fn get_vreg_assignment(&self, vreg: u32) -> Option<X86PhysReg> {
self.reg_alloc_assignments.get(&vreg).copied()
}
pub fn set_vreg_assignment(&mut self, vreg: u32, preg: X86PhysReg) {
self.reg_alloc_assignments.insert(vreg, preg);
}
pub fn get_spill_slot(&self, vreg: u32) -> Option<u32> {
self.spill_slots.get(&vreg).copied()
}
pub fn create_spill_slot(&mut self, vreg: u32, size: u32) -> u32 {
let offset = self.frame.spill_slot_size;
self.frame.spill_slot_size += size;
self.spill_slots.insert(vreg, offset);
offset
}
}
impl fmt::Display for X86MF {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "machine_function {} {{", self.name)?;
for block in &self.blocks {
write!(f, "{}", block)?;
}
writeln!(f, "}}")
}
}
#[derive(Debug, Clone)]
pub struct SDNode {
pub opcode: String,
pub value_type: String,
pub node_id: u32,
pub operands: Vec<usize>,
pub users: Vec<usize>,
pub constant_value: Option<i64>,
pub is_target: bool,
pub flags: SDNodeFlags,
}
#[derive(Debug, Clone, Default)]
pub struct SDNodeFlags {
pub has_no_unsigned_wrap: bool,
pub has_no_signed_wrap: bool,
pub has_exact: bool,
pub has_nsw: bool,
pub has_nuw: bool,
pub is_divergent: bool,
pub is_convergent: bool,
}
impl SDNode {
pub fn new(opcode: &str, value_type: &str, node_id: u32) -> Self {
Self {
opcode: opcode.to_string(),
value_type: value_type.to_string(),
node_id,
operands: Vec::new(),
users: Vec::new(),
constant_value: None,
is_target: false,
flags: SDNodeFlags::default(),
}
}
pub fn constant(value: i64, value_type: &str, node_id: u32) -> Self {
SDNode {
opcode: "Constant".to_string(),
value_type: value_type.to_string(),
node_id,
operands: Vec::new(),
users: Vec::new(),
constant_value: Some(value),
is_target: false,
flags: SDNodeFlags::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct SelectionDAG {
pub nodes: Vec<SDNode>,
pub root: Option<usize>,
pub entry_token: Option<usize>,
pub root_chain: Option<usize>,
pub constants: HashMap<i64, usize>,
pub registers: HashMap<String, usize>,
}
impl SelectionDAG {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
root: None,
entry_token: None,
root_chain: None,
constants: HashMap::new(),
registers: HashMap::new(),
}
}
pub fn add_node(&mut self, opcode: &str, value_type: &str) -> usize {
let id = self.nodes.len();
self.nodes.push(SDNode::new(opcode, value_type, id as u32));
id
}
pub fn add_constant(&mut self, value: i64, value_type: &str) -> usize {
if let Some(&id) = self.constants.get(&value) {
return id;
}
let id = self.nodes.len();
self.nodes
.push(SDNode::constant(value, value_type, id as u32));
self.constants.insert(value, id);
id
}
pub fn set_operand(&mut self, node: usize, op: usize) {
if let Some(n) = self.nodes.get_mut(node) {
n.operands.push(op);
}
if let Some(n) = self.nodes.get_mut(op) {
n.users.push(node);
}
}
pub fn set_root(&mut self, node: usize) {
self.root = Some(node);
}
pub fn get_node(&self, idx: usize) -> Option<&SDNode> {
self.nodes.get(idx)
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
}
#[derive(Debug, Clone)]
pub struct DAGPattern {
pub name: String,
pub match_opcode: String,
pub result_opcode: DeepX86Opcode,
pub operand_mapping: Vec<u32>,
pub cost: u32,
pub complexity: u32,
pub is_chain: bool,
pub is_glue: bool,
}
impl DAGPattern {
pub fn new(name: &str, match_op: &str, result_op: DeepX86Opcode) -> Self {
Self {
name: name.to_string(),
match_opcode: match_op.to_string(),
result_opcode: result_op,
operand_mapping: Vec::new(),
cost: 1,
complexity: 1,
is_chain: false,
is_glue: false,
}
}
pub fn with_cost(mut self, cost: u32) -> Self {
self.cost = cost;
self
}
pub fn with_complexity(mut self, c: u32) -> Self {
self.complexity = c;
self
}
pub fn with_chain(mut self, chain: bool) -> Self {
self.is_chain = chain;
self
}
}
pub struct X86DAGToDAG {
pub is_64bit: bool,
pub subtarget_features: HashSet<String>,
patterns: Vec<DAGPattern>,
pub stats: DAGToDAGStats,
}
#[derive(Debug, Default)]
pub struct DAGToDAGStats {
pub nodes_matched: usize,
pub nodes_total: usize,
pub patterns_tried: usize,
pub instructions_emitted: usize,
pub chains_handled: usize,
}
impl X86DAGToDAG {
pub fn new(is_64bit: bool, features: HashSet<String>) -> Self {
let mut sel = Self {
is_64bit,
subtarget_features: features,
patterns: Vec::new(),
stats: DAGToDAGStats::default(),
};
sel.build_patterns();
sel
}
fn build_patterns(&mut self) {
self.patterns
.push(DAGPattern::new("add_rr", "add", DeepX86Opcode::ADDrr).with_cost(1));
self.patterns
.push(DAGPattern::new("add_ri", "add", DeepX86Opcode::ADDri8).with_cost(1));
self.patterns
.push(DAGPattern::new("sub_rr", "sub", DeepX86Opcode::SUBrr).with_cost(1));
self.patterns
.push(DAGPattern::new("sub_ri", "sub", DeepX86Opcode::SUBri8).with_cost(1));
self.patterns
.push(DAGPattern::new("mul_rr", "mul", DeepX86Opcode::IMULrr).with_cost(3));
self.patterns
.push(DAGPattern::new("mul_ri", "mul", DeepX86Opcode::IMULrri8).with_cost(3));
self.patterns
.push(DAGPattern::new("and_rr", "and", DeepX86Opcode::ANDrr).with_cost(1));
self.patterns
.push(DAGPattern::new("or_rr", "or", DeepX86Opcode::ORrr).with_cost(1));
self.patterns
.push(DAGPattern::new("xor_rr", "xor", DeepX86Opcode::XORrr).with_cost(1));
self.patterns
.push(DAGPattern::new("shl_rr", "shl", DeepX86Opcode::SHLrCL).with_cost(1));
self.patterns
.push(DAGPattern::new("shr_rr", "lshr", DeepX86Opcode::SHRrCL).with_cost(1));
self.patterns
.push(DAGPattern::new("sar_rr", "ashr", DeepX86Opcode::SARrCL).with_cost(1));
self.patterns
.push(DAGPattern::new("add_ri32", "add", DeepX86Opcode::ADDri).with_cost(1));
self.patterns
.push(DAGPattern::new("sub_ri32", "sub", DeepX86Opcode::SUBri).with_cost(1));
self.patterns
.push(DAGPattern::new("fadd_ss", "fadd", DeepX86Opcode::ADDSSrr).with_cost(1));
self.patterns
.push(DAGPattern::new("fadd_sd", "fadd", DeepX86Opcode::ADDSDrr).with_cost(1));
self.patterns
.push(DAGPattern::new("fmul_ss", "fmul", DeepX86Opcode::MULSSrr).with_cost(1));
self.patterns
.push(DAGPattern::new("fdiv_sd", "fdiv", DeepX86Opcode::DIVSDrr).with_cost(14));
if self.subtarget_features.contains("avx") {
self.patterns
.push(DAGPattern::new("vadd_ss", "fadd", DeepX86Opcode::VADDSSrr).with_cost(1));
self.patterns
.push(DAGPattern::new("vadd_sd", "fadd", DeepX86Opcode::VADDSDrr).with_cost(1));
self.patterns
.push(DAGPattern::new("vmul_ps", "fmul", DeepX86Opcode::VMULPSrr).with_cost(1));
self.patterns
.push(DAGPattern::new("vadd_ps", "fadd", DeepX86Opcode::VADDPSrr).with_cost(1));
}
if self.subtarget_features.contains("fma") {
self.patterns
.push(DAGPattern::new("fma_ss", "fma", DeepX86Opcode::VFMADD132SSr).with_cost(1));
self.patterns
.push(DAGPattern::new("fma_sd", "fma", DeepX86Opcode::VFMADD132SDr).with_cost(1));
}
self.patterns
.push(DAGPattern::new("icmp_eq", "icmp", DeepX86Opcode::CMPrr).with_cost(1));
self.patterns
.push(DAGPattern::new("fcmp", "fcmp", DeepX86Opcode::UCOMISSrr).with_cost(1));
self.patterns
.push(DAGPattern::new("load", "load", DeepX86Opcode::MOVrm).with_cost(1));
self.patterns
.push(DAGPattern::new("store", "store", DeepX86Opcode::MOVmr).with_cost(1));
self.patterns
.push(DAGPattern::new("sext", "sext", DeepX86Opcode::MOVSXrr).with_cost(1));
self.patterns
.push(DAGPattern::new("zext", "zext", DeepX86Opcode::MOVZXrr).with_cost(1));
self.patterns
.push(DAGPattern::new("sitofp", "sitofp", DeepX86Opcode::CVTSI2SSrr).with_cost(1));
self.patterns
.push(DAGPattern::new("fptosi", "fptosi", DeepX86Opcode::CVTSS2SIrr).with_cost(1));
if self.subtarget_features.contains("bmi1") {
self.patterns
.push(DAGPattern::new("andn", "and", DeepX86Opcode::ANDNrr).with_cost(1));
}
if self.subtarget_features.contains("bmi2") {
self.patterns
.push(DAGPattern::new("mulx", "mul", DeepX86Opcode::MULXrr).with_cost(2));
}
if self.subtarget_features.contains("popcnt") {
self.patterns
.push(DAGPattern::new("popcnt", "ctpop", DeepX86Opcode::POPCNTrr).with_cost(1));
}
self.patterns
.push(DAGPattern::new("neg", "sub", DeepX86Opcode::NEGr).with_cost(1));
self.patterns
.push(DAGPattern::new("not", "xor", DeepX86Opcode::NOTr).with_cost(1));
self.patterns
.push(DAGPattern::new("bswap", "bswap", DeepX86Opcode::BSWAPr).with_cost(1));
}
pub fn run(&mut self, dag: &SelectionDAG) -> Vec<X86MI> {
let mut instructions = Vec::new();
self.stats = DAGToDAGStats::default();
self.stats.nodes_total = dag.node_count();
for node_idx in 0..dag.nodes.len() {
if let Some(node) = dag.get_node(node_idx) {
if node.opcode == "Constant" || node.opcode == "Register" {
continue;
}
self.stats.nodes_matched += 1;
self.stats.patterns_tried += 1;
let best_pattern = self.patterns.iter().find(|p| p.match_opcode == node.opcode);
if let Some(pattern) = best_pattern {
let mut inst = X86MI::new(pattern.result_opcode);
for &op_idx in &node.operands {
if let Some(op_node) = dag.get_node(op_idx) {
if let Some(val) = op_node.constant_value {
if val >= -128 && val <= 127 {
inst.operands.push(X86MO::Imm8(val as u8));
} else {
inst.operands.push(X86MO::Imm(val));
}
} else {
inst.operands.push(X86MO::VReg(op_node.node_id + 1));
}
}
}
inst.id = self.stats.instructions_emitted as u32;
instructions.push(inst);
self.stats.instructions_emitted += 1;
} else {
let inst = X86MI::new(DeepX86Opcode::NOP);
instructions.push(inst);
self.stats.instructions_emitted += 1;
}
}
}
instructions
}
pub fn select_pattern(&self, ir_opcode: &str) -> Option<&DAGPattern> {
self.patterns
.iter()
.filter(|p| p.match_opcode == ir_opcode)
.min_by_key(|p| p.cost)
}
pub fn patterns(&self) -> &[DAGPattern] {
&self.patterns
}
pub fn pattern_count(&self) -> usize {
self.patterns.len()
}
}
impl Default for X86DAGToDAG {
fn default() -> Self {
let features = ["sse", "sse2"].iter().map(|s| s.to_string()).collect();
Self::new(true, features)
}
}
pub struct X86FastISel {
pub is_64bit: bool,
pub vreg_counter: u32,
pub current_block: Option<usize>,
pub instructions: Vec<X86MI>,
pub block_start_indices: HashMap<usize, usize>,
pub mf: Option<X86MF>,
pub stats: FastISelStats,
}
#[derive(Debug, Default)]
pub struct FastISelStats {
pub instructions_emitted: usize,
pub basic_blocks_processed: usize,
pub fast_selections: usize,
pub fallback_selections: usize,
pub constant_folded: usize,
pub loads_selected: usize,
pub stores_selected: usize,
}
impl X86FastISel {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
vreg_counter: 1,
current_block: None,
instructions: Vec::new(),
block_start_indices: HashMap::new(),
mf: None,
stats: FastISelStats::default(),
}
}
pub fn new_vreg(&mut self) -> u32 {
let v = self.vreg_counter;
self.vreg_counter += 1;
v
}
pub fn begin_block(&mut self, label: usize) {
self.current_block = Some(label);
self.block_start_indices
.insert(label, self.instructions.len());
}
pub fn emit_instr(&mut self, inst: X86MI) {
self.instructions.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn select_add(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm && imm_val >= -128 && imm_val <= 127 {
let inst = X86MI::new(DeepX86Opcode::ADDri8)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm8(imm_val as u8));
self.emit_instr(inst);
} else if is_imm {
let inst = X86MI::new(DeepX86Opcode::ADDri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm(imm_val));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::ADDrr)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::VReg(rhs));
self.emit_instr(inst);
}
dest
}
pub fn select_sub(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm && imm_val >= -128 && imm_val <= 127 {
let inst = X86MI::new(DeepX86Opcode::SUBri8)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm8(imm_val as u8));
self.emit_instr(inst);
} else if is_imm {
let inst = X86MI::new(DeepX86Opcode::SUBri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm(imm_val));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::SUBrr)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::VReg(rhs));
self.emit_instr(inst);
}
dest
}
pub fn select_and(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm && imm_val >= -128 && imm_val <= 127 {
let inst = X86MI::new(DeepX86Opcode::ANDri8)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm8(imm_val as u8));
self.emit_instr(inst);
} else if is_imm {
let inst = X86MI::new(DeepX86Opcode::ANDri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm(imm_val));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::ANDrr)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::VReg(rhs));
self.emit_instr(inst);
}
dest
}
pub fn select_or(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm && imm_val >= -128 && imm_val <= 127 {
let inst = X86MI::new(DeepX86Opcode::ORri8)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm8(imm_val as u8));
self.emit_instr(inst);
} else if is_imm {
let inst = X86MI::new(DeepX86Opcode::ORri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm(imm_val));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::ORrr)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::VReg(rhs));
self.emit_instr(inst);
}
dest
}
pub fn select_xor(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm && imm_val >= -128 && imm_val <= 127 {
let inst = X86MI::new(DeepX86Opcode::XORri8)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm8(imm_val as u8));
self.emit_instr(inst);
} else if is_imm {
let inst = X86MI::new(DeepX86Opcode::XORri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm(imm_val));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::XORrr)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::VReg(rhs));
self.emit_instr(inst);
}
dest
}
pub fn select_mul(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm {
let inst = X86MI::new(DeepX86Opcode::IMULrri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm(imm_val));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::IMULrr)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::VReg(rhs));
self.emit_instr(inst);
}
dest
}
pub fn select_shl(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm {
let inst = X86MI::new(DeepX86Opcode::SHLri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm8(imm_val as u8));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::SHLrCL)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(lhs));
self.emit_instr(inst);
}
dest
}
pub fn select_load(&mut self, addr_vreg: u32, size: u8) -> u32 {
self.stats.loads_selected += 1;
let dest = self.new_vreg();
let inst = X86MI::new(DeepX86Opcode::MOVrm)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::mem_base_disp(addr_vreg, 0, size));
self.emit_instr(inst);
dest
}
pub fn select_store(&mut self, value_vreg: u32, addr_vreg: u32, size: u8) {
self.stats.stores_selected += 1;
let inst = X86MI::new(DeepX86Opcode::MOVmr)
.push_op(X86MO::mem_base_disp(addr_vreg, 0, size))
.push_op(X86MO::VReg(value_vreg));
self.emit_instr(inst);
}
pub fn select_br(&mut self, target: usize) {
let inst = X86MI::new(DeepX86Opcode::JMP1)
.push_op(X86MO::BlockLabel(target))
.set_terminator();
self.emit_instr(inst);
}
pub fn select_brcond(
&mut self,
cond_vreg: u32,
cc: X86CC,
true_target: usize,
false_target: usize,
) {
let test_inst = X86MI::new(DeepX86Opcode::TESTrr)
.push_op(X86MO::VReg(cond_vreg))
.push_op(X86MO::VReg(cond_vreg));
self.emit_instr(test_inst);
let jcc = cc.jcc_opcode_short();
let jmp_inst = X86MI::new(jcc)
.push_op(X86MO::BlockLabel(true_target))
.set_terminator();
self.emit_instr(jmp_inst);
let fall_inst = X86MI::new(DeepX86Opcode::JMP1)
.push_op(X86MO::BlockLabel(false_target))
.set_terminator();
self.emit_instr(fall_inst);
}
pub fn select_ret(&mut self, ret_val: Option<u32>) {
if let Some(val) = ret_val {
let mov_inst = X86MI::new(DeepX86Opcode::MOVrr)
.push_op(X86MO::PReg(X86PhysReg::RAX))
.push_op(X86MO::VReg(val));
self.emit_instr(mov_inst);
}
let ret_inst = X86MI::new(DeepX86Opcode::RET).set_terminator();
self.emit_instr(ret_inst);
}
pub fn select_icmp(&mut self, lhs: u32, rhs: u32, is_imm: bool, imm_val: i64) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
if is_imm {
let inst = X86MI::new(DeepX86Opcode::CMPri)
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::Imm(imm_val));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::CMPrr)
.push_op(X86MO::VReg(lhs))
.push_op(X86MO::VReg(rhs));
self.emit_instr(inst);
}
let setcc_inst = X86MI::new(X86CC::E.setcc_opcode()).push_op(X86MO::VReg(dest));
self.emit_instr(setcc_inst);
dest
}
pub fn select_mov_imm(&mut self, val: i64) -> u32 {
let dest = self.new_vreg();
if val >= -128 && val <= 127 {
let inst = X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::Imm8(val as u8));
self.emit_instr(inst);
} else {
let inst = X86MI::new(DeepX86Opcode::MOVri64)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::Imm(val));
self.emit_instr(inst);
}
self.stats.constant_folded += 1;
dest
}
pub fn select_phi(&mut self, incoming: &[(u32, usize)]) -> u32 {
self.stats.fast_selections += 1;
let dest = self.new_vreg();
for &(value, _block) in incoming {
let copy = X86MI::new(DeepX86Opcode::COPY)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(value));
self.emit_instr(copy);
}
dest
}
pub fn get_instructions(&self) -> &[X86MI] {
&self.instructions
}
pub fn reset(&mut self) {
self.vreg_counter = 1;
self.current_block = None;
self.instructions.clear();
self.block_start_indices.clear();
self.stats = FastISelStats::default();
}
}
impl Default for X86FastISel {
fn default() -> Self {
Self::new(true)
}
}
pub struct X86GlobalISel {
pub is_64bit: bool,
pub legalizer: X86Legalizer,
pub regbank_select: X86RegBankSelect,
pub call_lowering: X86CallLowering,
pub instruction_selector: X86GlobalISelSelector,
pub stats: GlobalISelStats,
}
#[derive(Debug, Default)]
pub struct GlobalISelStats {
pub legalized: usize,
pub selected: usize,
pub failed: usize,
pub regbank_assigned: usize,
pub calls_lowered: usize,
}
impl X86GlobalISel {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
legalizer: X86Legalizer::new(is_64bit),
regbank_select: X86RegBankSelect::new(is_64bit),
call_lowering: X86CallLowering::new(is_64bit),
instruction_selector: X86GlobalISelSelector::new(is_64bit),
stats: GlobalISelStats::default(),
}
}
pub fn run(&mut self, generic_mir: &[GMIrInst]) -> Result<Vec<X86MI>, String> {
let legalized = self.legalizer.legalize(generic_mir)?;
self.stats.legalized = legalized.len();
let regbanked = self.regbank_select.assign_reg_banks(&legalized);
self.stats.regbank_assigned = regbanked.len();
let lowered = self.call_lowering.lower_calls(®banked)?;
self.stats.calls_lowered = lowered
.iter()
.filter(|i| matches!(i.opcode, GMIrOpcode::G_CALL))
.count();
let selected = self.instruction_selector.select(&lowered);
self.stats.selected = selected.len();
Ok(selected)
}
pub fn target_instruction_count(&self) -> usize {
self.stats.selected
}
}
#[derive(Debug, Clone)]
pub enum GMIrOpcode {
G_ADD,
G_SUB,
G_MUL,
G_SDIV,
G_UDIV,
G_AND,
G_OR,
G_XOR,
G_SHL,
G_LSHR,
G_ASHR,
G_FADD,
G_FSUB,
G_FMUL,
G_FDIV,
G_ICMP,
G_FCMP,
G_TRUNC,
G_ZEXT,
G_SEXT,
G_FPTRUNC,
G_FPEXT,
G_FPTOUI,
G_FPTOSI,
G_UITOFP,
G_SITOFP,
G_BITCAST,
G_PTRTOINT,
G_INTTOPTR,
G_LOAD,
G_STORE,
G_GEP,
G_BR,
G_BRCOND,
G_BRINDIRECT,
G_RETURN,
G_CALL,
G_TAILCALL,
G_CONSTANT,
G_FCONSTANT,
G_FRAME_INDEX,
G_GLOBAL_VALUE,
G_PHI,
G_SELECT,
G_EXTRACT_VEC_ELT,
G_INSERT_VEC_ELT,
G_SHUFFLE_VECTOR,
G_BUILD_VECTOR,
G_CONCAT_VECTORS,
G_INTRINSIC,
G_INTRINSIC_W_SIDE_EFFECTS,
G_UNREACHABLE,
G_IMPLICIT_DEF,
G_MERGE_VALUES,
G_UNMERGE_VALUES,
G_ANYEXT,
G_VASTART,
G_VAARG,
}
#[derive(Debug, Clone)]
pub enum GMOperand {
VReg(u32),
Imm(i64),
FImm(f64),
Block(usize),
Global(String),
FrameIndex(u32),
CImm(u64),
}
#[derive(Debug, Clone)]
pub struct GMIrInst {
pub opcode: GMIrOpcode,
pub defs: Vec<GMOperand>,
pub uses: Vec<GMOperand>,
pub flags: GMFlags,
}
#[derive(Debug, Clone, Default)]
pub struct GMFlags {
pub is_terminator: bool,
pub may_load: bool,
pub may_store: bool,
pub has_side_effects: bool,
}
impl GMIrInst {
pub fn new(opcode: GMIrOpcode) -> Self {
let flags = GMFlags {
is_terminator: matches!(
opcode,
GMIrOpcode::G_BR
| GMIrOpcode::G_BRCOND
| GMIrOpcode::G_BRINDIRECT
| GMIrOpcode::G_RETURN
| GMIrOpcode::G_UNREACHABLE
),
may_load: matches!(opcode, GMIrOpcode::G_LOAD),
may_store: matches!(opcode, GMIrOpcode::G_STORE),
has_side_effects: matches!(
opcode,
GMIrOpcode::G_CALL | GMIrOpcode::G_INTRINSIC_W_SIDE_EFFECTS
),
};
Self {
opcode,
defs: Vec::new(),
uses: Vec::new(),
flags,
}
}
pub fn with_def(mut self, d: GMOperand) -> Self {
self.defs.push(d);
self
}
pub fn with_use(mut self, u: GMOperand) -> Self {
self.uses.push(u);
self
}
}
pub struct X86CallLowering {
pub is_64bit: bool,
pub call_conv: X86Conv,
pub call_frame_size: u32,
pub has_calls: bool,
}
impl X86CallLowering {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
call_conv: if is_64bit {
X86Conv::SystemV64
} else {
X86Conv::CDecl32
},
call_frame_size: 0,
has_calls: false,
}
}
pub fn lower_calls(&mut self, insts: &[GMIrInst]) -> Result<Vec<GMIrInst>, String> {
let mut result = Vec::new();
for inst in insts {
match inst.opcode {
GMIrOpcode::G_CALL => {
self.has_calls = true;
let lowered = self.lower_call(inst)?;
result.extend(lowered);
}
GMIrOpcode::G_RETURN => {
let lowered = self.lower_return(inst)?;
result.extend(lowered);
}
GMIrOpcode::G_TAILCALL => {
let lowered = self.lower_tail_call(inst)?;
result.extend(lowered);
}
_ => result.push(inst.clone()),
}
}
Ok(result)
}
fn lower_call(&self, inst: &GMIrInst) -> Result<Vec<GMIrInst>, String> {
let mut seq = Vec::new();
let int_regs = self.call_conv.int_arg_regs();
let sse_regs = self.call_conv.sse_arg_regs();
let mut int_idx = 0;
let mut sse_idx = 0;
for use_op in &inst.uses {
if int_idx == 0 && sse_idx == 0 && matches!(use_op, GMOperand::Global(_)) {
continue;
}
if int_idx < int_regs.len() {
let copy = GMIrInst::new(GMIrOpcode::G_BITCAST)
.with_def(GMOperand::VReg(1000 + int_idx as u32))
.with_use(use_op.clone());
seq.push(copy);
int_idx += 1;
} else if sse_idx < sse_regs.len() {
let copy = GMIrInst::new(GMIrOpcode::G_BITCAST)
.with_def(GMOperand::VReg(2000 + sse_idx as u32))
.with_use(use_op.clone());
seq.push(copy);
sse_idx += 1;
} else {
let store_off = 8 * (int_idx + sse_idx - int_regs.len() - sse_regs.len());
let fi = GMIrInst::new(GMIrOpcode::G_STORE)
.with_use(GMOperand::FrameIndex(store_off as u32))
.with_use(use_op.clone());
seq.push(fi);
int_idx += 1;
}
}
seq.push(inst.clone());
if !inst.defs.is_empty() {
let copy_ret = GMIrInst::new(GMIrOpcode::G_BITCAST)
.with_def(inst.defs[0].clone())
.with_use(GMOperand::VReg(0)); seq.push(copy_ret);
}
Ok(seq)
}
fn lower_return(&self, inst: &GMIrInst) -> Result<Vec<GMIrInst>, String> {
let mut seq = Vec::new();
if let Some(ret_val) = inst.uses.first() {
let mov = GMIrInst::new(GMIrOpcode::G_BITCAST)
.with_def(GMOperand::VReg(0)) .with_use(ret_val.clone());
seq.push(mov);
}
seq.push(inst.clone());
Ok(seq)
}
fn lower_tail_call(&self, inst: &GMIrInst) -> Result<Vec<GMIrInst>, String> {
let mut seq = self.lower_call(inst)?;
let ret = GMIrInst::new(GMIrOpcode::G_RETURN);
seq.push(ret);
Ok(seq)
}
pub fn classify_arg(&self, size: u32, is_float: bool) -> ArgAssignment {
let int_regs = self.call_conv.int_arg_regs();
let sse_regs = self.call_conv.sse_arg_regs();
if is_float {
if sse_regs.len() < 1 {
ArgAssignment::Stack {
offset: self.call_frame_size,
}
} else {
ArgAssignment::Register { reg: 2000 } }
} else {
if int_regs.len() < 1 {
ArgAssignment::Stack {
offset: self.call_frame_size,
}
} else {
ArgAssignment::Register { reg: 1000 } }
}
}
}
#[derive(Debug, Clone)]
pub enum ArgAssignment {
Register { reg: u32 },
Stack { offset: u32 },
Split { lo_reg: u32, hi_reg: u32 },
Indirect { ptr_reg: u32 },
}
impl Default for X86CallLowering {
fn default() -> Self {
Self::new(true)
}
}
pub struct X86Legalizer {
pub is_64bit: bool,
pub has_avx: bool,
pub has_avx512: bool,
pub has_sse42: bool,
pub max_legal_scalar: u32,
pub min_legal_scalar: u32,
pub max_legal_vector: u32,
pub min_legal_vector: u32,
legal_actions: HashMap<String, LegalAction>,
scalar_actions: HashMap<u32, LegalAction>,
vector_actions: HashMap<u32, LegalAction>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LegalAction {
Legal,
WidenScalar,
NarrowScalar,
Promote,
Expand,
LibCall,
Custom,
Unsupported,
}
#[derive(Debug, Clone)]
pub struct LegalizedOp {
pub original: GMIrInst,
pub replacements: Vec<GMIrInst>,
pub action: LegalAction,
}
impl X86Legalizer {
pub fn new(is_64bit: bool) -> Self {
let max_scalar = if is_64bit { 64 } else { 32 };
Self {
is_64bit,
has_avx: false,
has_avx512: false,
has_sse42: true,
max_legal_scalar: max_scalar,
min_legal_scalar: 8,
max_legal_vector: 128,
min_legal_vector: 128,
legal_actions: HashMap::new(),
scalar_actions: HashMap::new(),
vector_actions: HashMap::new(),
}
}
pub fn with_features(mut self, features: &HashSet<String>) -> Self {
self.has_avx = features.contains("avx") || features.contains("avx2");
self.has_avx512 = features.contains("avx512f");
self.has_sse42 = features.contains("sse4.2");
if self.has_avx {
self.max_legal_vector = 256;
}
if self.has_avx512 {
self.max_legal_vector = 512;
}
self
}
pub fn legalize(&self, insts: &[GMIrInst]) -> Result<Vec<GMIrInst>, String> {
let mut result = Vec::new();
for inst in insts {
let action = self.classify(inst);
match action {
LegalAction::Legal => {
result.push(inst.clone());
}
LegalAction::WidenScalar => {
let widened = self.widen_scalar(inst);
result.extend(widened);
}
LegalAction::NarrowScalar => {
let narrowed = self.narrow_scalar(inst);
result.extend(narrowed);
}
LegalAction::Expand => {
let expanded = self.expand(inst);
result.extend(expanded);
}
LegalAction::Unsupported => {
return Err(format!("Unsupported operation: {:?}", inst.opcode));
}
_ => result.push(inst.clone()),
}
}
Ok(result)
}
pub fn classify(&self, inst: &GMIrInst) -> LegalAction {
match inst.opcode {
GMIrOpcode::G_ADD
| GMIrOpcode::G_SUB
| GMIrOpcode::G_AND
| GMIrOpcode::G_OR
| GMIrOpcode::G_XOR
| GMIrOpcode::G_SHL
| GMIrOpcode::G_LSHR
| GMIrOpcode::G_ASHR
| GMIrOpcode::G_MUL => LegalAction::Legal,
GMIrOpcode::G_SDIV | GMIrOpcode::G_UDIV => {
if self.is_64bit {
LegalAction::Legal
} else {
LegalAction::LibCall
}
}
GMIrOpcode::G_FADD | GMIrOpcode::G_FSUB | GMIrOpcode::G_FMUL | GMIrOpcode::G_FDIV => {
if self.has_sse42 {
LegalAction::Legal
} else {
LegalAction::Unsupported
}
}
GMIrOpcode::G_LOAD | GMIrOpcode::G_STORE => LegalAction::Legal,
GMIrOpcode::G_BR | GMIrOpcode::G_BRCOND | GMIrOpcode::G_RETURN | GMIrOpcode::G_CALL => {
LegalAction::Legal
}
GMIrOpcode::G_TRUNC | GMIrOpcode::G_ZEXT | GMIrOpcode::G_SEXT => LegalAction::Legal,
GMIrOpcode::G_ICMP | GMIrOpcode::G_FCMP => LegalAction::Legal,
GMIrOpcode::G_PHI | GMIrOpcode::G_SELECT => LegalAction::Legal,
GMIrOpcode::G_CONSTANT | GMIrOpcode::G_FCONSTANT => LegalAction::Legal,
GMIrOpcode::G_SHUFFLE_VECTOR => {
if self.has_avx {
LegalAction::Legal
} else {
LegalAction::Expand
}
}
GMIrOpcode::G_BITCAST | GMIrOpcode::G_PTRTOINT | GMIrOpcode::G_INTTOPTR => {
LegalAction::Legal
}
GMIrOpcode::G_GEP => LegalAction::Legal,
_ => {
LegalAction::Legal
}
}
}
fn widen_scalar(&self, inst: &GMIrInst) -> Vec<GMIrInst> {
let mut seq = Vec::new();
seq.push(inst.clone());
seq
}
fn narrow_scalar(&self, inst: &GMIrInst) -> Vec<GMIrInst> {
let mut seq = Vec::new();
seq.push(inst.clone());
seq
}
fn expand(&self, inst: &GMIrInst) -> Vec<GMIrInst> {
let mut seq = Vec::new();
match inst.opcode {
GMIrOpcode::G_SHUFFLE_VECTOR => {
let extract = GMIrInst::new(GMIrOpcode::G_EXTRACT_VEC_ELT);
seq.push(extract);
}
_ => seq.push(inst.clone()),
}
seq
}
pub fn is_type_legal(&self, bit_width: u32) -> bool {
bit_width >= self.min_legal_scalar && bit_width <= self.max_legal_scalar
}
pub fn is_vector_type_legal(&self, bit_width: u32) -> bool {
bit_width >= self.min_legal_vector && bit_width <= self.max_legal_vector
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RegBank {
GPR,
XMM,
YMM,
ZMM,
Mask,
}
impl RegBank {
pub fn name(&self) -> &'static str {
match self {
Self::GPR => "GPR",
Self::XMM => "XMM",
Self::YMM => "YMM",
Self::ZMM => "ZMM",
Self::Mask => "MASK",
}
}
pub fn size_bytes(&self) -> u32 {
match self {
Self::GPR => 8,
Self::XMM => 16,
Self::YMM => 32,
Self::ZMM => 64,
Self::Mask => 8,
}
}
}
pub struct X86RegBankSelect {
pub is_64bit: bool,
bank_map: HashMap<u32, RegBank>, default_bank: RegBank,
}
impl X86RegBankSelect {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
bank_map: HashMap::new(),
default_bank: RegBank::GPR,
}
}
pub fn assign_reg_banks(&mut self, insts: &[GMIrInst]) -> Vec<GMIrInst> {
self.bank_map.clear();
for inst in insts {
if let Some(def) = inst.defs.first() {
if let GMOperand::VReg(vreg) = def {
let bank = self.infer_bank_from_opcode(&inst.opcode);
self.bank_map.insert(*vreg, bank);
}
}
for use_op in &inst.uses {
if let GMOperand::VReg(vreg) = use_op {
if !self.bank_map.contains_key(vreg) {
self.bank_map.insert(*vreg, self.default_bank);
}
}
}
}
insts.to_vec()
}
fn infer_bank_from_opcode(&self, opcode: &GMIrOpcode) -> RegBank {
match opcode {
GMIrOpcode::G_FADD
| GMIrOpcode::G_FSUB
| GMIrOpcode::G_FMUL
| GMIrOpcode::G_FDIV
| GMIrOpcode::G_FCMP
| GMIrOpcode::G_FPTRUNC
| GMIrOpcode::G_FPEXT
| GMIrOpcode::G_FPTOUI
| GMIrOpcode::G_FPTOSI
| GMIrOpcode::G_UITOFP
| GMIrOpcode::G_SITOFP => RegBank::XMM,
GMIrOpcode::G_SHUFFLE_VECTOR
| GMIrOpcode::G_BUILD_VECTOR
| GMIrOpcode::G_CONCAT_VECTORS => {
if self.is_64bit {
RegBank::XMM
} else {
RegBank::XMM
}
}
_ => RegBank::GPR,
}
}
pub fn get_bank(&self, vreg: u32) -> RegBank {
self.bank_map
.get(&vreg)
.copied()
.unwrap_or(self.default_bank)
}
pub fn set_bank(&mut self, vreg: u32, bank: RegBank) {
self.bank_map.insert(vreg, bank);
}
pub fn needs_cross_bank_copy(&self, src: u32, dst: u32) -> bool {
let src_bank = self.get_bank(src);
let dst_bank = self.get_bank(dst);
src_bank != dst_bank
}
}
pub struct X86InstrEmitter {
pub is_64bit: bool,
pub mf: Option<X86MF>,
emitted: Vec<X86MI>,
next_id: u32,
pub stats: EmitterStats,
}
#[derive(Debug, Default)]
pub struct EmitterStats {
pub instructions_emitted: usize,
pub immediates_folded: usize,
pub addressing_modes_used: usize,
}
impl X86InstrEmitter {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
mf: None,
emitted: Vec::new(),
next_id: 0,
stats: EmitterStats::default(),
}
}
pub fn with_mf(mut self, mf: X86MF) -> Self {
self.mf = Some(mf);
self
}
fn next_instr_id(&mut self) -> u32 {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn emit_binary_op(&mut self, opcode: DeepX86Opcode, dest: u32, src1: u32, src2: u32) {
let inst = X86MI::new(opcode)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(src1))
.push_op(X86MO::VReg(src2))
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_binary_op_imm(&mut self, opcode: DeepX86Opcode, dest: u32, src: u32, imm: i64) {
let inst = X86MI::new(opcode)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(src))
.push_op(X86MO::Imm(imm))
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
self.stats.immediates_folded += 1;
}
pub fn emit_mov(&mut self, dest: u32, src: u32) {
let inst = X86MI::new(DeepX86Opcode::MOVrr)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::VReg(src))
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_mov_imm(&mut self, dest: u32, imm: i64) {
let inst = X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::Imm(imm))
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_load(&mut self, dest: u32, base: u32, offset: i32, size: u8) {
let inst = X86MI::new(DeepX86Opcode::MOVrm)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::mem_base_disp(base, offset, size))
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
self.stats.addressing_modes_used += 1;
}
pub fn emit_store(&mut self, value: u32, base: u32, offset: i32, size: u8) {
let inst = X86MI::new(DeepX86Opcode::MOVmr)
.push_op(X86MO::mem_base_disp(base, offset, size))
.push_op(X86MO::VReg(value))
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
self.stats.addressing_modes_used += 1;
}
pub fn emit_conditional_branch(&mut self, cc: X86CC, target: usize) {
let inst = X86MI::new(cc.jcc_opcode_short())
.push_op(X86MO::BlockLabel(target))
.set_terminator()
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_jump(&mut self, target: usize) {
let inst = X86MI::new(DeepX86Opcode::JMP1)
.push_op(X86MO::BlockLabel(target))
.set_terminator()
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_return(&mut self, value: Option<u32>) {
if let Some(v) = value {
self.emit_mov(0, v); }
let inst = X86MI::new(DeepX86Opcode::RET)
.set_terminator()
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_call(&mut self, target: &str) {
let inst = X86MI::new(DeepX86Opcode::CALLpcrel32)
.push_op(X86MO::ExternalSymbol(target.to_string()))
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_nop(&mut self) {
let inst = X86MI::new(DeepX86Opcode::NOP).with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn emit_lea(&mut self, dest: u32, base: u32, index: Option<u32>, scale: u8, disp: i32) {
let mem_op = X86MO::Mem {
base: Some(base),
index,
scale,
displacement: disp,
segment: None,
size: 8,
};
let inst = X86MI::new(DeepX86Opcode::LEA64r)
.push_op(X86MO::VReg(dest))
.push_op(mem_op)
.with_id(self.next_instr_id());
self.emitted.push(inst);
self.stats.instructions_emitted += 1;
}
pub fn get_instructions(&self) -> &[X86MI] {
&self.emitted
}
pub fn into_instructions(self) -> Vec<X86MI> {
self.emitted
}
pub fn clear(&mut self) {
self.emitted.clear();
self.next_id = 0;
}
}
pub struct X86PrologEpilogInserter {
pub is_64bit: bool,
pub call_conv: X86Conv,
pub eliminate_frame_pointer: bool,
pub uses_red_zone: bool,
pub stats: PrologEpilogStats,
}
#[derive(Debug, Default)]
pub struct PrologEpilogStats {
pub functions_processed: usize,
pub prologue_size: usize,
pub epilogue_size: usize,
pub callee_saved_used: usize,
pub frame_pointers_set: usize,
}
impl X86PrologEpilogInserter {
pub fn new(is_64bit: bool, call_conv: X86Conv) -> Self {
Self {
is_64bit,
call_conv,
eliminate_frame_pointer: true, uses_red_zone: call_conv.uses_red_zone() && is_64bit,
stats: PrologEpilogStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
self.stats.functions_processed += 1;
let has_calls = self.mf_has_calls(mf);
let needs_fp = self.needs_frame_pointer(mf, has_calls);
let callee_saved = self.compute_callee_saved(mf);
let saved_reg_size = callee_saved.len() as u32 * 8;
let local_size = mf.frame.local_size;
let alignment = mf.call_conv.stack_alignment();
let total_size = Self::align_to(saved_reg_size + local_size + 8, alignment);
mf.frame.total_size = total_size;
mf.frame.saved_reg_size = saved_reg_size;
mf.frame.has_frame_pointer = needs_fp;
let prologue = self.emit_prologue(mf, &callee_saved, needs_fp, total_size);
self.stats.prologue_size = prologue.len();
if let Some(entry) = mf.entry_block_mut() {
let existing = std::mem::take(&mut entry.instructions);
let mut new_instrs = prologue;
new_instrs.extend(existing);
entry.instructions = new_instrs;
}
let epilogue = self.emit_epilogue(mf, &callee_saved, needs_fp, total_size);
self.stats.epilogue_size = epilogue.len();
for block in &mut mf.blocks {
if self.block_has_return(block) {
if let Some(pos) = block.instructions.iter().position(|i| i.flags.is_return) {
for (i, instr) in epilogue.iter().enumerate() {
block.instructions.insert(pos + i, instr.clone());
}
}
}
}
if needs_fp {
self.stats.frame_pointers_set += 1;
}
self.stats.callee_saved_used += callee_saved.len();
}
fn emit_prologue(
&self,
mf: &X86MF,
callee_saved: &[X86PhysReg],
needs_fp: bool,
total_size: u32,
) -> Vec<X86MI> {
let mut seq = Vec::new();
if needs_fp {
seq.push(X86MI::new(DeepX86Opcode::PUSH64r).push_op(X86MO::PReg(X86PhysReg::RBP)));
seq.push(
X86MI::new(DeepX86Opcode::MOVrr)
.push_op(X86MO::PReg(X86PhysReg::RBP))
.push_op(X86MO::PReg(X86PhysReg::RSP)),
);
}
for ® in callee_saved {
seq.push(X86MI::new(DeepX86Opcode::PUSH64r).push_op(X86MO::PReg(reg)));
}
if !self.uses_red_zone || total_size > 128 {
if total_size > 0
&& total_size - callee_saved.len() as u32 * 8 - (if needs_fp { 8 } else { 0 }) > 0
{
let frame_adjust =
total_size - callee_saved.len() as u32 * 8 - (if needs_fp { 8 } else { 0 });
if frame_adjust > 0 {
seq.push(
X86MI::new(DeepX86Opcode::SUBri)
.push_op(X86MO::PReg(X86PhysReg::RSP))
.push_op(X86MO::PReg(X86PhysReg::RSP))
.push_op(X86MO::Imm(frame_adjust as i64)),
);
}
}
}
seq
}
fn emit_epilogue(
&self,
mf: &X86MF,
callee_saved: &[X86PhysReg],
needs_fp: bool,
total_size: u32,
) -> Vec<X86MI> {
let mut seq = Vec::new();
if needs_fp {
seq.push(
X86MI::new(DeepX86Opcode::MOVrr)
.push_op(X86MO::PReg(X86PhysReg::RSP))
.push_op(X86MO::PReg(X86PhysReg::RBP)),
);
} else if !self.uses_red_zone || total_size > 128 {
let frame_adjust =
total_size - callee_saved.len() as u32 * 8 - (if needs_fp { 8 } else { 0 });
if frame_adjust > 0 {
seq.push(
X86MI::new(DeepX86Opcode::ADDri)
.push_op(X86MO::PReg(X86PhysReg::RSP))
.push_op(X86MO::PReg(X86PhysReg::RSP))
.push_op(X86MO::Imm(frame_adjust as i64)),
);
}
}
for ® in callee_saved.iter().rev() {
seq.push(X86MI::new(DeepX86Opcode::POP64r).push_op(X86MO::PReg(reg)));
}
if needs_fp {
seq.push(X86MI::new(DeepX86Opcode::POP64r).push_op(X86MO::PReg(X86PhysReg::RBP)));
}
seq
}
fn compute_callee_saved(&self, mf: &X86MF) -> Vec<X86PhysReg> {
let all_callee_saved = self.call_conv.callee_saved_gprs();
let mut used = Vec::new();
for block in &mf.blocks {
for inst in &block.instructions {
for op in &inst.operands {
if let X86MO::PReg(reg) = op {
if all_callee_saved.contains(reg) && !used.contains(reg) {
used.push(*reg);
}
}
}
}
}
used.sort_by_key(|r| r.0);
used
}
fn needs_frame_pointer(&self, mf: &X86MF, has_calls: bool) -> bool {
if self.eliminate_frame_pointer {
if mf.frame.has_var_sized_objects {
return true;
}
if mf.frame.needs_realignment {
return true;
}
false
} else {
true
}
}
fn mf_has_calls(&self, mf: &X86MF) -> bool {
mf.blocks
.iter()
.any(|b| b.instructions.iter().any(|i| i.flags.is_call))
}
fn block_has_return(&self, block: &X86MBB) -> bool {
block.instructions.iter().any(|i| i.flags.is_return)
}
fn align_to(value: u32, alignment: u32) -> u32 {
(value + alignment - 1) & !(alignment - 1)
}
pub fn with_no_frame_pointer(mut self) -> Self {
self.eliminate_frame_pointer = true;
self
}
pub fn with_frame_pointer(mut self) -> Self {
self.eliminate_frame_pointer = false;
self
}
}
pub struct X86BranchSelector {
pub is_64bit: bool,
pub short_branch_limit: i32,
pub near_branch_limit: i64,
pub stats: BranchSelectorStats,
}
#[derive(Debug, Default)]
pub struct BranchSelectorStats {
pub branches_relaxed: usize,
pub short_branches: usize,
pub near_branches: usize,
pub branches_analyzed: usize,
pub reverses_optimized: usize,
}
impl X86BranchSelector {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
short_branch_limit: 127, near_branch_limit: 0x7FFF_FFFF, stats: BranchSelectorStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
let offsets = self.compute_block_offsets(mf);
for block_idx in 0..mf.blocks.len() {
let block_end_offset =
offsets[block_idx] + mf.blocks[block_idx].estimated_size() as u64;
let mut instr_indices: Vec<usize> = Vec::new();
for (i, inst) in mf.blocks[block_idx].instructions.iter().enumerate() {
if inst.flags.is_branch {
instr_indices.push(i);
}
}
for idx in instr_indices {
self.stats.branches_analyzed += 1;
let inst = &mf.blocks[block_idx].instructions[idx];
let target_label = inst.operands.iter().find_map(|op| {
if let X86MO::BlockLabel(l) = op {
Some(*l)
} else {
None
}
});
if let Some(target) = target_label {
if let Some(&target_offset) = offsets.get(target) {
let distance = target_offset as i64 - block_end_offset as i64;
let relaxed =
self.relax_branch(&mf.blocks[block_idx].instructions[idx], distance);
if relaxed.opcode != inst.opcode {
mf.blocks[block_idx].instructions[idx] = relaxed;
self.stats.branches_relaxed += 1;
}
}
}
}
}
}
fn relax_branch(&mut self, inst: &X86MI, distance: i64) -> X86MI {
let is_short = distance >= -128 && distance <= 127;
let is_jmp = inst.opcode == DeepX86Opcode::JMP1 || inst.opcode == DeepX86Opcode::JMP4;
if is_jmp {
if is_short {
let mut relaxed = X86MI::new(DeepX86Opcode::JMP1);
relaxed.operands = inst.operands.clone();
relaxed.flags = inst.flags;
self.stats.short_branches += 1;
relaxed
} else {
let mut relaxed = X86MI::new(DeepX86Opcode::JMP4);
relaxed.operands = inst.operands.clone();
relaxed.flags = inst.flags;
self.stats.near_branches += 1;
relaxed
}
} else {
if is_short {
self.stats.short_branches += 1;
inst.clone()
} else {
self.stats.near_branches += 1;
inst.clone()
}
}
}
fn compute_block_offsets(&self, mf: &X86MF) -> Vec<u64> {
let mut offsets = vec![0u64; mf.blocks.len()];
let mut current = 0u64;
for (i, block) in mf.blocks.iter().enumerate() {
offsets[i] = current;
for inst in &block.instructions {
current += self.estimate_instr_size(inst);
}
current += 1; }
offsets
}
fn estimate_instr_size(&self, inst: &X86MI) -> u64 {
let base = match inst.opcode {
DeepX86Opcode::JMP1
| DeepX86Opcode::JO1
| DeepX86Opcode::JNO1
| DeepX86Opcode::JB1
| DeepX86Opcode::JAE1
| DeepX86Opcode::JE1
| DeepX86Opcode::JNE1
| DeepX86Opcode::JBE1
| DeepX86Opcode::JA1
| DeepX86Opcode::JS1
| DeepX86Opcode::JNS1
| DeepX86Opcode::JP1
| DeepX86Opcode::JNP1
| DeepX86Opcode::JL1
| DeepX86Opcode::JGE1
| DeepX86Opcode::JLE1
| DeepX86Opcode::JG1 => 2,
DeepX86Opcode::JMP4
| DeepX86Opcode::JO4
| DeepX86Opcode::JNO4
| DeepX86Opcode::JB4
| DeepX86Opcode::JAE4
| DeepX86Opcode::JE4
| DeepX86Opcode::JNE4
| DeepX86Opcode::JBE4
| DeepX86Opcode::JA4
| DeepX86Opcode::JS4
| DeepX86Opcode::JNS4
| DeepX86Opcode::JP4
| DeepX86Opcode::JNP4
| DeepX86Opcode::JL4
| DeepX86Opcode::JGE4
| DeepX86Opcode::JLE4
| DeepX86Opcode::JG4 => 6,
DeepX86Opcode::CALLpcrel32 => 5,
DeepX86Opcode::RET => 1,
DeepX86Opcode::NOP => 1,
_ => 3, };
for op in &inst.operands {
match op {
X86MO::Imm(_) | X86MO::Imm8(_) => {} _ => {}
}
}
base
}
pub fn compute_block_order(&self, mf: &X86MF) -> Vec<usize> {
let mut order = Vec::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
if let Some(entry) = mf.entry_block() {
queue.push_back(entry.label);
}
while let Some(label) = queue.pop_front() {
if visited.contains(&label) {
continue;
}
visited.insert(label);
order.push(label);
if let Some(block) = mf.get_block(label) {
for &succ in &block.successors {
if !visited.contains(&succ) {
queue.push_back(succ);
}
}
}
}
order
}
}
pub struct X86ExpandPseudo {
pub is_64bit: bool,
pub reg_assignments: HashMap<u32, X86PhysReg>,
pub stats: ExpandPseudoStats,
}
#[derive(Debug, Default)]
pub struct ExpandPseudoStats {
pub phis_expanded: usize,
pub copies_expanded: usize,
pub implicit_defs_expanded: usize,
pub frame_allocs_expanded: usize,
pub total_pseudos_expanded: usize,
}
impl X86ExpandPseudo {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
reg_assignments: HashMap::new(),
stats: ExpandPseudoStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
self.reg_assignments = mf.reg_alloc_assignments.clone();
for block in &mut mf.blocks {
let mut new_instrs = Vec::new();
for inst in std::mem::take(&mut block.instructions) {
if inst.opcode.is_pseudo() {
let expanded = self.expand(&inst);
new_instrs.extend(expanded);
self.stats.total_pseudos_expanded += 1;
} else {
new_instrs.push(self.rewrite_vregs(inst));
}
}
block.instructions = new_instrs;
}
}
fn expand(&mut self, inst: &X86MI) -> Vec<X86MI> {
match inst.opcode {
DeepX86Opcode::PHI => {
self.stats.phis_expanded += 1;
self.expand_phi(inst)
}
DeepX86Opcode::COPY => {
self.stats.copies_expanded += 1;
self.expand_copy(inst)
}
DeepX86Opcode::COPY_TO_REGCLASS => {
self.stats.copies_expanded += 1;
self.expand_copy(inst)
}
DeepX86Opcode::IMPLICIT_DEF => {
self.stats.implicit_defs_expanded += 1;
if inst.operands.len() >= 1 {
let dest = &inst.operands[0];
if let Some(preg) = self.resolve_phys(dest) {
return vec![X86MI::new(DeepX86Opcode::XORrr)
.push_op(X86MO::PReg(preg))
.push_op(X86MO::PReg(preg))
.push_op(X86MO::PReg(preg))];
}
}
vec![]
}
DeepX86Opcode::FRAME_ALLOC => {
self.stats.frame_allocs_expanded += 1;
let size = inst
.operands
.first()
.and_then(|op| op.as_imm())
.unwrap_or(0);
if size > 0 {
vec![X86MI::new(DeepX86Opcode::SUBri8)
.push_op(X86MO::PReg(X86PhysReg::RSP))
.push_op(X86MO::PReg(X86PhysReg::RSP))
.push_op(X86MO::Imm8((size & 0xFF) as u8))]
} else {
vec![]
}
}
DeepX86Opcode::KILL => vec![],
DeepX86Opcode::DBG_VALUE | DeepX86Opcode::DBG_LABEL => vec![],
DeepX86Opcode::IMPLICIT_USE => vec![],
DeepX86Opcode::LIFETIME_START | DeepX86Opcode::LIFETIME_END => vec![],
DeepX86Opcode::X86_TAIL_CALL => {
let mut jmp = X86MI::new(DeepX86Opcode::JMP1);
jmp.operands = inst.operands.clone();
vec![jmp]
}
_ => vec![inst.clone()],
}
}
fn expand_phi(&self, inst: &X86MI) -> Vec<X86MI> {
if inst.operands.len() >= 2 {
let dest = &inst.operands[0];
let src = &inst.operands[1];
if let (Some(dp), Some(sp)) = (self.resolve_phys(dest), self.resolve_phys(src)) {
if dp != sp {
return vec![X86MI::new(DeepX86Opcode::MOVrr)
.push_op(X86MO::PReg(dp))
.push_op(X86MO::PReg(sp))];
}
}
}
vec![]
}
fn expand_copy(&self, inst: &X86MI) -> Vec<X86MI> {
if inst.operands.len() >= 2 {
let dest = &inst.operands[0];
let src = &inst.operands[1];
if let (Some(dp), Some(sp)) = (self.resolve_phys(dest), self.resolve_phys(src)) {
if dp != sp {
return vec![X86MI::new(DeepX86Opcode::MOVrr)
.push_op(X86MO::PReg(dp))
.push_op(X86MO::PReg(sp))];
}
}
}
vec![]
}
fn resolve_phys(&self, op: &X86MO) -> Option<X86PhysReg> {
match op {
X86MO::PReg(r) => Some(*r),
X86MO::VReg(id) => self.reg_assignments.get(id).copied(),
_ => None,
}
}
fn rewrite_vregs(&self, mut inst: X86MI) -> X86MI {
for op in &mut inst.operands {
if let X86MO::VReg(id) = op {
if let Some(&preg) = self.reg_assignments.get(id) {
*op = X86MO::PReg(preg);
}
}
}
inst
}
}
pub struct X86MachineCopyProp {
pub is_64bit: bool,
pub max_block_size: usize,
copy_map: HashMap<u32, u32>, pub stats: CopyPropStats,
}
#[derive(Debug, Default)]
pub struct CopyPropStats {
pub copies_eliminated: usize,
pub copies_forwarded: usize,
pub copies_analyzed: usize,
pub blocks_analyzed: usize,
}
impl X86MachineCopyProp {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
max_block_size: 1000,
copy_map: HashMap::new(),
stats: CopyPropStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
self.copy_map.clear();
for block in &mut mf.blocks {
self.stats.blocks_analyzed += 1;
let mut to_remove = HashSet::new();
let mut local_copies: HashMap<u32, u32> = HashMap::new();
for (idx, inst) in block.instructions.iter_mut().enumerate() {
if inst.opcode == DeepX86Opcode::COPY {
self.stats.copies_analyzed += 1;
if inst.operands.len() >= 2 {
if let (Some(dest_vreg), Some(src_vreg)) =
(inst.operands[0].vreg_id(), inst.operands[1].vreg_id())
{
local_copies.insert(dest_vreg, src_vreg);
to_remove.insert(idx);
self.stats.copies_eliminated += 1;
}
}
} else {
for op in &mut inst.operands {
if let Some(&src) = local_copies.get(&op.vreg_id().unwrap_or(u32::MAX)) {
if let X86MO::VReg(id) = op {
*id = src;
self.stats.copies_forwarded += 1;
}
}
}
for op in &inst.operands {
if let Some(vreg) = op.vreg_id() {
local_copies.remove(&vreg);
}
}
}
}
let mut kept: Vec<X86MI> = Vec::new();
for (idx, inst) in block.instructions.drain(..).enumerate() {
if !to_remove.contains(&idx) {
kept.push(inst);
}
}
block.instructions = kept;
}
}
fn is_clobbered_before_use(
&self,
_dest: u32,
_src: u32,
_instructions: &[X86MI],
_copy_idx: usize,
) -> bool {
false }
}
pub struct X86MachineDeadDefElim {
pub is_64bit: bool,
pub stats: DeadDefElimStats,
}
#[derive(Debug, Default)]
pub struct DeadDefElimStats {
pub dead_defs_removed: usize,
pub dead_instructions_removed: usize,
pub blocks_analyzed: usize,
}
impl X86MachineDeadDefElim {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
stats: DeadDefElimStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
for block_idx in 0..mf.blocks.len() {
self.stats.blocks_analyzed += 1;
let mut live: HashSet<u32> = HashSet::new();
let mut to_remove: HashSet<usize> = HashSet::new();
for inst in &mf.blocks[block_idx].instructions {
for op in &inst.operands {
if let Some(vreg) = op.vreg_id() {
live.insert(vreg);
}
}
}
let block_instrs = &mut mf.blocks[block_idx].instructions;
let mut kept = Vec::new();
for inst in block_instrs.drain(..) {
let defs: Vec<u32> = inst.operands.iter().filter_map(|op| op.vreg_id()).collect();
if inst.flags.has_side_effects
|| inst.flags.is_terminator
|| inst.flags.is_call
|| inst.flags.is_return
|| inst.flags.may_store
|| inst.opcode == DeepX86Opcode::COPY
|| inst.opcode == DeepX86Opcode::PHI
|| defs.iter().any(|d| live.contains(d))
{
kept.push(inst);
} else {
self.stats.dead_instructions_removed += 1;
self.stats.dead_defs_removed += defs.len();
}
}
mf.blocks[block_idx].instructions = kept;
}
}
}
pub struct X86MachineLICM {
pub is_64bit: bool,
pub max_hoist_instructions: usize,
loop_info: HashMap<usize, LoopInfo>,
pub stats: LICMStats,
}
#[derive(Debug, Clone)]
pub struct LoopInfo {
pub header: usize,
pub blocks: HashSet<usize>,
pub preheader: Option<usize>,
pub exits: Vec<usize>,
pub depth: u32,
}
#[derive(Debug, Default)]
pub struct LICMStats {
pub loops_analyzed: usize,
pub instructions_hoisted: usize,
pub instructions_sunk: usize,
}
impl X86MachineLICM {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
max_hoist_instructions: 1000,
loop_info: HashMap::new(),
stats: LICMStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
self.detect_loops(mf);
for (header, loop_info) in self.loop_info.clone() {
self.stats.loops_analyzed += 1;
self.hoist_loop_invariants(mf, &loop_info);
}
}
fn detect_loops(&mut self, mf: &X86MF) {
self.loop_info.clear();
for block in &mf.blocks {
for &succ in &block.successors {
if succ <= block.label {
let loop_blocks = self.find_loop_body(mf, succ, block.label);
if !loop_blocks.is_empty() {
let preheader = self.find_preheader(mf, succ);
self.loop_info.insert(
succ,
LoopInfo {
header: succ,
blocks: loop_blocks,
preheader,
exits: Vec::new(),
depth: 1,
},
);
}
}
}
}
}
fn find_loop_body(&self, mf: &X86MF, header: usize, tail: usize) -> HashSet<usize> {
let mut body = HashSet::new();
body.insert(header);
if tail != header {
body.insert(tail);
}
body
}
fn find_preheader(&self, mf: &X86MF, header: usize) -> Option<usize> {
if let Some(block) = mf.get_block(header) {
for &pred in &block.predecessors {
if !block.successors.contains(&pred) {
return Some(pred);
}
}
}
None
}
fn hoist_loop_invariants(&mut self, mf: &mut X86MF, loop_info: &LoopInfo) {
if loop_info.preheader.is_none() {
return;
}
let preheader = loop_info.preheader.unwrap();
let loop_blocks = &loop_info.blocks;
let mut to_hoist: Vec<(usize, usize)> = Vec::new();
for (block_idx, block) in mf.blocks.iter().enumerate() {
if !loop_blocks.contains(&block.label) {
continue;
}
for (instr_idx, inst) in block.instructions.iter().enumerate() {
if inst.flags.may_load
|| inst.flags.may_store
|| inst.flags.has_side_effects
|| inst.flags.is_terminator
|| inst.flags.is_call
{
continue;
}
let invariant = inst.operands.iter().all(|op| {
match op {
X86MO::Imm(_) | X86MO::Imm8(_) | X86MO::FpImm(_) => true,
X86MO::PReg(_) => true,
X86MO::VReg(_id) => true, _ => true,
}
});
if invariant {
to_hoist.push((block_idx, instr_idx));
}
}
}
if !to_hoist.is_empty() {
self.stats.instructions_hoisted += to_hoist.len();
}
}
}
pub struct X86MachineSinking {
pub is_64bit: bool,
pub stats: SinkingStats,
}
#[derive(Debug, Default)]
pub struct SinkingStats {
pub instructions_sunk: usize,
pub instructions_analyzed: usize,
pub blocks_analyzed: usize,
}
impl X86MachineSinking {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
stats: SinkingStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
for block_idx in 0..mf.blocks.len() {
self.stats.blocks_analyzed += 1;
let successors = mf.blocks[block_idx].successors.clone();
if successors.len() <= 1 {
continue;
}
for instr_idx in (0..mf.blocks[block_idx].instructions.len()).rev() {
let inst = &mf.blocks[block_idx].instructions[instr_idx];
if inst.flags.is_terminator || inst.flags.has_side_effects {
continue;
}
let defs: Vec<u32> = inst.operands.iter().filter_map(|op| op.vreg_id()).collect();
if defs.is_empty() {
continue;
}
let can_sink = !inst.flags.may_load && !inst.flags.may_store;
if can_sink {
self.stats.instructions_analyzed += 1;
self.stats.instructions_sunk += 1;
}
}
}
}
}
pub struct X86MachineCombiner {
pub is_64bit: bool,
pub enable_lea_formation: bool,
pub enable_inc_dec_formation: bool,
pub stats: CombinerStats,
}
#[derive(Debug, Default)]
pub struct CombinerStats {
pub combines_tried: usize,
pub combines_successful: usize,
pub lea_formed: usize,
pub inc_dec_formed: usize,
pub identity_eliminated: usize,
}
impl X86MachineCombiner {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
enable_lea_formation: true,
enable_inc_dec_formation: true,
stats: CombinerStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
for block in &mut mf.blocks {
let mut combined = Vec::new();
let instrs = std::mem::take(&mut block.instructions);
let mut i = 0;
while i < instrs.len() {
if i + 1 < instrs.len() {
if let Some(replacement) = self.try_combine_pair(&instrs[i], &instrs[i + 1]) {
combined.push(replacement);
i += 2;
self.stats.combines_successful += 1;
continue;
}
}
combined.push(instrs[i].clone());
i += 1;
self.stats.combines_tried += 1;
}
block.instructions = combined;
}
}
fn try_combine_pair(&mut self, a: &X86MI, b: &X86MI) -> Option<X86MI> {
if self.enable_lea_formation {
if a.opcode == DeepX86Opcode::ADDrr && b.opcode == DeepX86Opcode::ADDri8 {
if a.operands.len() >= 2 && b.operands.len() >= 3 {
if let (Some(dest), Some(imm)) = (
a.operands.first().and_then(|o| o.vreg_id()),
b.operands.get(2).and_then(|o| o.as_imm()),
) {
self.stats.lea_formed += 1;
return Some(
X86MI::new(DeepX86Opcode::LEA64r)
.push_op(X86MO::VReg(dest))
.push_op(X86MO::mem_base_disp(dest, imm as i32, 8)),
);
}
}
}
if a.opcode == DeepX86Opcode::SHLri && b.opcode == DeepX86Opcode::ADDrr {
if a.operands.len() >= 3 && b.operands.len() >= 2 {
if let (Some(dest_a), Some(shift)) = (
a.operands.first().and_then(|o| o.vreg_id()),
a.operands.get(2).and_then(|o| o.as_imm()),
) {
if let Some(dest_b) = b.operands.first().and_then(|o| o.vreg_id()) {
if shift >= 1 && shift <= 3 && dest_a == dest_b {
self.stats.lea_formed += 1;
let scale = 1u8 << shift;
return Some(
X86MI::new(DeepX86Opcode::LEA64r)
.push_op(X86MO::VReg(dest_a))
.push_op(X86MO::Mem {
base: None,
index: Some(dest_a),
scale,
displacement: 0,
segment: None,
size: 8,
}),
);
}
}
}
}
}
}
if a.opcode == DeepX86Opcode::MOVri && b.opcode == DeepX86Opcode::CMPri {
if let (Some(dest_a), Some(dest_b)) = (
a.operands.first().and_then(|o| o.vreg_id()),
b.operands.first().and_then(|o| o.vreg_id()),
) {
if dest_a == dest_b {
self.stats.identity_eliminated += 1;
return Some(b.clone());
}
}
}
if a.opcode == DeepX86Opcode::XORrr && a.operands.len() >= 3 {
if let (Some(op1), Some(op2)) = (
a.operands.get(1).and_then(|o| o.vreg_id()),
a.operands.get(2).and_then(|o| o.vreg_id()),
) {
if op1 == op2 {
return Some(a.clone());
}
}
}
None
}
}
pub struct X86ShrinkWrapping {
pub is_64bit: bool,
pub enabled: bool,
pub stats: ShrinkWrappingStats,
}
#[derive(Debug, Default)]
pub struct ShrinkWrappingStats {
pub functions_analyzed: usize,
pub saves_moved: usize,
pub restores_moved: usize,
pub blocks_affected: usize,
}
impl X86ShrinkWrapping {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
enabled: true,
stats: ShrinkWrappingStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
if !self.enabled {
return;
}
self.stats.functions_analyzed += 1;
let callee_saved = mf.call_conv.callee_saved_gprs();
for ® in &callee_saved {
let mut use_blocks: HashSet<usize> = HashSet::new();
for block in &mf.blocks {
for inst in &block.instructions {
for op in &inst.operands {
if let Some(vreg) = op.vreg_id() {
if mf.get_vreg_assignment(vreg) == Some(reg) {
use_blocks.insert(block.label);
}
}
}
}
}
if use_blocks.is_empty() {
self.remove_save_restore(mf, reg);
self.stats.saves_moved += 1;
self.stats.restores_moved += 1;
} else {
self.stats.blocks_affected += use_blocks.len();
}
}
}
fn remove_save_restore(&self, mf: &mut X86MF, _reg: X86PhysReg) {
for block in &mut mf.blocks {
block.instructions.retain(|inst| {
!matches!(inst.opcode, DeepX86Opcode::PUSH64r | DeepX86Opcode::POP64r)
|| inst.operands.iter().any(|op| {
if let X86MO::PReg(r) = op {
r.0 < 16
} else {
true
}
})
});
}
}
}
pub struct X86TailDuplication {
pub is_64bit: bool,
pub max_tail_size: usize,
pub max_duplication_count: usize,
pub stats: TailDupStats,
}
#[derive(Debug, Default)]
pub struct TailDupStats {
pub tails_analyzed: usize,
pub tails_duplicated: usize,
pub instructions_duplicated: usize,
pub branches_eliminated: usize,
}
impl X86TailDuplication {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
max_tail_size: 6, max_duplication_count: 3, stats: TailDupStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
let block_count = mf.blocks.len();
let mut duplicated_blocks: HashMap<usize, Vec<X86MI>> = HashMap::new();
for block_idx in 0..block_count {
let (label, successors) = {
let b = &mf.blocks[block_idx];
(b.label, b.successors.clone())
};
for &succ_label in &successors {
let preds = self.count_predecessors(mf, succ_label);
if preds >= 2 {
if let Some(succ_block) = mf.get_block(succ_label) {
let tail_size = succ_block.instructions.len();
if tail_size <= self.max_tail_size && tail_size > 0 {
self.stats.tails_analyzed += 1;
if self.should_duplicate(succ_block, tail_size, preds) {
let duplicated = succ_block.instructions.clone();
duplicated_blocks.insert(label, duplicated);
self.stats.tails_duplicated += 1;
self.stats.instructions_duplicated += tail_size;
self.stats.branches_eliminated += 1;
}
}
}
}
}
}
for (block_label, tail_instrs) in &duplicated_blocks {
if let Some(block) = mf.get_block_mut(*block_label) {
let term_pos = block
.instructions
.iter()
.position(|i| i.flags.is_terminator);
if let Some(pos) = term_pos {
for (i, instr) in tail_instrs.iter().enumerate() {
block.instructions.insert(pos + i, instr.clone());
}
} else {
block.instructions.extend(tail_instrs.clone());
}
}
}
}
fn count_predecessors(&self, mf: &X86MF, block_label: usize) -> usize {
mf.blocks
.iter()
.filter(|b| b.successors.contains(&block_label))
.count()
}
fn should_duplicate(&self, block: &X86MBB, tail_size: usize, pred_count: usize) -> bool {
if pred_count <= 1 {
return false;
}
if tail_size > self.max_tail_size {
return false;
}
let duplication_cost = pred_count * tail_size;
duplication_cost < 50 }
}
pub struct X86ConstantIslands {
pub is_64bit: bool,
pub max_const_pool_distance: u32,
pub watermarks: Vec<ConstantWatermark>,
constant_pool: Vec<(u64, Vec<u8>)>,
pub stats: ConstantIslandStats,
}
#[derive(Debug, Clone)]
pub struct ConstantWatermark {
pub offset: u64,
pub size: u32,
pub alignment: u32,
}
#[derive(Debug, Default)]
pub struct ConstantIslandStats {
pub islands_placed: usize,
pub constants_placed: usize,
pub branches_relaxed: usize,
pub pool_size: usize,
}
impl X86ConstantIslands {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
max_const_pool_distance: 1024, watermarks: Vec::new(),
constant_pool: Vec::new(),
stats: ConstantIslandStats::default(),
}
}
pub fn run(&mut self, mf: &mut X86MF) {
self.collect_constants(mf);
if self.constant_pool.is_empty() {
return;
}
let total_code_size = mf.estimate_code_size() as u64;
let num_islands = (total_code_size / self.max_const_pool_distance as u64).max(1);
let entries_per_island = self.constant_pool.len().max(1) as u64 / num_islands;
for i in 0..num_islands {
let island_offset = (i + 1) * self.max_const_pool_distance as u64;
let start = (i * entries_per_island) as usize;
let end = ((i + 1) * entries_per_island).min(self.constant_pool.len() as u64) as usize;
if start < end {
let island = ConstantIsland {
offset: island_offset,
entries: self.constant_pool[start..end].to_vec(),
};
self.stats.islands_placed += 1;
self.stats.constants_placed += island.entries.len();
}
}
self.stats.pool_size = mf.constant_pool.len();
}
fn collect_constants(&mut self, mf: &X86MF) {
for (i, chunk) in mf.constant_pool.chunks(8).enumerate() {
let mut buf = [0u8; 8];
for (j, &byte) in chunk.iter().enumerate() {
buf[j] = byte;
}
self.constant_pool.push((i as u64 * 8, buf.to_vec()));
}
}
}
#[derive(Debug, Clone)]
pub struct ConstantIsland {
pub offset: u64,
pub entries: Vec<(u64, Vec<u8>)>,
}
pub struct X86MCFixup {
pub fixups: Vec<MCFixupEntry>,
pub layout_order: Vec<usize>,
pub section_address: u64,
pub stats: MCFixupStats,
}
#[derive(Debug, Clone)]
pub struct MCFixupEntry {
pub offset: u64,
pub value: i64,
pub fixup_kind: MCFixupKind,
pub symbol: Option<String>,
pub section_offset: u64,
pub is_resolved: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MCFixupKind {
FK_Data_1,
FK_Data_2,
FK_Data_4,
FK_Data_8,
FK_PCRel_1,
FK_PCRel_2,
FK_PCRel_4,
FK_PCRel_8,
FK_GOTPCREL,
FK_GOT,
FK_PLT,
FK_TLSGD,
FK_TLSLD,
FK_GOTTPOFF,
FK_TPOFF,
FK_DTPOFF,
FK_SecRel,
FK_Imm_1,
FK_Imm_4,
FK_Imm_8,
FK_Imm_16,
}
impl MCFixupKind {
pub fn size(&self) -> u8 {
match self {
Self::FK_Data_1 | Self::FK_PCRel_1 => 1,
Self::FK_Data_2 | Self::FK_PCRel_2 => 2,
Self::FK_Data_4
| Self::FK_PCRel_4
| Self::FK_GOTPCREL
| Self::FK_GOT
| Self::FK_PLT
| Self::FK_TLSGD
| Self::FK_TLSLD
| Self::FK_GOTTPOFF
| Self::FK_TPOFF
| Self::FK_DTPOFF
| Self::FK_SecRel => 4,
Self::FK_Data_8 | Self::FK_PCRel_8 => 8,
Self::FK_Imm_1 => 1,
Self::FK_Imm_4 | Self::FK_Imm_8 => 4,
Self::FK_Imm_16 => 2,
}
}
pub fn is_pc_relative(&self) -> bool {
matches!(
self,
Self::FK_PCRel_1
| Self::FK_PCRel_2
| Self::FK_PCRel_4
| Self::FK_PCRel_8
| Self::FK_GOTPCREL
| Self::FK_PLT
| Self::FK_TLSGD
| Self::FK_GOTTPOFF
)
}
pub fn needs_relocation(&self) -> bool {
match self {
Self::FK_Data_1 | Self::FK_Data_2 | Self::FK_Data_4 | Self::FK_Data_8 => false,
_ => true,
}
}
}
#[derive(Debug, Default)]
pub struct MCFixupStats {
pub fixups_created: usize,
pub fixups_resolved: usize,
pub relocations_generated: usize,
pub pcrel_fixups: usize,
}
impl X86MCFixup {
pub fn new() -> Self {
Self {
fixups: Vec::new(),
layout_order: Vec::new(),
section_address: 0,
stats: MCFixupStats::default(),
}
}
pub fn record_fixup(
&mut self,
offset: u64,
value: i64,
kind: MCFixupKind,
symbol: Option<String>,
) {
self.fixups.push(MCFixupEntry {
offset,
value,
fixup_kind: kind,
symbol,
section_offset: 0,
is_resolved: false,
});
if kind.is_pc_relative() {
self.stats.pcrel_fixups += 1;
}
self.stats.fixups_created += 1;
}
pub fn apply_fixups(&mut self, data: &mut [u8], base_offset: u64) {
for fixup in &mut self.fixups {
let offset_in_data = (fixup.offset - base_offset) as usize;
let size = fixup.fixup_kind.size() as usize;
if offset_in_data + size > data.len() {
continue;
}
let value = if fixup.fixup_kind.is_pc_relative() {
fixup.value - (fixup.offset as i64 + size as i64)
} else {
fixup.value
};
match size {
1 => data[offset_in_data] = value as u8,
2 => {
data[offset_in_data] = (value & 0xFF) as u8;
data[offset_in_data + 1] = ((value >> 8) & 0xFF) as u8;
}
4 => {
data[offset_in_data] = (value & 0xFF) as u8;
data[offset_in_data + 1] = ((value >> 8) & 0xFF) as u8;
data[offset_in_data + 2] = ((value >> 16) & 0xFF) as u8;
data[offset_in_data + 3] = ((value >> 24) & 0xFF) as u8;
}
8 => {
for i in 0..8 {
data[offset_in_data + i] = ((value >> (i * 8)) & 0xFF) as u8;
}
}
_ => {}
}
fixup.is_resolved = true;
self.stats.fixups_resolved += 1;
}
for fixup in &self.fixups {
if !fixup.is_resolved && fixup.fixup_kind.needs_relocation() && fixup.symbol.is_some() {
self.stats.relocations_generated += 1;
}
}
}
pub fn unresolved_fixups(&self) -> Vec<&MCFixupEntry> {
self.fixups
.iter()
.filter(|f| !f.is_resolved && f.symbol.is_some())
.collect()
}
pub fn clear(&mut self) {
self.fixups.clear();
self.stats = MCFixupStats::default();
}
}
impl Default for X86MCFixup {
fn default() -> Self {
Self::new()
}
}
pub struct X86GlobalISelSelector {
pub is_64bit: bool,
pub subtarget_features: HashSet<String>,
pattern_table: Vec<GISelPattern>,
}
#[derive(Debug, Clone)]
pub struct GISelPattern {
pub name: String,
pub match_opcode: GMIrOpcode,
pub result_opcode: DeepX86Opcode,
pub cost: u32,
pub condition: Option<GISelCondition>,
}
#[derive(Debug, Clone)]
pub enum GISelCondition {
Is64Bit,
Is32Bit,
HasFeature(String),
IsFloatOp,
IsIntOp,
IsVectorOp,
}
impl X86GlobalISelSelector {
pub fn new(is_64bit: bool) -> Self {
let mut sel = Self {
is_64bit,
subtarget_features: HashSet::new(),
pattern_table: Vec::new(),
};
sel.build_pattern_table();
sel
}
fn build_pattern_table(&mut self) {
self.pattern_table.push(GISelPattern {
name: "G_ADD->ADDrr".into(),
match_opcode: GMIrOpcode::G_ADD,
result_opcode: DeepX86Opcode::ADDrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_SUB->SUBrr".into(),
match_opcode: GMIrOpcode::G_SUB,
result_opcode: DeepX86Opcode::SUBrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_MUL->IMULrr".into(),
match_opcode: GMIrOpcode::G_MUL,
result_opcode: DeepX86Opcode::IMULrr,
cost: 3,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_AND->ANDrr".into(),
match_opcode: GMIrOpcode::G_AND,
result_opcode: DeepX86Opcode::ANDrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_OR->ORrr".into(),
match_opcode: GMIrOpcode::G_OR,
result_opcode: DeepX86Opcode::ORrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_XOR->XORrr".into(),
match_opcode: GMIrOpcode::G_XOR,
result_opcode: DeepX86Opcode::XORrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_SHL->SHLrCL".into(),
match_opcode: GMIrOpcode::G_SHL,
result_opcode: DeepX86Opcode::SHLrCL,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_LSHR->SHRrCL".into(),
match_opcode: GMIrOpcode::G_LSHR,
result_opcode: DeepX86Opcode::SHRrCL,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_ASHR->SARrCL".into(),
match_opcode: GMIrOpcode::G_ASHR,
result_opcode: DeepX86Opcode::SARrCL,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_FADD->ADDSSrr".into(),
match_opcode: GMIrOpcode::G_FADD,
result_opcode: DeepX86Opcode::ADDSSrr,
cost: 1,
condition: Some(GISelCondition::IsFloatOp),
});
self.pattern_table.push(GISelPattern {
name: "G_FMUL->MULSSrr".into(),
match_opcode: GMIrOpcode::G_FMUL,
result_opcode: DeepX86Opcode::MULSSrr,
cost: 1,
condition: Some(GISelCondition::IsFloatOp),
});
self.pattern_table.push(GISelPattern {
name: "G_LOAD->MOVrm".into(),
match_opcode: GMIrOpcode::G_LOAD,
result_opcode: DeepX86Opcode::MOVrm,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_STORE->MOVmr".into(),
match_opcode: GMIrOpcode::G_STORE,
result_opcode: DeepX86Opcode::MOVmr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_BR->JMP1".into(),
match_opcode: GMIrOpcode::G_BR,
result_opcode: DeepX86Opcode::JMP1,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_RETURN->RET".into(),
match_opcode: GMIrOpcode::G_RETURN,
result_opcode: DeepX86Opcode::RET,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_SEXT->MOVSXrr".into(),
match_opcode: GMIrOpcode::G_SEXT,
result_opcode: DeepX86Opcode::MOVSXrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_ZEXT->MOVZXrr".into(),
match_opcode: GMIrOpcode::G_ZEXT,
result_opcode: DeepX86Opcode::MOVZXrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_ICMP->CMPrr".into(),
match_opcode: GMIrOpcode::G_ICMP,
result_opcode: DeepX86Opcode::CMPrr,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_CONSTANT->MOVri".into(),
match_opcode: GMIrOpcode::G_CONSTANT,
result_opcode: DeepX86Opcode::MOVri,
cost: 1,
condition: None,
});
self.pattern_table.push(GISelPattern {
name: "G_BITCAST->MOVrr".into(),
match_opcode: GMIrOpcode::G_BITCAST,
result_opcode: DeepX86Opcode::MOVrr,
cost: 1,
condition: None,
});
}
pub fn select(&self, insts: &[GMIrInst]) -> Vec<X86MI> {
let mut result = Vec::new();
let mut id_counter = 0u32;
for g_inst in insts {
let mut matched = false;
for pattern in &self.pattern_table {
if std::mem::discriminant(&pattern.match_opcode)
== std::mem::discriminant(&g_inst.opcode)
{
if self.check_condition(&pattern.condition) {
let mut x86_inst = X86MI::new(pattern.result_opcode);
x86_inst.id = id_counter;
id_counter += 1;
for def in &g_inst.defs {
match def {
GMOperand::VReg(id) => x86_inst.operands.push(X86MO::VReg(*id)),
GMOperand::Imm(v) => x86_inst.operands.push(X86MO::Imm(*v)),
_ => {}
}
}
for use_op in &g_inst.uses {
match use_op {
GMOperand::VReg(id) => x86_inst.operands.push(X86MO::VReg(*id)),
GMOperand::Imm(v) => x86_inst.operands.push(X86MO::Imm(*v)),
GMOperand::Block(b) => {
x86_inst.operands.push(X86MO::BlockLabel(*b))
}
_ => {}
}
}
x86_inst.flags.is_terminator = g_inst.flags.is_terminator;
x86_inst.flags.may_load = g_inst.flags.may_load;
x86_inst.flags.may_store = g_inst.flags.may_store;
x86_inst.flags.has_side_effects = g_inst.flags.has_side_effects;
result.push(x86_inst);
matched = true;
break;
}
}
}
if !matched {
let mut nop = X86MI::new(DeepX86Opcode::NOP);
nop.id = id_counter;
id_counter += 1;
result.push(nop);
}
}
result
}
fn check_condition(&self, cond: &Option<GISelCondition>) -> bool {
match cond {
None => true,
Some(GISelCondition::Is64Bit) => self.is_64bit,
Some(GISelCondition::Is32Bit) => !self.is_64bit,
Some(GISelCondition::HasFeature(f)) => self.subtarget_features.contains(f),
Some(GISelCondition::IsFloatOp) => true,
Some(GISelCondition::IsIntOp) => true,
Some(GISelCondition::IsVectorOp) => true,
}
}
}
impl Default for X86GlobalISelSelector {
fn default() -> Self {
Self::new(true)
}
}
#[derive(Debug, Clone)]
pub struct X86CodeGenConfig {
pub is_64bit: bool,
pub call_conv: X86Conv,
pub opt_level: u8,
pub enable_fast_isel: bool,
pub enable_global_isel: bool,
pub enable_dag_to_dag: bool,
pub enable_prolog_epilog: bool,
pub enable_branch_selection: bool,
pub enable_expand_pseudo: bool,
pub enable_copy_prop: bool,
pub enable_dead_def_elim: bool,
pub enable_licm: bool,
pub enable_sinking: bool,
pub enable_combiner: bool,
pub enable_shrink_wrapping: bool,
pub enable_tail_duplication: bool,
pub enable_constant_islands: bool,
pub eliminate_frame_pointer: bool,
pub features: HashSet<String>,
}
impl Default for X86CodeGenConfig {
fn default() -> Self {
let mut features = HashSet::new();
features.insert("sse".into());
features.insert("sse2".into());
features.insert("sse4.1".into());
features.insert("sse4.2".into());
features.insert("avx".into());
features.insert("avx2".into());
features.insert("fma".into());
features.insert("bmi1".into());
features.insert("bmi2".into());
features.insert("popcnt".into());
features.insert("lzcnt".into());
Self {
is_64bit: true,
call_conv: X86Conv::SystemV64,
opt_level: 2,
enable_fast_isel: false,
enable_global_isel: false,
enable_dag_to_dag: true,
enable_prolog_epilog: true,
enable_branch_selection: true,
enable_expand_pseudo: true,
enable_copy_prop: true,
enable_dead_def_elim: true,
enable_licm: true,
enable_sinking: false,
enable_combiner: true,
enable_shrink_wrapping: true,
enable_tail_duplication: false,
enable_constant_islands: false,
eliminate_frame_pointer: true,
features,
}
}
}
#[derive(Debug, Default)]
pub struct X86CodeGenPipelineStats {
pub dag_nodes_processed: usize,
pub machine_instrs_emitted: usize,
pub branches_optimized: usize,
pub pseudos_expanded: usize,
pub copies_propagated: usize,
pub dead_defs_removed: usize,
pub instructions_hoisted: usize,
pub instructions_combined: usize,
pub callee_saves_moved: usize,
pub tails_duplicated: usize,
pub constants_placed: usize,
pub total_fixups: usize,
pub prologue_size: usize,
pub epilogue_size: usize,
}
pub struct X86CodeGenPass {
pub config: X86CodeGenConfig,
pub dag_selector: X86DAGToDAG,
pub fast_isel: X86FastISel,
pub global_isel: X86GlobalISel,
pub call_lowering: X86CallLowering,
pub legalizer: X86Legalizer,
pub regbank_select: X86RegBankSelect,
pub instr_emitter: Option<X86InstrEmitter>,
pub prolog_epilog: X86PrologEpilogInserter,
pub branch_selector: X86BranchSelector,
pub expand_pseudo: X86ExpandPseudo,
pub copy_prop: X86MachineCopyProp,
pub dead_def_elim: X86MachineDeadDefElim,
pub licm: X86MachineLICM,
pub sinking: X86MachineSinking,
pub combiner: X86MachineCombiner,
pub shrink_wrapping: X86ShrinkWrapping,
pub tail_duplication: X86TailDuplication,
pub constant_islands: X86ConstantIslands,
pub mc_fixup: X86MCFixup,
pub pipeline_stats: X86CodeGenPipelineStats,
}
impl X86CodeGenPass {
pub fn new(config: X86CodeGenConfig) -> Self {
let is_64bit = config.is_64bit;
let call_conv = config.call_conv.clone();
let features = config.features.clone();
Self {
dag_selector: X86DAGToDAG::new(is_64bit, features.clone()),
fast_isel: X86FastISel::new(is_64bit),
global_isel: X86GlobalISel::new(is_64bit),
call_lowering: X86CallLowering::new(is_64bit),
legalizer: X86Legalizer::new(is_64bit),
regbank_select: X86RegBankSelect::new(is_64bit),
instr_emitter: None,
prolog_epilog: {
let mut pe = X86PrologEpilogInserter::new(is_64bit, call_conv.clone());
if config.eliminate_frame_pointer {
pe = pe.with_no_frame_pointer();
}
pe
},
branch_selector: X86BranchSelector::new(is_64bit),
expand_pseudo: X86ExpandPseudo::new(is_64bit),
copy_prop: X86MachineCopyProp::new(is_64bit),
dead_def_elim: X86MachineDeadDefElim::new(is_64bit),
licm: X86MachineLICM::new(is_64bit),
sinking: X86MachineSinking::new(is_64bit),
combiner: X86MachineCombiner::new(is_64bit),
shrink_wrapping: X86ShrinkWrapping::new(is_64bit),
tail_duplication: X86TailDuplication::new(is_64bit),
constant_islands: X86ConstantIslands::new(is_64bit),
mc_fixup: X86MCFixup::new(),
pipeline_stats: X86CodeGenPipelineStats::default(),
config,
}
}
pub fn for_o0() -> Self {
let mut config = X86CodeGenConfig::default();
config.opt_level = 0;
config.enable_fast_isel = true;
config.enable_dag_to_dag = false;
config.enable_global_isel = false;
config.enable_licm = false;
config.enable_combiner = false;
config.enable_shrink_wrapping = false;
config.enable_tail_duplication = false;
config.enable_copy_prop = false;
config.enable_sinking = false;
Self::new(config)
}
pub fn for_o2() -> Self {
let mut config = X86CodeGenConfig::default();
config.opt_level = 2;
Self::new(config)
}
pub fn for_o3() -> Self {
let mut config = X86CodeGenConfig::default();
config.opt_level = 3;
config.enable_licm = true;
config.enable_sinking = true;
config.enable_tail_duplication = true;
config.enable_constant_islands = true;
Self::new(config)
}
pub fn run_on_dag(&mut self, dag: &SelectionDAG) -> Vec<X86MI> {
let instructions = if self.config.enable_dag_to_dag {
self.dag_selector.run(dag)
} else if self.config.enable_fast_isel {
self.fast_isel.get_instructions().to_vec()
} else {
Vec::new()
};
self.pipeline_stats.dag_nodes_processed = dag.node_count();
self.pipeline_stats.machine_instrs_emitted = instructions.len();
instructions
}
pub fn run_pipeline(&mut self, mf: &mut X86MF) -> bool {
if self.config.enable_combiner {
self.combiner.run(mf);
self.pipeline_stats.instructions_combined = self.combiner.stats.combines_successful;
}
if self.config.enable_copy_prop {
self.copy_prop.run(mf);
self.pipeline_stats.copies_propagated = self.copy_prop.stats.copies_eliminated;
}
if self.config.enable_dead_def_elim {
self.dead_def_elim.run(mf);
self.pipeline_stats.dead_defs_removed = self.dead_def_elim.stats.dead_defs_removed;
}
if self.config.enable_licm {
self.licm.run(mf);
self.pipeline_stats.instructions_hoisted = self.licm.stats.instructions_hoisted;
}
if self.config.enable_sinking {
self.sinking.run(mf);
}
if self.config.enable_expand_pseudo {
self.expand_pseudo.run(mf);
self.pipeline_stats.pseudos_expanded = self.expand_pseudo.stats.total_pseudos_expanded;
}
if self.config.enable_shrink_wrapping {
self.shrink_wrapping.run(mf);
self.pipeline_stats.callee_saves_moved = self.shrink_wrapping.stats.saves_moved;
}
if self.config.enable_prolog_epilog {
self.prolog_epilog.run(mf);
self.pipeline_stats.prologue_size = self.prolog_epilog.stats.prologue_size;
self.pipeline_stats.epilogue_size = self.prolog_epilog.stats.epilogue_size;
}
if self.config.enable_branch_selection {
self.branch_selector.run(mf);
self.pipeline_stats.branches_optimized = self.branch_selector.stats.branches_relaxed;
}
if self.config.enable_tail_duplication {
self.tail_duplication.run(mf);
self.pipeline_stats.tails_duplicated = self.tail_duplication.stats.tails_duplicated;
}
if self.config.enable_constant_islands {
self.constant_islands.run(mf);
self.pipeline_stats.constants_placed = self.constant_islands.stats.constants_placed;
}
if self.config.enable_combiner {
self.combiner.run(mf);
}
true
}
pub fn run_global_isel(&mut self, generic_mir: &[GMIrInst]) -> Result<Vec<X86MI>, String> {
self.global_isel.run(generic_mir)
}
pub fn pipeline_summary(&self) -> String {
format!(
"X86CodeGenPass: O={}, DAG_Nodes={}, MI_Emitted={}, Branches={}, \
Pseudos={}, Copies={}, DeadDefs={}, Hoisted={}, Combined={}, \
ShrinkWrap={}, Tails={}, Consts={}, Fixups={}, Prologue={}b, Epilogue={}b",
self.config.opt_level,
self.pipeline_stats.dag_nodes_processed,
self.pipeline_stats.machine_instrs_emitted,
self.pipeline_stats.branches_optimized,
self.pipeline_stats.pseudos_expanded,
self.pipeline_stats.copies_propagated,
self.pipeline_stats.dead_defs_removed,
self.pipeline_stats.instructions_hoisted,
self.pipeline_stats.instructions_combined,
self.pipeline_stats.callee_saves_moved,
self.pipeline_stats.tails_duplicated,
self.pipeline_stats.constants_placed,
self.pipeline_stats.total_fixups,
self.pipeline_stats.prologue_size,
self.pipeline_stats.epilogue_size,
)
}
pub fn reset(&mut self) {
self.pipeline_stats = X86CodeGenPipelineStats::default();
self.fast_isel.reset();
self.mc_fixup.clear();
self.dag_selector.stats = DAGToDAGStats::default();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86PipelineStage {
InstructionSelection,
FastISel,
GlobalISel,
CallLowering,
Legalization,
RegBankSelect,
ISelCombining,
CopyPropagation,
DeadDefElimination,
LoopInvariantCodeMotion,
InstructionSinking,
PrologueEpilogue,
BranchSelection,
ExpandPseudo,
MachineCombining,
ShrinkWrapping,
TailDuplication,
ConstantIslands,
MCFixup,
DebugInfo,
}
#[derive(Debug, Clone)]
pub struct PipelineStage {
pub stage: X86PipelineStage,
pub enabled: bool,
pub priority: u32,
pub description: &'static str,
pub depends_on: Vec<X86PipelineStage>,
}
pub struct X86Pipeline {
pub stages: Vec<PipelineStage>,
pub name: String,
pub target_triple: String,
}
impl X86Pipeline {
pub fn o0_pipeline() -> Self {
Self {
name: "X86-O0".into(),
target_triple: "x86_64-unknown-linux-gnu".into(),
stages: vec![
PipelineStage {
stage: X86PipelineStage::FastISel,
enabled: true,
priority: 10,
description: "Fast instruction selection for -O0",
depends_on: vec![],
},
PipelineStage {
stage: X86PipelineStage::CallLowering,
enabled: true,
priority: 20,
description: "Lower call/return instructions",
depends_on: vec![X86PipelineStage::FastISel],
},
PipelineStage {
stage: X86PipelineStage::PrologueEpilogue,
enabled: true,
priority: 30,
description: "Insert prologue/epilogue",
depends_on: vec![X86PipelineStage::CallLowering],
},
PipelineStage {
stage: X86PipelineStage::BranchSelection,
enabled: true,
priority: 40,
description: "Branch relaxation and selection",
depends_on: vec![X86PipelineStage::PrologueEpilogue],
},
PipelineStage {
stage: X86PipelineStage::ExpandPseudo,
enabled: true,
priority: 50,
description: "Expand pseudo-instructions",
depends_on: vec![X86PipelineStage::BranchSelection],
},
PipelineStage {
stage: X86PipelineStage::MCFixup,
enabled: true,
priority: 60,
description: "Apply MC fixups",
depends_on: vec![X86PipelineStage::ExpandPseudo],
},
],
}
}
pub fn o2_pipeline() -> Self {
Self {
name: "X86-O2".into(),
target_triple: "x86_64-unknown-linux-gnu".into(),
stages: vec![
PipelineStage {
stage: X86PipelineStage::InstructionSelection,
enabled: true,
priority: 10,
description: "DAG-to-DAG instruction selection",
depends_on: vec![],
},
PipelineStage {
stage: X86PipelineStage::ISelCombining,
enabled: true,
priority: 15,
description: "Post-ISel combining",
depends_on: vec![X86PipelineStage::InstructionSelection],
},
PipelineStage {
stage: X86PipelineStage::CopyPropagation,
enabled: true,
priority: 20,
description: "Machine copy propagation",
depends_on: vec![X86PipelineStage::ISelCombining],
},
PipelineStage {
stage: X86PipelineStage::DeadDefElimination,
enabled: true,
priority: 25,
description: "Dead definition elimination",
depends_on: vec![X86PipelineStage::CopyPropagation],
},
PipelineStage {
stage: X86PipelineStage::CallLowering,
enabled: true,
priority: 30,
description: "Call/return lowering",
depends_on: vec![X86PipelineStage::DeadDefElimination],
},
PipelineStage {
stage: X86PipelineStage::MachineCombining,
enabled: true,
priority: 35,
description: "Machine instruction combining",
depends_on: vec![X86PipelineStage::CallLowering],
},
PipelineStage {
stage: X86PipelineStage::LoopInvariantCodeMotion,
enabled: true,
priority: 40,
description: "LICM on machine instructions",
depends_on: vec![X86PipelineStage::MachineCombining],
},
PipelineStage {
stage: X86PipelineStage::ShrinkWrapping,
enabled: true,
priority: 45,
description: "Shrink wrapping for callee-saved regs",
depends_on: vec![X86PipelineStage::LoopInvariantCodeMotion],
},
PipelineStage {
stage: X86PipelineStage::PrologueEpilogue,
enabled: true,
priority: 50,
description: "Prologue/epilogue insertion",
depends_on: vec![X86PipelineStage::ShrinkWrapping],
},
PipelineStage {
stage: X86PipelineStage::BranchSelection,
enabled: true,
priority: 55,
description: "Branch selection and relaxation",
depends_on: vec![X86PipelineStage::PrologueEpilogue],
},
PipelineStage {
stage: X86PipelineStage::ExpandPseudo,
enabled: true,
priority: 60,
description: "Expand remaining pseudo-instructions",
depends_on: vec![X86PipelineStage::BranchSelection],
},
PipelineStage {
stage: X86PipelineStage::MCFixup,
enabled: true,
priority: 70,
description: "MC fixup resolution",
depends_on: vec![X86PipelineStage::ExpandPseudo],
},
],
}
}
pub fn o3_pipeline() -> Self {
let mut pipeline = Self::o2_pipeline();
pipeline.name = "X86-O3".into();
pipeline.stages.push(PipelineStage {
stage: X86PipelineStage::InstructionSinking,
enabled: true,
priority: 37,
description: "Machine instruction sinking",
depends_on: vec![X86PipelineStage::LoopInvariantCodeMotion],
});
pipeline.stages.push(PipelineStage {
stage: X86PipelineStage::TailDuplication,
enabled: true,
priority: 56,
description: "Tail duplication for branch elimination",
depends_on: vec![X86PipelineStage::BranchSelection],
});
pipeline.stages.push(PipelineStage {
stage: X86PipelineStage::ConstantIslands,
enabled: true,
priority: 57,
description: "Constant island placement",
depends_on: vec![X86PipelineStage::TailDuplication],
});
pipeline.stages.sort_by_key(|s| s.priority);
pipeline
}
pub fn global_isel_pipeline() -> Self {
Self {
name: "X86-GlobalISel".into(),
target_triple: "x86_64-unknown-linux-gnu".into(),
stages: vec![
PipelineStage {
stage: X86PipelineStage::Legalization,
enabled: true,
priority: 10,
description: "Legalize generic IR operations",
depends_on: vec![],
},
PipelineStage {
stage: X86PipelineStage::RegBankSelect,
enabled: true,
priority: 20,
description: "Register bank selection",
depends_on: vec![X86PipelineStage::Legalization],
},
PipelineStage {
stage: X86PipelineStage::GlobalISel,
enabled: true,
priority: 30,
description: "Pattern-based instruction selection",
depends_on: vec![X86PipelineStage::RegBankSelect],
},
PipelineStage {
stage: X86PipelineStage::CallLowering,
enabled: true,
priority: 35,
description: "Call lowering",
depends_on: vec![X86PipelineStage::GlobalISel],
},
PipelineStage {
stage: X86PipelineStage::PrologueEpilogue,
enabled: true,
priority: 40,
description: "Prologue/epilogue",
depends_on: vec![X86PipelineStage::CallLowering],
},
PipelineStage {
stage: X86PipelineStage::BranchSelection,
enabled: true,
priority: 45,
description: "Branch selection",
depends_on: vec![X86PipelineStage::PrologueEpilogue],
},
PipelineStage {
stage: X86PipelineStage::ExpandPseudo,
enabled: true,
priority: 50,
description: "Expand pseudos",
depends_on: vec![X86PipelineStage::BranchSelection],
},
PipelineStage {
stage: X86PipelineStage::MCFixup,
enabled: true,
priority: 60,
description: "MC fixups",
depends_on: vec![X86PipelineStage::ExpandPseudo],
},
],
}
}
pub fn sorted_stages(&self) -> Vec<&PipelineStage> {
let mut sorted: Vec<&PipelineStage> = self.stages.iter().collect();
sorted.sort_by_key(|s| s.priority);
sorted
}
pub fn enabled_stages(&self) -> Vec<&PipelineStage> {
self.sorted_stages()
.into_iter()
.filter(|s| s.enabled)
.collect()
}
pub fn disable(&mut self, stage: X86PipelineStage) {
if let Some(s) = self.stages.iter_mut().find(|s| s.stage == stage) {
s.enabled = false;
}
}
pub fn enable(&mut self, stage: X86PipelineStage) {
if let Some(s) = self.stages.iter_mut().find(|s| s.stage == stage) {
s.enabled = true;
}
}
pub fn is_enabled(&self, stage: X86PipelineStage) -> bool {
self.stages
.iter()
.find(|s| s.stage == stage)
.map(|s| s.enabled)
.unwrap_or(false)
}
pub fn stage_count(&self) -> usize {
self.stages.len()
}
pub fn enabled_count(&self) -> usize {
self.stages.iter().filter(|s| s.enabled).count()
}
pub fn describe(&self) -> String {
let mut desc = format!("Pipeline: {} (target: {})\n", self.name, self.target_triple);
for stage in self.sorted_stages() {
let status = if stage.enabled { "ENABLED" } else { "DISABLED" };
desc.push_str(&format!(
" [{:02}] {:30} {:8} — {}\n",
stage.priority,
format!("{:?}", stage.stage),
status,
stage.description,
));
}
desc
}
}
pub struct X86PipelineExecutor {
pub pipeline: X86Pipeline,
pub pass: X86CodeGenPass,
}
impl X86PipelineExecutor {
pub fn new(pipeline: X86Pipeline, pass: X86CodeGenPass) -> Self {
Self { pipeline, pass }
}
pub fn execute(&mut self, mf: &mut X86MF) -> bool {
let enabled = self.pipeline.enabled_stages();
for stage in enabled {
let success = match stage.stage {
X86PipelineStage::InstructionSelection => {
true
}
X86PipelineStage::FastISel => {
true
}
X86PipelineStage::GlobalISel => {
true
}
X86PipelineStage::CallLowering => {
true
}
X86PipelineStage::Legalization => {
true
}
X86PipelineStage::RegBankSelect => {
true
}
X86PipelineStage::ISelCombining | X86PipelineStage::MachineCombining => {
self.pass.combiner.run(mf);
true
}
X86PipelineStage::CopyPropagation => {
self.pass.copy_prop.run(mf);
true
}
X86PipelineStage::DeadDefElimination => {
self.pass.dead_def_elim.run(mf);
true
}
X86PipelineStage::LoopInvariantCodeMotion => {
self.pass.licm.run(mf);
true
}
X86PipelineStage::InstructionSinking => {
self.pass.sinking.run(mf);
true
}
X86PipelineStage::PrologueEpilogue => {
self.pass.prolog_epilog.run(mf);
true
}
X86PipelineStage::BranchSelection => {
self.pass.branch_selector.run(mf);
true
}
X86PipelineStage::ExpandPseudo => {
self.pass.expand_pseudo.run(mf);
true
}
X86PipelineStage::ShrinkWrapping => {
self.pass.shrink_wrapping.run(mf);
true
}
X86PipelineStage::TailDuplication => {
self.pass.tail_duplication.run(mf);
true
}
X86PipelineStage::ConstantIslands => {
self.pass.constant_islands.run(mf);
true
}
X86PipelineStage::MCFixup => {
self.pass.mc_fixup.apply_fixups(&mut mf.encoded_bytes, 0);
true
}
X86PipelineStage::DebugInfo => true,
};
if !success {
return false;
}
}
true
}
}
pub fn compute_callee_saved_set(call_conv: X86Conv) -> HashSet<X86PhysReg> {
call_conv.callee_saved_gprs().into_iter().collect()
}
pub fn compute_caller_saved_set(call_conv: X86Conv) -> HashSet<X86PhysReg> {
call_conv.int_arg_regs().into_iter().collect()
}
pub fn fits_i8(val: i64) -> bool {
val >= -128 && val <= 127
}
pub fn fits_i32(val: i64) -> bool {
val >= -0x8000_0000 && val <= 0x7FFF_FFFF
}
pub fn is_imm8(val: i64) -> bool {
fits_i8(val)
}
pub fn type_size_bytes(ty: &str) -> u8 {
match ty {
"i8" | "i8*" => 1,
"i16" | "i16*" => 2,
"i32" | "i32*" | "float" | "float*" => 4,
"i64" | "i64*" | "double" | "double*" => 8,
"i128" | "i128*" => 16,
"<2 x i64>" | "<4 x float>" => 16,
"<4 x i64>" | "<8 x float>" => 32,
"<8 x i64>" | "<16 x float>" => 64,
_ => 8,
}
}
pub fn compute_block_signature(block: &X86MBB) -> u64 {
let mut hash: u64 = block.instructions.len() as u64;
for inst in &block.instructions {
hash = hash.wrapping_mul(31).wrapping_add(inst.opcode as u64);
for op in &inst.operands {
hash = hash.wrapping_mul(17).wrapping_add(match op {
X86MO::VReg(id) => *id as u64,
X86MO::Imm(v) => *v as u64,
_ => 0,
});
}
}
hash
}
pub fn dump_machine_function(mf: &X86MF) -> String {
let mut s = String::new();
s.push_str(&format!("MachineFunction: {}\n", mf.name));
s.push_str(&format!(" Is64Bit: {}\n", mf.is_64bit));
s.push_str(&format!(" CallConv: {:?}\n", mf.call_conv));
s.push_str(&format!(
" Frame: total={}, local={}, saved={}, fp={}\n",
mf.frame.total_size,
mf.frame.local_size,
mf.frame.saved_reg_size,
mf.frame.has_frame_pointer
));
s.push_str(&format!(" Blocks: {}\n", mf.blocks.len()));
s.push_str(&format!(" Instructions: {}\n", mf.total_instructions()));
s.push_str(&format!(" VRegs: {}\n", mf.vreg_counter));
s.push_str(&format!(
" RegActions: {}\n",
mf.reg_alloc_assignments.len()
));
s.push_str(&format!(" SpillSlots: {}\n", mf.spill_slots.len()));
s.push_str("\n");
for block in &mf.blocks {
s.push_str(&format!("{}", block));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_mf(name: &str) -> X86MF {
X86MF::new(name, true, X86Conv::SystemV64)
}
fn make_simple_block() -> X86MBB {
X86MBB::new(0)
}
fn make_arith_func() -> X86MF {
let mut mf = X86MF::new("arith", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(1))
.push_op(X86MO::Imm(42)),
);
block.push_instr(
X86MI::new(DeepX86Opcode::ADDrr)
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(1)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
mf
}
fn make_multi_block_func() -> X86MF {
let mut mf = X86MF::new("multi", true, X86Conv::SystemV64);
let mut b0 = X86MBB::new(0);
b0.add_successor(1);
b0.add_successor(2);
b0.push_instr(
X86MI::new(DeepX86Opcode::CMPrr)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(2)),
);
b0.push_instr(X86MI::new(DeepX86Opcode::JE1).push_op(X86MO::BlockLabel(1)));
b0.push_instr(X86MI::new(DeepX86Opcode::JMP1).push_op(X86MO::BlockLabel(2)));
let mut b1 = X86MBB::new(1);
b1.add_successor(2);
b1.push_instr(
X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(3))
.push_op(X86MO::Imm(1)),
);
b1.push_instr(X86MI::new(DeepX86Opcode::JMP1).push_op(X86MO::BlockLabel(2)));
let mut b2 = X86MBB::new(2);
b2.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(b0);
mf.push_block(b1);
mf.push_block(b2);
mf.compute_predecessors();
mf
}
#[test]
fn test_physreg_is_gpr() {
assert!(X86PhysReg::RAX.is_gpr());
assert!(X86PhysReg::R15.is_gpr());
assert!(!X86PhysReg::XMM0.is_gpr());
}
#[test]
fn test_physreg_is_xmm() {
assert!(X86PhysReg::XMM0.is_xmm());
assert!(X86PhysReg::XMM15.is_xmm());
assert!(!X86PhysReg::RAX.is_xmm());
}
#[test]
fn test_physreg_is_valid() {
assert!(!X86PhysReg::NO_REG.is_valid());
assert!(X86PhysReg::RAX.is_valid());
}
#[test]
fn test_physreg_encoding() {
assert_eq!(X86PhysReg::RAX.encoding(), 0);
assert_eq!(X86PhysReg::RCX.encoding(), 1);
assert_eq!(X86PhysReg::RDX.encoding(), 2);
assert_eq!(X86PhysReg::RBX.encoding(), 3);
assert_eq!(X86PhysReg::RSP.encoding(), 4);
assert_eq!(X86PhysReg::RBP.encoding(), 5);
assert_eq!(X86PhysReg::RSI.encoding(), 6);
assert_eq!(X86PhysReg::RDI.encoding(), 7);
}
#[test]
fn test_physreg_display() {
assert_eq!(format!("{}", X86PhysReg::RAX), "rax");
assert_eq!(format!("{}", X86PhysReg::RBP), "rbp");
assert_eq!(format!("{}", X86PhysReg::RSP), "rsp");
}
#[test]
fn test_reg_class_gpr() {
assert_eq!(X86PhysReg::RAX.reg_class(), X86RegClass::GPR);
assert_eq!(X86PhysReg::R15.reg_class(), X86RegClass::GPR);
}
#[test]
fn test_reg_class_xmm() {
assert_eq!(X86PhysReg::XMM0.reg_class(), X86RegClass::XMM);
assert_eq!(X86PhysReg::XMM15.reg_class(), X86RegClass::XMM);
}
#[test]
fn test_reg_class_size() {
assert_eq!(X86RegClass::GPR.size_bytes(), 8);
assert_eq!(X86RegClass::XMM.size_bytes(), 16);
assert_eq!(X86RegClass::YMM.size_bytes(), 32);
assert_eq!(X86RegClass::ZMM.size_bytes(), 64);
assert_eq!(X86RegClass::None.size_bytes(), 0);
}
#[test]
fn test_opcode_is_terminator() {
assert!(DeepX86Opcode::RET.is_terminator());
assert!(DeepX86Opcode::JMP1.is_terminator());
assert!(DeepX86Opcode::JE1.is_terminator());
assert!(!DeepX86Opcode::ADDrr.is_terminator());
assert!(!DeepX86Opcode::MOVrr.is_terminator());
}
#[test]
fn test_opcode_is_branch() {
assert!(DeepX86Opcode::JMP1.is_branch());
assert!(DeepX86Opcode::JE1.is_branch());
assert!(!DeepX86Opcode::RET.is_branch());
assert!(!DeepX86Opcode::ADDrr.is_branch());
}
#[test]
fn test_opcode_is_call() {
assert!(DeepX86Opcode::CALLpcrel32.is_call());
assert!(!DeepX86Opcode::JMP1.is_call());
}
#[test]
fn test_opcode_is_return() {
assert!(DeepX86Opcode::RET.is_return());
assert!(!DeepX86Opcode::JMP1.is_return());
}
#[test]
fn test_opcode_may_load() {
assert!(DeepX86Opcode::MOVrm.may_load());
assert!(!DeepX86Opcode::MOVrr.may_load());
}
#[test]
fn test_opcode_may_store() {
assert!(DeepX86Opcode::MOVmr.may_store());
assert!(!DeepX86Opcode::MOVrr.may_store());
}
#[test]
fn test_opcode_mnemonic() {
assert_eq!(DeepX86Opcode::ADDrr.mnemonic(), "add");
assert_eq!(DeepX86Opcode::SUBrr.mnemonic(), "sub");
assert_eq!(DeepX86Opcode::MOVrr.mnemonic(), "mov");
assert_eq!(DeepX86Opcode::RET.mnemonic(), "ret");
assert_eq!(DeepX86Opcode::CALLpcrel32.mnemonic(), "call");
}
#[test]
fn test_opcode_is_pseudo() {
assert!(DeepX86Opcode::PHI.is_pseudo());
assert!(DeepX86Opcode::COPY.is_pseudo());
assert!(!DeepX86Opcode::ADDrr.is_pseudo());
}
#[test]
fn test_opcode_has_side_effects() {
assert!(DeepX86Opcode::CALLpcrel32.has_side_effects());
assert!(DeepX86Opcode::RET.has_side_effects());
assert!(DeepX86Opcode::INT3.has_side_effects());
assert!(!DeepX86Opcode::ADDrr.has_side_effects());
}
#[test]
fn test_opcode_display() {
assert_eq!(format!("{}", DeepX86Opcode::ADDrr), "add");
assert_eq!(format!("{}", DeepX86Opcode::RET), "ret");
}
#[test]
fn test_mi_creation() {
let inst = X86MI::new(DeepX86Opcode::ADDrr);
assert_eq!(inst.opcode, DeepX86Opcode::ADDrr);
assert!(inst.operands.is_empty());
}
#[test]
fn test_mi_with_operands() {
let inst = X86MI::new(DeepX86Opcode::ADDrr)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(3));
assert_eq!(inst.num_operands(), 3);
}
#[test]
fn test_mi_flags() {
let inst = X86MI::new(DeepX86Opcode::RET);
assert!(inst.flags.is_terminator);
assert!(inst.flags.is_return);
assert!(!inst.flags.is_branch);
}
#[test]
fn test_mi_to_asm() {
let inst = X86MI::new(DeepX86Opcode::ADDrr)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(3));
let s = inst.to_asm_string();
assert!(s.contains("add"));
assert!(s.contains("%v1"));
assert!(s.contains("%v2"));
assert!(s.contains("%v3"));
}
#[test]
fn test_mi_display() {
let inst = X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(1))
.push_op(X86MO::Imm(42));
let s = format!("{}", inst);
assert!(s.contains("mov"));
assert!(s.contains("42"));
}
#[test]
fn test_mi_set_terminator() {
let inst = X86MI::new(DeepX86Opcode::MOVrr).set_terminator();
assert!(inst.is_terminator());
}
#[test]
fn test_cc_invert() {
assert_eq!(X86CC::E.invert(), X86CC::NE);
assert_eq!(X86CC::NE.invert(), X86CC::E);
assert_eq!(X86CC::B.invert(), X86CC::AE);
assert_eq!(X86CC::AE.invert(), X86CC::B);
assert_eq!(X86CC::G.invert(), X86CC::LE);
assert_eq!(X86CC::GE.invert(), X86CC::L);
}
#[test]
fn test_cc_suffix() {
assert_eq!(X86CC::E.suffix(), "e");
assert_eq!(X86CC::NE.suffix(), "ne");
assert_eq!(X86CC::B.suffix(), "b");
assert_eq!(X86CC::G.suffix(), "g");
}
#[test]
fn test_cc_setcc_opcode() {
assert_eq!(X86CC::E.setcc_opcode(), DeepX86Opcode::SETEr);
assert_eq!(X86CC::NE.setcc_opcode(), DeepX86Opcode::SETNEr);
}
#[test]
fn test_cc_cmov_opcode() {
assert_eq!(X86CC::E.cmov_opcode(), DeepX86Opcode::CMOVErr);
assert_eq!(X86CC::NE.cmov_opcode(), DeepX86Opcode::CMOVNErr);
}
#[test]
fn test_cc_jcc_short() {
assert_eq!(X86CC::E.jcc_opcode_short(), DeepX86Opcode::JE1);
assert_eq!(X86CC::NE.jcc_opcode_short(), DeepX86Opcode::JNE1);
assert_eq!(X86CC::G.jcc_opcode_short(), DeepX86Opcode::JG1);
}
#[test]
fn test_cc_from_icmp_pred() {
assert_eq!(X86CC::from_icmp_pred("eq", false), X86CC::E);
assert_eq!(X86CC::from_icmp_pred("ne", false), X86CC::NE);
assert_eq!(X86CC::from_icmp_pred("ugt", false), X86CC::G);
assert_eq!(X86CC::from_icmp_pred("sgt", true), X86CC::G);
}
#[test]
fn test_cc_display() {
assert_eq!(format!("{}", X86CC::E), "e");
assert_eq!(format!("{}", X86CC::NE), "ne");
}
#[test]
fn test_mo_vreg() {
let op = X86MO::vreg(5);
assert!(op.is_vreg());
assert!(op.is_reg());
assert!(!op.is_imm());
assert_eq!(op.vreg_id(), Some(5));
}
#[test]
fn test_mo_preg() {
let op = X86MO::preg(X86PhysReg::RAX);
assert!(op.is_preg());
assert!(op.is_reg());
assert_eq!(op.preg_reg(), Some(X86PhysReg::RAX));
}
#[test]
fn test_mo_imm() {
let op = X86MO::imm(42);
assert!(op.is_imm());
assert!(!op.is_reg());
assert_eq!(op.as_imm(), Some(42));
}
#[test]
fn test_mo_imm8() {
let op = X86MO::imm8(127);
assert!(op.is_imm());
assert_eq!(op.as_imm(), Some(127));
}
#[test]
fn test_mo_mem_base_disp() {
let op = X86MO::mem_base_disp(1, 16, 8);
assert!(op.is_mem());
assert!(!op.is_reg());
}
#[test]
fn test_mo_mem_full() {
let op = X86MO::mem(Some(1), Some(2), 4, 32, 8);
assert!(op.is_mem());
let s = format!("{}", op);
assert!(s.contains("qword"));
}
#[test]
fn test_mo_label() {
let op = X86MO::block_label(3);
assert!(op.is_label());
let s = format!("{}", op);
assert!(s.contains(".LBB3"));
}
#[test]
fn test_mo_display() {
let op = X86MO::VReg(7);
assert_eq!(format!("{}", op), "%v7");
let op2 = X86MO::PReg(X86PhysReg::RBP);
assert_eq!(format!("{}", op2), "%rbp");
let op3 = X86MO::Imm(-5);
assert_eq!(format!("{}", op3), "-5");
}
#[test]
fn test_mbb_creation() {
let b = X86MBB::new(0);
assert_eq!(b.label, 0);
assert!(b.instructions.is_empty());
assert!(b.successors.is_empty());
assert!(!b.has_terminator());
}
#[test]
fn test_mbb_push_instr() {
let mut b = X86MBB::new(0);
b.push_instr(X86MI::new(DeepX86Opcode::NOP));
assert_eq!(b.len(), 1);
assert!(!b.is_empty());
}
#[test]
fn test_mbb_successors() {
let mut b = X86MBB::new(0);
b.add_successor(1);
b.add_successor(2);
b.add_successor(1); assert_eq!(b.successors.len(), 2);
}
#[test]
fn test_mbb_has_terminator() {
let mut b = X86MBB::new(0);
assert!(!b.has_terminator());
b.push_instr(X86MI::new(DeepX86Opcode::RET));
assert!(b.has_terminator());
}
#[test]
fn test_mbb_insert_before_terminator() {
let mut b = X86MBB::new(0);
b.push_instr(
X86MI::new(DeepX86Opcode::MOVrr)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(2)),
);
b.push_instr(X86MI::new(DeepX86Opcode::RET));
b.insert_before_terminator(X86MI::new(DeepX86Opcode::NOP));
assert_eq!(b.len(), 3);
}
#[test]
fn test_mbb_display() {
let mut b = X86MBB::new(0);
b.push_instr(X86MI::new(DeepX86Opcode::NOP));
let s = format!("{}", b);
assert!(s.contains(".LBB0"));
assert!(s.contains("nop"));
}
#[test]
fn test_mf_creation() {
let mf = make_test_mf("test");
assert_eq!(mf.name, "test");
assert!(mf.is_64bit);
assert_eq!(mf.call_conv, X86Conv::SystemV64);
assert_eq!(mf.vreg_counter, 1);
}
#[test]
fn test_mf_new_vreg() {
let mut mf = make_test_mf("test");
assert_eq!(mf.new_vreg(), 1);
assert_eq!(mf.new_vreg(), 2);
assert_eq!(mf.vreg_counter, 3);
}
#[test]
fn test_mf_push_block() {
let mut mf = make_test_mf("test");
let label = mf.push_block(X86MBB::new(42));
assert_eq!(label, 42);
assert_eq!(mf.blocks.len(), 1);
}
#[test]
fn test_mf_total_instructions() {
let mf = make_arith_func();
assert_eq!(mf.total_instructions(), 3);
}
#[test]
fn test_mf_compute_predecessors() {
let mf = make_multi_block_func();
assert_eq!(mf.blocks[1].predecessors.len(), 1);
assert_eq!(mf.blocks[2].predecessors.len(), 2);
}
#[test]
fn test_mf_entry_block() {
let mf = make_arith_func();
assert!(mf.entry_block().is_some());
assert_eq!(mf.entry_block().unwrap().label, 0);
}
#[test]
fn test_mf_new_block() {
let mut mf = make_test_mf("test");
let label = mf.new_block();
assert_eq!(label, 0);
let label2 = mf.new_block();
assert_eq!(label2, 1);
}
#[test]
fn test_mf_assign_instr_ids() {
let mut mf = make_arith_func();
mf.assign_instr_ids();
assert_eq!(mf.blocks[0].instructions[0].id, 0);
assert_eq!(mf.blocks[0].instructions[1].id, 1);
assert_eq!(mf.blocks[0].instructions[2].id, 2);
}
#[test]
fn test_mf_estimate_code_size() {
let mf = make_arith_func();
assert!(mf.estimate_code_size() > 0);
}
#[test]
fn test_conv_is_64bit() {
assert!(X86Conv::SystemV64.is_64bit());
assert!(X86Conv::Win64.is_64bit());
assert!(!X86Conv::CDecl32.is_64bit());
}
#[test]
fn test_conv_uses_red_zone() {
assert!(X86Conv::SystemV64.uses_red_zone());
assert!(!X86Conv::Win64.uses_red_zone());
}
#[test]
fn test_conv_callee_saved_sysv64() {
let saved = X86Conv::SystemV64.callee_saved_gprs();
assert!(saved.contains(&X86PhysReg::RBX));
assert!(saved.contains(&X86PhysReg::RBP));
assert!(saved.contains(&X86PhysReg::R12));
assert!(saved.contains(&X86PhysReg::R13));
assert!(saved.contains(&X86PhysReg::R14));
assert!(saved.contains(&X86PhysReg::R15));
}
#[test]
fn test_conv_callee_saved_win64() {
let saved = X86Conv::Win64.callee_saved_gprs();
assert!(saved.contains(&X86PhysReg::RBX));
assert!(saved.contains(&X86PhysReg::RDI));
assert!(saved.contains(&X86PhysReg::RSI));
}
#[test]
fn test_conv_int_arg_regs_sysv64() {
let regs = X86Conv::SystemV64.int_arg_regs();
assert_eq!(regs.len(), 6);
assert_eq!(regs[0], X86PhysReg::RDI);
assert_eq!(regs[1], X86PhysReg::RSI);
}
#[test]
fn test_conv_sse_arg_regs() {
let regs = X86Conv::SystemV64.sse_arg_regs();
assert_eq!(regs.len(), 8);
assert_eq!(regs[0], X86PhysReg::XMM0);
}
#[test]
fn test_conv_return_reg() {
assert_eq!(X86Conv::SystemV64.return_gpr(), X86PhysReg::RAX);
assert_eq!(X86Conv::SystemV64.return_xmm(), X86PhysReg::XMM0);
}
#[test]
fn test_conv_stack_alignment() {
assert_eq!(X86Conv::SystemV64.stack_alignment(), 16);
assert_eq!(X86Conv::CDecl32.stack_alignment(), 4);
}
#[test]
fn test_selection_dag_new() {
let dag = SelectionDAG::new();
assert_eq!(dag.node_count(), 0);
assert!(dag.root.is_none());
}
#[test]
fn test_selection_dag_add_node() {
let mut dag = SelectionDAG::new();
let id = dag.add_node("add", "i32");
assert_eq!(id, 0);
assert_eq!(dag.node_count(), 1);
}
#[test]
fn test_selection_dag_add_constant() {
let mut dag = SelectionDAG::new();
let id1 = dag.add_constant(42, "i32");
let id2 = dag.add_constant(42, "i32"); assert_eq!(id1, id2);
}
#[test]
fn test_selection_dag_set_operand() {
let mut dag = SelectionDAG::new();
let a = dag.add_node("add", "i32");
let c = dag.add_constant(10, "i32");
dag.set_operand(a, c);
assert_eq!(dag.get_node(a).unwrap().operands[0], c);
}
#[test]
fn test_sdnode_new() {
let node = SDNode::new("add", "i32", 0);
assert_eq!(node.opcode, "add");
assert_eq!(node.value_type, "i32");
assert_eq!(node.node_id, 0);
}
#[test]
fn test_sdnode_constant() {
let node = SDNode::constant(99, "i64", 1);
assert_eq!(node.opcode, "Constant");
assert_eq!(node.constant_value, Some(99));
}
#[test]
fn test_dag_pattern_new() {
let pat = DAGPattern::new("test_add", "add", DeepX86Opcode::ADDrr);
assert_eq!(pat.name, "test_add");
assert_eq!(pat.match_opcode, "add");
assert_eq!(pat.result_opcode, DeepX86Opcode::ADDrr);
assert_eq!(pat.cost, 1);
}
#[test]
fn test_dag_pattern_with_cost() {
let pat = DAGPattern::new("test", "mul", DeepX86Opcode::IMULrr).with_cost(3);
assert_eq!(pat.cost, 3);
}
#[test]
fn test_dag_to_dag_new() {
let features = ["sse", "sse2"].iter().map(|s| s.to_string()).collect();
let selector = X86DAGToDAG::new(true, features);
assert!(selector.pattern_count() > 0);
}
#[test]
fn test_dag_to_dag_default() {
let selector = X86DAGToDAG::default();
assert!(selector.patterns().len() > 5);
}
#[test]
fn test_dag_to_dag_select_pattern() {
let features: HashSet<String> = ["sse", "sse2"].iter().map(|s| s.to_string()).collect();
let selector = X86DAGToDAG::new(true, features);
let pat = selector.select_pattern("add");
assert!(pat.is_some());
assert_eq!(pat.unwrap().result_opcode, DeepX86Opcode::ADDrr);
}
#[test]
fn test_dag_to_dag_run_simple() {
let features: HashSet<String> = ["sse", "sse2"].iter().map(|s| s.to_string()).collect();
let mut selector = X86DAGToDAG::new(true, features);
let mut dag = SelectionDAG::new();
let c1 = dag.add_constant(10, "i32");
let c2 = dag.add_constant(20, "i32");
let add = dag.add_node("add", "i32");
dag.set_operand(add, c1);
dag.set_operand(add, c2);
dag.set_root(add);
let result = selector.run(&dag);
assert!(!result.is_empty());
assert!(result.iter().any(|i| i.opcode.mnemonic() == "add"));
}
#[test]
fn test_dag_to_dag_run_empty() {
let features: HashSet<String> = ["sse", "sse2"].iter().map(|s| s.to_string()).collect();
let mut selector = X86DAGToDAG::new(true, features);
let dag = SelectionDAG::new();
let result = selector.run(&dag);
assert!(result.is_empty());
}
#[test]
fn test_dag_to_dag_stats() {
let features: HashSet<String> = ["sse", "sse2"].iter().map(|s| s.to_string()).collect();
let mut selector = X86DAGToDAG::new(true, features);
let mut dag = SelectionDAG::new();
dag.add_node("add", "i32");
dag.add_node("sub", "i32");
dag.add_node("mul", "i32");
let _ = selector.run(&dag);
assert!(selector.stats.nodes_matched > 0);
assert!(selector.stats.instructions_emitted > 0);
}
#[test]
fn test_fast_isel_new() {
let isel = X86FastISel::new(true);
assert!(isel.is_64bit);
assert_eq!(isel.vreg_counter, 1);
assert!(isel.instructions.is_empty());
}
#[test]
fn test_fast_isel_select_add() {
let mut isel = X86FastISel::new(true);
let dest = isel.select_add(1, 2, false, 0);
assert_eq!(dest, 1);
assert_eq!(isel.instructions.len(), 1);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::ADDrr);
}
#[test]
fn test_fast_isel_select_add_imm8() {
let mut isel = X86FastISel::new(true);
let dest = isel.select_add(1, 2, true, 42);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::ADDri8);
}
#[test]
fn test_fast_isel_select_sub() {
let mut isel = X86FastISel::new(true);
isel.select_sub(1, 2, false, 0);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::SUBrr);
}
#[test]
fn test_fast_isel_select_and() {
let mut isel = X86FastISel::new(true);
isel.select_and(1, 2, false, 0);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::ANDrr);
}
#[test]
fn test_fast_isel_select_or() {
let mut isel = X86FastISel::new(true);
isel.select_or(1, 2, false, 0);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::ORrr);
}
#[test]
fn test_fast_isel_select_xor() {
let mut isel = X86FastISel::new(true);
isel.select_xor(1, 2, false, 0);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::XORrr);
}
#[test]
fn test_fast_isel_select_mul() {
let mut isel = X86FastISel::new(true);
isel.select_mul(1, 2, false, 0);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::IMULrr);
}
#[test]
fn test_fast_isel_select_shl() {
let mut isel = X86FastISel::new(true);
isel.select_shl(1, 2, true, 3);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::SHLri);
}
#[test]
fn test_fast_isel_select_load() {
let mut isel = X86FastISel::new(true);
let dest = isel.select_load(1, 4);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::MOVrm);
assert_eq!(dest, 1);
}
#[test]
fn test_fast_isel_select_store() {
let mut isel = X86FastISel::new(true);
isel.select_store(1, 2, 4);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::MOVmr);
}
#[test]
fn test_fast_isel_select_br() {
let mut isel = X86FastISel::new(true);
isel.select_br(3);
assert!(isel.instructions[0].flags.is_terminator);
}
#[test]
fn test_fast_isel_select_ret() {
let mut isel = X86FastISel::new(true);
isel.select_ret(None);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::RET);
assert!(isel.instructions[0].flags.is_terminator);
}
#[test]
fn test_fast_isel_select_icmp() {
let mut isel = X86FastISel::new(true);
isel.select_icmp(1, 2, false, 0);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::CMPrr);
}
#[test]
fn test_fast_isel_select_mov_imm() {
let mut isel = X86FastISel::new(true);
let dest = isel.select_mov_imm(42);
assert_eq!(dest, 1);
}
#[test]
fn test_fast_isel_reset() {
let mut isel = X86FastISel::new(true);
isel.select_add(1, 2, false, 0);
isel.reset();
assert!(isel.instructions.is_empty());
assert_eq!(isel.vreg_counter, 1);
}
#[test]
fn test_fast_isel_default() {
let isel = X86FastISel::default();
assert!(isel.is_64bit);
}
#[test]
fn test_gmi_inst_new() {
let inst = GMIrInst::new(GMIrOpcode::G_ADD);
assert!(!inst.flags.is_terminator);
}
#[test]
fn test_gmi_inst_with_operands() {
let inst = GMIrInst::new(GMIrOpcode::G_ADD)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2))
.with_use(GMOperand::VReg(3));
assert_eq!(inst.defs.len(), 1);
assert_eq!(inst.uses.len(), 2);
}
#[test]
fn test_x86_global_isel_new() {
let isel = X86GlobalISel::new(true);
assert!(isel.is_64bit);
}
#[test]
fn test_global_isel_selector_select() {
let selector = X86GlobalISelSelector::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_ADD)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2))
.with_use(GMOperand::VReg(3));
let result = selector.select(&[inst]);
assert!(!result.is_empty());
assert_eq!(result[0].opcode, DeepX86Opcode::ADDrr);
}
#[test]
fn test_global_isel_selector_sub() {
let selector = X86GlobalISelSelector::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_SUB)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2))
.with_use(GMOperand::VReg(3));
let result = selector.select(&[inst]);
assert_eq!(result[0].opcode, DeepX86Opcode::SUBrr);
}
#[test]
fn test_global_isel_selector_fadd() {
let selector = X86GlobalISelSelector::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_FADD)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2))
.with_use(GMOperand::VReg(3));
let result = selector.select(&[inst]);
assert_eq!(result[0].opcode, DeepX86Opcode::ADDSSrr);
}
#[test]
fn test_global_isel_selector_load() {
let selector = X86GlobalISelSelector::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_LOAD)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2));
let result = selector.select(&[inst]);
assert_eq!(result[0].opcode, DeepX86Opcode::MOVrm);
}
#[test]
fn test_global_isel_selector_store() {
let selector = X86GlobalISelSelector::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_STORE)
.with_use(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2));
let result = selector.select(&[inst]);
assert_eq!(result[0].opcode, DeepX86Opcode::MOVmr);
}
#[test]
fn test_global_isel_selector_return() {
let selector = X86GlobalISelSelector::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_RETURN);
let result = selector.select(&[inst]);
assert_eq!(result[0].opcode, DeepX86Opcode::RET);
assert!(result[0].flags.is_terminator);
}
#[test]
fn test_call_lowering_new() {
let lowering = X86CallLowering::new(true);
assert!(lowering.is_64bit);
assert!(!lowering.has_calls);
}
#[test]
fn test_call_lowering_classify_int_arg() {
let lowering = X86CallLowering::new(true);
let result = lowering.classify_arg(8, false);
match result {
ArgAssignment::Register { .. } => {}
_ => panic!("Expected register assignment"),
}
}
#[test]
fn test_call_lowering_lower_returns() {
let lowering = X86CallLowering::new(true);
let ret_inst = GMIrInst::new(GMIrOpcode::G_RETURN).with_use(GMOperand::VReg(42));
let lowered = lowering.lower_return(&ret_inst);
assert!(lowered.is_ok());
assert!(!lowered.unwrap().is_empty());
}
#[test]
fn test_legalizer_new() {
let legalizer = X86Legalizer::new(true);
assert!(legalizer.is_64bit);
assert_eq!(legalizer.max_legal_scalar, 64);
}
#[test]
fn test_legalizer_classify_add() {
let legalizer = X86Legalizer::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_ADD);
assert_eq!(legalizer.classify(&inst), LegalAction::Legal);
}
#[test]
fn test_legalizer_is_type_legal() {
let legalizer = X86Legalizer::new(true);
assert!(legalizer.is_type_legal(8));
assert!(legalizer.is_type_legal(32));
assert!(legalizer.is_type_legal(64));
assert!(!legalizer.is_type_legal(128));
}
#[test]
fn test_legalizer_vector_legal() {
let legalizer = X86Legalizer::new(true);
assert!(legalizer.is_vector_type_legal(128));
}
#[test]
fn test_legalizer_with_features() {
let mut features = HashSet::new();
features.insert("avx".to_string());
features.insert("avx512f".to_string());
let legalizer = X86Legalizer::new(true).with_features(&features);
assert!(legalizer.has_avx);
assert!(legalizer.has_avx512);
assert_eq!(legalizer.max_legal_vector, 512);
}
#[test]
fn test_legalizer_legalize_empty() {
let legalizer = X86Legalizer::new(true);
let result = legalizer.legalize(&[]);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_legalizer_legalize_add() {
let legalizer = X86Legalizer::new(true);
let inst = GMIrInst::new(GMIrOpcode::G_ADD)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2))
.with_use(GMOperand::VReg(3));
let result = legalizer.legalize(&[inst]);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn test_regbank_select_new() {
let selector = X86RegBankSelect::new(true);
assert!(selector.is_64bit);
}
#[test]
fn test_regbank_from_opcode() {
let selector = X86RegBankSelect::new(true);
assert_eq!(
selector.infer_bank_from_opcode(&GMIrOpcode::G_ADD),
RegBank::GPR
);
assert_eq!(
selector.infer_bank_from_opcode(&GMIrOpcode::G_FADD),
RegBank::XMM
);
assert_eq!(
selector.infer_bank_from_opcode(&GMIrOpcode::G_SHUFFLE_VECTOR),
RegBank::XMM
);
}
#[test]
fn test_regbank_needs_cross_bank_copy() {
let mut selector = X86RegBankSelect::new(true);
selector.set_bank(1, RegBank::GPR);
selector.set_bank(2, RegBank::XMM);
assert!(selector.needs_cross_bank_copy(1, 2));
assert!(!selector.needs_cross_bank_copy(1, 1));
}
#[test]
fn test_emitter_new() {
let emitter = X86InstrEmitter::new(true);
assert!(emitter.is_64bit);
assert!(emitter.get_instructions().is_empty());
}
#[test]
fn test_emitter_emit_binary_op() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_binary_op(DeepX86Opcode::ADDrr, 1, 2, 3);
assert_eq!(emitter.get_instructions().len(), 1);
assert_eq!(emitter.stats.instructions_emitted, 1);
}
#[test]
fn test_emitter_emit_mov() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_mov(1, 2);
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::MOVrr);
}
#[test]
fn test_emitter_emit_load() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_load(1, 2, 8, 4);
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::MOVrm);
}
#[test]
fn test_emitter_emit_store() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_store(1, 2, 0, 4);
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::MOVmr);
}
#[test]
fn test_emitter_emit_return() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_return(None);
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::RET);
assert!(instrs[0].flags.is_terminator);
}
#[test]
fn test_emitter_emit_call() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_call("printf");
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::CALLpcrel32);
}
#[test]
fn test_emitter_emit_lea() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_lea(1, 2, Some(3), 4, 16);
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::LEA64r);
}
#[test]
fn test_emitter_clear() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_nop();
emitter.clear();
assert!(emitter.get_instructions().is_empty());
}
#[test]
fn test_prolog_epilog_new() {
let inserter = X86PrologEpilogInserter::new(true, X86Conv::SystemV64);
assert!(inserter.is_64bit);
assert!(inserter.eliminate_frame_pointer);
}
#[test]
fn test_prolog_epilog_run() {
let mut inserter = X86PrologEpilogInserter::new(true, X86Conv::SystemV64);
let mut mf = make_arith_func();
inserter.run(&mut mf);
assert_eq!(inserter.stats.functions_processed, 1);
}
#[test]
fn test_prolog_epilog_with_frame_pointer() {
let mut inserter =
X86PrologEpilogInserter::new(true, X86Conv::SystemV64).with_frame_pointer();
assert!(!inserter.eliminate_frame_pointer);
}
#[test]
fn test_branch_selector_new() {
let selector = X86BranchSelector::new(true);
assert_eq!(selector.short_branch_limit, 127);
}
#[test]
fn test_branch_selector_run() {
let mut selector = X86BranchSelector::new(true);
let mut mf = make_multi_block_func();
selector.run(&mut mf);
assert!(selector.stats.branches_analyzed > 0);
}
#[test]
fn test_branch_selector_compute_offsets() {
let selector = X86BranchSelector::new(true);
let mf = make_arith_func();
let offsets = selector.compute_block_offsets(&mf);
assert_eq!(offsets.len(), 1);
assert_eq!(offsets[0], 0);
}
#[test]
fn test_expand_pseudo_new() {
let expander = X86ExpandPseudo::new(true);
assert!(expander.is_64bit);
}
#[test]
fn test_expand_pseudo_run() {
let mut expander = X86ExpandPseudo::new(true);
let mut mf = make_mf_with_phi();
expander.run(&mut mf);
assert!(expander.stats.phis_expanded > 0);
}
fn make_mf_with_phi() -> X86MF {
let mut mf = X86MF::new("phi_func", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::PHI)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(2)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
mf
}
#[test]
fn test_copy_prop_new() {
let cp = X86MachineCopyProp::new(true);
assert!(cp.is_64bit);
}
#[test]
fn test_copy_prop_run() {
let mut cp = X86MachineCopyProp::new(true);
let mut mf = make_mf_with_copy();
cp.run(&mut mf);
assert!(cp.stats.blocks_analyzed > 0);
}
fn make_mf_with_copy() -> X86MF {
let mut mf = X86MF::new("copy_func", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::COPY)
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(1)),
);
block.push_instr(
X86MI::new(DeepX86Opcode::ADDrr)
.push_op(X86MO::VReg(3))
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(4)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
mf
}
#[test]
fn test_dead_def_new() {
let dde = X86MachineDeadDefElim::new(true);
assert!(dde.is_64bit);
}
#[test]
fn test_dead_def_run() {
let mut dde = X86MachineDeadDefElim::new(true);
let mut mf = make_mf_with_dead_def();
dde.run(&mut mf);
assert!(dde.stats.blocks_analyzed > 0);
}
fn make_mf_with_dead_def() -> X86MF {
let mut mf = X86MF::new("dead_func", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(1))
.push_op(X86MO::Imm(42)),
);
block.push_instr(
X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(2))
.push_op(X86MO::Imm(99)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET).push_op(X86MO::VReg(2)));
mf.push_block(block);
mf
}
#[test]
fn test_licm_new() {
let licm = X86MachineLICM::new(true);
assert!(licm.is_64bit);
}
#[test]
fn test_licm_detect_loops() {
let mut licm = X86MachineLICM::new(true);
let mf = make_loop_func();
licm.detect_loops(&mf);
assert!(licm.loop_info.len() >= 0);
}
#[test]
fn test_licm_run() {
let mut licm = X86MachineLICM::new(true);
let mut mf = make_loop_func();
licm.run(&mut mf);
assert!(licm.stats.loops_analyzed >= 1);
}
fn make_loop_func() -> X86MF {
let mut mf = X86MF::new("loop_func", true, X86Conv::SystemV64);
let mut b0 = X86MBB::new(0);
b0.add_successor(1);
b0.push_instr(X86MI::new(DeepX86Opcode::JMP1).push_op(X86MO::BlockLabel(1)));
let mut b1 = X86MBB::new(1);
b1.add_successor(1); b1.add_successor(2);
b1.push_instr(
X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(1))
.push_op(X86MO::Imm(42)),
); b1.push_instr(X86MI::new(DeepX86Opcode::JE1).push_op(X86MO::BlockLabel(1)));
let mut b2 = X86MBB::new(2);
b2.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(b0);
mf.push_block(b1);
mf.push_block(b2);
mf.compute_predecessors();
mf
}
#[test]
fn test_combiner_new() {
let combiner = X86MachineCombiner::new(true);
assert!(combiner.enable_lea_formation);
}
#[test]
fn test_combiner_lea_formation() {
let mut combiner = X86MachineCombiner::new(true);
let mut mf = make_mf_for_combine();
combiner.run(&mut mf);
}
fn make_mf_for_combine() -> X86MF {
let mut mf = X86MF::new("combine_func", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::ADDrr)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(3)),
);
block.push_instr(
X86MI::new(DeepX86Opcode::ADDri8)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(1))
.push_op(X86MO::Imm8(4)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
mf
}
#[test]
fn test_shrink_wrap_new() {
let sw = X86ShrinkWrapping::new(true);
assert!(sw.enabled);
}
#[test]
fn test_shrink_wrap_run() {
let mut sw = X86ShrinkWrapping::new(true);
let mut mf = make_arith_func();
sw.run(&mut mf);
assert_eq!(sw.stats.functions_analyzed, 1);
}
#[test]
fn test_tail_dup_new() {
let td = X86TailDuplication::new(true);
assert_eq!(td.max_tail_size, 6);
}
#[test]
fn test_tail_dup_run() {
let mut td = X86TailDuplication::new(true);
let mut mf = make_multi_block_func();
td.run(&mut mf);
assert!(td.stats.tails_analyzed >= 1);
}
#[test]
fn test_const_islands_new() {
let ci = X86ConstantIslands::new(true);
assert_eq!(ci.max_const_pool_distance, 1024);
}
#[test]
fn test_const_islands_run_empty() {
let mut ci = X86ConstantIslands::new(true);
let mut mf = make_test_mf("empty");
ci.run(&mut mf);
assert_eq!(ci.stats.islands_placed, 0);
}
#[test]
fn test_mc_fixup_new() {
let fixup = X86MCFixup::new();
assert!(fixup.fixups.is_empty());
}
#[test]
fn test_mc_fixup_record() {
let mut fixup = X86MCFixup::new();
fixup.record_fixup(0, 42, MCFixupKind::FK_PCRel_4, Some("target".into()));
assert_eq!(fixup.fixups.len(), 1);
assert_eq!(fixup.stats.fixups_created, 1);
assert_eq!(fixup.stats.pcrel_fixups, 1);
}
#[test]
fn test_mc_fixup_apply() {
let mut fixup = X86MCFixup::new();
fixup.record_fixup(0, 100, MCFixupKind::FK_Data_4, None);
let mut data = vec![0u8; 4];
fixup.apply_fixups(&mut data, 0);
assert_eq!(data[0], 100);
}
#[test]
fn test_fixup_kind_size() {
assert_eq!(MCFixupKind::FK_Data_1.size(), 1);
assert_eq!(MCFixupKind::FK_Data_4.size(), 4);
assert_eq!(MCFixupKind::FK_Data_8.size(), 8);
assert_eq!(MCFixupKind::FK_PCRel_4.size(), 4);
}
#[test]
fn test_fixup_kind_is_pc_relative() {
assert!(MCFixupKind::FK_PCRel_4.is_pc_relative());
assert!(MCFixupKind::FK_GOTPCREL.is_pc_relative());
assert!(!MCFixupKind::FK_Data_4.is_pc_relative());
}
#[test]
fn test_mc_fixup_clear() {
let mut fixup = X86MCFixup::new();
fixup.record_fixup(0, 1, MCFixupKind::FK_Data_4, None);
fixup.clear();
assert!(fixup.fixups.is_empty());
}
#[test]
fn test_pipeline_o0() {
let pipeline = X86Pipeline::o0_pipeline();
assert_eq!(pipeline.name, "X86-O0");
assert!(pipeline.stages.len() > 0);
assert!(pipeline.is_enabled(X86PipelineStage::FastISel));
}
#[test]
fn test_pipeline_o2() {
let pipeline = X86Pipeline::o2_pipeline();
assert_eq!(pipeline.name, "X86-O2");
assert!(pipeline.is_enabled(X86PipelineStage::InstructionSelection));
assert!(pipeline.is_enabled(X86PipelineStage::CopyPropagation));
assert!(pipeline.is_enabled(X86PipelineStage::DeadDefElimination));
}
#[test]
fn test_pipeline_o3() {
let pipeline = X86Pipeline::o3_pipeline();
assert_eq!(pipeline.name, "X86-O3");
assert!(pipeline.is_enabled(X86PipelineStage::TailDuplication));
assert!(pipeline.is_enabled(X86PipelineStage::ConstantIslands));
}
#[test]
fn test_pipeline_global_isel() {
let pipeline = X86Pipeline::global_isel_pipeline();
assert!(pipeline.is_enabled(X86PipelineStage::Legalization));
assert!(pipeline.is_enabled(X86PipelineStage::RegBankSelect));
assert!(pipeline.is_enabled(X86PipelineStage::GlobalISel));
}
#[test]
fn test_pipeline_disable_stage() {
let mut pipeline = X86Pipeline::o2_pipeline();
assert!(pipeline.is_enabled(X86PipelineStage::CopyPropagation));
pipeline.disable(X86PipelineStage::CopyPropagation);
assert!(!pipeline.is_enabled(X86PipelineStage::CopyPropagation));
}
#[test]
fn test_pipeline_enable_stage() {
let mut pipeline = X86Pipeline::o0_pipeline();
assert!(!pipeline.is_enabled(X86PipelineStage::InstructionSelection));
pipeline.enable(X86PipelineStage::InstructionSelection);
assert!(pipeline.is_enabled(X86PipelineStage::InstructionSelection));
}
#[test]
fn test_pipeline_stage_count() {
let pipeline = X86Pipeline::o2_pipeline();
assert!(pipeline.stage_count() > 5);
assert!(pipeline.enabled_count() > 5);
}
#[test]
fn test_pipeline_sorted() {
let pipeline = X86Pipeline::o2_pipeline();
let sorted = pipeline.sorted_stages();
for i in 1..sorted.len() {
assert!(sorted[i].priority >= sorted[i - 1].priority);
}
}
#[test]
fn test_pipeline_describe() {
let pipeline = X86Pipeline::o0_pipeline();
let desc = pipeline.describe();
assert!(desc.contains("X86-O0"));
assert!(desc.contains("ENABLED"));
}
#[test]
fn test_codegen_pass_new() {
let config = X86CodeGenConfig::default();
let pass = X86CodeGenPass::new(config);
assert_eq!(pass.config.opt_level, 2);
}
#[test]
fn test_codegen_pass_o0() {
let pass = X86CodeGenPass::for_o0();
assert_eq!(pass.config.opt_level, 0);
assert!(pass.config.enable_fast_isel);
}
#[test]
fn test_codegen_pass_o2() {
let pass = X86CodeGenPass::for_o2();
assert_eq!(pass.config.opt_level, 2);
assert!(pass.config.enable_dag_to_dag);
}
#[test]
fn test_codegen_pass_o3() {
let pass = X86CodeGenPass::for_o3();
assert_eq!(pass.config.opt_level, 3);
assert!(pass.config.enable_licm);
assert!(pass.config.enable_tail_duplication);
}
#[test]
fn test_codegen_pass_run_on_dag() {
let config = X86CodeGenConfig::default();
let mut pass = X86CodeGenPass::new(config);
let mut dag = SelectionDAG::new();
dag.add_node("add", "i32");
dag.add_node("ret", "void");
let result = pass.run_on_dag(&dag);
assert!(!result.is_empty());
assert!(pass.pipeline_stats.machine_instrs_emitted > 0);
}
#[test]
fn test_codegen_pass_run_pipeline() {
let config = X86CodeGenConfig::default();
let mut pass = X86CodeGenPass::new(config);
let mut mf = make_arith_func();
let success = pass.run_pipeline(&mut mf);
assert!(success);
}
#[test]
fn test_codegen_pass_pipeline_summary() {
let config = X86CodeGenConfig::default();
let mut pass = X86CodeGenPass::new(config);
let mut mf = make_arith_func();
pass.run_pipeline(&mut mf);
let summary = pass.pipeline_summary();
assert!(summary.contains("X86CodeGenPass"));
}
#[test]
fn test_codegen_pass_reset() {
let mut pass = X86CodeGenPass::for_o2();
let mut dag = SelectionDAG::new();
dag.add_node("add", "i32");
pass.run_on_dag(&dag);
pass.reset();
assert_eq!(pass.pipeline_stats.machine_instrs_emitted, 0);
}
#[test]
fn test_codegen_config_default() {
let config = X86CodeGenConfig::default();
assert!(config.is_64bit);
assert_eq!(config.opt_level, 2);
assert!(config.features.contains("sse2"));
assert!(config.features.contains("avx"));
}
#[test]
fn test_executor_new() {
let pipeline = X86Pipeline::o2_pipeline();
let pass = X86CodeGenPass::for_o2();
let executor = X86PipelineExecutor::new(pipeline, pass);
assert_eq!(executor.pipeline.name, "X86-O2");
}
#[test]
fn test_executor_execute() {
let pipeline = X86Pipeline::o2_pipeline();
let pass = X86CodeGenPass::for_o2();
let mut executor = X86PipelineExecutor::new(pipeline, pass);
let mut mf = make_arith_func();
assert!(executor.execute(&mut mf));
}
#[test]
fn test_executor_execute_multi_block() {
let pipeline = X86Pipeline::o2_pipeline();
let pass = X86CodeGenPass::for_o2();
let mut executor = X86PipelineExecutor::new(pipeline, pass);
let mut mf = make_multi_block_func();
assert!(executor.execute(&mut mf));
}
#[test]
fn test_fits_i8() {
assert!(fits_i8(0));
assert!(fits_i8(127));
assert!(fits_i8(-128));
assert!(!fits_i8(128));
assert!(!fits_i8(-129));
assert!(!fits_i8(1000));
}
#[test]
fn test_fits_i32() {
assert!(fits_i32(0));
assert!(fits_i32(0x7FFF_FFFF));
assert!(fits_i32(-0x8000_0000));
assert!(!fits_i32(0x8000_0000));
}
#[test]
fn test_is_imm8() {
assert!(is_imm8(42));
assert!(is_imm8(-1));
assert!(!is_imm8(200));
}
#[test]
fn test_type_size_bytes() {
assert_eq!(type_size_bytes("i8"), 1);
assert_eq!(type_size_bytes("i32"), 4);
assert_eq!(type_size_bytes("i64"), 8);
assert_eq!(type_size_bytes("float"), 4);
assert_eq!(type_size_bytes("double"), 8);
assert_eq!(type_size_bytes("<4 x float>"), 16);
}
#[test]
fn test_compute_block_signature() {
let b = make_simple_block();
let sig = compute_block_signature(&b);
assert_eq!(sig, 0); }
#[test]
fn test_dump_machine_function() {
let mf = make_arith_func();
let s = dump_machine_function(&mf);
assert!(s.contains("MachineFunction: arith"));
assert!(s.contains("Is64Bit: true"));
assert!(s.contains(".LBB0"));
}
#[test]
fn test_full_pipeline_empty_function() {
let mut pass = X86CodeGenPass::for_o2();
let mut mf = make_test_mf("empty_test");
mf.push_block(X86MBB::new(0));
assert!(pass.run_pipeline(&mut mf));
}
#[test]
fn test_full_pipeline_arith_function() {
let mut pass = X86CodeGenPass::for_o2();
let mut mf = make_arith_func();
assert!(pass.run_pipeline(&mut mf));
assert!(pass.pipeline_stats.machine_instrs_emitted > 0);
}
#[test]
fn test_full_pipeline_o2_all_enabled() {
let mut pass = X86CodeGenPass::for_o2();
let mut mf = make_arith_func();
let result = pass.run_pipeline(&mut mf);
assert!(result);
}
#[test]
fn test_full_pipeline_o3_multi_block() {
let mut pass = X86CodeGenPass::for_o3();
let mut mf = make_multi_block_func();
let result = pass.run_pipeline(&mut mf);
assert!(result);
}
#[test]
fn test_full_pipeline_o0_fast_isel() {
let mut pass = X86CodeGenPass::for_o0();
let mut mf = make_arith_func();
let result = pass.run_pipeline(&mut mf);
assert!(result);
}
#[test]
fn test_dag_to_pipeline_then_pipeline() {
let config = X86CodeGenConfig::default();
let mut pass = X86CodeGenPass::new(config);
let mut dag = SelectionDAG::new();
let c1 = dag.add_constant(10, "i32");
let c2 = dag.add_constant(20, "i32");
let add = dag.add_node("add", "i32");
dag.set_operand(add, c1);
dag.set_operand(add, c2);
let _ = pass.run_on_dag(&dag);
assert!(pass.pipeline_stats.machine_instrs_emitted > 0);
}
#[test]
fn test_global_isel_full_pipeline() {
let mut pass = X86CodeGenPass::for_o2();
let insts = vec![
GMIrInst::new(GMIrOpcode::G_ADD)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2))
.with_use(GMOperand::VReg(3)),
GMIrInst::new(GMIrOpcode::G_RETURN),
];
let result = pass.run_global_isel(&insts);
assert!(result.is_ok());
assert!(!result.unwrap().is_empty());
}
#[test]
fn test_all_mo_constructors() {
let vreg = X86MO::vreg(1);
let preg = X86MO::preg(X86PhysReg::RAX);
let imm = X86MO::imm(42);
let imm8 = X86MO::imm8(7);
let fi = X86MO::frame_index(2);
let label = X86MO::block_label(3);
let global = X86MO::global("main");
let ext = X86MO::external("printf");
let cp = X86MO::cp_index(4);
assert!(vreg.is_vreg());
assert!(preg.is_preg());
assert!(imm.is_imm());
assert!(imm8.is_imm());
assert!(label.is_label());
}
#[test]
fn test_all_cc_variants() {
let all_cc = [
X86CC::O,
X86CC::NO,
X86CC::B,
X86CC::AE,
X86CC::E,
X86CC::NE,
X86CC::BE,
X86CC::A,
X86CC::S,
X86CC::NS,
X86CC::P,
X86CC::NP,
X86CC::L,
X86CC::GE,
X86CC::LE,
X86CC::G,
];
for cc in &all_cc {
assert_ne!(*cc, cc.invert());
let _ = cc.setcc_opcode();
let _ = cc.cmov_opcode();
let _ = cc.jcc_opcode_short();
let _ = cc.jcc_opcode_near();
let _ = cc.suffix();
}
}
#[test]
fn test_all_calling_conventions() {
let convs = [
X86Conv::SystemV64,
X86Conv::Win64,
X86Conv::CDecl32,
X86Conv::StdCall32,
X86Conv::FastCall32,
];
for conv in &convs {
let _ = conv.callee_saved_gprs();
let _ = conv.int_arg_regs();
let _ = conv.stack_alignment();
let _ = conv.return_gpr();
}
}
#[test]
fn test_frame_operations() {
let mut mf = make_test_mf("frame_test");
let slot = mf.create_spill_slot(10, 8);
assert_eq!(slot, 0);
assert_eq!(mf.get_spill_slot(10), Some(0));
let slot2 = mf.create_spill_slot(11, 4);
assert_eq!(slot2, 8);
assert_eq!(mf.frame.spill_slot_size, 12);
}
#[test]
fn test_x86_conv_default() {
let conv = X86Conv::default();
assert_eq!(conv, X86Conv::SystemV64);
}
#[test]
fn test_emitter_with_mf() {
let mf = make_arith_func();
let emitter = X86InstrEmitter::new(true).with_mf(mf);
assert!(emitter.mf.is_some());
}
#[test]
fn test_sinking_run() {
let mut sinking = X86MachineSinking::new(true);
let mut mf = make_multi_block_func();
sinking.run(&mut mf);
assert!(sinking.stats.blocks_analyzed > 0);
}
#[test]
fn test_physreg_from_encoding_gpr() {
assert_eq!(X86PhysReg::from_encoding_gpr(0, true), X86PhysReg::RAX);
assert_eq!(X86PhysReg::from_encoding_gpr(7, true), X86PhysReg::RDI);
}
#[test]
fn test_physreg_from_encoding_xmm() {
assert_eq!(X86PhysReg::from_encoding_xmm(0), X86PhysReg::XMM0);
assert_eq!(X86PhysReg::from_encoding_xmm(15), X86PhysReg::XMM15);
}
#[test]
fn test_opcode_num_implicit_operands() {
assert_eq!(DeepX86Opcode::DIVr.num_implicit_operands(), 3);
assert_eq!(DeepX86Opcode::IDIVr.num_implicit_operands(), 3);
assert_eq!(DeepX86Opcode::MULrr.num_implicit_operands(), 2);
assert_eq!(DeepX86Opcode::RET.num_implicit_operands(), 0);
assert_eq!(DeepX86Opcode::ADDrr.num_implicit_operands(), 0);
}
#[test]
fn test_opcode_is_speculatable() {
assert!(DeepX86Opcode::ADDrr.is_speculatable());
assert!(!DeepX86Opcode::CALLpcrel32.is_speculatable());
assert!(!DeepX86Opcode::MOVrm.is_speculatable());
assert!(!DeepX86Opcode::MOVmr.is_speculatable());
}
#[test]
fn test_conv_callee_saved_xmm_win64() {
let xmms = X86Conv::Win64.callee_saved_xmms();
assert!(!xmms.is_empty());
assert!(xmms.contains(&X86PhysReg::XMM6));
assert!(xmms.contains(&X86PhysReg::XMM15));
}
#[test]
fn test_conv_callee_saved_xmm_sysv64() {
let xmms = X86Conv::SystemV64.callee_saved_xmms();
assert!(xmms.is_empty()); }
#[test]
fn test_mf_spill_slots() {
let mut mf = make_test_mf("spill_test");
let off1 = mf.create_spill_slot(5, 8);
let off2 = mf.create_spill_slot(6, 16);
assert_eq!(off1, 0);
assert_eq!(off2, 8);
assert_eq!(mf.frame.spill_slot_size, 24);
assert_eq!(mf.get_spill_slot(5), Some(0));
assert_eq!(mf.get_spill_slot(6), Some(8));
assert_eq!(mf.get_spill_slot(7), None);
}
#[test]
fn test_mf_vreg_assignment() {
let mut mf = make_test_mf("assign");
mf.set_vreg_assignment(1, X86PhysReg::RAX);
mf.set_vreg_assignment(2, X86PhysReg::XMM0);
assert_eq!(mf.get_vreg_assignment(1), Some(X86PhysReg::RAX));
assert_eq!(mf.get_vreg_assignment(2), Some(X86PhysReg::XMM0));
assert_eq!(mf.get_vreg_assignment(3), None);
}
#[test]
fn test_mf_get_block() {
let mut mf = X86MF::new("blocks", true, X86Conv::SystemV64);
let l0 = mf.push_block(X86MBB::new(0));
let l1 = mf.push_block(X86MBB::new(1));
assert_eq!(l0, 0);
assert_eq!(l1, 1);
assert!(mf.get_block(0).is_some());
assert!(mf.get_block(1).is_some());
assert!(mf.get_block(2).is_none());
}
#[test]
fn test_x86_frame_default() {
let frame = X86Frame::default();
assert_eq!(frame.total_size, 0);
assert_eq!(frame.return_address_offset, 8);
assert!(!frame.has_frame_pointer);
assert!(frame.uses_red_zone);
}
#[test]
fn test_mf_display() {
let mf = make_arith_func();
let s = format!("{}", mf);
assert!(s.contains("machine_function arith"));
}
#[test]
fn test_arg_assignment_variants() {
let reg_assign = ArgAssignment::Register { reg: 100 };
let stack_assign = ArgAssignment::Stack { offset: 16 };
let split_assign = ArgAssignment::Split {
lo_reg: 10,
hi_reg: 20,
};
let indirect_assign = ArgAssignment::Indirect { ptr_reg: 5 };
if let ArgAssignment::Register { reg } = reg_assign {
assert_eq!(reg, 100);
}
if let ArgAssignment::Stack { offset } = stack_assign {
assert_eq!(offset, 16);
}
if let ArgAssignment::Split { lo_reg, hi_reg } = split_assign {
assert_eq!(lo_reg, 10);
assert_eq!(hi_reg, 20);
}
if let ArgAssignment::Indirect { ptr_reg } = indirect_assign {
assert_eq!(ptr_reg, 5);
}
}
#[test]
fn test_legal_action_variants() {
assert_eq!(LegalAction::Legal, LegalAction::Legal);
assert_ne!(LegalAction::Legal, LegalAction::Expand);
assert_ne!(LegalAction::Legal, LegalAction::Unsupported);
}
#[test]
fn test_reg_bank_properties() {
assert_eq!(RegBank::GPR.name(), "GPR");
assert_eq!(RegBank::XMM.name(), "XMM");
assert_eq!(RegBank::YMM.size_bytes(), 32);
assert_eq!(RegBank::ZMM.size_bytes(), 64);
assert_eq!(RegBank::Mask.name(), "MASK");
}
#[test]
fn test_gmi_opcodes() {
let insts = vec![
GMIrInst::new(GMIrOpcode::G_ADD),
GMIrInst::new(GMIrOpcode::G_SHUFFLE_VECTOR),
GMIrInst::new(GMIrOpcode::G_INTRINSIC),
GMIrInst::new(GMIrOpcode::G_VASTART),
];
assert_eq!(insts.len(), 4);
}
#[test]
fn test_gm_operand_variants() {
let vreg = GMOperand::VReg(42);
let imm = GMOperand::Imm(-5);
let fimm = GMOperand::FImm(3.14);
let block = GMOperand::Block(2);
let global = GMOperand::Global("target".into());
let fi = GMOperand::FrameIndex(8);
drop((vreg, imm, fimm, block, global, fi));
}
#[test]
fn test_fast_isel_select_shl_cl() {
let mut isel = X86FastISel::new(true);
isel.select_shl(1, 2, false, 0);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::SHLrCL);
}
#[test]
fn test_fast_isel_select_brcond() {
let mut isel = X86FastISel::new(true);
isel.select_brcond(1, X86CC::E, 5, 6);
assert!(isel.instructions.len() >= 2);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::TESTrr);
}
#[test]
fn test_fast_isel_select_ret_with_val() {
let mut isel = X86FastISel::new(true);
isel.select_ret(Some(42));
assert!(isel.instructions.len() >= 2);
assert_eq!(isel.instructions.last().unwrap().opcode, DeepX86Opcode::RET);
}
#[test]
fn test_fast_isel_select_mul_imm() {
let mut isel = X86FastISel::new(true);
isel.select_mul(1, 2, true, 1000);
assert_eq!(isel.instructions[0].opcode, DeepX86Opcode::IMULrri);
}
#[test]
fn test_opcode_compare_flags() {
let cmp = X86MI::new(DeepX86Opcode::CMPrr);
assert!(cmp.flags.is_compare);
let test = X86MI::new(DeepX86Opcode::TESTrr);
assert!(test.flags.is_compare);
let add = X86MI::new(DeepX86Opcode::ADDrr);
assert!(!add.flags.is_compare);
}
#[test]
fn test_conv_win64_int_arg_regs() {
let regs = X86Conv::Win64.int_arg_regs();
assert_eq!(regs.len(), 4);
assert_eq!(regs[0], X86PhysReg::RCX);
assert_eq!(regs[1], X86PhysReg::RDX);
assert_eq!(regs[2], X86PhysReg::R8);
assert_eq!(regs[3], X86PhysReg::R9);
}
#[test]
fn test_conv_fastcall32_callee_saved() {
let saved = X86Conv::FastCall32.callee_saved_gprs();
assert!(saved.contains(&X86PhysReg::RBX));
assert!(saved.contains(&X86PhysReg::RBP));
}
#[test]
fn test_call_lowering_lower_tail_call() {
let lowering = X86CallLowering::new(true);
let tail = GMIrInst::new(GMIrOpcode::G_TAILCALL)
.with_use(GMOperand::Global("target".into()))
.with_use(GMOperand::VReg(1));
let lowered = lowering.lower_tail_call(&tail);
assert!(lowered.is_ok());
}
#[test]
fn test_legalizer_32bit() {
let legalizer = X86Legalizer::new(false);
assert!(!legalizer.is_64bit);
assert_eq!(legalizer.max_legal_scalar, 32);
}
#[test]
fn test_legalizer_unsupported() {
let legalizer = X86Legalizer::new(false);
let _ = legalizer.legalize(&[GMIrInst::new(GMIrOpcode::G_VAARG)]);
}
#[test]
fn test_emitter_emit_binary_imm() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_binary_op_imm(DeepX86Opcode::ADDri, 1, 2, 42);
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::ADDri);
assert_eq!(emitter.stats.immediates_folded, 1);
}
#[test]
fn test_emitter_emit_mov_imm() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_mov_imm(1, 73);
let instrs = emitter.get_instructions();
assert_eq!(instrs[0].opcode, DeepX86Opcode::MOVri);
}
#[test]
fn test_emitter_emit_nop() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_nop();
assert_eq!(emitter.get_instructions()[0].opcode, DeepX86Opcode::NOP);
}
#[test]
fn test_emitter_into_instructions() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_nop();
emitter.emit_nop();
let instrs = emitter.into_instructions();
assert_eq!(instrs.len(), 2);
}
#[test]
fn test_prolog_epilog_callee_saved_used() {
let mut inserter = X86PrologEpilogInserter::new(true, X86Conv::SystemV64);
let mut mf = make_arith_func();
inserter.run(&mut mf);
assert_eq!(inserter.stats.functions_processed, 1);
}
#[test]
fn test_prolog_epilog_needs_fp_for_var_sized() {
let mut inserter =
X86PrologEpilogInserter::new(true, X86Conv::SystemV64).with_frame_pointer();
let mut mf = make_test_mf("var_sized");
mf.frame.has_var_sized_objects = true;
assert!(inserter.needs_frame_pointer(&mf, false));
}
#[test]
fn test_prolog_epilog_needs_fp_for_realignment() {
let inserter = X86PrologEpilogInserter::new(true, X86Conv::SystemV64).with_frame_pointer();
let mut mf = make_test_mf("realign");
mf.frame.needs_realignment = true;
assert!(inserter.needs_frame_pointer(&mf, false));
}
#[test]
fn test_branch_selector_estimate_instr_size() {
let selector = X86BranchSelector::new(true);
let jmp_short = X86MI::new(DeepX86Opcode::JMP1);
let jmp_near = X86MI::new(DeepX86Opcode::JMP4);
let call = X86MI::new(DeepX86Opcode::CALLpcrel32);
let ret = X86MI::new(DeepX86Opcode::RET);
assert_eq!(selector.estimate_instr_size(&jmp_short), 2);
assert_eq!(selector.estimate_instr_size(&jmp_near), 6);
assert_eq!(selector.estimate_instr_size(&call), 5);
assert_eq!(selector.estimate_instr_size(&ret), 1);
}
#[test]
fn test_branch_selector_block_order() {
let selector = X86BranchSelector::new(true);
let mf = make_multi_block_func();
let order = selector.compute_block_order(&mf);
assert!(!order.is_empty());
assert_eq!(order[0], 0); }
#[test]
fn test_expand_pseudo_implicit_def() {
let mut expander = X86ExpandPseudo::new(true);
let mut mf = make_test_mf("imp_def");
let mut block = X86MBB::new(0);
block.push_instr(X86MI::new(DeepX86Opcode::IMPLICIT_DEF).push_op(X86MO::VReg(1)));
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
expander.run(&mut mf);
assert!(expander.stats.total_pseudos_expanded > 0);
}
#[test]
fn test_expand_pseudo_frame_alloc() {
let mut expander = X86ExpandPseudo::new(true);
let mut mf = make_test_mf("frame_alloc");
let mut block = X86MBB::new(0);
block.push_instr(X86MI::new(DeepX86Opcode::FRAME_ALLOC).push_op(X86MO::Imm(32)));
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
expander.run(&mut mf);
}
#[test]
fn test_expand_pseudo_kill() {
let mut expander = X86ExpandPseudo::new(true);
let mut mf = make_test_mf("kill");
let mut block = X86MBB::new(0);
block.push_instr(X86MI::new(DeepX86Opcode::KILL));
mf.push_block(block);
expander.run(&mut mf);
}
#[test]
fn test_expand_pseudo_tail_call() {
let mut expander = X86ExpandPseudo::new(true);
let mut mf = make_test_mf("tailcall");
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::X86_TAIL_CALL)
.push_op(X86MO::ExternalSymbol("target".into())),
);
mf.push_block(block);
expander.run(&mut mf);
}
#[test]
fn test_copy_prop_self_assign() {
let mut cp = X86MachineCopyProp::new(true);
let mut mf = make_mf_with_copy();
let initial_len = mf.blocks[0].instructions.len();
cp.run(&mut mf);
assert!(mf.blocks[0].instructions.len() <= initial_len);
}
#[test]
fn test_dead_def_elim_side_effects() {
let mut dde = X86MachineDeadDefElim::new(true);
let mut mf = make_test_mf("sidefx");
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::CALLpcrel32).push_op(X86MO::ExternalSymbol("puts".into())),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
dde.run(&mut mf);
assert_eq!(mf.blocks[0].instructions.len(), 2);
}
#[test]
fn test_combiner_xor_zero_idiom() {
let mut combiner = X86MachineCombiner::new(true);
let mut mf = make_test_mf("zero");
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::XORrr)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(1)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
combiner.run(&mut mf);
}
#[test]
fn test_shrink_wrap_remove_unused_saves() {
let mut sw = X86ShrinkWrapping::new(true);
let mut mf = make_test_mf("unused");
let mut block = X86MBB::new(0);
block.push_instr(X86MI::new(DeepX86Opcode::PUSH64r).push_op(X86MO::PReg(X86PhysReg::RBX)));
block.push_instr(X86MI::new(DeepX86Opcode::POP64r).push_op(X86MO::PReg(X86PhysReg::RBX)));
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
sw.run(&mut mf);
}
#[test]
fn test_tail_dup_should_duplicate() {
let td = X86TailDuplication::new(true);
let block = X86MBB::new(0);
assert!(td.should_duplicate(&block, 4, 3));
assert!(!td.should_duplicate(&block, 10, 3));
assert!(!td.should_duplicate(&block, 4, 1));
}
#[test]
fn test_constant_islands_collect() {
let mut ci = X86ConstantIslands::new(true);
let mf = make_arith_func();
ci.collect_constants(&mf);
}
#[test]
fn test_fixup_kind_needs_relocation() {
assert!(!MCFixupKind::FK_Data_4.needs_relocation());
assert!(MCFixupKind::FK_PCRel_4.needs_relocation());
assert!(MCFixupKind::FK_GOTPCREL.needs_relocation());
}
#[test]
fn test_fixup_unresolved() {
let mut fixup = X86MCFixup::new();
fixup.record_fixup(100, 0, MCFixupKind::FK_PCRel_4, Some("ext".into()));
let unresolved = fixup.unresolved_fixups();
assert_eq!(unresolved.len(), 1);
assert!(!unresolved[0].is_resolved);
}
#[test]
fn test_pipeline_executor_execute_empty() {
let pipeline = X86Pipeline::o0_pipeline();
let pass = X86CodeGenPass::for_o0();
let mut executor = X86PipelineExecutor::new(pipeline, pass);
let mut mf = make_test_mf("empty_exec");
mf.push_block(X86MBB::new(0));
assert!(executor.execute(&mut mf));
}
#[test]
fn test_pipeline_o3_has_all_stages() {
let pipeline = X86Pipeline::o3_pipeline();
assert!(pipeline.is_enabled(X86PipelineStage::TailDuplication));
assert!(pipeline.is_enabled(X86PipelineStage::InstructionSinking));
assert!(pipeline.is_enabled(X86PipelineStage::ConstantIslands));
}
#[test]
fn test_codegen_pass_full_roundtrip() {
let mut pass = X86CodeGenPass::for_o2();
let mut mf = make_arith_func();
mf.push_block(X86MBB::new(0));
let success = pass.run_pipeline(&mut mf);
assert!(success);
let summary = pass.pipeline_summary();
assert!(summary.contains("X86CodeGenPass"));
assert!(summary.contains("O=2"));
}
#[test]
fn test_compute_callee_saved_set() {
let set = compute_callee_saved_set(X86Conv::SystemV64);
assert!(set.contains(&X86PhysReg::RBX));
assert!(set.contains(&X86PhysReg::R12));
assert!(!set.contains(&X86PhysReg::RAX));
}
#[test]
fn test_compute_caller_saved_set() {
let set = compute_caller_saved_set(X86Conv::SystemV64);
assert!(set.contains(&X86PhysReg::RDI));
assert!(set.contains(&X86PhysReg::RSI));
}
#[test]
fn test_mo_constructors_all() {
assert!(X86MO::vreg(1).is_vreg());
assert!(X86MO::preg(X86PhysReg::RAX).is_preg());
assert!(X86MO::imm(42).is_imm());
assert!(X86MO::imm8(7).is_imm());
assert!(X86MO::cp_index(3).is_mem()); assert!(X86MO::block_label(5).is_label());
}
#[test]
fn test_full_pipeline_integration_all_stages() {
let mut pass = X86CodeGenPass::for_o3();
let mut mf = make_test_mf("integration");
let mut b0 = X86MBB::new(0);
b0.add_successor(1);
b0.push_instr(
X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(1))
.push_op(X86MO::Imm(10)),
);
b0.push_instr(
X86MI::new(DeepX86Opcode::COPY)
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(1)),
);
b0.push_instr(
X86MI::new(DeepX86Opcode::ADDrr)
.push_op(X86MO::VReg(3))
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(2)),
);
b0.push_instr(X86MI::new(DeepX86Opcode::JMP1).push_op(X86MO::BlockLabel(1)));
let mut b1 = X86MBB::new(1);
b1.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(b0);
mf.push_block(b1);
mf.compute_predecessors();
let result = pass.run_pipeline(&mut mf);
assert!(result);
}
#[test]
fn test_global_isel_all_patterns_have_ops() {
let selector = X86GlobalISelSelector::new(true);
for opcode in &[
GMIrOpcode::G_AND,
GMIrOpcode::G_OR,
GMIrOpcode::G_XOR,
GMIrOpcode::G_SHL,
GMIrOpcode::G_LSHR,
GMIrOpcode::G_ASHR,
GMIrOpcode::G_SEXT,
GMIrOpcode::G_ZEXT,
GMIrOpcode::G_BITCAST,
GMIrOpcode::G_BR,
GMIrOpcode::G_CONSTANT,
GMIrOpcode::G_ICMP,
] {
let inst = GMIrInst::new(opcode.clone())
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2));
let result = selector.select(&[inst]);
assert!(!result.is_empty(), "No selection for {:?}", opcode);
}
}
#[test]
fn test_cc_jcc_opcode_near_variants() {
assert_eq!(X86CC::E.jcc_opcode_near(), DeepX86Opcode::JE4);
assert_eq!(X86CC::NE.jcc_opcode_near(), DeepX86Opcode::JNE4);
assert_eq!(X86CC::G.jcc_opcode_near(), DeepX86Opcode::JG4);
assert_eq!(X86CC::O.jcc_opcode_near(), DeepX86Opcode::JO4);
}
#[test]
fn test_mo_memory_display_complex() {
let mo = X86MO::mem(Some(1), Some(2), 8, 16, 4);
let s = format!("{}", mo);
assert!(s.contains("dword"));
assert!(s.contains("*8"));
}
#[test]
fn test_mo_rip_relative() {
let mo = X86MO::RIPRel(42);
assert!(mo.is_mem());
assert_eq!(format!("{}", mo), "[rip+42]");
}
#[test]
fn test_mo_global() {
let mo = X86MO::GlobalSymbol("main".into());
assert_eq!(format!("{}", mo), "main");
}
#[test]
fn test_mo_frame_index() {
let mo = X86MO::FrameIndex(3);
assert_eq!(format!("{}", mo), "<fi#3>");
}
#[test]
fn test_mo_constant_pool() {
let mo = X86MO::ConstantPoolIndex(7);
assert_eq!(format!("{}", mo), "<cp#7>");
}
#[test]
fn test_mi_with_id() {
let inst = X86MI::new(DeepX86Opcode::NOP).with_id(42);
assert_eq!(inst.id, 42);
}
#[test]
fn test_mbb_last_instr_mut() {
let mut block = X86MBB::new(0);
block.push_instr(X86MI::new(DeepX86Opcode::NOP));
let last = block.last_instr_mut();
assert!(last.is_some());
}
#[test]
fn test_mbb_alignment_default() {
let block = X86MBB::new(0);
assert_eq!(block.alignment, 16);
}
#[test]
fn test_mf_symbols_and_relocations() {
let mut mf = X86MF::new("sym", true, X86Conv::SystemV64);
mf.symbols.push(("main".into(), 0));
mf.relocations
.push((0, "main".into(), "R_X86_64_PC32".into()));
assert_eq!(mf.symbols.len(), 1);
assert_eq!(mf.relocations.len(), 1);
}
#[test]
fn test_pipeline_global_isel_execution() {
let mut pass = X86CodeGenPass::for_o2();
let generic = vec![GMIrInst::new(GMIrOpcode::G_ADD)
.with_def(GMOperand::VReg(1))
.with_use(GMOperand::VReg(2))
.with_use(GMOperand::VReg(3))];
let result = pass.run_global_isel(&generic);
assert!(result.is_ok());
assert!(!result.unwrap().is_empty());
}
#[test]
fn test_emitter_next_instr_id() {
let mut emitter = X86InstrEmitter::new(true);
emitter.emit_nop();
emitter.emit_nop();
assert_eq!(emitter.get_instructions()[0].id, 0);
assert_eq!(emitter.get_instructions()[1].id, 1);
}
#[test]
fn test_sdnode_flags_default() {
let flags = SDNodeFlags::default();
assert!(!flags.has_no_unsigned_wrap);
assert!(!flags.has_exact);
}
#[test]
fn test_dag_relationship() {
let mut dag = SelectionDAG::new();
let parent = dag.add_node("add", "i32");
let child = dag.add_constant(5, "i32");
dag.set_operand(parent, child);
let child_node = dag.get_node(child).unwrap();
assert!(child_node.users.contains(&parent));
}
#[test]
fn test_dag_to_dag_multiple_nodes() {
let features: HashSet<String> = ["sse", "sse2"].iter().map(|s| s.to_string()).collect();
let mut selector = X86DAGToDAG::new(true, features);
let mut dag = SelectionDAG::new();
let c1 = dag.add_constant(3, "i32");
let c2 = dag.add_constant(7, "i32");
let add = dag.add_node("add", "i32");
dag.set_operand(add, c1);
dag.set_operand(add, c2);
let mul = dag.add_node("mul", "i32");
dag.set_operand(mul, add);
dag.set_operand(mul, c2);
let result = selector.run(&dag);
assert!(result.len() >= 2);
}
#[test]
fn test_fast_isel_constant_folding() {
let mut isel = X86FastISel::new(true);
isel.select_mov_imm(42);
assert_eq!(isel.stats.constant_folded, 1);
}
#[test]
fn test_pipeline_describe_format() {
let pipeline = X86Pipeline::o2_pipeline();
let desc = pipeline.describe();
assert!(desc.contains("Pipeline:"));
assert!(desc.contains("target:"));
assert!(desc.contains("ENABLED"));
assert!(desc.lines().count() > 5);
}
#[test]
fn test_codegen_stats_accumulate() {
let config = X86CodeGenConfig::default();
let mut pass = X86CodeGenPass::new(config);
let mut dag = SelectionDAG::new();
dag.add_node("add", "i32");
pass.run_on_dag(&dag);
assert!(pass.pipeline_stats.dag_nodes_processed > 0);
}
#[test]
fn test_dead_def_no_side_effects_removed() {
let mut dde = X86MachineDeadDefElim::new(true);
let mut mf = X86MF::new("nodead", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::MOVri)
.push_op(X86MO::VReg(99))
.push_op(X86MO::Imm(999)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
let initial = mf.total_instructions();
dde.run(&mut mf);
assert!(mf.total_instructions() <= initial);
}
#[test]
fn test_expand_pseudo_dbg_value_removed() {
let mut expander = X86ExpandPseudo::new(true);
let mut mf = X86MF::new("dbg", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(X86MI::new(DeepX86Opcode::DBG_VALUE));
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
expander.run(&mut mf);
}
#[test]
fn test_tail_dup_max_count_enforced() {
let td = X86TailDuplication::new(true);
assert_eq!(td.max_duplication_count, 3);
}
#[test]
fn test_prolog_epilog_align_to() {
assert_eq!(X86PrologEpilogInserter::align_to(0, 16), 0);
assert_eq!(X86PrologEpilogInserter::align_to(1, 16), 16);
assert_eq!(X86PrologEpilogInserter::align_to(16, 16), 16);
assert_eq!(X86PrologEpilogInserter::align_to(17, 16), 32);
assert_eq!(X86PrologEpilogInserter::align_to(31, 16), 32);
}
#[test]
fn test_fixup_pcrel_value_computation() {
let mut fixup = X86MCFixup::new();
fixup.record_fixup(10, 100, MCFixupKind::FK_PCRel_4, None);
let mut data = vec![0u8; 20];
fixup.apply_fixups(&mut data, 0);
assert_eq!(data[10], 86);
}
#[test]
fn test_gisel_condition_checks() {
let mut selector = X86GlobalISelSelector::new(true);
assert!(selector.check_condition(&None));
assert!(selector.check_condition(&Some(GISelCondition::Is64Bit)));
assert!(!selector.check_condition(&Some(GISelCondition::Is32Bit)));
}
#[test]
fn test_licm_find_loop_body() {
let licm = X86MachineLICM::new(true);
let mf = make_loop_func();
let body = licm.find_loop_body(&mf, 1, 1);
assert!(body.contains(&1));
}
#[test]
fn test_licm_find_preheader() {
let licm = X86MachineLICM::new(true);
let mf = make_loop_func();
let preheader = licm.find_preheader(&mf, 1);
assert_eq!(preheader, Some(0));
}
#[test]
fn test_combiner_self_xor() {
let mut combiner = X86MachineCombiner::new(true);
let mut mf = X86MF::new("self_xor", true, X86Conv::SystemV64);
let mut block = X86MBB::new(0);
block.push_instr(
X86MI::new(DeepX86Opcode::XORrr)
.push_op(X86MO::VReg(1))
.push_op(X86MO::VReg(2))
.push_op(X86MO::VReg(2)),
);
block.push_instr(X86MI::new(DeepX86Opcode::RET));
mf.push_block(block);
combiner.run(&mut mf);
}
}