use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum A64Opcode {
ADD,
ADDS,
SUB,
SUBS,
ADC,
ADCS,
SBC,
SBCS,
NEG,
NEGS,
CMP,
CMN,
AND,
ANDS,
ORR,
EOR,
BIC,
BICS,
ORN,
EON,
MOVZ,
MOVK,
MOVN,
MOV,
LSL,
LSR,
ASR,
ROR,
MUL,
MADD,
MSUB,
MNEG,
SMULL,
UMULL,
SMADDL,
SMSUBL,
UMADDL,
UMSUBL,
SMULH,
UMULH,
SDIV,
UDIV,
BFM,
SBFM,
UBFM,
BFI,
BFXIL,
SBFIZ,
UBFIZ,
SBFX,
UBFX,
EXTR,
CSEL,
CSINC,
CSINV,
CSNEG,
CINC,
CINV,
CNEG,
CSET,
CSETM,
CCMN,
CCMP,
B,
BL,
BR,
BLR,
RET,
CBZ,
CBNZ,
TBZ,
TBNZ,
ADRP,
ADR,
LDR,
LDUR,
LDRB,
LDRH,
LDRW,
LDRSB,
LDRSH,
LDRSW,
STR,
STUR,
STRB,
STRH,
STRW,
LDP,
STP,
LDAR,
STLR,
LDAXR,
STLXR,
LDXR,
STXR,
PRFM,
FADD,
FSUB,
FMUL,
FDIV,
FNEG,
FABS,
FSQRT,
FMADD,
FMSUB,
FNMADD,
FNMSUB,
FMAX,
FMIN,
FMAXNM,
FMINNM,
FCMP,
FCMPZ,
FCCMP,
FCCMPZ,
FCSEL,
FCVT,
FCVTZS,
FCVTZU,
SCVTF,
UCVTF,
FMOV,
FMOVImm,
FMOVGenToFP,
FMOVFPToGen,
ADDv,
SUBv,
MULv,
MLAv,
MLSv,
FMLAv,
FMLSv,
SHLv,
SHRv,
SLIv,
SRIv,
XTNv,
SQXTNv,
UQXTNv,
REVv,
TRNv,
UZPv,
ZIPv,
TBLv,
TBXv,
DUPv,
MOVv,
MOVIv,
ANDv,
ORRv,
EORv,
BICv,
ORNv,
NOTv,
CMEQv,
CMGTv,
CMGEv,
CMLTv,
CMLEv,
BSLv,
CLSv,
CLZv,
CNTv,
ABSv,
NEGv,
EXTv,
LD1v,
ST1v,
LD2v,
ST2v,
LD3v,
ST3v,
LD4v,
ST4v,
ADD_sve,
SUB_sve,
FADD_sve,
FMUL_sve,
WHILELT,
PTRUE,
INDEX,
CNT_sve,
LD1W_sve,
ST1W_sve,
CMPEQ_sve,
FCMPEQ_sve,
SEL_sve,
COMPACT_sve,
LASTB_sve,
AND_sve_p,
ORR_sve_p,
NOT_sve_p,
NOP,
SVC,
HINT,
DMB,
DSB,
ISB,
MSR,
MRS,
}
impl fmt::Display for A64Opcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
A64Opcode::ADD => "add",
A64Opcode::ADDS => "adds",
A64Opcode::SUB => "sub",
A64Opcode::SUBS => "subs",
A64Opcode::ADC => "adc",
A64Opcode::ADCS => "adcs",
A64Opcode::SBC => "sbc",
A64Opcode::SBCS => "sbcs",
A64Opcode::NEG => "neg",
A64Opcode::NEGS => "negs",
A64Opcode::CMP => "cmp",
A64Opcode::CMN => "cmn",
A64Opcode::AND => "and",
A64Opcode::ANDS => "ands",
A64Opcode::ORR => "orr",
A64Opcode::EOR => "eor",
A64Opcode::BIC => "bic",
A64Opcode::BICS => "bics",
A64Opcode::ORN => "orn",
A64Opcode::EON => "eon",
A64Opcode::MOVZ => "movz",
A64Opcode::MOVK => "movk",
A64Opcode::MOVN => "movn",
A64Opcode::MOV => "mov",
A64Opcode::LSL => "lsl",
A64Opcode::LSR => "lsr",
A64Opcode::ASR => "asr",
A64Opcode::ROR => "ror",
A64Opcode::MUL => "mul",
A64Opcode::MADD => "madd",
A64Opcode::MSUB => "msub",
A64Opcode::MNEG => "mneg",
A64Opcode::SMULL => "smull",
A64Opcode::UMULL => "umull",
A64Opcode::SMADDL => "smaddl",
A64Opcode::SMSUBL => "smsubl",
A64Opcode::UMADDL => "umaddl",
A64Opcode::UMSUBL => "umsubl",
A64Opcode::SMULH => "smulh",
A64Opcode::UMULH => "umulh",
A64Opcode::SDIV => "sdiv",
A64Opcode::UDIV => "udiv",
A64Opcode::BFM => "bfm",
A64Opcode::SBFM => "sbfm",
A64Opcode::UBFM => "ubfm",
A64Opcode::BFI => "bfi",
A64Opcode::BFXIL => "bfxil",
A64Opcode::SBFIZ => "sbfiz",
A64Opcode::UBFIZ => "ubfiz",
A64Opcode::SBFX => "sbfx",
A64Opcode::UBFX => "ubfx",
A64Opcode::EXTR => "extr",
A64Opcode::CSEL => "csel",
A64Opcode::CSINC => "csinc",
A64Opcode::CSINV => "csinv",
A64Opcode::CSNEG => "csneg",
A64Opcode::CINC => "cinc",
A64Opcode::CINV => "cinv",
A64Opcode::CNEG => "cneg",
A64Opcode::CSET => "cset",
A64Opcode::CSETM => "csetm",
A64Opcode::CCMN => "ccmn",
A64Opcode::CCMP => "ccmp",
A64Opcode::B => "b",
A64Opcode::BL => "bl",
A64Opcode::BR => "br",
A64Opcode::BLR => "blr",
A64Opcode::RET => "ret",
A64Opcode::CBZ => "cbz",
A64Opcode::CBNZ => "cbnz",
A64Opcode::TBZ => "tbz",
A64Opcode::TBNZ => "tbnz",
A64Opcode::ADRP => "adrp",
A64Opcode::ADR => "adr",
A64Opcode::LDR => "ldr",
A64Opcode::LDUR => "ldur",
A64Opcode::LDRB => "ldrb",
A64Opcode::LDRH => "ldrh",
A64Opcode::LDRW => "ldr",
A64Opcode::LDRSB => "ldrsb",
A64Opcode::LDRSH => "ldrsh",
A64Opcode::LDRSW => "ldrsw",
A64Opcode::STR => "str",
A64Opcode::STUR => "stur",
A64Opcode::STRB => "strb",
A64Opcode::STRH => "strh",
A64Opcode::STRW => "str",
A64Opcode::LDP => "ldp",
A64Opcode::STP => "stp",
A64Opcode::LDAR => "ldar",
A64Opcode::STLR => "stlr",
A64Opcode::LDAXR => "ldaxr",
A64Opcode::STLXR => "stlxr",
A64Opcode::LDXR => "ldxr",
A64Opcode::STXR => "stxr",
A64Opcode::PRFM => "prfm",
A64Opcode::FADD => "fadd",
A64Opcode::FSUB => "fsub",
A64Opcode::FMUL => "fmul",
A64Opcode::FDIV => "fdiv",
A64Opcode::FNEG => "fneg",
A64Opcode::FABS => "fabs",
A64Opcode::FSQRT => "fsqrt",
A64Opcode::FMADD => "fmadd",
A64Opcode::FMSUB => "fmsub",
A64Opcode::FNMADD => "fnmadd",
A64Opcode::FNMSUB => "fnmsub",
A64Opcode::FMAX => "fmax",
A64Opcode::FMIN => "fmin",
A64Opcode::FMAXNM => "fmaxnm",
A64Opcode::FMINNM => "fminnm",
A64Opcode::FCMP => "fcmp",
A64Opcode::FCMPZ => "fcmp",
A64Opcode::FCCMP => "fccmp",
A64Opcode::FCCMPZ => "fccmp",
A64Opcode::FCSEL => "fcsel",
A64Opcode::FCVT => "fcvt",
A64Opcode::FCVTZS => "fcvtzs",
A64Opcode::FCVTZU => "fcvtzu",
A64Opcode::SCVTF => "scvtf",
A64Opcode::UCVTF => "ucvtf",
A64Opcode::FMOV => "fmov",
A64Opcode::FMOVImm => "fmov",
A64Opcode::FMOVGenToFP => "fmov",
A64Opcode::FMOVFPToGen => "fmov",
A64Opcode::ADDv => "add",
A64Opcode::SUBv => "sub",
A64Opcode::MULv => "mul",
A64Opcode::MLAv => "mla",
A64Opcode::MLSv => "mls",
A64Opcode::FMLAv => "fmla",
A64Opcode::FMLSv => "fmls",
A64Opcode::SHLv => "shl",
A64Opcode::SHRv => "shr",
A64Opcode::SLIv => "sli",
A64Opcode::SRIv => "sri",
A64Opcode::XTNv => "xtn",
A64Opcode::SQXTNv => "sqxtn",
A64Opcode::UQXTNv => "uqxtn",
A64Opcode::REVv => "rev",
A64Opcode::TRNv => "trn",
A64Opcode::UZPv => "uzp",
A64Opcode::ZIPv => "zip",
A64Opcode::TBLv => "tbl",
A64Opcode::TBXv => "tbx",
A64Opcode::DUPv => "dup",
A64Opcode::MOVv => "mov",
A64Opcode::MOVIv => "movi",
A64Opcode::ANDv => "and",
A64Opcode::ORRv => "orr",
A64Opcode::EORv => "eor",
A64Opcode::BICv => "bic",
A64Opcode::ORNv => "orn",
A64Opcode::NOTv => "not",
A64Opcode::CMEQv => "cmeq",
A64Opcode::CMGTv => "cmgt",
A64Opcode::CMGEv => "cmge",
A64Opcode::CMLTv => "cmlt",
A64Opcode::CMLEv => "cmle",
A64Opcode::BSLv => "bsl",
A64Opcode::CLSv => "cls",
A64Opcode::CLZv => "clz",
A64Opcode::CNTv => "cnt",
A64Opcode::ABSv => "abs",
A64Opcode::NEGv => "neg",
A64Opcode::EXTv => "ext",
A64Opcode::LD1v => "ld1",
A64Opcode::ST1v => "st1",
A64Opcode::LD2v => "ld2",
A64Opcode::ST2v => "st2",
A64Opcode::LD3v => "ld3",
A64Opcode::ST3v => "st3",
A64Opcode::LD4v => "ld4",
A64Opcode::ST4v => "st4",
A64Opcode::ADD_sve => "add",
A64Opcode::SUB_sve => "sub",
A64Opcode::FADD_sve => "fadd",
A64Opcode::FMUL_sve => "fmul",
A64Opcode::WHILELT => "whilelt",
A64Opcode::PTRUE => "ptrue",
A64Opcode::INDEX => "index",
A64Opcode::CNT_sve => "cnt",
A64Opcode::LD1W_sve => "ld1w",
A64Opcode::ST1W_sve => "st1w",
A64Opcode::CMPEQ_sve => "cmpeq",
A64Opcode::FCMPEQ_sve => "fcmpeq",
A64Opcode::SEL_sve => "sel",
A64Opcode::COMPACT_sve => "compact",
A64Opcode::LASTB_sve => "lastb",
A64Opcode::AND_sve_p => "and",
A64Opcode::ORR_sve_p => "orr",
A64Opcode::NOT_sve_p => "not",
A64Opcode::NOP => "nop",
A64Opcode::SVC => "svc",
A64Opcode::HINT => "hint",
A64Opcode::DMB => "dmb",
A64Opcode::DSB => "dsb",
A64Opcode::ISB => "isb",
A64Opcode::MSR => "msr",
A64Opcode::MRS => "mrs",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstrCategory {
ALU,
MulDiv,
Load,
Store,
Branch,
FP,
SIMD,
SVE,
System,
Misc,
}
impl A64Opcode {
pub fn category(self) -> InstrCategory {
use A64Opcode::*;
match self {
ADD | ADDS | SUB | SUBS | ADC | ADCS | SBC | SBCS | NEG | NEGS | CMP | CMN | AND
| ANDS | ORR | EOR | BIC | BICS | ORN | EON | MOVZ | MOVK | MOVN | MOV | LSL | LSR
| ASR | ROR | ADRP | ADR | CSEL | CSINC | CSINV | CSNEG | CINC | CINV | CNEG | CSET
| CSETM | CCMN | CCMP | EXTR | BFM | SBFM | UBFM | BFI | BFXIL | SBFIZ | UBFIZ
| SBFX | UBFX => InstrCategory::ALU,
MUL | MADD | MSUB | MNEG | SMULL | UMULL | SMADDL | SMSUBL | UMADDL | UMSUBL
| SMULH | UMULH | SDIV | UDIV => InstrCategory::MulDiv,
LDR | LDUR | LDRB | LDRH | LDRW | LDRSB | LDRSH | LDRSW | LDP | LDAR | LDAXR | LDXR
| PRFM | LD1v | LD2v | LD3v | LD4v | LD1W_sve => InstrCategory::Load,
STR | STUR | STRB | STRH | STRW | STP | STLR | STLXR | STXR | ST1v | ST2v | ST3v
| ST4v | ST1W_sve => InstrCategory::Store,
B | BL | BR | BLR | RET | CBZ | CBNZ | TBZ | TBNZ => InstrCategory::Branch,
FADD | FSUB | FMUL | FDIV | FNEG | FABS | FSQRT | FMADD | FMSUB | FNMADD | FNMSUB
| FMAX | FMIN | FMAXNM | FMINNM | FCMP | FCMPZ | FCCMP | FCCMPZ | FCSEL | FCVT
| FCVTZS | FCVTZU | SCVTF | UCVTF | FMOV | FMOVImm | FMOVGenToFP | FMOVFPToGen => {
InstrCategory::FP
}
ADDv | SUBv | MULv | MLAv | MLSv | FMLAv | FMLSv | SHLv | SHRv | SLIv | SRIv | XTNv
| SQXTNv | UQXTNv | REVv | TRNv | UZPv | ZIPv | TBLv | TBXv | DUPv | MOVv | MOVIv
| ANDv | ORRv | EORv | BICv | ORNv | NOTv | CMEQv | CMGTv | CMGEv | CMLTv | CMLEv
| BSLv | CLSv | CLZv | CNTv | ABSv | NEGv | EXTv => InstrCategory::SIMD,
ADD_sve | SUB_sve | FADD_sve | FMUL_sve | WHILELT | PTRUE | INDEX | CNT_sve
| CMPEQ_sve | FCMPEQ_sve | SEL_sve | COMPACT_sve | LASTB_sve | AND_sve_p
| ORR_sve_p | NOT_sve_p => InstrCategory::SVE,
SVC | MSR | MRS => InstrCategory::System,
DMB | DSB | ISB => InstrCategory::System,
NOP | HINT => InstrCategory::Misc,
}
}
}
pub type PhysReg = u16;
pub const X0: PhysReg = 0;
pub const X1: PhysReg = 1;
pub const X2: PhysReg = 2;
pub const X3: PhysReg = 3;
pub const X4: PhysReg = 4;
pub const X5: PhysReg = 5;
pub const X6: PhysReg = 6;
pub const X7: PhysReg = 7;
pub const X8: PhysReg = 8;
pub const X9: PhysReg = 9;
pub const X10: PhysReg = 10;
pub const X11: PhysReg = 11;
pub const X12: PhysReg = 12;
pub const X13: PhysReg = 13;
pub const X14: PhysReg = 14;
pub const X15: PhysReg = 15;
pub const X16: PhysReg = 16;
pub const X17: PhysReg = 17;
pub const X18: PhysReg = 18;
pub const X19: PhysReg = 19;
pub const X20: PhysReg = 20;
pub const X21: PhysReg = 21;
pub const X22: PhysReg = 22;
pub const X23: PhysReg = 23;
pub const X24: PhysReg = 24;
pub const X25: PhysReg = 25;
pub const X26: PhysReg = 26;
pub const X27: PhysReg = 27;
pub const X28: PhysReg = 28;
pub const X29: PhysReg = 29;
pub const X30: PhysReg = 30;
pub const FP: PhysReg = X29; pub const LR: PhysReg = X30;
pub const XZR: PhysReg = 31; pub const SP: PhysReg = 32; pub const WZR: PhysReg = 33; pub const WSP: PhysReg = 34;
pub const W0: PhysReg = 100;
pub const W1: PhysReg = 101;
pub const W2: PhysReg = 102;
pub const W3: PhysReg = 103;
pub const W4: PhysReg = 104;
pub const W5: PhysReg = 105;
pub const W6: PhysReg = 106;
pub const W7: PhysReg = 107;
pub const W8: PhysReg = 108;
pub const W9: PhysReg = 109;
pub const W10: PhysReg = 110;
pub const W11: PhysReg = 111;
pub const W12: PhysReg = 112;
pub const W13: PhysReg = 113;
pub const W14: PhysReg = 114;
pub const W15: PhysReg = 115;
pub const W16: PhysReg = 116;
pub const W17: PhysReg = 117;
pub const W18: PhysReg = 118;
pub const W19: PhysReg = 119;
pub const W20: PhysReg = 120;
pub const W21: PhysReg = 121;
pub const W22: PhysReg = 122;
pub const W23: PhysReg = 123;
pub const W24: PhysReg = 124;
pub const W25: PhysReg = 125;
pub const W26: PhysReg = 126;
pub const W27: PhysReg = 127;
pub const W28: PhysReg = 128;
pub const W29: PhysReg = 129;
pub const W30: PhysReg = 130;
pub const V0: PhysReg = 200;
pub const V1: PhysReg = 201;
pub const V2: PhysReg = 202;
pub const V3: PhysReg = 203;
pub const V4: PhysReg = 204;
pub const V5: PhysReg = 205;
pub const V6: PhysReg = 206;
pub const V7: PhysReg = 207;
pub const V8: PhysReg = 208;
pub const V9: PhysReg = 209;
pub const V10: PhysReg = 210;
pub const V11: PhysReg = 211;
pub const V12: PhysReg = 212;
pub const V13: PhysReg = 213;
pub const V14: PhysReg = 214;
pub const V15: PhysReg = 215;
pub const V16: PhysReg = 216;
pub const V17: PhysReg = 217;
pub const V18: PhysReg = 218;
pub const V19: PhysReg = 219;
pub const V20: PhysReg = 220;
pub const V21: PhysReg = 221;
pub const V22: PhysReg = 222;
pub const V23: PhysReg = 223;
pub const V24: PhysReg = 224;
pub const V25: PhysReg = 225;
pub const V26: PhysReg = 226;
pub const V27: PhysReg = 227;
pub const V28: PhysReg = 228;
pub const V29: PhysReg = 229;
pub const V30: PhysReg = 230;
pub const V31: PhysReg = 231;
pub const Q0: PhysReg = V0;
pub const Q31: PhysReg = V31;
pub const D0: PhysReg = V0;
pub const D31: PhysReg = V31;
pub const S0: PhysReg = V0;
pub const S31: PhysReg = V31;
pub const H0: PhysReg = V0;
pub const H31: PhysReg = V31;
pub const B0: PhysReg = V0;
pub const B31: PhysReg = V31;
pub const NZCV: PhysReg = 250; pub const FPCR: PhysReg = 251; pub const FPSR: PhysReg = 252;
pub const AARCH64_DEEP_REG_COUNT: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum A64RegClass {
GPR64,
GPR32,
FPR128,
FPR64,
FPR32,
FPR16,
FPR8,
Flags,
}
impl A64RegClass {
pub fn default_allocatable_count(self) -> usize {
match self {
A64RegClass::GPR64 => 28, A64RegClass::GPR32 => 28,
A64RegClass::FPR128 => 32,
A64RegClass::FPR64 => 32,
A64RegClass::FPR32 => 32,
A64RegClass::FPR16 => 32,
A64RegClass::FPR8 => 32,
A64RegClass::Flags => 1,
}
}
pub fn width_bits(self) -> u16 {
match self {
A64RegClass::GPR64 => 64,
A64RegClass::GPR32 => 32,
A64RegClass::FPR128 => 128,
A64RegClass::FPR64 => 64,
A64RegClass::FPR32 => 32,
A64RegClass::FPR16 => 16,
A64RegClass::FPR8 => 8,
A64RegClass::Flags => 4,
}
}
}
impl fmt::Display for A64RegClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
A64RegClass::GPR64 => write!(f, "GPR64"),
A64RegClass::GPR32 => write!(f, "GPR32"),
A64RegClass::FPR128 => write!(f, "FPR128"),
A64RegClass::FPR64 => write!(f, "FPR64"),
A64RegClass::FPR32 => write!(f, "FPR32"),
A64RegClass::FPR16 => write!(f, "FPR16"),
A64RegClass::FPR8 => write!(f, "FPR8"),
A64RegClass::Flags => write!(f, "Flags"),
}
}
}
#[derive(Debug)]
pub struct AArch64RegisterInfo;
impl AArch64RegisterInfo {
pub fn get_asm_name(reg: PhysReg) -> &'static str {
match reg {
X0 => "x0",
X1 => "x1",
X2 => "x2",
X3 => "x3",
X4 => "x4",
X5 => "x5",
X6 => "x6",
X7 => "x7",
X8 => "x8",
X9 => "x9",
X10 => "x10",
X11 => "x11",
X12 => "x12",
X13 => "x13",
X14 => "x14",
X15 => "x15",
X16 => "x16",
X17 => "x17",
X18 => "x18",
X19 => "x19",
X20 => "x20",
X21 => "x21",
X22 => "x22",
X23 => "x23",
X24 => "x24",
X25 => "x25",
X26 => "x26",
X27 => "x27",
X28 => "x28",
X29 => "x29",
X30 => "x30",
SP => "sp",
XZR => "xzr",
W0 => "w0",
W1 => "w1",
W2 => "w2",
W3 => "w3",
W4 => "w4",
W5 => "w5",
W6 => "w6",
W7 => "w7",
W8 => "w8",
W9 => "w9",
W10 => "w10",
W11 => "w11",
W12 => "w12",
W13 => "w13",
W14 => "w14",
W15 => "w15",
W16 => "w16",
W17 => "w17",
W18 => "w18",
W19 => "w19",
W20 => "w20",
W21 => "w21",
W22 => "w22",
W23 => "w23",
W24 => "w24",
W25 => "w25",
W26 => "w26",
W27 => "w27",
W28 => "w28",
W29 => "w29",
W30 => "w30",
WSP => "wsp",
WZR => "wzr",
V0 => "v0",
V1 => "v1",
V2 => "v2",
V3 => "v3",
V4 => "v4",
V5 => "v5",
V6 => "v6",
V7 => "v7",
V8 => "v8",
V9 => "v9",
V10 => "v10",
V11 => "v11",
V12 => "v12",
V13 => "v13",
V14 => "v14",
V15 => "v15",
V16 => "v16",
V17 => "v17",
V18 => "v18",
V19 => "v19",
V20 => "v20",
V21 => "v21",
V22 => "v22",
V23 => "v23",
V24 => "v24",
V25 => "v25",
V26 => "v26",
V27 => "v27",
V28 => "v28",
V29 => "v29",
V30 => "v30",
V31 => "v31",
NZCV => "nzcv",
FPCR => "fpcr",
FPSR => "fpsr",
_ => "?",
}
}
pub fn get_reg_class(reg: PhysReg) -> Option<A64RegClass> {
match reg {
X0..=X30 | XZR => Some(A64RegClass::GPR64),
W0..=W30 | WZR | WSP => Some(A64RegClass::GPR32),
V0..=V31 => Some(A64RegClass::FPR128),
NZCV => Some(A64RegClass::Flags),
SP | FP | LR => Some(A64RegClass::GPR64),
_ => None,
}
}
pub fn get_reg_width(reg: PhysReg) -> u16 {
match Self::get_reg_class(reg) {
Some(cls) => cls.width_bits(),
None => 0,
}
}
pub fn is_callee_saved(reg: PhysReg) -> bool {
matches!(
reg,
X19 | X20 | X21 | X22 | X23 | X24 | X25 | X26 | X27 | X28 | X29 | FP | SP
)
}
pub fn is_caller_saved(reg: PhysReg) -> bool {
matches!(reg, X0..=X18)
}
pub fn is_reserved(reg: PhysReg) -> bool {
matches!(reg, XZR | SP | WZR | WSP | X30 | LR | NZCV | FPCR | FPSR)
}
pub fn get_sub_reg_32(reg: PhysReg) -> Option<PhysReg> {
match reg {
X0..=X30 => Some(W0 + (reg - X0)),
XZR => Some(WZR),
SP => Some(WSP),
_ => None,
}
}
pub fn get_super_reg_64(reg: PhysReg) -> Option<PhysReg> {
match reg {
W0..=W30 => Some(X0 + (reg - W0)),
WZR => Some(XZR),
WSP => Some(SP),
_ => None,
}
}
pub fn get_v_index(reg: PhysReg) -> Option<u8> {
match reg {
V0..=V31 => Some((reg - V0) as u8),
_ => None,
}
}
pub fn get_allocatable_gprs() -> Vec<PhysReg> {
let mut regs = Vec::with_capacity(28);
for r in X0..=X28 {
regs.push(r);
}
regs
}
pub fn get_allocatable_fprs() -> Vec<PhysReg> {
(V0..=V31).collect()
}
pub fn get_callee_saved_gprs() -> Vec<PhysReg> {
vec![
X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, SP,
]
}
pub fn get_callee_saved_fprs() -> Vec<PhysReg> {
(V8..=V15).collect()
}
pub fn get_caller_saved_gprs() -> Vec<PhysReg> {
(X0..=X18).collect()
}
pub fn get_caller_saved_fprs() -> Vec<PhysReg> {
let mut v = Vec::with_capacity(24);
v.extend(V0..=V7);
v.extend(V16..=V31);
v
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum MachineOperand {
None,
Reg(PhysReg),
VReg(u32),
Imm(i64),
FImm(f64),
Mem {
base: PhysReg,
index: Option<PhysReg>,
disp: i64,
scale: u8, },
Label(String),
Block(u32),
Cond(A64Cond),
Shift { kind: A64ShiftKind, amount: u8 },
BitfieldImm { immr: u8, imms: u8 },
SysReg(String),
}
impl fmt::Display for MachineOperand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MachineOperand::None => write!(f, "<none>"),
MachineOperand::Reg(r) => write!(f, "{}", AArch64RegisterInfo::get_asm_name(*r)),
MachineOperand::VReg(v) => write!(f, "%{}", v),
MachineOperand::Imm(i) => write!(f, "#{}", i),
MachineOperand::FImm(x) => write!(f, "#{}", x),
MachineOperand::Mem {
base,
index,
disp,
scale,
} => {
if let Some(idx) = index {
write!(
f,
"[{}, {}, lsl #{}]",
AArch64RegisterInfo::get_asm_name(*base),
AArch64RegisterInfo::get_asm_name(*idx),
scale
)
} else if *disp != 0 {
write!(
f,
"[{}, #{}]",
AArch64RegisterInfo::get_asm_name(*base),
disp
)
} else {
write!(f, "[{}]", AArch64RegisterInfo::get_asm_name(*base))
}
}
MachineOperand::Label(l) => write!(f, "{}", l),
MachineOperand::Block(b) => write!(f, ".LBB{}", b),
MachineOperand::Cond(c) => write!(f, "{}", c),
MachineOperand::Shift { kind, amount } => write!(f, "{}, #{}", kind, amount),
MachineOperand::BitfieldImm { immr, imms } => write!(f, "#{}, #{}", immr, imms),
MachineOperand::SysReg(s) => write!(f, "{}", s),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum A64Cond {
EQ, NE, CS, HS, CC, LO, MI, PL, VS, VC, HI, LS, GE, LT, GT, LE, AL, NV, }
impl fmt::Display for A64Cond {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
A64Cond::EQ => "eq",
A64Cond::NE => "ne",
A64Cond::CS | A64Cond::HS => "cs",
A64Cond::CC | A64Cond::LO => "cc",
A64Cond::MI => "mi",
A64Cond::PL => "pl",
A64Cond::VS => "vs",
A64Cond::VC => "vc",
A64Cond::HI => "hi",
A64Cond::LS => "ls",
A64Cond::GE => "ge",
A64Cond::LT => "lt",
A64Cond::GT => "gt",
A64Cond::LE => "le",
A64Cond::AL => "al",
A64Cond::NV => "nv",
};
write!(f, "{}", s)
}
}
impl A64Cond {
pub fn invert(self) -> A64Cond {
match self {
A64Cond::EQ => A64Cond::NE,
A64Cond::NE => A64Cond::EQ,
A64Cond::CS | A64Cond::HS => A64Cond::CC,
A64Cond::CC | A64Cond::LO => A64Cond::CS,
A64Cond::MI => A64Cond::PL,
A64Cond::PL => A64Cond::MI,
A64Cond::VS => A64Cond::VC,
A64Cond::VC => A64Cond::VS,
A64Cond::HI => A64Cond::LS,
A64Cond::LS => A64Cond::HI,
A64Cond::GE => A64Cond::LT,
A64Cond::LT => A64Cond::GE,
A64Cond::GT => A64Cond::LE,
A64Cond::LE => A64Cond::GT,
A64Cond::AL => A64Cond::NV,
A64Cond::NV => A64Cond::AL,
}
}
pub fn encoding(self) -> u8 {
match self {
A64Cond::EQ => 0,
A64Cond::NE => 1,
A64Cond::CS | A64Cond::HS => 2,
A64Cond::CC | A64Cond::LO => 3,
A64Cond::MI => 4,
A64Cond::PL => 5,
A64Cond::VS => 6,
A64Cond::VC => 7,
A64Cond::HI => 8,
A64Cond::LS => 9,
A64Cond::GE => 10,
A64Cond::LT => 11,
A64Cond::GT => 12,
A64Cond::LE => 13,
A64Cond::AL => 14,
A64Cond::NV => 15,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum A64ShiftKind {
LSL,
LSR,
ASR,
ROR,
}
impl fmt::Display for A64ShiftKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
A64ShiftKind::LSL => write!(f, "lsl"),
A64ShiftKind::LSR => write!(f, "lsr"),
A64ShiftKind::ASR => write!(f, "asr"),
A64ShiftKind::ROR => write!(f, "ror"),
}
}
}
#[derive(Debug, Clone)]
pub struct MachineInstr {
pub opcode: A64Opcode,
pub def: MachineOperand,
pub uses: Vec<MachineOperand>,
pub is_terminator: bool,
pub is_call: bool,
pub is_branch: bool,
pub is_load: bool,
pub is_store: bool,
pub has_side_effects: bool,
pub comment: Option<String>,
}
impl MachineInstr {
pub fn new(opcode: A64Opcode) -> Self {
let (is_term, is_call, is_branch, is_load, is_store) = match opcode {
A64Opcode::B
| A64Opcode::BR
| A64Opcode::RET
| A64Opcode::CBZ
| A64Opcode::CBNZ
| A64Opcode::TBZ
| A64Opcode::TBNZ => (true, false, true, false, false),
A64Opcode::BL | A64Opcode::BLR => (false, true, true, false, false),
A64Opcode::LDR
| A64Opcode::LDUR
| A64Opcode::LDRB
| A64Opcode::LDRH
| A64Opcode::LDRW
| A64Opcode::LDRSB
| A64Opcode::LDRSH
| A64Opcode::LDRSW
| A64Opcode::LDP
| A64Opcode::LDAR
| A64Opcode::LDAXR
| A64Opcode::LDXR
| A64Opcode::LD1v
| A64Opcode::LD2v
| A64Opcode::LD3v
| A64Opcode::LD4v
| A64Opcode::LD1W_sve => (false, false, false, true, false),
A64Opcode::STR
| A64Opcode::STUR
| A64Opcode::STRB
| A64Opcode::STRH
| A64Opcode::STRW
| A64Opcode::STP
| A64Opcode::STLR
| A64Opcode::STLXR
| A64Opcode::STXR
| A64Opcode::ST1v
| A64Opcode::ST2v
| A64Opcode::ST3v
| A64Opcode::ST4v
| A64Opcode::ST1W_sve => (false, false, false, false, true),
_ => (false, false, false, false, false),
};
MachineInstr {
opcode,
def: MachineOperand::None,
uses: Vec::new(),
is_terminator: is_term,
is_call,
is_branch,
is_load,
is_store,
has_side_effects: matches!(
opcode,
A64Opcode::SVC
| A64Opcode::DMB
| A64Opcode::DSB
| A64Opcode::ISB
| A64Opcode::MSR
| A64Opcode::MRS
),
comment: None,
}
}
pub fn with_def(mut self, op: MachineOperand) -> Self {
self.def = op;
self
}
pub fn with_use(mut self, op: MachineOperand) -> Self {
self.uses.push(op);
self
}
pub fn with_uses(mut self, ops: Vec<MachineOperand>) -> Self {
self.uses = ops;
self
}
pub fn with_comment(mut self, c: impl Into<String>) -> Self {
self.comment = Some(c.into());
self
}
pub fn has_def(&self) -> bool {
!matches!(self.def, MachineOperand::None)
}
pub fn to_asm(&self) -> String {
let mut s = String::new();
s.push_str(&format!("{}", self.opcode));
if matches!(
self.opcode,
A64Opcode::CMP | A64Opcode::CMN | A64Opcode::FCMP | A64Opcode::FCMPZ
) {
if !self.uses.is_empty() {
s.push(' ');
s.push_str(
&self
.uses
.iter()
.map(|u| u.to_string())
.collect::<Vec<_>>()
.join(", "),
);
}
} else if matches!(self.opcode, A64Opcode::B | A64Opcode::BL) {
if !self.uses.is_empty() {
s.push(' ');
s.push_str(&self.uses[0].to_string());
}
} else if matches!(
self.opcode,
A64Opcode::CBZ | A64Opcode::CBNZ | A64Opcode::TBZ | A64Opcode::TBNZ
) {
if self.uses.len() >= 2 {
s.push(' ');
s.push_str(&self.uses[0].to_string());
s.push_str(", ");
s.push_str(&self.uses[1].to_string());
}
} else if self.opcode == A64Opcode::RET {
if !self.uses.is_empty() {
s.push(' ');
s.push_str(&self.uses[0].to_string());
}
} else {
let mut parts = Vec::new();
if self.has_def() {
parts.push(self.def.to_string());
}
for u in &self.uses {
parts.push(u.to_string());
}
if !parts.is_empty() {
s.push(' ');
s.push_str(&parts.join(", "));
}
}
if let Some(ref c) = self.comment {
s.push_str(&format!(" // {}", c));
}
s
}
}
#[derive(Debug, Clone)]
pub struct AArch64InstrDesc {
pub opcode: A64Opcode,
pub name: &'static str,
pub category: InstrCategory,
pub num_sources: u8,
pub num_defs: u8,
pub writes_flags: bool,
pub reads_flags: bool,
pub is_flag_setting: bool,
pub is_simd: bool,
pub has_side_effects: bool,
pub is_terminator: bool,
pub is_call: bool,
pub latency: u8,
pub throughput: f32,
}
impl AArch64InstrDesc {
pub const fn new(
opcode: A64Opcode,
name: &'static str,
category: InstrCategory,
num_sources: u8,
num_defs: u8,
writes_flags: bool,
reads_flags: bool,
is_flag_setting: bool,
is_simd: bool,
has_side_effects: bool,
is_terminator: bool,
is_call: bool,
latency: u8,
throughput: f32,
) -> Self {
AArch64InstrDesc {
opcode,
name,
category,
num_sources,
num_defs,
writes_flags,
reads_flags,
is_flag_setting,
is_simd,
has_side_effects,
is_terminator,
is_call,
latency,
throughput,
}
}
}
#[derive(Debug)]
pub struct AArch64InstrInfo {
pub table: HashMap<A64Opcode, AArch64InstrDesc>,
}
impl AArch64InstrInfo {
pub fn new() -> Self {
use A64Opcode::*;
use InstrCategory::*;
let entries: Vec<AArch64InstrDesc> = vec![
AArch64InstrDesc::new(
ADD, "add", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ADDS, "adds", ALU, 2, 1, true, false, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SUB, "sub", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SUBS, "subs", ALU, 2, 1, true, false, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ADC, "adc", ALU, 2, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ADCS, "adcs", ALU, 2, 1, true, true, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SBC, "sbc", ALU, 2, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SBCS, "sbcs", ALU, 2, 1, true, true, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
NEG, "neg", ALU, 1, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
NEGS, "negs", ALU, 1, 1, true, false, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CMP, "cmp", ALU, 2, 0, true, false, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CMN, "cmn", ALU, 2, 0, true, false, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
AND, "and", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ANDS, "ands", ALU, 2, 1, true, false, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ORR, "orr", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
EOR, "eor", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
BIC, "bic", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
BICS, "bics", ALU, 2, 1, true, false, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ORN, "orn", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
EON, "eon", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MOVZ, "movz", ALU, 1, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MOVK, "movk", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MOVN, "movn", ALU, 1, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MOV, "mov", ALU, 1, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
LSL, "lsl", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
LSR, "lsr", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ASR, "asr", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ROR, "ror", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MUL, "mul", MulDiv, 2, 1, false, false, false, false, false, false, false, 3, 1.0,
),
AArch64InstrDesc::new(
MADD, "madd", MulDiv, 3, 1, false, false, false, false, false, false, false, 3, 1.0,
),
AArch64InstrDesc::new(
MSUB, "msub", MulDiv, 3, 1, false, false, false, false, false, false, false, 3, 1.0,
),
AArch64InstrDesc::new(
MNEG, "mneg", MulDiv, 2, 1, false, false, false, false, false, false, false, 3, 1.0,
),
AArch64InstrDesc::new(
SMULL, "smull", MulDiv, 2, 1, false, false, false, true, false, false, false, 3,
1.0,
),
AArch64InstrDesc::new(
UMULL, "umull", MulDiv, 2, 1, false, false, false, true, false, false, false, 3,
1.0,
),
AArch64InstrDesc::new(
SMADDL, "smaddl", MulDiv, 3, 1, false, false, false, false, false, false, false, 3,
1.0,
),
AArch64InstrDesc::new(
SMSUBL, "smsubl", MulDiv, 3, 1, false, false, false, false, false, false, false, 3,
1.0,
),
AArch64InstrDesc::new(
UMADDL, "umaddl", MulDiv, 3, 1, false, false, false, false, false, false, false, 3,
1.0,
),
AArch64InstrDesc::new(
UMSUBL, "umsubl", MulDiv, 3, 1, false, false, false, false, false, false, false, 3,
1.0,
),
AArch64InstrDesc::new(
SMULH, "smulh", MulDiv, 2, 1, false, false, false, false, false, false, false, 3,
0.5,
),
AArch64InstrDesc::new(
UMULH, "umulh", MulDiv, 2, 1, false, false, false, false, false, false, false, 3,
0.5,
),
AArch64InstrDesc::new(
SDIV, "sdiv", MulDiv, 2, 1, false, false, false, false, false, false, false, 12,
1.0,
),
AArch64InstrDesc::new(
UDIV, "udiv", MulDiv, 2, 1, false, false, false, false, false, false, false, 12,
1.0,
),
AArch64InstrDesc::new(
BFM, "bfm", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SBFM, "sbfm", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
UBFM, "ubfm", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
BFI, "bfi", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
BFXIL, "bfxil", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SBFIZ, "sbfiz", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
UBFIZ, "ubfiz", ALU, 2, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SBFX, "sbfx", ALU, 1, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
UBFX, "ubfx", ALU, 1, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
EXTR, "extr", ALU, 3, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CSEL, "csel", ALU, 3, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CSINC, "csinc", ALU, 3, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CSINV, "csinv", ALU, 3, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CSNEG, "csneg", ALU, 3, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CINC, "cinc", ALU, 2, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CINV, "cinv", ALU, 2, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CNEG, "cneg", ALU, 2, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CSET, "cset", ALU, 1, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CSETM, "csetm", ALU, 1, 1, false, true, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CCMN, "ccmn", ALU, 2, 0, true, true, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CCMP, "ccmp", ALU, 2, 0, true, true, true, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
B, "b", Branch, 0, 0, false, false, false, false, false, true, false, 1, 0.5,
),
AArch64InstrDesc::new(
BL, "bl", Branch, 0, 0, false, false, false, false, false, false, true, 1, 0.5,
),
AArch64InstrDesc::new(
BR, "br", Branch, 0, 0, false, false, false, false, false, true, false, 1, 0.5,
),
AArch64InstrDesc::new(
BLR, "blr", Branch, 0, 0, false, false, false, false, false, false, true, 1, 0.5,
),
AArch64InstrDesc::new(
RET, "ret", Branch, 0, 0, false, false, false, false, false, true, false, 1, 0.5,
),
AArch64InstrDesc::new(
CBZ, "cbz", Branch, 1, 0, false, false, false, false, false, true, false, 1, 0.5,
),
AArch64InstrDesc::new(
CBNZ, "cbnz", Branch, 1, 0, false, false, false, false, false, true, false, 1, 0.5,
),
AArch64InstrDesc::new(
TBZ, "tbz", Branch, 1, 0, false, false, false, false, false, true, false, 1, 0.5,
),
AArch64InstrDesc::new(
TBNZ, "tbnz", Branch, 1, 0, false, false, false, false, false, true, false, 1, 0.5,
),
AArch64InstrDesc::new(
ADRP, "adrp", ALU, 0, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ADR, "adr", ALU, 0, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
LDR, "ldr", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
LDUR, "ldur", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
LDRB, "ldrb", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
LDRH, "ldrh", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
LDRW, "ldr", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
LDRSB, "ldrsb", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
LDRSH, "ldrsh", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
LDRSW, "ldrsw", Load, 0, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
STR, "str", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
STUR, "stur", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
STRB, "strb", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
STRH, "strh", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
STRW, "str", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
LDP, "ldp", Load, 0, 2, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
STP, "stp", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
LDAR, "ldar", Load, 0, 1, false, false, false, false, true, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
STLR, "stlr", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
LDAXR, "ldaxr", Load, 0, 1, false, false, false, false, true, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
STLXR, "stlxr", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
LDXR, "ldxr", Load, 0, 1, false, false, false, false, true, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
STXR, "stxr", Store, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
PRFM, "prfm", Load, 0, 0, false, false, false, false, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
FADD, "fadd", FP, 2, 1, false, false, false, false, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FSUB, "fsub", FP, 2, 1, false, false, false, false, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FMUL, "fmul", FP, 2, 1, false, false, false, false, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FDIV, "fdiv", FP, 2, 1, false, false, false, false, false, false, false, 10, 1.0,
),
AArch64InstrDesc::new(
FNEG, "fneg", FP, 1, 1, false, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FABS, "fabs", FP, 1, 1, false, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FSQRT, "fsqrt", FP, 1, 1, false, false, false, false, false, false, false, 10, 1.0,
),
AArch64InstrDesc::new(
FMADD, "fmadd", FP, 3, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
FMSUB, "fmsub", FP, 3, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
FNMADD, "fnmadd", FP, 3, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
FNMSUB, "fnmsub", FP, 3, 1, false, false, false, false, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
FMAX, "fmax", FP, 2, 1, false, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FMIN, "fmin", FP, 2, 1, false, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FMAXNM, "fmaxnm", FP, 2, 1, false, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FMINNM, "fminnm", FP, 2, 1, false, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FCMP, "fcmp", FP, 2, 0, true, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FCMPZ, "fcmp", FP, 1, 0, true, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FCCMP, "fccmp", FP, 2, 0, true, true, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FCCMPZ, "fccmp", FP, 1, 0, true, true, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FCSEL, "fcsel", FP, 3, 1, false, true, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FCVT, "fcvt", FP, 1, 1, false, false, false, false, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FCVTZS, "fcvtzs", FP, 1, 1, false, false, false, false, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FCVTZU, "fcvtzu", FP, 1, 1, false, false, false, false, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
SCVTF, "scvtf", FP, 1, 1, false, false, false, false, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
UCVTF, "ucvtf", FP, 1, 1, false, false, false, false, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FMOV, "fmov", FP, 1, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
FMOVImm, "fmov", FP, 0, 1, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
FMOVGenToFP,
"fmov",
FP,
1,
1,
false,
false,
false,
false,
false,
false,
false,
2,
0.5,
),
AArch64InstrDesc::new(
FMOVFPToGen,
"fmov",
FP,
1,
1,
false,
false,
false,
false,
false,
false,
false,
2,
0.5,
),
AArch64InstrDesc::new(
ADDv, "add", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
SUBv, "sub", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
MULv, "mul", SIMD, 2, 1, false, false, false, true, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
MLAv, "mla", SIMD, 3, 1, false, false, false, true, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
MLSv, "mls", SIMD, 3, 1, false, false, false, true, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FMLAv, "fmla", SIMD, 3, 1, false, false, false, true, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FMLSv, "fmls", SIMD, 3, 1, false, false, false, true, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
SHLv, "shl", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
SHRv, "shr", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
SLIv, "sli", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
SRIv, "sri", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
XTNv, "xtn", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
SQXTNv, "sqxtn", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
UQXTNv, "uqxtn", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
REVv, "rev", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
TRNv, "trn", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
UZPv, "uzp", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
ZIPv, "zip", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
TBLv, "tbl", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
TBXv, "tbx", SIMD, 3, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
DUPv, "dup", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
MOVv, "mov", SIMD, 1, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MOVIv, "movi", SIMD, 0, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ANDv, "and", SIMD, 2, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ORRv, "orr", SIMD, 2, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
EORv, "eor", SIMD, 2, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
BICv, "bic", SIMD, 2, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ORNv, "orn", SIMD, 2, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
NOTv, "not", SIMD, 1, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CMEQv, "cmeq", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
CMGTv, "cmgt", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
CMGEv, "cmge", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
CMLTv, "cmlt", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
CMLEv, "cmle", SIMD, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
BSLv, "bsl", SIMD, 3, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
CLSv, "cls", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
CLZv, "clz", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
CNTv, "cnt", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
ABSv, "abs", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
NEGv, "neg", SIMD, 1, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
EXTv, "ext", SIMD, 3, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
LD1v, "ld1", Load, 0, 1, false, false, false, true, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
ST1v, "st1", Store, 0, 0, false, false, false, true, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
LD2v, "ld2", Load, 0, 2, false, false, false, true, false, false, false, 4, 0.5,
),
AArch64InstrDesc::new(
ST2v, "st2", Store, 0, 0, false, false, false, true, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
LD3v, "ld3", Load, 0, 3, false, false, false, true, false, false, false, 5, 0.5,
),
AArch64InstrDesc::new(
ST3v, "st3", Store, 0, 0, false, false, false, true, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
LD4v, "ld4", Load, 0, 4, false, false, false, true, false, false, false, 6, 0.5,
),
AArch64InstrDesc::new(
ST4v, "st4", Store, 0, 0, false, false, false, true, true, false, false, 1, 0.5,
),
AArch64InstrDesc::new(
ADD_sve, "add", SVE, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
SUB_sve, "sub", SVE, 2, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
FADD_sve, "fadd", SVE, 2, 1, false, false, false, true, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
FMUL_sve, "fmul", SVE, 2, 1, false, false, false, true, false, false, false, 3, 0.5,
),
AArch64InstrDesc::new(
WHILELT, "whilelt", SVE, 2, 0, false, false, false, true, false, false, false, 1,
0.5,
),
AArch64InstrDesc::new(
PTRUE, "ptrue", SVE, 0, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
INDEX, "index", SVE, 2, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
CNT_sve, "cnt", SVE, 0, 1, false, false, false, true, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
LD1W_sve, "ld1w", Load, 0, 1, false, false, false, true, false, false, false, 4,
0.5,
),
AArch64InstrDesc::new(
ST1W_sve, "st1w", Store, 0, 0, false, false, false, true, true, false, false, 1,
0.5,
),
AArch64InstrDesc::new(
CMPEQ_sve, "cmpeq", SVE, 2, 0, false, false, false, true, false, false, false, 2,
0.5,
),
AArch64InstrDesc::new(
FCMPEQ_sve, "fcmpeq", SVE, 2, 0, false, false, false, true, false, false, false, 2,
0.5,
),
AArch64InstrDesc::new(
SEL_sve, "sel", SVE, 3, 1, false, false, false, true, false, false, false, 2, 0.5,
),
AArch64InstrDesc::new(
COMPACT_sve,
"compact",
SVE,
1,
1,
false,
false,
false,
true,
false,
false,
false,
2,
0.5,
),
AArch64InstrDesc::new(
LASTB_sve, "lastb", SVE, 1, 1, false, false, false, true, false, false, false, 2,
0.5,
),
AArch64InstrDesc::new(
AND_sve_p, "and", SVE, 2, 1, false, false, false, true, false, false, false, 1,
0.25,
),
AArch64InstrDesc::new(
ORR_sve_p, "orr", SVE, 2, 1, false, false, false, true, false, false, false, 1,
0.25,
),
AArch64InstrDesc::new(
NOT_sve_p, "not", SVE, 1, 1, false, false, false, true, false, false, false, 1,
0.25,
),
AArch64InstrDesc::new(
NOP, "nop", Misc, 0, 0, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
SVC, "svc", System, 0, 0, false, false, false, false, true, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
HINT, "hint", Misc, 0, 0, false, false, false, false, false, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
DMB, "dmb", System, 0, 0, false, false, false, false, true, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
DSB, "dsb", System, 0, 0, false, false, false, false, true, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
ISB, "isb", System, 0, 0, false, false, false, false, true, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MSR, "msr", System, 0, 0, false, false, false, false, true, false, false, 1, 0.25,
),
AArch64InstrDesc::new(
MRS, "mrs", System, 0, 1, false, false, false, false, true, false, false, 1, 0.25,
),
];
let mut table = HashMap::with_capacity(entries.len());
for e in entries {
table.insert(e.opcode, e);
}
AArch64InstrInfo { table }
}
pub fn get(&self, opcode: A64Opcode) -> Option<&AArch64InstrDesc> {
self.table.get(&opcode)
}
pub fn writes_flags(&self, opcode: A64Opcode) -> bool {
self.table
.get(&opcode)
.map(|d| d.writes_flags)
.unwrap_or(false)
}
pub fn has_side_effects(&self, opcode: A64Opcode) -> bool {
self.table
.get(&opcode)
.map(|d| d.has_side_effects)
.unwrap_or(false)
}
pub fn len(&self) -> usize {
self.table.len()
}
}
impl Default for AArch64InstrInfo {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct AArch64FrameLowering {
pub local_area_size: u32,
pub callee_saved_gprs: u32,
pub callee_saved_fprs: u32,
pub has_fp: bool,
pub red_zone_size: u32,
pub has_var_sized_objects: bool,
pub total_frame_size: u32,
}
impl AArch64FrameLowering {
pub fn new(local_area_size: u32) -> Self {
AArch64FrameLowering {
local_area_size,
callee_saved_gprs: 0,
callee_saved_fprs: 0,
has_fp: local_area_size > 0,
red_zone_size: 0,
has_var_sized_objects: false,
total_frame_size: 0,
}
}
pub fn compute_frame_size(&mut self) -> u32 {
let aligned_local = (self.local_area_size + 15) & !15;
let gpr_spill_size = self.callee_saved_gprs * 8; let fpr_spill_size = self.callee_saved_fprs * 8; let spill_area = gpr_spill_size + fpr_spill_size;
let aligned_spill = (spill_area + 15) & !15;
self.total_frame_size = aligned_local + aligned_spill;
self.total_frame_size
}
pub fn emit_prologue(&self) -> Vec<MachineInstr> {
let frame_size = self.total_frame_size as i64;
let mut instrs = Vec::new();
instrs.push(
MachineInstr::new(A64Opcode::STP)
.with_uses(vec![
MachineOperand::Reg(X29),
MachineOperand::Reg(X30),
MachineOperand::Mem {
base: SP,
index: None,
disp: -16,
scale: 1,
},
])
.with_comment("save frame pointer and link register"),
);
instrs.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(X29))
.with_use(MachineOperand::Reg(SP))
.with_comment("set up frame pointer"),
);
if frame_size > 0 {
instrs.push(
MachineInstr::new(A64Opcode::SUB)
.with_def(MachineOperand::Reg(SP))
.with_uses(vec![
MachineOperand::Reg(SP),
MachineOperand::Imm(frame_size),
])
.with_comment("allocate stack space for locals"),
);
}
instrs
}
pub fn emit_epilogue(&self) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
if self.has_fp {
instrs.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(SP))
.with_use(MachineOperand::Reg(X29))
.with_comment("restore stack pointer from frame pointer"),
);
} else {
let frame_size = self.total_frame_size as i64;
if frame_size > 0 {
instrs.push(
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(SP))
.with_uses(vec![
MachineOperand::Reg(SP),
MachineOperand::Imm(frame_size),
])
.with_comment("deallocate stack space"),
);
}
}
instrs.push(
MachineInstr::new(A64Opcode::LDP)
.with_def(MachineOperand::Reg(X29))
.with_def(MachineOperand::Reg(X30))
.with_uses(vec![MachineOperand::Mem {
base: SP,
index: None,
disp: 0,
scale: 1,
}])
.with_comment("restore frame pointer and link register"),
);
instrs.push(
MachineInstr::new(A64Opcode::RET)
.with_use(MachineOperand::Reg(X30))
.with_comment("return to caller"),
);
instrs
}
pub fn get_frame_index_offset(&self, slot: u32) -> i64 {
-(slot as i64 + 2) * 8 }
pub fn frame_size(&self) -> u32 {
self.total_frame_size
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AArch64ArgClass {
Integer,
Float,
Memory,
HFA,
HVA,
HFA64,
}
#[derive(Debug, Clone)]
pub struct AArch64ArgInfo {
pub class: AArch64ArgClass,
pub reg: Option<PhysReg>,
pub stack_offset: Option<i64>,
pub size: u32,
pub alignment: u32,
}
#[derive(Debug)]
pub struct AArch64CallingConvention {
next_int_arg_reg: u8, next_fp_arg_reg: u8, stack_arg_offset: i64,
return_info: Option<Vec<AArch64ArgInfo>>,
}
impl AArch64CallingConvention {
pub fn new() -> Self {
AArch64CallingConvention {
next_int_arg_reg: 0,
next_fp_arg_reg: 0,
stack_arg_offset: 0,
return_info: None,
}
}
pub fn reset(&mut self) {
self.next_int_arg_reg = 0;
self.next_fp_arg_reg = 0;
self.stack_arg_offset = 0;
self.return_info = None;
}
pub fn classify_arg(&mut self, ty: &str) -> AArch64ArgInfo {
match ty {
"i8" | "i16" | "i32" | "i64" | "ptr" => {
if self.next_int_arg_reg < 8 {
let reg = X0 + self.next_int_arg_reg as PhysReg;
self.next_int_arg_reg += 1;
AArch64ArgInfo {
class: AArch64ArgClass::Integer,
reg: Some(reg),
stack_offset: None,
size: if ty == "i32" { 4 } else { 8 },
alignment: if ty == "i32" { 4 } else { 8 },
}
} else {
let offset = self.stack_arg_offset;
self.stack_arg_offset += 8;
AArch64ArgInfo {
class: AArch64ArgClass::Memory,
reg: None,
stack_offset: Some(offset),
size: 8,
alignment: 8,
}
}
}
"f32" | "f64" => {
if self.next_fp_arg_reg < 8 {
let reg = V0 + self.next_fp_arg_reg as PhysReg;
self.next_fp_arg_reg += 1;
let sz = if ty == "f64" { 8 } else { 4 };
AArch64ArgInfo {
class: AArch64ArgClass::Float,
reg: Some(reg),
stack_offset: None,
size: sz,
alignment: sz,
}
} else {
let offset = self.stack_arg_offset;
let sz = if ty == "f64" { 8 } else { 4 };
self.stack_arg_offset += sz as i64;
AArch64ArgInfo {
class: AArch64ArgClass::Memory,
reg: None,
stack_offset: Some(offset),
size: sz,
alignment: sz,
}
}
}
_ => {
let offset = self.stack_arg_offset;
self.stack_arg_offset += 8;
AArch64ArgInfo {
class: AArch64ArgClass::Memory,
reg: None,
stack_offset: Some(offset),
size: 8,
alignment: 8,
}
}
}
}
pub fn classify_return(&mut self, ty: &str) -> Vec<AArch64ArgInfo> {
let info = match ty {
"i8" | "i16" | "i32" | "i64" | "ptr" => vec![AArch64ArgInfo {
class: AArch64ArgClass::Integer,
reg: Some(X0),
stack_offset: None,
size: if ty == "i32" { 4 } else { 8 },
alignment: if ty == "i32" { 4 } else { 8 },
}],
"f32" | "f64" => vec![AArch64ArgInfo {
class: AArch64ArgClass::Float,
reg: Some(V0),
stack_offset: None,
size: if ty == "f64" { 8 } else { 4 },
alignment: if ty == "f64" { 8 } else { 4 },
}],
_ => vec![AArch64ArgInfo {
class: AArch64ArgClass::Integer,
reg: Some(X0),
stack_offset: None,
size: 8,
alignment: 8,
}],
};
self.return_info = Some(info.clone());
info
}
pub fn emit_args_setup(&mut self, arg_types: &[&str], arg_vregs: &[u32]) -> Vec<MachineInstr> {
let mut instrs = Vec::new();
self.reset();
for (&ty, &vreg) in arg_types.iter().zip(arg_vregs.iter()) {
let info = self.classify_arg(ty);
match info.class {
AArch64ArgClass::Integer | AArch64ArgClass::Float => {
let dest = info.reg.unwrap();
instrs.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(dest))
.with_use(MachineOperand::VReg(vreg))
.with_comment(format!("arg #{}, type {}", self.next_int_arg_reg, ty)),
);
}
AArch64ArgClass::Memory => {
let offset = info.stack_offset.unwrap();
instrs.push(
MachineInstr::new(A64Opcode::STR)
.with_uses(vec![
MachineOperand::VReg(vreg),
MachineOperand::Mem {
base: SP,
index: None,
disp: offset,
scale: 1,
},
])
.with_comment(format!("spill arg to stack, offset {}", offset)),
);
}
_ => {}
}
}
instrs
}
pub fn emit_call(&self, target_reg: PhysReg) -> Vec<MachineInstr> {
vec![MachineInstr::new(A64Opcode::BLR)
.with_use(MachineOperand::Reg(target_reg))
.with_comment("call through register")]
}
pub fn emit_return(&self) -> Vec<MachineInstr> {
vec![MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30))]
}
pub fn callee_saved_gprs() -> Vec<PhysReg> {
vec![X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30]
}
pub fn callee_saved_fprs() -> Vec<PhysReg> {
(V8..=V15).collect()
}
}
impl Default for AArch64CallingConvention {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IROpcode {
Add,
Sub,
Mul,
SDiv,
UDiv,
Neg,
And,
Or,
Xor,
Shl,
LShr,
AShr,
Load,
Store,
Br,
BrCond,
Ret,
Call,
ICmp,
FCmp,
SExt,
ZExt,
Trunc,
FPToUI,
FPToSI,
UIToFP,
SIToFP,
FAdd,
FSub,
FMul,
FDiv,
FNeg,
Phi,
Select,
Alloca,
Bitcast,
PtrToInt,
IntToPtr,
Const,
}
impl fmt::Display for IROpcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone)]
pub struct IRInst {
pub opcode: IROpcode,
pub dest: u32,
pub src1: u32,
pub src2: u32,
pub src3: u32, pub imm: Option<i64>,
pub label: Option<String>,
pub cond: Option<A64Cond>,
pub ty: String, }
impl IRInst {
pub fn new(opcode: IROpcode, dest: u32, ty: &str) -> Self {
IRInst {
opcode,
dest,
src1: 0,
src2: 0,
src3: 0,
imm: None,
label: None,
cond: None,
ty: ty.to_string(),
}
}
pub fn binary(opcode: IROpcode, dest: u32, src1: u32, src2: u32, ty: &str) -> Self {
IRInst {
opcode,
dest,
src1,
src2,
src3: 0,
imm: None,
label: None,
cond: None,
ty: ty.to_string(),
}
}
pub fn unary(opcode: IROpcode, dest: u32, src: u32, ty: &str) -> Self {
IRInst {
opcode,
dest,
src1: src,
src2: 0,
src3: 0,
imm: None,
label: None,
cond: None,
ty: ty.to_string(),
}
}
pub fn with_imm(mut self, imm: i64) -> Self {
self.imm = Some(imm);
self
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_cond(mut self, cond: A64Cond) -> Self {
self.cond = Some(cond);
self
}
}
#[derive(Debug)]
pub struct AArch64ISel {
pub reg_map: HashMap<u32, PhysReg>,
next_gpr: PhysReg,
next_fpr: PhysReg,
gpr_alloc_count: u32,
pub output: Vec<MachineInstr>,
}
impl AArch64ISel {
pub fn new() -> Self {
AArch64ISel {
reg_map: HashMap::new(),
next_gpr: X0,
next_fpr: V0,
gpr_alloc_count: 0,
output: Vec::new(),
}
}
pub fn select(&mut self, ir: &[IRInst]) -> Vec<MachineInstr> {
self.output.clear();
self.reg_map.clear();
self.next_gpr = X0;
self.next_fpr = V0;
self.gpr_alloc_count = 0;
for inst in ir {
self.select_one(inst);
}
if let Some(last) = self.output.last() {
if !last.is_terminator {
self.output
.push(MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30)));
}
} else {
self.output
.push(MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30)));
}
self.output.clone()
}
fn alloc_gpr(&mut self, vreg: u32) -> PhysReg {
if let Some(&preg) = self.reg_map.get(&vreg) {
return preg;
}
let preg = X0 + self.gpr_alloc_count as PhysReg;
self.gpr_alloc_count += 1;
self.reg_map.insert(vreg, preg);
preg
}
fn alloc_fpr(&mut self, vreg: u32) -> PhysReg {
if let Some(&preg) = self.reg_map.get(&vreg) {
return preg;
}
let preg = self.next_fpr;
self.next_fpr += 1;
self.reg_map.insert(vreg, preg);
preg
}
fn get_reg(&mut self, vreg: u32, is_fp: bool) -> PhysReg {
if is_fp {
self.alloc_fpr(vreg)
} else {
self.alloc_gpr(vreg)
}
}
fn is_fp_type(ty: &str) -> bool {
ty == "f32" || ty == "f64" || ty == "f16" || ty == "f128"
}
fn select_one(&mut self, inst: &IRInst) {
let is_fp = Self::is_fp_type(&inst.ty);
match inst.opcode {
IROpcode::Add => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::Sub => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::SUB)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::SUB)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::Mul => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::MUL)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
IROpcode::SDiv => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::SDIV)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
IROpcode::UDiv => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::UDIV)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
IROpcode::Neg => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::NEG)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::And => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::AND)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::AND)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::Or => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::ORR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::ORR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::Xor => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::EOR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::EOR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::Shl => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::LSL)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::LSL)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::LShr => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::LSR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::LSR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::AShr => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::ASR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(imm)]),
);
} else {
let src2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::ASR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
}
IROpcode::Load => {
let dst = self.alloc_gpr(inst.dest);
let addr = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::LDR)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Mem {
base: addr,
index: None,
disp: inst.imm.unwrap_or(0),
scale: 1,
}),
);
}
IROpcode::Store => {
let val = self.get_reg(inst.src1, false);
let addr = self.get_reg(inst.src2, false);
self.output
.push(MachineInstr::new(A64Opcode::STR).with_uses(vec![
MachineOperand::Reg(val),
MachineOperand::Mem {
base: addr,
index: None,
disp: inst.imm.unwrap_or(0),
scale: 1,
},
]));
}
IROpcode::Br => {
if let Some(ref label) = inst.label {
self.output.push(
MachineInstr::new(A64Opcode::B)
.with_use(MachineOperand::Label(label.clone())),
);
}
}
IROpcode::BrCond => {
let cond = inst.cond.unwrap_or(A64Cond::EQ);
let src1 = self.get_reg(inst.src1, false);
if let Some(ref label) = inst.label {
self.output.push(
MachineInstr::new(A64Opcode::CMP)
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Imm(0)])
.with_comment("compare for conditional branch"),
);
let (branch_op, branch_cond) = match cond {
A64Cond::EQ => (A64Opcode::CBZ, A64Cond::EQ),
A64Cond::NE => (A64Opcode::CBNZ, A64Cond::NE),
A64Cond::LT => (A64Opcode::CBZ, A64Cond::EQ), A64Cond::GT => (A64Opcode::CBNZ, A64Cond::NE), _ => (A64Opcode::CBNZ, A64Cond::NE),
};
self.output
.push(MachineInstr::new(branch_op).with_uses(vec![
MachineOperand::Reg(src1),
MachineOperand::Label(label.clone()),
]));
}
}
IROpcode::Ret => {
if inst.src1 != 0 {
let val = self.get_reg(inst.src1, is_fp);
self.output.push(
MachineInstr::new(if is_fp {
A64Opcode::FMOV
} else {
A64Opcode::MOV
})
.with_def(MachineOperand::Reg(if is_fp { V0 } else { X0 }))
.with_use(MachineOperand::Reg(val)),
);
}
self.output
.push(MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30)));
}
IROpcode::Call => {
let target = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::BLR)
.with_use(MachineOperand::Reg(target))
.with_comment("function call"),
);
if inst.dest != 0 {
let dst = self.alloc_gpr(inst.dest);
self.output.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(X0)),
);
}
}
IROpcode::ICmp => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, false);
let src2 = if inst.imm.is_some() {
self.output
.push(MachineInstr::new(A64Opcode::CMP).with_uses(vec![
MachineOperand::Reg(src1),
MachineOperand::Imm(inst.imm.unwrap()),
]));
XZR } else {
let s2 = self.get_reg(inst.src2, false);
self.output.push(
MachineInstr::new(A64Opcode::CMP)
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(s2)]),
);
s2
};
let cond = inst.cond.unwrap_or(A64Cond::EQ);
self.output.push(
MachineInstr::new(A64Opcode::CSET)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Cond(cond)),
);
}
IROpcode::FCmp => {
let dst = self.alloc_gpr(inst.dest);
let src1 = self.get_reg(inst.src1, true);
let src2 = self.get_reg(inst.src2, true);
self.output.push(
MachineInstr::new(A64Opcode::FCMP)
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
let cond = inst.cond.unwrap_or(A64Cond::EQ);
self.output.push(
MachineInstr::new(A64Opcode::CSET)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Cond(cond)),
);
}
IROpcode::SExt => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::SBFM)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![
MachineOperand::Reg(src),
MachineOperand::BitfieldImm { immr: 0, imms: 31 },
]),
);
}
IROpcode::ZExt => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::UBFM)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![
MachineOperand::Reg(src),
MachineOperand::BitfieldImm { immr: 0, imms: 31 },
]),
);
}
IROpcode::Trunc => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::FPToSI => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, true);
self.output.push(
MachineInstr::new(A64Opcode::FCVTZS)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::FPToUI => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, true);
self.output.push(
MachineInstr::new(A64Opcode::FCVTZU)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::SIToFP => {
let dst = self.alloc_fpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::SCVTF)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::UIToFP => {
let dst = self.alloc_fpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::UCVTF)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::FAdd => {
let dst = self.alloc_fpr(inst.dest);
let src1 = self.get_reg(inst.src1, true);
let src2 = self.get_reg(inst.src2, true);
self.output.push(
MachineInstr::new(A64Opcode::FADD)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
IROpcode::FSub => {
let dst = self.alloc_fpr(inst.dest);
let src1 = self.get_reg(inst.src1, true);
let src2 = self.get_reg(inst.src2, true);
self.output.push(
MachineInstr::new(A64Opcode::FSUB)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
IROpcode::FMul => {
let dst = self.alloc_fpr(inst.dest);
let src1 = self.get_reg(inst.src1, true);
let src2 = self.get_reg(inst.src2, true);
self.output.push(
MachineInstr::new(A64Opcode::FMUL)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
IROpcode::FDiv => {
let dst = self.alloc_fpr(inst.dest);
let src1 = self.get_reg(inst.src1, true);
let src2 = self.get_reg(inst.src2, true);
self.output.push(
MachineInstr::new(A64Opcode::FDIV)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(src1), MachineOperand::Reg(src2)]),
);
}
IROpcode::FNeg => {
let dst = self.alloc_fpr(inst.dest);
let src = self.get_reg(inst.src1, true);
self.output.push(
MachineInstr::new(A64Opcode::FNEG)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::Phi => {
let dst = self.alloc_gpr(inst.dest);
if inst.src1 != 0 {
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src))
.with_comment("phi copy"),
);
}
}
IROpcode::Select => {
let dst = self.alloc_gpr(inst.dest);
let src_true = self.get_reg(inst.src1, false);
let src_false = self.get_reg(inst.src2, false);
let cond = inst.cond.unwrap_or(A64Cond::EQ);
self.output.push(
MachineInstr::new(A64Opcode::CSEL)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![
MachineOperand::Reg(src_true),
MachineOperand::Reg(src_false),
MachineOperand::Cond(cond),
]),
);
}
IROpcode::Alloca => {
let dst = self.alloc_gpr(inst.dest);
let size = inst.imm.unwrap_or(8);
self.output.push(
MachineInstr::new(A64Opcode::SUB)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Reg(SP), MachineOperand::Imm(size)])
.with_comment("alloca: compute stack address"),
);
}
IROpcode::Bitcast => {
let dst = if is_fp {
self.alloc_fpr(inst.dest)
} else {
self.alloc_gpr(inst.dest)
};
let src = self.get_reg(inst.src1, !is_fp);
let op = if is_fp {
A64Opcode::FMOVGenToFP
} else {
A64Opcode::FMOVFPToGen
};
self.output.push(
MachineInstr::new(op)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::PtrToInt => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::IntToPtr => {
let dst = self.alloc_gpr(inst.dest);
let src = self.get_reg(inst.src1, false);
self.output.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(src)),
);
}
IROpcode::Const => {
let dst = self.alloc_gpr(inst.dest);
if let Some(imm) = inst.imm {
self.output.push(
MachineInstr::new(A64Opcode::MOVZ)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Imm(imm)),
);
}
}
}
}
pub fn print_asm(&self) -> String {
let mut s = String::new();
for instr in &self.output {
s.push_str(&format!("\t{}\n", instr.to_asm()));
}
s
}
pub fn get_reg_mapping(&self) -> &HashMap<u32, PhysReg> {
&self.reg_map
}
}
impl Default for AArch64ISel {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct AArch64Deep {
pub reg_info: AArch64RegisterInfo,
pub instr_info: AArch64InstrInfo,
pub frame_lowering: Option<AArch64FrameLowering>,
pub calling_convention: AArch64CallingConvention,
pub isel: AArch64ISel,
pub machine_code: Vec<MachineInstr>,
}
impl AArch64Deep {
pub fn new() -> Self {
AArch64Deep {
reg_info: AArch64RegisterInfo,
instr_info: AArch64InstrInfo::new(),
frame_lowering: None,
calling_convention: AArch64CallingConvention::new(),
isel: AArch64ISel::new(),
machine_code: Vec::new(),
}
}
pub fn with_frame(mut self, local_area_size: u32) -> Self {
self.frame_lowering = Some(AArch64FrameLowering::new(local_area_size));
self
}
pub fn compile(&mut self, ir: &[IRInst]) -> Vec<MachineInstr> {
let mut selected = self.isel.select(ir);
if let Some(ref frame) = self.frame_lowering {
let mut frame_fl = frame.clone();
frame_fl.compute_frame_size();
let prologue = frame_fl.emit_prologue();
let mut result = Vec::new();
result.extend(prologue);
result.append(&mut selected);
let epilogue = frame_fl.emit_epilogue();
let mut final_instrs = Vec::new();
for mi in result {
if mi.opcode == A64Opcode::RET {
final_instrs.extend(epilogue.clone());
} else {
final_instrs.push(mi);
}
}
self.machine_code = final_instrs;
} else {
self.machine_code = selected;
}
self.machine_code.clone()
}
pub fn print_asm(&self) -> String {
let mut s = String::new();
for instr in &self.machine_code {
s.push_str(&format!("\t{}\n", instr.to_asm()));
}
s
}
pub fn validate(&self) -> Result<(), String> {
for mi in &self.machine_code {
if matches!(&mi.def, MachineOperand::Reg(XZR)) {
return Err(format!(
"instruction '{}' writes to XZR (zero register cannot be destination)",
mi.to_asm()
));
}
if mi.is_call || mi.is_branch {
}
}
Ok(())
}
pub fn instr_count(&self) -> usize {
self.machine_code.len()
}
pub fn get_opcode(&self, idx: usize) -> Option<A64Opcode> {
self.machine_code.get(idx).map(|mi| mi.opcode)
}
}
impl Default for AArch64Deep {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MCInst {
pub encoding: u32,
pub opcode: A64Opcode,
}
impl MCInst {
pub fn new(encoding: u32, opcode: A64Opcode) -> Self {
MCInst { encoding, opcode }
}
pub fn bits(&self, msb: u32, lsb: u32) -> u32 {
(self.encoding >> lsb) & ((1u32 << (msb - lsb + 1)) - 1)
}
pub fn opcode_field(&self) -> u32 {
self.bits(31, 22)
}
pub fn rd(&self) -> u32 {
self.bits(4, 0)
}
pub fn rn(&self) -> u32 {
self.bits(9, 5)
}
pub fn rm(&self) -> u32 {
self.bits(20, 16)
}
pub fn imm12(&self) -> u32 {
self.bits(21, 10)
}
pub fn rt(&self) -> u32 {
self.bits(4, 0)
}
pub fn rn_ldst(&self) -> u32 {
self.bits(9, 5)
}
pub fn decode(word: u32) -> Option<MCInst> {
let top_bits = (word >> 25) & 0x7F;
match top_bits {
0x40..=0x43 => Some(MCInst::decode_dp_imm(word)),
0x05 => Some(MCInst::decode_branch(word)),
0x48..=0x4B | 0x58..=0x5B | 0x68..=0x6B | 0x78..=0x7B => {
Some(MCInst::decode_load_store(word))
}
0x2A | 0x2B | 0x4A | 0x4B | 0x6A | 0x6B => Some(MCInst::decode_dp_reg(word)),
_ => None,
}
}
fn decode_dp_imm(word: u32) -> Self {
let opc = (word >> 29) & 0x3;
let sf = (word >> 31) & 0x1;
let opcode = match opc {
0 => {
if sf == 1 {
A64Opcode::ADD
} else {
A64Opcode::ADD
}
}
1 => {
if sf == 1 {
A64Opcode::ADDS
} else {
A64Opcode::ADDS
}
}
2 => {
if sf == 1 {
A64Opcode::SUB
} else {
A64Opcode::SUB
}
}
3 => {
if sf == 1 {
A64Opcode::SUBS
} else {
A64Opcode::SUBS
}
}
_ => A64Opcode::ADD,
};
MCInst::new(word, opcode)
}
fn decode_branch(word: u32) -> Self {
let link = (word >> 31) & 1;
if link == 1 {
MCInst::new(word, A64Opcode::BL)
} else {
MCInst::new(word, A64Opcode::B)
}
}
fn decode_load_store(word: u32) -> Self {
let size = (word >> 30) & 3;
let is_load = ((word >> 22) & 1) == 1;
let opcode = match (is_load, size) {
(true, 3) => A64Opcode::LDR,
(false, 3) => A64Opcode::STR,
(true, 0) => A64Opcode::LDRB,
(false, 0) => A64Opcode::STRB,
(true, 1) => A64Opcode::LDRH,
(false, 1) => A64Opcode::STRH,
_ => {
if is_load {
A64Opcode::LDR
} else {
A64Opcode::STR
}
}
};
MCInst::new(word, opcode)
}
fn decode_dp_reg(word: u32) -> Self {
let opc = (word >> 29) & 3;
let opcode = match opc {
0 => A64Opcode::AND,
1 => A64Opcode::ORR,
2 => A64Opcode::EOR,
3 => A64Opcode::ANDS,
_ => A64Opcode::AND,
};
MCInst::new(word, opcode)
}
}
#[derive(Debug)]
pub struct AArch64MCEncoder {
pub buffer: Vec<u8>,
current_addr: u64,
}
impl AArch64MCEncoder {
pub fn new() -> Self {
AArch64MCEncoder {
buffer: Vec::new(),
current_addr: 0,
}
}
pub fn encode(&mut self, mi: &MachineInstr) -> Option<u32> {
let word = match mi.opcode {
A64Opcode::ADD => self.encode_add_sub(mi, false, false),
A64Opcode::SUB => self.encode_add_sub(mi, true, false),
A64Opcode::ADDS => self.encode_add_sub(mi, false, true),
A64Opcode::SUBS => self.encode_add_sub(mi, true, true),
A64Opcode::AND => self.encode_logical(mi, 0),
A64Opcode::ORR => self.encode_logical(mi, 1),
A64Opcode::EOR => self.encode_logical(mi, 2),
A64Opcode::ANDS => self.encode_logical(mi, 3),
A64Opcode::B => self.encode_b_imm(mi),
A64Opcode::BL => self.encode_bl_imm(mi),
A64Opcode::RET => self.encode_ret(mi),
A64Opcode::LDR => self.encode_ldr_str(mi, true, 3),
A64Opcode::STR => self.encode_ldr_str(mi, false, 3),
A64Opcode::LDRB => self.encode_ldr_str(mi, true, 0),
A64Opcode::STRB => self.encode_ldr_str(mi, false, 0),
A64Opcode::LDRH => self.encode_ldr_str(mi, true, 1),
A64Opcode::STRH => self.encode_ldr_str(mi, false, 1),
A64Opcode::MOV => self.encode_mov_orr(mi),
A64Opcode::MOVZ => self.encode_mov_wide(mi, 2),
A64Opcode::MOVK => self.encode_mov_wide(mi, 3),
A64Opcode::MOVN => self.encode_mov_wide(mi, 0),
A64Opcode::CMP => self.encode_subs_xzr(mi),
A64Opcode::CMN => self.encode_adds_xzr(mi),
A64Opcode::NOP => Some(0xD503201F),
A64Opcode::FADD => self.encode_fp_data_proc2(mi, 0b0010),
A64Opcode::FSUB => self.encode_fp_data_proc2(mi, 0b0011),
A64Opcode::FMUL => self.encode_fp_data_proc2(mi, 0b0000),
A64Opcode::FDIV => self.encode_fp_data_proc2(mi, 0b0001),
A64Opcode::FNEG => self.encode_fp_data_proc1(mi, 0b10),
A64Opcode::FABS => self.encode_fp_data_proc1(mi, 0b01),
A64Opcode::FSQRT => self.encode_fp_data_proc1(mi, 0b11),
A64Opcode::FCMP => self.encode_fcmp(mi),
A64Opcode::MUL => self.encode_dp_reg3(mi, 0b000),
A64Opcode::SDIV => self.encode_dp_reg3(mi, 0b011),
A64Opcode::UDIV => self.encode_dp_reg3(mi, 0b010),
A64Opcode::LSL => self.encode_shift(mi, 0b00),
A64Opcode::LSR => self.encode_shift(mi, 0b01),
A64Opcode::ASR => self.encode_shift(mi, 0b10),
A64Opcode::BR => self.encode_br(mi),
A64Opcode::BLR => self.encode_blr(mi),
A64Opcode::CBZ => self.encode_cbz(mi, false),
A64Opcode::CBNZ => self.encode_cbz(mi, true),
A64Opcode::STP => self.encode_stp_ldp(mi, false),
A64Opcode::LDP => self.encode_stp_ldp(mi, true),
A64Opcode::CSEL => self.encode_csel(mi),
A64Opcode::CSINC => self.encode_csinc(mi),
A64Opcode::CSINV => self.encode_csinv(mi),
A64Opcode::CSNEG => self.encode_csneg(mi),
_ => None,
};
if let Some(w) = word {
self.buffer.extend_from_slice(&w.to_le_bytes());
self.current_addr += 4;
}
word
}
fn encode_add_sub(&self, mi: &MachineInstr, is_sub: bool, set_flags: bool) -> Option<u32> {
let sf: u32 = 1; let op: u32 = if set_flags {
if is_sub {
3
} else {
1
}
} else {
if is_sub {
2
} else {
0
}
};
let s: u32 = if set_flags { 1 } else { 0 };
let rd = self.extract_reg(&mi.def)?;
let rn = self.extract_reg(mi.uses.get(0)?)?;
let (is_imm, imm12, sh) = if let Some(MachineOperand::Imm(v)) = mi.uses.get(1) {
let (enc_imm, enc_sh) = Self::encode_aarch64_imm12(*v);
(1u32, enc_imm, enc_sh)
} else {
(0, 0, 0)
};
Some(
(sf << 31)
| (op << 29)
| (s << 29) | (sh << 22)
| (imm12 << 10)
| (rn << 5)
| rd,
)
}
fn encode_logical(&self, mi: &MachineInstr, opc: u32) -> Option<u32> {
let sf: u32 = 1;
let rd = self.extract_reg(&mi.def)?;
let rn = self.extract_reg(mi.uses.get(0)?)?;
let rm = self.extract_reg(mi.uses.get(1)?)?;
Some(
(sf << 31)
| (0b10101010u32 << 21) | (rm << 16)
| (0u32 << 13) | (opc << 29)
| (rn << 5)
| rd,
)
}
fn encode_b_imm(&self, mi: &MachineInstr) -> Option<u32> {
Some(0x14000000)
}
fn encode_bl_imm(&self, mi: &MachineInstr) -> Option<u32> {
Some(0x94000000)
}
fn encode_ret(&self, mi: &MachineInstr) -> Option<u32> {
let rn = mi
.uses
.get(0)
.and_then(|u| self.extract_reg(u))
.unwrap_or(30);
Some(0xD65F0000 | (rn << 5))
}
fn encode_ldr_str(&self, mi: &MachineInstr, is_load: bool, size: u32) -> Option<u32> {
let sf: u32 = if size == 3 { 1 } else { 0 };
let rt = if is_load {
self.extract_reg(&mi.def)?
} else {
self.extract_reg(mi.uses.get(0)?)?
};
let (rn, imm9) = if is_load {
self.extract_mem_reg_disp(mi.uses.get(0)?)?
} else {
self.extract_mem_reg_disp(mi.uses.get(1)?)?
};
let l: u32 = if is_load { 1 } else { 0 };
Some(
(size << 30)
| (0b1110u32 << 26)
| (0b0u32 << 24) | (0b01u32 << 22) | (l << 22)
| (imm9 << 12)
| (rn << 5)
| rt,
)
}
fn encode_mov_orr(&self, mi: &MachineInstr) -> Option<u32> {
let rd = self.extract_reg(&mi.def)?;
let rm = self.extract_reg(mi.uses.get(0)?)?;
Some(
(1u32 << 31)
| (0b0101010u32 << 24)
| (0u32 << 22)
| (rm << 16)
| (31u32 << 5) | rd,
)
}
fn encode_mov_wide(&self, mi: &MachineInstr, opc: u32) -> Option<u32> {
let sf: u32 = 1;
let rd = self.extract_reg(&mi.def)?;
let imm16: u32 = match mi.uses.get(0)? {
MachineOperand::Imm(v) => (*v as u32) & 0xFFFF,
_ => 0,
};
let hw: u32 = 0; Some((sf << 31) | (0b10100101u32 << 23) | (hw << 21) | (imm16 << 5) | rd)
}
fn encode_subs_xzr(&self, mi: &MachineInstr) -> Option<u32> {
let rn = self.extract_reg(mi.uses.get(0)?)?;
let rm = self.extract_reg(mi.uses.get(1)?)?;
Some(
(1u32 << 31) | (0b1110u32 << 24) | (0b1011u32 << 24)
| (rm << 16)
| (rn << 5)
| 31, )
}
fn encode_adds_xzr(&self, mi: &MachineInstr) -> Option<u32> {
let rn = self.extract_reg(mi.uses.get(0)?)?;
let rm = self.extract_reg(mi.uses.get(1)?)?;
Some((1u32 << 31) | (0b1010u32 << 24) | (0b1011u32 << 24) | (rm << 16) | (rn << 5) | 31)
}
fn encode_fp_data_proc2(&self, mi: &MachineInstr, opc: u32) -> Option<u32> {
let rd = self.extract_reg(&mi.def)?;
let rn = self.extract_reg(mi.uses.get(0)?)?;
let rm = self.extract_reg(mi.uses.get(1)?)?;
let ty: u32 = 1; Some(
(0b00011110u32 << 24)
| (ty << 22)
| (1u32 << 21)
| (rm << 16)
| (opc << 12)
| (rn << 5)
| rd,
)
}
fn encode_fp_data_proc1(&self, mi: &MachineInstr, opc: u32) -> Option<u32> {
let rd = self.extract_reg(&mi.def)?;
let rn = self.extract_reg(mi.uses.get(0)?)?;
let ty: u32 = 1;
Some(
(0b00011110u32 << 24)
| (ty << 22)
| (1u32 << 21)
| (opc << 15)
| (0b10000u32 << 10)
| (rn << 5)
| rd,
)
}
fn encode_fcmp(&self, mi: &MachineInstr) -> Option<u32> {
let rn = self.extract_reg(mi.uses.get(0)?)?;
let rm = self.extract_reg(mi.uses.get(1)?)?;
Some(
(0b00011110u32 << 24)
| (1u32 << 22)
| (1u32 << 21)
| (rm << 16)
| (0b001000u32 << 10)
| (rn << 5),
)
}
fn encode_dp_reg3(&self, mi: &MachineInstr, opc: u32) -> Option<u32> {
let rd = self.extract_reg(&mi.def)?;
let rn = self.extract_reg(mi.uses.get(0)?)?;
let rm = self.extract_reg(mi.uses.get(1)?)?;
let sf: u32 = 1;
Some((sf << 31) | (0b11010110u32 << 21) | (rm << 16) | (opc << 10) | (rn << 5) | rd)
}
fn encode_shift(&self, mi: &MachineInstr, opc: u32) -> Option<u32> {
let rd = self.extract_reg(&mi.def)?;
let rn = self.extract_reg(mi.uses.get(0)?)?;
let shift = match mi.uses.get(1)? {
MachineOperand::Imm(v) => *v as u32,
_ => 0,
};
let sf: u32 = 1;
Some(
(sf << 31)
| (0b1101001101u32 << 21)
| (rn << 16)
| (opc << 29)
| (shift << 10)
| (rn << 5)
| rd,
)
}
fn encode_br(&self, mi: &MachineInstr) -> Option<u32> {
let rn = self.extract_reg(mi.uses.get(0)?)?;
Some(0xD61F0000 | (rn << 5))
}
fn encode_blr(&self, mi: &MachineInstr) -> Option<u32> {
let rn = self.extract_reg(mi.uses.get(0)?)?;
Some(0xD63F0000 | (rn << 5))
}
fn encode_cbz(&self, mi: &MachineInstr, non_zero: bool) -> Option<u32> {
let rt = self.extract_reg(mi.uses.get(0)?)?;
let sf: u32 = 1;
let op: u32 = if non_zero { 1 } else { 0 };
Some(
(sf << 31)
| (0b011010u32 << 25)
| (0u32 << 24) | (op << 24)
| (rt),
)
}
fn encode_stp_ldp(&self, mi: &MachineInstr, is_load: bool) -> Option<u32> {
let l: u32 = if is_load { 1 } else { 0 };
let rt = self.extract_reg(mi.uses.get(0)?)?;
let rt2 = self.extract_reg(mi.uses.get(1)?)?;
let rn = if let Some(MachineOperand::Mem { base, .. }) = mi.uses.get(2) {
if *base >= X0 && *base <= X30 {
(*base - X0) as u32
} else if *base == SP {
31
} else {
return None;
}
} else {
return None;
};
Some(
(0b10u32 << 30)
| (0b1010u32 << 26)
| (0b0u32 << 24)
| (l << 22)
| (0b11111u32 << 15) | (rt2 << 10)
| (rn << 5)
| rt,
)
}
fn encode_csel(&self, mi: &MachineInstr) -> Option<u32> {
self.encode_cond_sel(mi, 0b00)
}
fn encode_csinc(&self, mi: &MachineInstr) -> Option<u32> {
self.encode_cond_sel(mi, 0b01)
}
fn encode_csinv(&self, mi: &MachineInstr) -> Option<u32> {
self.encode_cond_sel(mi, 0b10)
}
fn encode_csneg(&self, mi: &MachineInstr) -> Option<u32> {
self.encode_cond_sel(mi, 0b11)
}
fn encode_cond_sel(&self, mi: &MachineInstr, op: u32) -> Option<u32> {
let rd = self.extract_reg(&mi.def)?;
let rn = self.extract_reg(mi.uses.get(0)?)?;
let rm = self.extract_reg(mi.uses.get(1)?)?;
let cond = match mi.uses.get(2)? {
MachineOperand::Cond(c) => c.encoding() as u32,
_ => 0,
};
let sf: u32 = 1;
Some(
(sf << 31)
| (0b1101010100u32 << 21)
| (rm << 16)
| (cond << 12)
| (op << 10)
| (rn << 5)
| rd,
)
}
fn extract_reg(&self, op: &MachineOperand) -> Option<u32> {
match op {
MachineOperand::Reg(r) => {
if *r >= X0 && *r <= X30 {
Some((*r - X0) as u32)
} else if *r == XZR || *r == SP {
Some(31)
} else if *r >= V0 && *r <= V31 {
Some((*r - V0) as u32)
} else {
None
}
}
_ => None,
}
}
fn extract_mem_reg_disp(&self, op: &MachineOperand) -> Option<(u32, u32)> {
match op {
MachineOperand::Mem { base, disp, .. } => {
let rn = if *base >= X0 && *base <= X30 {
(*base - X0) as u32
} else if *base == SP {
31
} else {
return None;
};
Some((rn, *disp as u32))
}
_ => None,
}
}
pub fn encode_aarch64_imm12(value: i64) -> (u32, u32) {
if value >= 0 && value <= 0xFFF {
(value as u32, 0)
} else if value % 4096 == 0 && value / 4096 >= 0 && value / 4096 <= 0xFFF {
((value / 4096) as u32, 1)
} else {
(0, 0) }
}
pub fn encode_function(&mut self, instrs: &[MachineInstr]) -> Vec<u8> {
self.buffer.clear();
self.current_addr = 0;
for mi in instrs {
self.encode(mi);
}
self.buffer.clone()
}
pub fn decode_buffer(data: &[u8]) -> Vec<MCInst> {
let mut result = Vec::new();
for chunk in data.chunks_exact(4) {
let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
if let Some(inst) = MCInst::decode(word) {
result.push(inst);
}
}
result
}
}
impl Default for AArch64MCEncoder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct AArch64AsmPrinter {
pub output: String,
pub emit_labels: bool,
pub emit_addresses: bool,
pub indent: String,
}
impl AArch64AsmPrinter {
pub fn new() -> Self {
AArch64AsmPrinter {
output: String::new(),
emit_labels: true,
emit_addresses: false,
indent: "\t".to_string(),
}
}
pub fn print_function(&mut self, name: &str, instrs: &[MachineInstr]) -> String {
self.output.clear();
self.output.push_str(&format!(".globl {}\n", name));
self.output
.push_str(&format!(".type {}, @function\n", name));
self.output.push_str(&format!("{}:\n", name));
let mut addr: u64 = 0;
for (i, mi) in instrs.iter().enumerate() {
if self.emit_addresses {
self.output.push_str(&format!(" {:08x}:", addr));
}
self.output.push_str(&self.indent);
self.print_instr(mi);
self.output.push('\n');
addr += 4;
}
self.output
.push_str(&format!(".size {}, .-{}\n", name, name));
self.output.clone()
}
fn print_instr(&mut self, mi: &MachineInstr) {
self.output.push_str(&mi.to_asm());
}
pub fn print_data_section(&mut self, label: &str, data: &[u8]) -> String {
self.output.clear();
self.output.push_str(&format!(".section .rodata\n"));
self.output.push_str(&format!("{}:\n", label));
for chunk in data.chunks_exact(4) {
let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
self.output.push_str(&format!("\t.word 0x{:08x}\n", word));
}
self.output.clone()
}
pub fn comment(&mut self, text: &str) {
self.output.push_str(&format!("// {}\n", text));
}
pub fn label(&mut self, name: &str) {
self.output.push_str(&format!("{}:\n", name));
}
pub fn directive(&mut self, dir: &str) {
self.output.push_str(&format!(".{}\n", dir));
}
}
impl Default for AArch64AsmPrinter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct RegAssignment {
pub vreg: u32,
pub preg: PhysReg,
pub class: A64RegClass,
pub spilled: bool,
pub spill_slot: Option<i64>,
}
#[derive(Debug)]
pub struct AArch64RegAlloc {
pub assignments: HashMap<u32, PhysReg>,
free_gprs: Vec<PhysReg>,
free_fprs: Vec<PhysReg>,
pub spills: Vec<RegAssignment>,
next_spill_slot: i64,
pub total_instrs: usize,
pub spill_count: usize,
}
impl AArch64RegAlloc {
pub fn new() -> Self {
let mut free_gprs = Vec::with_capacity(28);
for r in X0..=X18 {
free_gprs.push(r);
}
for r in X19..=X28 {
free_gprs.push(r);
}
let free_fprs: Vec<PhysReg> = (V0..=V31).collect();
AArch64RegAlloc {
assignments: HashMap::new(),
free_gprs,
free_fprs,
spills: Vec::new(),
next_spill_slot: -16, total_instrs: 0,
spill_count: 0,
}
}
pub fn alloc_gpr(&mut self, vreg: u32) -> PhysReg {
if let Some(&preg) = self.assignments.get(&vreg) {
return preg;
}
if let Some(preg) = self.free_gprs.pop() {
self.assignments.insert(vreg, preg);
preg
} else {
self.spill_count += 1;
let slot = self.next_spill_slot;
self.next_spill_slot -= 8;
self.spills.push(RegAssignment {
vreg,
preg: 0,
class: A64RegClass::GPR64,
spilled: true,
spill_slot: Some(slot),
});
let spill_temp = X9;
self.assignments.insert(vreg, spill_temp);
spill_temp
}
}
pub fn alloc_fpr(&mut self, vreg: u32) -> PhysReg {
if let Some(&preg) = self.assignments.get(&vreg) {
return preg;
}
if let Some(preg) = self.free_fprs.pop() {
self.assignments.insert(vreg, preg);
preg
} else {
self.spill_count += 1;
let slot = self.next_spill_slot;
self.next_spill_slot -= 8;
self.spills.push(RegAssignment {
vreg,
preg: 0,
class: A64RegClass::FPR64,
spilled: true,
spill_slot: Some(slot),
});
let spill_temp = V16; self.assignments.insert(vreg, spill_temp);
spill_temp
}
}
pub fn free_reg(&mut self, preg: PhysReg) {
if preg >= X0 && preg <= X28 {
if !self.free_gprs.contains(&preg) {
self.free_gprs.push(preg);
}
} else if preg >= V0 && preg <= V31 {
if !self.free_fprs.contains(&preg) {
self.free_fprs.push(preg);
}
}
}
pub fn run(&mut self, instrs: &[MachineInstr]) -> Vec<MachineInstr> {
let mut result = Vec::new();
for mi in instrs {
self.total_instrs += 1;
result.push(mi.clone());
}
result
}
pub fn insert_spill_store(
&self,
instrs: &mut Vec<MachineInstr>,
idx: usize,
vreg: u32,
slot: i64,
) {
let preg = *self.assignments.get(&vreg).unwrap_or(&XZR);
let spill_instr = MachineInstr::new(A64Opcode::STR)
.with_uses(vec![
MachineOperand::Reg(preg),
MachineOperand::Mem {
base: FP,
index: None,
disp: slot,
scale: 1,
},
])
.with_comment(format!("spill vreg {} to [FP, #{}]", vreg, slot));
instrs.insert(idx, spill_instr);
}
pub fn insert_spill_reload(
&self,
instrs: &mut Vec<MachineInstr>,
idx: usize,
vreg: u32,
slot: i64,
) {
let preg = *self.assignments.get(&vreg).unwrap_or(&XZR);
let reload_instr = MachineInstr::new(A64Opcode::LDR)
.with_def(MachineOperand::Reg(preg))
.with_use(MachineOperand::Mem {
base: FP,
index: None,
disp: slot,
scale: 1,
})
.with_comment(format!("reload vreg {} from [FP, #{}]", vreg, slot));
instrs.insert(idx + 1, reload_instr);
}
pub fn get_assignment(&self, vreg: u32) -> Option<PhysReg> {
self.assignments.get(&vreg).copied()
}
pub fn allocated_count(&self) -> usize {
self.assignments.len()
}
}
impl Default for AArch64RegAlloc {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct AArch64Peephole {
pub opt_count: usize,
}
impl AArch64Peephole {
pub fn new() -> Self {
AArch64Peephole { opt_count: 0 }
}
pub fn optimize(&mut self, instrs: &mut Vec<MachineInstr>) {
self.opt_count = 0;
self.eliminate_mov_xzr(instrs);
self.fold_mov_into_operand(instrs);
self.eliminate_redundant_mov(instrs);
self.merge_load_store_pair(instrs);
self.convert_mov_add_to_add_imm(instrs);
}
fn eliminate_mov_xzr(&mut self, instrs: &mut Vec<MachineInstr>) {
}
fn fold_mov_into_operand(&mut self, instrs: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instrs.len() {
if instrs[i].opcode == A64Opcode::MOV && matches!(instrs[i].def, MachineOperand::Reg(_))
{
let mov_dst = match &instrs[i].def {
MachineOperand::Reg(r) => *r,
_ => {
i += 1;
continue;
}
};
let mov_src = match instrs[i].uses.get(0) {
Some(MachineOperand::Reg(r)) => *r,
_ => {
i += 1;
continue;
}
};
let next = &mut instrs[i + 1];
let mut modified = false;
for u in next.uses.iter_mut() {
if let MachineOperand::Reg(r) = u {
if *r == mov_dst {
*u = MachineOperand::Reg(mov_src);
modified = true;
}
}
}
if modified {
instrs.remove(i);
self.opt_count += 1;
continue;
}
}
i += 1;
}
}
fn eliminate_redundant_mov(&mut self, instrs: &mut Vec<MachineInstr>) {
instrs.retain(|mi| {
if mi.opcode == A64Opcode::MOV {
if let MachineOperand::Reg(def_r) = &mi.def {
if let Some(MachineOperand::Reg(use_r)) = mi.uses.get(0) {
if def_r == use_r {
self.opt_count += 1;
return false;
}
}
}
}
true
});
}
fn merge_load_store_pair(&mut self, instrs: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instrs.len() {
let a = &instrs[i];
let b = &instrs[i + 1];
if a.opcode == A64Opcode::STR && b.opcode == A64Opcode::STR {
if let (
MachineOperand::Mem {
base: ba, disp: da, ..
},
MachineOperand::Mem {
base: bb, disp: db, ..
},
) = (
a.uses.get(1).unwrap_or(&MachineOperand::None),
b.uses.get(1).unwrap_or(&MachineOperand::None),
) {
if ba == bb && (*db - *da) == 8 {
let rt = match &a.uses[0] {
MachineOperand::Reg(r) => *r,
_ => {
i += 1;
continue;
}
};
let rt2 = match &b.uses[0] {
MachineOperand::Reg(r) => *r,
_ => {
i += 1;
continue;
}
};
let stp = MachineInstr::new(A64Opcode::STP).with_uses(vec![
MachineOperand::Reg(rt),
MachineOperand::Reg(rt2),
MachineOperand::Mem {
base: *ba,
index: None,
disp: *da,
scale: 1,
},
]);
instrs[i] = stp;
instrs.remove(i + 1);
self.opt_count += 1;
continue;
}
}
}
i += 1;
}
}
fn convert_mov_add_to_add_imm(&mut self, instrs: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instrs.len() {
if instrs[i].opcode == A64Opcode::MOVZ {
if let Some(MachineOperand::Imm(imm)) = instrs[i].uses.get(0).cloned() {
if let MachineOperand::Reg(mov_dst) = instrs[i].def {
let next = &mut instrs[i + 1];
if next.opcode == A64Opcode::ADD {
if next.uses.len() >= 2 {
if let MachineOperand::Reg(r) = next.uses[1] {
if r == mov_dst && imm >= 0 && imm <= 4095 {
next.uses[1] = MachineOperand::Imm(imm);
instrs.remove(i);
self.opt_count += 1;
continue;
}
}
}
}
}
}
}
i += 1;
}
}
}
impl Default for AArch64Peephole {
fn default() -> Self {
Self::new()
}
}
impl AArch64ISel {
pub fn select_indexed_load(
&mut self,
dest: u32,
base: u32,
offset: i64,
writeback: bool,
) -> MachineInstr {
let dst = self.alloc_gpr(dest);
let base_reg = self.alloc_gpr(base);
if writeback {
MachineInstr::new(A64Opcode::LDR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Mem {
base: base_reg,
index: None,
disp: offset,
scale: 1,
}])
.with_comment("indexed load with writeback")
} else {
MachineInstr::new(A64Opcode::LDR)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![MachineOperand::Mem {
base: base_reg,
index: None,
disp: offset,
scale: 1,
}])
}
}
pub fn try_match_fma(&mut self, inst: &IRInst) -> Option<MachineInstr> {
if inst.opcode == IROpcode::FAdd && inst.src3 == 0 {
None
} else {
None
}
}
pub fn try_match_bfi(&mut self, inst: &IRInst) -> Option<MachineInstr> {
if inst.opcode == IROpcode::And {
None
} else {
None
}
}
pub fn legalize_large_imm(&mut self, dest: u32, value: i64) -> Vec<MachineInstr> {
let dst = self.alloc_gpr(dest);
let mut instrs = Vec::new();
if value >= 0 && value <= 0xFFFF {
instrs.push(
MachineInstr::new(A64Opcode::MOVZ)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Imm(value)),
);
} else if value >= -0x10000 && value < 0 {
instrs.push(
MachineInstr::new(A64Opcode::MOVN)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Imm(!value as u64 as i64 & 0xFFFF)),
);
} else {
let chunks = [
(value & 0xFFFF),
((value >> 16) & 0xFFFF),
((value >> 32) & 0xFFFF),
((value >> 48) & 0xFFFF),
];
let mut first = true;
for (i, &chunk) in chunks.iter().enumerate() {
if chunk != 0 || first {
if first {
instrs.push(
MachineInstr::new(A64Opcode::MOVZ)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Imm(chunk)),
);
first = false;
} else {
let shift_imm = MachineOperand::Imm((i as i64) << 4);
instrs.push(
MachineInstr::new(A64Opcode::MOVK)
.with_def(MachineOperand::Reg(dst))
.with_uses(vec![
MachineOperand::Reg(dst),
MachineOperand::Imm(chunk),
shift_imm,
]),
);
}
}
}
if first {
instrs.push(
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Reg(XZR)),
);
}
}
instrs
}
pub fn try_match_scaled_mem(
&mut self,
base: u32,
index: u32,
scale_log2: u8,
) -> MachineOperand {
let base_reg = self.alloc_gpr(base);
let index_reg = self.alloc_gpr(index);
MachineOperand::Mem {
base: base_reg,
index: Some(index_reg),
disp: 0,
scale: 1 << scale_log2,
}
}
pub fn select_ext_load(
&mut self,
dest: u32,
addr: u32,
is_signed: bool,
size_bits: u8,
) -> MachineInstr {
let dst = self.alloc_gpr(dest);
let addr_reg = self.alloc_gpr(addr);
let op = match (is_signed, size_bits) {
(false, 8) => A64Opcode::LDRB,
(false, 16) => A64Opcode::LDRH,
(true, 8) => A64Opcode::LDRSB,
(true, 16) => A64Opcode::LDRSH,
(true, 32) => A64Opcode::LDRSW,
_ => A64Opcode::LDR,
};
MachineInstr::new(op)
.with_def(MachineOperand::Reg(dst))
.with_use(MachineOperand::Mem {
base: addr_reg,
index: None,
disp: 0,
scale: 1,
})
.with_comment(format!(
"extended load {}bit {}",
size_bits,
if is_signed { "signed" } else { "unsigned" }
))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_add_ir() -> Vec<IRInst> {
vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(42),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(58),
IRInst::binary(IROpcode::Add, 3, 1, 2, "i64"),
IRInst::unary(IROpcode::Ret, 0, 3, "i64"),
]
}
fn build_mul_ir() -> Vec<IRInst> {
vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(6),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(7),
IRInst::binary(IROpcode::Mul, 3, 1, 2, "i64"),
IRInst::unary(IROpcode::Ret, 0, 3, "i64"),
]
}
fn build_fp_ir() -> Vec<IRInst> {
vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(0x3FF0000000000000u64 as i64), IRInst::new(IROpcode::Const, 2, "i64").with_imm(0x4000000000000000u64 as i64), IRInst {
opcode: IROpcode::Bitcast,
dest: 10,
src1: 1,
src2: 0,
src3: 0,
imm: None,
label: None,
cond: None,
ty: "f64".to_string(),
},
IRInst {
opcode: IROpcode::Bitcast,
dest: 20,
src1: 2,
src2: 0,
src3: 0,
imm: None,
label: None,
cond: None,
ty: "f64".to_string(),
},
IRInst::binary(IROpcode::FAdd, 30, 10, 20, "f64"),
IRInst::unary(IROpcode::Ret, 0, 30, "f64"),
]
}
fn build_branch_ir() -> Vec<IRInst> {
vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(1),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(0),
IRInst::binary(IROpcode::ICmp, 3, 1, 2, "i64").with_cond(A64Cond::NE),
IRInst::new(IROpcode::BrCond, 0, "i64")
.with_label("else")
.with_cond(A64Cond::NE),
]
}
fn build_store_load_ir() -> Vec<IRInst> {
vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(99),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(0x1000), IRInst {
opcode: IROpcode::Store,
dest: 0,
src1: 1,
src2: 2,
src3: 0,
imm: Some(0),
label: None,
cond: None,
ty: "i64".to_string(),
},
IRInst {
opcode: IROpcode::Load,
dest: 3,
src1: 2,
src2: 0,
src3: 0,
imm: None,
label: None,
cond: None,
ty: "i64".to_string(),
},
IRInst::unary(IROpcode::Ret, 0, 3, "i64"),
]
}
#[test]
fn test_register_ids_are_unique() {
let ids = vec![
X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18,
X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30,
];
for i in 0..ids.len() {
assert_eq!(ids[i], i as PhysReg, "X{} should have id {}", i, i);
}
}
#[test]
fn test_gpr_constants() {
assert_eq!(X0, 0);
assert_eq!(X30, 30);
assert_eq!(FP, 29);
assert_eq!(LR, 30);
assert_eq!(SP, 32);
assert_eq!(XZR, 31);
}
#[test]
fn test_fpr_constants() {
assert!(V0 >= 200);
assert_eq!(V1, V0 + 1);
assert_eq!(V31, V0 + 31);
}
#[test]
fn test_w_reg_constants() {
assert_eq!(W0, 100);
assert_eq!(W30, 130);
assert_eq!(WZR, 33);
assert_eq!(WSP, 34);
}
#[test]
fn test_asm_names_gpr() {
assert_eq!(AArch64RegisterInfo::get_asm_name(X0), "x0");
assert_eq!(AArch64RegisterInfo::get_asm_name(X10), "x10");
assert_eq!(AArch64RegisterInfo::get_asm_name(X30), "x30");
assert_eq!(AArch64RegisterInfo::get_asm_name(SP), "sp");
assert_eq!(AArch64RegisterInfo::get_asm_name(XZR), "xzr");
assert_eq!(AArch64RegisterInfo::get_asm_name(W0), "w0");
assert_eq!(AArch64RegisterInfo::get_asm_name(WSP), "wsp");
assert_eq!(AArch64RegisterInfo::get_asm_name(WZR), "wzr");
}
#[test]
fn test_asm_names_fpr() {
assert_eq!(AArch64RegisterInfo::get_asm_name(V0), "v0");
assert_eq!(AArch64RegisterInfo::get_asm_name(V15), "v15");
assert_eq!(AArch64RegisterInfo::get_asm_name(V31), "v31");
}
#[test]
fn test_reg_class_gpr64() {
assert_eq!(
AArch64RegisterInfo::get_reg_class(X0),
Some(A64RegClass::GPR64)
);
assert_eq!(
AArch64RegisterInfo::get_reg_class(X30),
Some(A64RegClass::GPR64)
);
assert_eq!(
AArch64RegisterInfo::get_reg_class(XZR),
Some(A64RegClass::GPR64)
);
}
#[test]
fn test_reg_class_gpr32() {
assert_eq!(
AArch64RegisterInfo::get_reg_class(W0),
Some(A64RegClass::GPR32)
);
assert_eq!(
AArch64RegisterInfo::get_reg_class(WZR),
Some(A64RegClass::GPR32)
);
}
#[test]
fn test_reg_class_fpr() {
assert_eq!(
AArch64RegisterInfo::get_reg_class(V0),
Some(A64RegClass::FPR128)
);
assert_eq!(
AArch64RegisterInfo::get_reg_class(V31),
Some(A64RegClass::FPR128)
);
}
#[test]
fn test_reg_class_flags() {
assert_eq!(
AArch64RegisterInfo::get_reg_class(NZCV),
Some(A64RegClass::Flags)
);
}
#[test]
fn test_reg_width() {
assert_eq!(AArch64RegisterInfo::get_reg_width(X0), 64);
assert_eq!(AArch64RegisterInfo::get_reg_width(W0), 32);
assert_eq!(AArch64RegisterInfo::get_reg_width(V0), 128);
assert_eq!(AArch64RegisterInfo::get_reg_width(NZCV), 4);
}
#[test]
fn test_callee_saved() {
assert!(AArch64RegisterInfo::is_callee_saved(X19));
assert!(AArch64RegisterInfo::is_callee_saved(X20));
assert!(AArch64RegisterInfo::is_callee_saved(X28));
assert!(AArch64RegisterInfo::is_callee_saved(X29));
assert!(!AArch64RegisterInfo::is_callee_saved(X0));
assert!(!AArch64RegisterInfo::is_callee_saved(X18));
}
#[test]
fn test_caller_saved() {
assert!(AArch64RegisterInfo::is_caller_saved(X0));
assert!(AArch64RegisterInfo::is_caller_saved(X9));
assert!(AArch64RegisterInfo::is_caller_saved(X18));
assert!(!AArch64RegisterInfo::is_caller_saved(X19));
assert!(!AArch64RegisterInfo::is_caller_saved(X29));
}
#[test]
fn test_reserved() {
assert!(AArch64RegisterInfo::is_reserved(XZR));
assert!(AArch64RegisterInfo::is_reserved(SP));
assert!(AArch64RegisterInfo::is_reserved(LR));
assert!(!AArch64RegisterInfo::is_reserved(X0));
assert!(!AArch64RegisterInfo::is_reserved(X19));
}
#[test]
fn test_sub_reg_32() {
assert_eq!(AArch64RegisterInfo::get_sub_reg_32(X0), Some(W0));
assert_eq!(AArch64RegisterInfo::get_sub_reg_32(X15), Some(W15));
assert_eq!(AArch64RegisterInfo::get_sub_reg_32(X30), Some(W30));
assert_eq!(AArch64RegisterInfo::get_sub_reg_32(XZR), Some(WZR));
assert_eq!(AArch64RegisterInfo::get_sub_reg_32(SP), Some(WSP));
assert_eq!(AArch64RegisterInfo::get_sub_reg_32(V0), None);
}
#[test]
fn test_super_reg_64() {
assert_eq!(AArch64RegisterInfo::get_super_reg_64(W0), Some(X0));
assert_eq!(AArch64RegisterInfo::get_super_reg_64(W20), Some(X20));
assert_eq!(AArch64RegisterInfo::get_super_reg_64(WZR), Some(XZR));
assert_eq!(AArch64RegisterInfo::get_super_reg_64(WSP), Some(SP));
}
#[test]
fn test_v_index() {
assert_eq!(AArch64RegisterInfo::get_v_index(V0), Some(0));
assert_eq!(AArch64RegisterInfo::get_v_index(V7), Some(7));
assert_eq!(AArch64RegisterInfo::get_v_index(V31), Some(31));
assert_eq!(AArch64RegisterInfo::get_v_index(X0), None);
}
#[test]
fn test_allocatable_gprs() {
let gprs = AArch64RegisterInfo::get_allocatable_gprs();
assert_eq!(gprs.len(), 29); assert_eq!(gprs[0], X0);
assert_eq!(gprs[28], X28);
}
#[test]
fn test_allocatable_fprs() {
let fprs = AArch64RegisterInfo::get_allocatable_fprs();
assert_eq!(fprs.len(), 32);
assert_eq!(fprs[0], V0);
assert_eq!(fprs[31], V31);
}
#[test]
fn test_callee_saved_gprs() {
let cs = AArch64RegisterInfo::get_callee_saved_gprs();
assert!(cs.contains(&X19));
assert!(cs.contains(&X29));
assert!(cs.contains(&X30));
}
#[test]
fn test_callee_saved_fprs() {
let cs = AArch64RegisterInfo::get_callee_saved_fprs();
assert_eq!(cs.len(), 8); assert!(cs.contains(&V8));
assert!(cs.contains(&V15));
}
#[test]
fn test_opcode_display() {
assert_eq!(format!("{}", A64Opcode::ADD), "add");
assert_eq!(format!("{}", A64Opcode::SUB), "sub");
assert_eq!(format!("{}", A64Opcode::MUL), "mul");
assert_eq!(format!("{}", A64Opcode::B), "b");
assert_eq!(format!("{}", A64Opcode::BL), "bl");
assert_eq!(format!("{}", A64Opcode::RET), "ret");
assert_eq!(format!("{}", A64Opcode::NOP), "nop");
}
#[test]
fn test_opcode_count() {
let all = vec![
A64Opcode::ADD,
A64Opcode::SUB,
A64Opcode::MUL,
A64Opcode::SDIV,
A64Opcode::UDIV,
A64Opcode::AND,
A64Opcode::ORR,
A64Opcode::EOR,
A64Opcode::B,
A64Opcode::BL,
A64Opcode::BR,
A64Opcode::BLR,
A64Opcode::RET,
A64Opcode::FADD,
A64Opcode::FSUB,
A64Opcode::FMUL,
A64Opcode::FDIV,
A64Opcode::FNEG,
A64Opcode::FABS,
A64Opcode::FSQRT,
];
assert!(all.len() >= 20);
}
#[test]
fn test_opcode_category_alu() {
assert_eq!(A64Opcode::ADD.category(), InstrCategory::ALU);
assert_eq!(A64Opcode::SUB.category(), InstrCategory::ALU);
assert_eq!(A64Opcode::LSL.category(), InstrCategory::ALU);
assert_eq!(A64Opcode::CSEL.category(), InstrCategory::ALU);
}
#[test]
fn test_opcode_category_muldiv() {
assert_eq!(A64Opcode::MUL.category(), InstrCategory::MulDiv);
assert_eq!(A64Opcode::SDIV.category(), InstrCategory::MulDiv);
assert_eq!(A64Opcode::UDIV.category(), InstrCategory::MulDiv);
}
#[test]
fn test_opcode_category_fp() {
assert_eq!(A64Opcode::FADD.category(), InstrCategory::FP);
assert_eq!(A64Opcode::FDIV.category(), InstrCategory::FP);
}
#[test]
fn test_opcode_category_branch() {
assert_eq!(A64Opcode::B.category(), InstrCategory::Branch);
assert_eq!(A64Opcode::BL.category(), InstrCategory::Branch);
assert_eq!(A64Opcode::RET.category(), InstrCategory::Branch);
assert_eq!(A64Opcode::CBZ.category(), InstrCategory::Branch);
}
#[test]
fn test_opcode_category_simd() {
assert_eq!(A64Opcode::ADDv.category(), InstrCategory::SIMD);
assert_eq!(A64Opcode::MULv.category(), InstrCategory::SIMD);
assert_eq!(A64Opcode::LD1v.category(), InstrCategory::Load);
}
#[test]
fn test_opcode_category_sve() {
assert_eq!(A64Opcode::ADD_sve.category(), InstrCategory::SVE);
assert_eq!(A64Opcode::FADD_sve.category(), InstrCategory::SVE);
}
#[test]
fn test_cond_invert() {
assert_eq!(A64Cond::EQ.invert(), A64Cond::NE);
assert_eq!(A64Cond::NE.invert(), A64Cond::EQ);
assert_eq!(A64Cond::LT.invert(), A64Cond::GE);
assert_eq!(A64Cond::GE.invert(), A64Cond::LT);
assert_eq!(A64Cond::GT.invert(), A64Cond::LE);
assert_eq!(A64Cond::LE.invert(), A64Cond::GT);
assert_eq!(A64Cond::HI.invert(), A64Cond::LS);
assert_eq!(A64Cond::AL.invert(), A64Cond::NV);
}
#[test]
fn test_cond_encoding() {
assert_eq!(A64Cond::EQ.encoding(), 0);
assert_eq!(A64Cond::NE.encoding(), 1);
assert_eq!(A64Cond::CS.encoding(), 2);
assert_eq!(A64Cond::CC.encoding(), 3);
assert_eq!(A64Cond::AL.encoding(), 14);
assert_eq!(A64Cond::NV.encoding(), 15);
}
#[test]
fn test_cond_display() {
assert_eq!(format!("{}", A64Cond::EQ), "eq");
assert_eq!(format!("{}", A64Cond::NE), "ne");
assert_eq!(format!("{}", A64Cond::LT), "lt");
assert_eq!(format!("{}", A64Cond::GT), "gt");
}
#[test]
fn test_instr_info_table_size() {
let info = AArch64InstrInfo::new();
assert!(
info.len() >= 150,
"Should have at least 150 entries, got {}",
info.len()
);
}
#[test]
fn test_instr_info_get() {
let info = AArch64InstrInfo::new();
let desc = info.get(A64Opcode::ADD);
assert!(desc.is_some());
let desc = desc.unwrap();
assert_eq!(desc.name, "add");
assert_eq!(desc.category, InstrCategory::ALU);
assert_eq!(desc.num_sources, 2);
assert_eq!(desc.num_defs, 1);
}
#[test]
fn test_instr_info_mul() {
let info = AArch64InstrInfo::new();
let desc = info.get(A64Opcode::MUL).unwrap();
assert_eq!(desc.category, InstrCategory::MulDiv);
assert_eq!(desc.latency, 3);
}
#[test]
fn test_instr_info_div_latency() {
let info = AArch64InstrInfo::new();
let desc = info.get(A64Opcode::SDIV).unwrap();
assert_eq!(desc.latency, 12);
}
#[test]
fn test_instr_info_branch() {
let info = AArch64InstrInfo::new();
let b = info.get(A64Opcode::B).unwrap();
assert!(b.is_terminator);
assert!(!b.is_call);
let bl = info.get(A64Opcode::BL).unwrap();
assert!(!bl.is_terminator);
assert!(bl.is_call);
}
#[test]
fn test_instr_info_store_has_side_effects() {
let info = AArch64InstrInfo::new();
let str_desc = info.get(A64Opcode::STR).unwrap();
assert!(str_desc.has_side_effects);
let ldr_desc = info.get(A64Opcode::LDR).unwrap();
assert!(!ldr_desc.has_side_effects);
}
#[test]
fn test_instr_info_writes_flags() {
let info = AArch64InstrInfo::new();
assert!(info.writes_flags(A64Opcode::ADDS));
assert!(info.writes_flags(A64Opcode::SUBS));
assert!(!info.writes_flags(A64Opcode::ADD));
assert!(!info.writes_flags(A64Opcode::MOV));
}
#[test]
fn test_machine_instr_new() {
let mi = MachineInstr::new(A64Opcode::ADD);
assert_eq!(mi.opcode, A64Opcode::ADD);
assert!(!mi.is_terminator);
assert!(!mi.is_call);
assert!(!mi.is_load);
assert!(!mi.is_store);
}
#[test]
fn test_machine_instr_branch_terminator() {
let mi = MachineInstr::new(A64Opcode::B);
assert!(mi.is_terminator);
assert!(mi.is_branch);
}
#[test]
fn test_machine_instr_load_store() {
let ld = MachineInstr::new(A64Opcode::LDR);
assert!(ld.is_load);
assert!(!ld.is_store);
let st = MachineInstr::new(A64Opcode::STR);
assert!(st.is_store);
assert!(!st.is_load);
}
#[test]
fn test_machine_instr_to_asm_add() {
let mi = MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X0))
.with_use(MachineOperand::Reg(X1))
.with_use(MachineOperand::Reg(X2));
let asm = mi.to_asm();
assert!(asm.contains("add"));
assert!(asm.contains("x0"));
assert!(asm.contains("x1"));
assert!(asm.contains("x2"));
}
#[test]
fn test_machine_instr_to_asm_cmp() {
let mi = MachineInstr::new(A64Opcode::CMP)
.with_use(MachineOperand::Reg(X0))
.with_use(MachineOperand::Imm(0));
let asm = mi.to_asm();
assert!(asm.contains("cmp"));
assert!(asm.contains("x0"));
assert!(asm.contains("#0"));
}
#[test]
fn test_machine_instr_to_asm_branch() {
let mi =
MachineInstr::new(A64Opcode::B).with_use(MachineOperand::Label(".LBB0_1".to_string()));
let asm = mi.to_asm();
assert!(asm.contains("b"));
assert!(asm.contains(".LBB0_1"));
}
#[test]
fn test_machine_instr_to_asm_cbz() {
let mi = MachineInstr::new(A64Opcode::CBZ)
.with_use(MachineOperand::Reg(X0))
.with_use(MachineOperand::Label("loop".to_string()));
let asm = mi.to_asm();
assert!(asm.contains("cbz"));
assert!(asm.contains("x0"));
assert!(asm.contains("loop"));
}
#[test]
fn test_machine_instr_to_asm_ret() {
let mi = MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30));
let asm = mi.to_asm();
assert!(asm.contains("ret"));
assert!(asm.contains("x30"));
}
#[test]
fn test_machine_instr_to_asm_ldr_mem() {
let mi = MachineInstr::new(A64Opcode::LDR)
.with_def(MachineOperand::Reg(X0))
.with_use(MachineOperand::Mem {
base: X1,
index: None,
disp: 8,
scale: 1,
});
let asm = mi.to_asm();
assert!(asm.contains("ldr"));
assert!(asm.contains("[x1, #8]"));
}
#[test]
fn test_machine_instr_has_def() {
let mi = MachineInstr::new(A64Opcode::ADD).with_def(MachineOperand::Reg(X0));
assert!(mi.has_def());
let mi2 = MachineInstr::new(A64Opcode::CMP);
assert!(!mi2.has_def());
}
#[test]
fn test_frame_lowering_new() {
let fl = AArch64FrameLowering::new(64);
assert_eq!(fl.local_area_size, 64);
assert_eq!(fl.callee_saved_gprs, 0);
assert_eq!(fl.callee_saved_fprs, 0);
assert!(fl.has_fp);
}
#[test]
fn test_frame_compute_size() {
let mut fl = AArch64FrameLowering::new(64);
let size = fl.compute_frame_size();
assert_eq!(size, 64);
assert_eq!(fl.total_frame_size, 64);
}
#[test]
fn test_frame_compute_size_alignment() {
let mut fl = AArch64FrameLowering::new(17);
let size = fl.compute_frame_size();
assert_eq!(size, 32);
}
#[test]
fn test_frame_prologue_contains_stp() {
let mut fl = AArch64FrameLowering::new(32);
fl.compute_frame_size();
let prologue = fl.emit_prologue();
assert!(
prologue.len() >= 3,
"Expected at least 3 prologue instructions"
);
assert_eq!(prologue[0].opcode, A64Opcode::STP);
assert_eq!(prologue[1].opcode, A64Opcode::MOV);
assert_eq!(prologue[2].opcode, A64Opcode::SUB);
}
#[test]
fn test_frame_prologue_uses_fp_lr() {
let mut fl = AArch64FrameLowering::new(32);
fl.compute_frame_size();
let prologue = fl.emit_prologue();
let stp = &prologue[0];
let uses: Vec<String> = stp.uses.iter().map(|u| u.to_string()).collect();
assert!(uses.iter().any(|u| u.contains("x29")));
assert!(uses.iter().any(|u| u.contains("x30")));
}
#[test]
fn test_frame_epilogue_contains_ldp_ret() {
let mut fl = AArch64FrameLowering::new(32);
fl.compute_frame_size();
let epilogue = fl.emit_epilogue();
assert!(epilogue.len() >= 3);
let has_ldp = epilogue.iter().any(|mi| mi.opcode == A64Opcode::LDP);
let has_ret = epilogue.iter().any(|mi| mi.opcode == A64Opcode::RET);
assert!(has_ldp, "Epilogue should contain LDP");
assert!(has_ret, "Epilogue should contain RET");
}
#[test]
fn test_frame_get_offset() {
let fl = AArch64FrameLowering::new(32);
let off = fl.get_frame_index_offset(0);
assert!(
off < 0 || off == -16,
"First local slot offset from FP should be negative or -16, got {}",
off
);
}
#[test]
fn test_cc_new_and_reset() {
let mut cc = AArch64CallingConvention::new();
let info = cc.classify_arg("i64");
assert_eq!(info.class, AArch64ArgClass::Integer);
assert_eq!(info.reg, Some(X0));
cc.reset();
let info2 = cc.classify_arg("i64");
assert_eq!(info2.reg, Some(X0));
}
#[test]
fn test_cc_integer_args() {
let mut cc = AArch64CallingConvention::new();
for i in 0..8 {
let info = cc.classify_arg("i64");
assert_eq!(info.class, AArch64ArgClass::Integer);
assert_eq!(info.reg, Some(X0 + i as PhysReg));
}
let info9 = cc.classify_arg("i64");
assert_eq!(info9.class, AArch64ArgClass::Memory);
assert!(info9.reg.is_none());
assert_eq!(info9.stack_offset, Some(0));
}
#[test]
fn test_cc_fp_args() {
let mut cc = AArch64CallingConvention::new();
for i in 0..8 {
let info = cc.classify_arg("f64");
assert_eq!(info.class, AArch64ArgClass::Float);
assert_eq!(info.reg, Some(V0 + i as PhysReg));
}
let info9 = cc.classify_arg("f64");
assert_eq!(info9.class, AArch64ArgClass::Memory);
}
#[test]
fn test_cc_mixed_args() {
let mut cc = AArch64CallingConvention::new();
let a0 = cc.classify_arg("i64");
assert_eq!(a0.reg, Some(X0));
let a1 = cc.classify_arg("f64");
assert_eq!(a1.reg, Some(V0));
let a2 = cc.classify_arg("i64");
assert_eq!(a2.reg, Some(X1));
let a3 = cc.classify_arg("f32");
assert_eq!(a3.reg, Some(V1));
}
#[test]
fn test_cc_classify_return_i64() {
let mut cc = AArch64CallingConvention::new();
let ret = cc.classify_return("i64");
assert_eq!(ret.len(), 1);
assert_eq!(ret[0].class, AArch64ArgClass::Integer);
assert_eq!(ret[0].reg, Some(X0));
}
#[test]
fn test_cc_classify_return_f64() {
let mut cc = AArch64CallingConvention::new();
let ret = cc.classify_return("f64");
assert_eq!(ret.len(), 1);
assert_eq!(ret[0].class, AArch64ArgClass::Float);
assert_eq!(ret[0].reg, Some(V0));
}
#[test]
fn test_cc_classify_return_ptr() {
let mut cc = AArch64CallingConvention::new();
let ret = cc.classify_return("ptr");
assert_eq!(ret.len(), 1);
assert_eq!(ret[0].class, AArch64ArgClass::Integer);
assert_eq!(ret[0].reg, Some(X0));
}
#[test]
fn test_cc_emit_args_setup() {
let mut cc = AArch64CallingConvention::new();
let types = &["i64", "i64", "f64"];
let vregs = &[100, 101, 200];
let instrs = cc.emit_args_setup(types, vregs);
assert_eq!(instrs.len(), 3);
assert_eq!(instrs[0].opcode, A64Opcode::MOV);
assert_eq!(instrs[2].opcode, A64Opcode::MOV); }
#[test]
fn test_cc_emit_call() {
let cc = AArch64CallingConvention::new();
let instrs = cc.emit_call(X1);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, A64Opcode::BLR);
}
#[test]
fn test_cc_emit_return() {
let cc = AArch64CallingConvention::new();
let instrs = cc.emit_return();
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, A64Opcode::RET);
}
#[test]
fn test_cc_callee_saved_lists() {
let gprs = AArch64CallingConvention::callee_saved_gprs();
assert!(gprs.contains(&X19));
assert!(gprs.contains(&X29));
assert!(gprs.contains(&X30));
assert!(!gprs.contains(&X0));
let fprs = AArch64CallingConvention::callee_saved_fprs();
assert_eq!(fprs.len(), 8);
assert!(fprs.contains(&V8));
assert!(!fprs.contains(&V0));
}
#[test]
fn test_isel_add() {
let ir = build_add_ir();
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
let has_add = code.iter().any(|mi| mi.opcode == A64Opcode::ADD);
assert!(has_add, "ISel should produce an ADD for integer addition");
}
#[test]
fn test_isel_mul() {
let ir = build_mul_ir();
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
let has_mul = code.iter().any(|mi| mi.opcode == A64Opcode::MUL);
assert!(has_mul, "ISel should produce a MUL for integer multiply");
}
#[test]
fn test_isel_output_not_empty() {
let ir = build_add_ir();
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(!code.is_empty());
}
#[test]
fn test_isel_print_asm_non_empty() {
let ir = build_add_ir();
let mut isel = AArch64ISel::new();
isel.select(&ir);
let asm = isel.print_asm();
assert!(!asm.is_empty());
assert!(asm.contains("add"));
}
#[test]
fn test_isel_reg_mapping() {
let ir = build_add_ir();
let mut isel = AArch64ISel::new();
isel.select(&ir);
let map = isel.get_reg_mapping();
assert!(!map.is_empty());
}
#[test]
fn test_isel_and_or_xor() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(0xFF),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(0x0F),
IRInst::binary(IROpcode::And, 3, 1, 2, "i64"),
IRInst::binary(IROpcode::Or, 4, 3, 1, "i64"),
IRInst::binary(IROpcode::Xor, 5, 4, 2, "i64"),
IRInst::unary(IROpcode::Ret, 0, 5, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::AND));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::ORR));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::EOR));
}
#[test]
fn test_isel_shifts() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(1),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(3),
IRInst::binary(IROpcode::Shl, 3, 1, 2, "i64"),
IRInst::binary(IROpcode::LShr, 4, 3, 2, "i64"),
IRInst::binary(IROpcode::AShr, 5, 4, 2, "i64"),
IRInst::unary(IROpcode::Ret, 0, 5, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::LSL));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::LSR));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::ASR));
}
#[test]
fn test_isel_sdiv_udiv() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(100),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(7),
IRInst::binary(IROpcode::SDiv, 3, 1, 2, "i64"),
IRInst::binary(IROpcode::UDiv, 4, 1, 2, "i64"),
IRInst::unary(IROpcode::Ret, 0, 4, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::SDIV));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::UDIV));
}
#[test]
fn test_isel_icmp() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(5),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(3),
IRInst::binary(IROpcode::ICmp, 3, 1, 2, "i64").with_cond(A64Cond::GT),
IRInst::unary(IROpcode::Ret, 0, 3, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::CMP));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::CSET));
}
#[test]
fn test_isel_fcmp() {
let ir = vec![
IRInst::new(IROpcode::Const, 10, "f64").with_imm(0),
IRInst::new(IROpcode::Const, 20, "f64").with_imm(0),
IRInst::binary(IROpcode::FCmp, 30, 10, 20, "f64").with_cond(A64Cond::EQ),
IRInst::unary(IROpcode::Ret, 0, 30, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FCMP));
}
#[test]
fn test_isel_select() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(10),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(20),
IRInst::binary(IROpcode::ICmp, 3, 1, 2, "i64").with_cond(A64Cond::LT),
IRInst {
opcode: IROpcode::Select,
dest: 4,
src1: 1,
src2: 2,
src3: 0,
imm: None,
label: None,
cond: Some(A64Cond::LT),
ty: "i64".to_string(),
},
IRInst::unary(IROpcode::Ret, 0, 4, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::CSEL));
}
#[test]
fn test_isel_load_store() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "ptr").with_imm(0x1000),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(42),
IRInst {
opcode: IROpcode::Store,
dest: 0,
src1: 2,
src2: 1,
src3: 0,
imm: Some(0),
label: None,
cond: None,
ty: "i64".to_string(),
},
IRInst {
opcode: IROpcode::Load,
dest: 3,
src1: 1,
src2: 0,
src3: 0,
imm: None,
label: None,
cond: None,
ty: "i64".to_string(),
},
IRInst::unary(IROpcode::Ret, 0, 3, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::STR));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::LDR));
}
#[test]
fn test_isel_conversions() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i32").with_imm(42),
IRInst::unary(IROpcode::SExt, 2, 1, "i64"),
IRInst::unary(IROpcode::ZExt, 3, 1, "i64"),
IRInst::unary(IROpcode::Trunc, 4, 2, "i32"),
IRInst::unary(IROpcode::Ret, 0, 4, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::SBFM));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::UBFM));
}
#[test]
fn test_isel_fp_arith() {
let ir = vec![
IRInst::new(IROpcode::Const, 10, "f64").with_imm(0),
IRInst::new(IROpcode::Const, 20, "f64").with_imm(0),
IRInst::binary(IROpcode::FAdd, 30, 10, 20, "f64"),
IRInst::binary(IROpcode::FSub, 40, 30, 10, "f64"),
IRInst::binary(IROpcode::FMul, 50, 40, 20, "f64"),
IRInst::binary(IROpcode::FDiv, 60, 50, 20, "f64"),
IRInst::unary(IROpcode::FNeg, 70, 60, "f64"),
IRInst::unary(IROpcode::Ret, 0, 70, "f64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FADD));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FSUB));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FMUL));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FDIV));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FNEG));
}
#[test]
fn test_isel_fp_int_conversions() {
let ir = vec![
IRInst::new(IROpcode::Const, 10, "f64").with_imm(0),
IRInst::unary(IROpcode::FPToSI, 20, 10, "i64"),
IRInst::unary(IROpcode::FPToUI, 30, 10, "i64"),
IRInst::unary(IROpcode::SIToFP, 40, 20, "f64"),
IRInst::unary(IROpcode::UIToFP, 50, 30, "f64"),
IRInst::unary(IROpcode::Ret, 0, 50, "f64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FCVTZS));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FCVTZU));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::SCVTF));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::UCVTF));
}
#[test]
fn test_isel_branch() {
let ir = vec![IRInst::new(IROpcode::Br, 0, "").with_label(".Lend")];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code
.iter()
.any(|mi| matches!(mi.opcode, A64Opcode::B | A64Opcode::CBZ | A64Opcode::CBNZ)));
}
#[test]
fn test_isel_call() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "ptr").with_imm(0x4000),
IRInst {
opcode: IROpcode::Call,
dest: 2,
src1: 1,
src2: 0,
src3: 0,
imm: None,
label: None,
cond: None,
ty: "i64".to_string(),
},
IRInst::unary(IROpcode::Ret, 0, 2, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::BLR));
}
#[test]
fn test_aarch64_deep_new() {
let backend = AArch64Deep::new();
assert_eq!(backend.instr_info.len(), backend.instr_info.table.len());
assert_eq!(backend.instr_count(), 0);
}
#[test]
fn test_aarch64_deep_compile_add() {
let ir = build_add_ir();
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
assert!(!code.is_empty());
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::ADD));
}
#[test]
fn test_aarch64_deep_compile_mul() {
let ir = build_mul_ir();
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::MUL));
}
#[test]
fn test_aarch64_deep_print_asm() {
let ir = build_add_ir();
let mut backend = AArch64Deep::new();
backend.compile(&ir);
let asm = backend.print_asm();
assert!(!asm.is_empty());
assert!(asm.contains("\t"));
}
#[test]
fn test_aarch64_deep_instr_count() {
let ir = build_add_ir();
let mut backend = AArch64Deep::new();
backend.compile(&ir);
assert!(backend.instr_count() > 0);
}
#[test]
fn test_aarch64_deep_get_opcode() {
let ir = build_add_ir();
let mut backend = AArch64Deep::new();
backend.compile(&ir);
let op = backend.get_opcode(0);
assert!(op.is_some());
}
#[test]
fn test_aarch64_deep_validate_no_xzr_dest() {
let ir = build_add_ir();
let mut backend = AArch64Deep::new();
backend.compile(&ir);
let result = backend.validate();
assert!(result.is_ok(), "Validation failed: {:?}", result.err());
}
#[test]
fn test_aarch64_deep_with_frame() {
let backend = AArch64Deep::new().with_frame(48);
assert!(backend.frame_lowering.is_some());
assert_eq!(backend.frame_lowering.as_ref().unwrap().local_area_size, 48);
}
#[test]
fn test_aarch64_deep_compile_with_frame() {
let ir = build_add_ir();
let mut backend = AArch64Deep::new().with_frame(64);
backend.compile(&ir);
let code = backend.machine_code.clone();
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::STP));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::RET));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::ADD));
}
#[test]
fn test_end_to_end_pipeline() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(100),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(200),
IRInst::binary(IROpcode::Add, 3, 1, 2, "i64"),
IRInst::binary(IROpcode::Mul, 4, 3, 1, "i64"),
IRInst::unary(IROpcode::Ret, 0, 4, "i64"),
];
let mut backend = AArch64Deep::new().with_frame(32);
let code = backend.compile(&ir);
assert!(!code.is_empty());
assert!(backend.validate().is_ok());
let asm = backend.print_asm();
assert!(asm.contains("add"));
assert!(asm.contains("mul"));
assert!(asm.contains("stp") || asm.contains("x29")); }
#[test]
fn test_end_to_end_fp_pipeline() {
let ir = vec![
IRInst::new(IROpcode::Const, 10, "f64").with_imm(0),
IRInst::new(IROpcode::Const, 20, "f64").with_imm(0),
IRInst::binary(IROpcode::FAdd, 30, 10, 20, "f64"),
IRInst::binary(IROpcode::FMul, 40, 30, 10, "f64"),
IRInst::unary(IROpcode::Ret, 0, 40, "f64"),
];
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FADD));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FMUL));
}
#[test]
fn test_end_to_end_branch_pipeline() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(1),
IRInst::new(IROpcode::Const, 2, "i64").with_imm(0),
IRInst::binary(IROpcode::ICmp, 3, 1, 2, "i64").with_cond(A64Cond::NE),
IRInst::new(IROpcode::BrCond, 0, "i64")
.with_label(".Lskip")
.with_cond(A64Cond::NE),
IRInst::new(IROpcode::Const, 4, "i64").with_imm(99),
IRInst::unary(IROpcode::Ret, 0, 4, "i64"),
];
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::CMP));
let has_branch = code.iter().any(|mi| mi.is_branch);
assert!(has_branch);
}
#[test]
fn test_reg_class_default_allocatable_counts() {
assert_eq!(A64RegClass::GPR64.default_allocatable_count(), 28);
assert_eq!(A64RegClass::FPR128.default_allocatable_count(), 32);
assert_eq!(A64RegClass::Flags.default_allocatable_count(), 1);
}
#[test]
fn test_reg_class_widths() {
assert_eq!(A64RegClass::GPR64.width_bits(), 64);
assert_eq!(A64RegClass::GPR32.width_bits(), 32);
assert_eq!(A64RegClass::FPR128.width_bits(), 128);
assert_eq!(A64RegClass::FPR64.width_bits(), 64);
assert_eq!(A64RegClass::FPR32.width_bits(), 32);
assert_eq!(A64RegClass::FPR16.width_bits(), 16);
assert_eq!(A64RegClass::FPR8.width_bits(), 8);
assert_eq!(A64RegClass::Flags.width_bits(), 4);
}
#[test]
fn test_reg_class_display() {
assert_eq!(format!("{}", A64RegClass::GPR64), "GPR64");
assert_eq!(format!("{}", A64RegClass::FPR128), "FPR128");
assert_eq!(format!("{}", A64RegClass::Flags), "Flags");
}
#[test]
fn test_operand_display_reg() {
let op = MachineOperand::Reg(X0);
assert_eq!(format!("{}", op), "x0");
}
#[test]
fn test_operand_display_imm() {
let op = MachineOperand::Imm(42);
assert_eq!(format!("{}", op), "#42");
}
#[test]
fn test_operand_display_fimm() {
let op = MachineOperand::FImm(3.14);
assert_eq!(format!("{}", op), "#3.14");
}
#[test]
fn test_operand_display_mem_base_only() {
let op = MachineOperand::Mem {
base: X1,
index: None,
disp: 0,
scale: 1,
};
assert_eq!(format!("{}", op), "[x1]");
}
#[test]
fn test_operand_display_mem_disp() {
let op = MachineOperand::Mem {
base: SP,
index: None,
disp: 16,
scale: 1,
};
assert_eq!(format!("{}", op), "[sp, #16]");
}
#[test]
fn test_operand_display_mem_index() {
let op = MachineOperand::Mem {
base: X0,
index: Some(X1),
disp: 0,
scale: 2,
};
assert_eq!(format!("{}", op), "[x0, x1, lsl #2]");
}
#[test]
fn test_operand_display_label() {
let op = MachineOperand::Label(".LBB0_1".to_string());
assert_eq!(format!("{}", op), ".LBB0_1");
}
#[test]
fn test_operand_display_block() {
let op = MachineOperand::Block(3);
assert_eq!(format!("{}", op), ".LBB3");
}
#[test]
fn test_operand_display_cond() {
let op = MachineOperand::Cond(A64Cond::EQ);
assert_eq!(format!("{}", op), "eq");
}
#[test]
fn test_operand_display_shift() {
let op = MachineOperand::Shift {
kind: A64ShiftKind::LSL,
amount: 3,
};
assert_eq!(format!("{}", op), "lsl, #3");
}
#[test]
fn test_operand_display_bitfield_imm() {
let op = MachineOperand::BitfieldImm { immr: 0, imms: 31 };
assert_eq!(format!("{}", op), "#0, #31");
}
#[test]
fn test_operand_display_none() {
let op = MachineOperand::None;
assert_eq!(format!("{}", op), "<none>");
}
#[test]
fn test_shift_kind_display() {
assert_eq!(format!("{}", A64ShiftKind::LSL), "lsl");
assert_eq!(format!("{}", A64ShiftKind::LSR), "lsr");
assert_eq!(format!("{}", A64ShiftKind::ASR), "asr");
assert_eq!(format!("{}", A64ShiftKind::ROR), "ror");
}
#[test]
fn test_ir_inst_new() {
let inst = IRInst::new(IROpcode::Add, 1, "i64");
assert_eq!(inst.opcode, IROpcode::Add);
assert_eq!(inst.dest, 1);
assert_eq!(inst.ty, "i64");
}
#[test]
fn test_ir_inst_binary() {
let inst = IRInst::binary(IROpcode::Add, 3, 1, 2, "i32");
assert_eq!(inst.dest, 3);
assert_eq!(inst.src1, 1);
assert_eq!(inst.src2, 2);
}
#[test]
fn test_ir_inst_unary() {
let inst = IRInst::unary(IROpcode::Neg, 5, 3, "i64");
assert_eq!(inst.dest, 5);
assert_eq!(inst.src1, 3);
}
#[test]
fn test_ir_inst_with_imm() {
let inst = IRInst::new(IROpcode::Add, 1, "i64").with_imm(42);
assert_eq!(inst.imm, Some(42));
}
#[test]
fn test_ir_inst_with_label() {
let inst = IRInst::new(IROpcode::Br, 0, "").with_label("loop");
assert_eq!(inst.label, Some("loop".to_string()));
}
#[test]
fn test_ir_inst_with_cond() {
let inst = IRInst::new(IROpcode::BrCond, 0, "i64").with_cond(A64Cond::NE);
assert_eq!(inst.cond, Some(A64Cond::NE));
}
#[test]
fn test_ir_opcode_display() {
assert!(format!("{}", IROpcode::Add).contains("Add"));
assert!(format!("{}", IROpcode::FAdd).contains("FAdd"));
}
#[test]
fn test_instr_category_variants() {
let cats = vec![
InstrCategory::ALU,
InstrCategory::MulDiv,
InstrCategory::Load,
InstrCategory::Store,
InstrCategory::Branch,
InstrCategory::FP,
InstrCategory::SIMD,
InstrCategory::SVE,
InstrCategory::System,
InstrCategory::Misc,
];
assert_eq!(cats.len(), 10);
}
#[test]
fn test_complex_integer_pipeline() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(100), IRInst::new(IROpcode::Const, 2, "i64").with_imm(50), IRInst::new(IROpcode::Const, 3, "i64").with_imm(200), IRInst::new(IROpcode::Const, 4, "i64").with_imm(30), IRInst::binary(IROpcode::Add, 5, 1, 2, "i64"), IRInst::binary(IROpcode::Sub, 6, 3, 4, "i64"), IRInst::binary(IROpcode::Mul, 7, 5, 6, "i64"), IRInst::new(IROpcode::Const, 8, "i64").with_imm(0xFF),
IRInst::binary(IROpcode::And, 9, 1, 8, "i64"), IRInst::binary(IROpcode::SDiv, 10, 7, 9, "i64"), IRInst::unary(IROpcode::Ret, 0, 10, "i64"),
];
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::ADD));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::SUB));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::MUL));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::AND));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::SDIV));
}
#[test]
fn test_fp_fma_pattern() {
let ir = vec![
IRInst::new(IROpcode::Const, 10, "f64").with_imm(0),
IRInst::new(IROpcode::Const, 20, "f64").with_imm(0),
IRInst::new(IROpcode::Const, 30, "f64").with_imm(0),
IRInst::binary(IROpcode::FMul, 40, 10, 20, "f64"),
IRInst::binary(IROpcode::FAdd, 50, 40, 30, "f64"),
IRInst::unary(IROpcode::Ret, 0, 50, "f64"),
];
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FMUL));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::FADD));
}
#[test]
fn test_no_empty_output_on_empty_ir() {
let ir: Vec<IRInst> = vec![];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::RET));
}
#[test]
fn test_instr_info_covers_all_opcodes() {
let info = AArch64InstrInfo::new();
let must_have = vec![
A64Opcode::ADD,
A64Opcode::SUB,
A64Opcode::ADC,
A64Opcode::SBC,
A64Opcode::NEG,
A64Opcode::CMP,
A64Opcode::CMN,
A64Opcode::AND,
A64Opcode::ORR,
A64Opcode::EOR,
A64Opcode::BIC,
A64Opcode::ORN,
A64Opcode::EON,
A64Opcode::LSL,
A64Opcode::LSR,
A64Opcode::ASR,
A64Opcode::ROR,
A64Opcode::MUL,
A64Opcode::MADD,
A64Opcode::MSUB,
A64Opcode::MNEG,
A64Opcode::SDIV,
A64Opcode::UDIV,
A64Opcode::BFM,
A64Opcode::SBFM,
A64Opcode::UBFM,
A64Opcode::BFI,
A64Opcode::EXTR,
A64Opcode::CSEL,
A64Opcode::CSINC,
A64Opcode::CSINV,
A64Opcode::CSNEG,
A64Opcode::CCMN,
A64Opcode::CCMP,
A64Opcode::B,
A64Opcode::BL,
A64Opcode::BR,
A64Opcode::BLR,
A64Opcode::RET,
A64Opcode::CBZ,
A64Opcode::CBNZ,
A64Opcode::TBZ,
A64Opcode::TBNZ,
A64Opcode::LDR,
A64Opcode::STR,
A64Opcode::LDP,
A64Opcode::STP,
A64Opcode::LDUR,
A64Opcode::STUR,
A64Opcode::LDAR,
A64Opcode::STLR,
A64Opcode::FADD,
A64Opcode::FSUB,
A64Opcode::FMUL,
A64Opcode::FDIV,
A64Opcode::FNEG,
A64Opcode::FABS,
A64Opcode::FSQRT,
A64Opcode::FMADD,
A64Opcode::FMSUB,
A64Opcode::FNMADD,
A64Opcode::FNMSUB,
A64Opcode::FCMP,
A64Opcode::FCCMP,
A64Opcode::FCVT,
A64Opcode::FCVTZS,
A64Opcode::SCVTF,
A64Opcode::ADDv,
A64Opcode::MULv,
A64Opcode::SHLv,
A64Opcode::LD1v,
A64Opcode::ST1v,
A64Opcode::TBLv,
A64Opcode::ADD_sve,
A64Opcode::FADD_sve,
A64Opcode::WHILELT,
A64Opcode::PTRUE,
A64Opcode::INDEX,
A64Opcode::CNT_sve,
A64Opcode::NOP,
A64Opcode::SVC,
];
for op in &must_have {
assert!(info.get(*op).is_some(), "Missing instr info for {:?}", op);
}
}
#[test]
fn test_large_function_many_adds() {
let mut ir = Vec::new();
ir.push(IRInst::new(IROpcode::Const, 1, "i64").with_imm(1));
for i in 2..=100 {
ir.push(IRInst::binary(IROpcode::Add, i, i - 1, i - 1, "i64"));
}
ir.push(IRInst::unary(IROpcode::Ret, 0, 100, "i64"));
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
let add_count = code.iter().filter(|mi| mi.opcode == A64Opcode::ADD).count();
assert_eq!(add_count, 99);
}
#[test]
fn test_many_fp_operations() {
let mut ir = Vec::new();
ir.push(IRInst::new(IROpcode::Const, 10, "f64").with_imm(0));
ir.push(IRInst::new(IROpcode::Const, 20, "f64").with_imm(0));
for i in 0..50 {
let dst = 30 + i * 10;
let s1 = 10 + (i as u32) * 10;
let s2 = 20 + (i as u32) * 10;
ir.push(IRInst::binary(IROpcode::FAdd, dst, s1, s2, "f64"));
}
ir.push(IRInst::unary(IROpcode::Ret, 0, 30 + 49 * 10, "f64"));
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
let fadd_count = code
.iter()
.filter(|mi| mi.opcode == A64Opcode::FADD)
.count();
assert_eq!(fadd_count, 50);
}
#[test]
fn test_isel_neg() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(42),
IRInst::unary(IROpcode::Neg, 2, 1, "i64"),
IRInst::unary(IROpcode::Ret, 0, 2, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::NEG));
}
#[test]
fn test_isel_phi() {
let ir = vec![
IRInst::new(IROpcode::Const, 1, "i64").with_imm(99),
IRInst::binary(IROpcode::Phi, 2, 1, 1, "i64"),
IRInst::unary(IROpcode::Ret, 0, 2, "i64"),
];
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::MOV));
}
#[test]
fn test_fp_constants_known() {
assert_eq!(FP, X29);
assert_eq!(LR, X30);
assert_eq!(SP, 32);
}
#[test]
fn test_mc_encoder_new() {
let enc = AArch64MCEncoder::new();
assert!(enc.buffer.is_empty());
}
#[test]
fn test_mc_encode_add() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Imm(5)]);
let word = enc.encode(&mi);
assert!(word.is_some(), "ADD should encode successfully");
}
#[test]
fn test_mc_encode_nop() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::NOP);
let word = enc.encode(&mi);
assert_eq!(word, Some(0xD503201F));
}
#[test]
fn test_mc_encode_ret() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30));
let word = enc.encode(&mi);
assert!(word.is_some());
let w = word.unwrap();
assert_eq!(w & 0x1F, 30); }
#[test]
fn test_mc_encode_mov_orr() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(X0))
.with_use(MachineOperand::Reg(X1));
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_movz() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::MOVZ)
.with_def(MachineOperand::Reg(X2))
.with_use(MachineOperand::Imm(0x42));
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_ldr() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::LDR)
.with_def(MachineOperand::Reg(X3))
.with_use(MachineOperand::Mem {
base: X4,
index: None,
disp: 0,
scale: 1,
});
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_str() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::STR).with_uses(vec![
MachineOperand::Reg(X5),
MachineOperand::Mem {
base: X6,
index: None,
disp: 16,
scale: 1,
},
]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_fadd() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::FADD)
.with_def(MachineOperand::Reg(V0))
.with_uses(vec![MachineOperand::Reg(V1), MachineOperand::Reg(V2)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_fmul() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::FMUL)
.with_def(MachineOperand::Reg(V3))
.with_uses(vec![MachineOperand::Reg(V4), MachineOperand::Reg(V5)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_br() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::BR).with_use(MachineOperand::Reg(X1));
let word = enc.encode(&mi);
assert!(word.is_some());
let w = word.unwrap();
assert_eq!(w & 0x1F, 1); }
#[test]
fn test_mc_encode_blr() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::BLR).with_use(MachineOperand::Reg(X2));
let word = enc.encode(&mi);
assert!(word.is_some());
let w = word.unwrap();
assert_eq!(w & 0x1F, 2);
}
#[test]
fn test_mc_encode_csel() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::CSEL)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![
MachineOperand::Reg(X1),
MachineOperand::Reg(X2),
MachineOperand::Cond(A64Cond::EQ),
]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_csinc() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::CSINC)
.with_def(MachineOperand::Reg(X3))
.with_uses(vec![
MachineOperand::Reg(X4),
MachineOperand::Reg(X5),
MachineOperand::Cond(A64Cond::NE),
]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_mul() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::MUL)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Reg(X2)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_sdiv() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::SDIV)
.with_def(MachineOperand::Reg(X10))
.with_uses(vec![MachineOperand::Reg(X11), MachineOperand::Reg(X12)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_udiv() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::UDIV)
.with_def(MachineOperand::Reg(X13))
.with_uses(vec![MachineOperand::Reg(X14), MachineOperand::Reg(X15)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_and() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::AND)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Reg(X2)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_orr() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::ORR)
.with_def(MachineOperand::Reg(X5))
.with_uses(vec![MachineOperand::Reg(X6), MachineOperand::Reg(X7)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_eor() {
let mut enc = AArch64MCEncoder::new();
let mi = MachineInstr::new(A64Opcode::EOR)
.with_def(MachineOperand::Reg(X8))
.with_uses(vec![MachineOperand::Reg(X9), MachineOperand::Reg(X10)]);
let word = enc.encode(&mi);
assert!(word.is_some());
}
#[test]
fn test_mc_encode_function() {
let mut enc = AArch64MCEncoder::new();
let instrs = vec![
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Imm(1)]),
MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30)),
];
let blob = enc.encode_function(&instrs);
assert_eq!(blob.len(), 8); }
#[test]
fn test_mc_encode_function_non_empty() {
let mut enc = AArch64MCEncoder::new();
let instrs = vec![
MachineInstr::new(A64Opcode::NOP),
MachineInstr::new(A64Opcode::NOP),
MachineInstr::new(A64Opcode::NOP),
];
let blob = enc.encode_function(&instrs);
assert_eq!(blob.len(), 12);
}
#[test]
fn test_encode_aarch64_imm12_small() {
let (imm, sh) = AArch64MCEncoder::encode_aarch64_imm12(42);
assert_eq!(imm, 42);
assert_eq!(sh, 0);
}
#[test]
fn test_encode_aarch64_imm12_large_aligned() {
let (imm, sh) = AArch64MCEncoder::encode_aarch64_imm12(4096);
assert_eq!(imm, 1);
assert_eq!(sh, 1);
}
#[test]
fn test_mc_inst_new() {
let inst = MCInst::new(0x8B010000, A64Opcode::ADD);
assert_eq!(inst.opcode, A64Opcode::ADD);
assert_eq!(inst.encoding, 0x8B010000);
}
#[test]
fn test_mc_inst_bits() {
let inst = MCInst::new(0b1100_0000_0000_0000_0000_0000_0000_0000, A64Opcode::NOP);
assert_eq!(inst.bits(31, 30), 0b11);
assert_eq!(inst.bits(31, 0), 0xC0000000);
}
#[test]
fn test_mc_inst_rd() {
let inst = MCInst::new(0x00000005, A64Opcode::ADD);
assert_eq!(inst.rd(), 5);
}
#[test]
fn test_mc_inst_rn() {
let inst = MCInst::new(0x000000A0, A64Opcode::ADD);
assert_eq!(inst.rn(), 5);
}
#[test]
fn test_mc_inst_rm() {
let inst = MCInst::new(0x00050000, A64Opcode::ADD);
assert_eq!(inst.rm(), 5);
}
#[test]
fn test_mc_decode_branch() {
let word: u32 = 0x14000001; let inst = MCInst::decode(word);
assert!(inst.is_some());
assert_eq!(inst.unwrap().opcode, A64Opcode::B);
}
#[test]
fn test_mc_decode_bl() {
let word: u32 = 0x94000001;
let inst = MCInst::decode(word);
assert!(inst.is_some());
assert_eq!(inst.unwrap().opcode, A64Opcode::BL);
}
#[test]
fn test_mc_decode_load() {
let word: u32 = 0xF9400020; let inst = MCInst::decode(word);
}
#[test]
fn test_mc_decode_invalid() {
let word: u32 = 0x00000000; let inst = MCInst::decode(word);
assert!(inst.is_none());
}
#[test]
fn test_mc_decode_buffer() {
let data: Vec<u8> = vec![
0x00, 0x00, 0x00, 0x14, 0x1F, 0x20, 0x03, 0xD5, ];
let insts = AArch64MCEncoder::decode_buffer(&data);
assert_eq!(insts.len(), 2);
}
#[test]
fn test_mc_imm12_method() {
let inst = MCInst::new(0x00400C00, A64Opcode::ADD); assert_eq!(inst.imm12(), 1);
}
#[test]
fn test_mc_rt() {
let inst = MCInst::new(0x0000000A, A64Opcode::LDR);
assert_eq!(inst.rt(), 10);
}
#[test]
fn test_mc_rn_ldst() {
let inst = MCInst::new(0x00000140, A64Opcode::LDR);
assert_eq!(inst.rn_ldst(), 10);
}
#[test]
fn test_asm_printer_new() {
let printer = AArch64AsmPrinter::new();
assert!(printer.output.is_empty());
assert!(printer.emit_labels);
assert!(!printer.emit_addresses);
}
#[test]
fn test_asm_printer_print_function() {
let mut printer = AArch64AsmPrinter::new();
let instrs = vec![
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Imm(1)]),
MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30)),
];
let output = printer.print_function("test_fn", &instrs);
assert!(output.contains(".globl test_fn"));
assert!(output.contains("test_fn:"));
assert!(output.contains("add"));
assert!(output.contains("ret"));
assert!(output.contains(".size test_fn"));
}
#[test]
fn test_asm_printer_empty_function() {
let mut printer = AArch64AsmPrinter::new();
let output = printer.print_function("empty", &[]);
assert!(output.contains(".globl empty"));
assert!(output.contains("empty:"));
assert!(output.contains(".size empty"));
}
#[test]
fn test_asm_printer_with_addresses() {
let mut printer = AArch64AsmPrinter::new();
printer.emit_addresses = true;
let instrs = vec![MachineInstr::new(A64Opcode::NOP)];
let output = printer.print_function("addr_test", &instrs);
assert!(output.contains("00000000:"));
}
#[test]
fn test_asm_printer_data_section() {
let mut printer = AArch64AsmPrinter::new();
let data: Vec<u8> = vec![0x42, 0x00, 0x00, 0x00];
let output = printer.print_data_section("mydata", &data);
assert!(output.contains(".section .rodata"));
assert!(output.contains("mydata:"));
assert!(output.contains(".word 0x00000042"));
}
#[test]
fn test_asm_printer_comment() {
let mut printer = AArch64AsmPrinter::new();
printer.comment("This is a test comment");
assert!(printer.output.contains("// This is a test comment"));
}
#[test]
fn test_asm_printer_label() {
let mut printer = AArch64AsmPrinter::new();
printer.label(".LBB0_1");
assert!(printer.output.contains(".LBB0_1:"));
}
#[test]
fn test_asm_printer_directive() {
let mut printer = AArch64AsmPrinter::new();
printer.directive("align 4");
assert!(printer.output.contains(".align 4"));
}
#[test]
fn test_asm_printer_multiple_instrs() {
let mut printer = AArch64AsmPrinter::new();
let instrs: Vec<MachineInstr> =
(0..10).map(|_| MachineInstr::new(A64Opcode::NOP)).collect();
let output = printer.print_function("many_nops", &instrs);
let nop_count = output.matches("nop").count();
assert_eq!(nop_count, 10);
}
#[test]
fn test_reg_alloc_new() {
let alloc = AArch64RegAlloc::new();
assert_eq!(alloc.allocated_count(), 0);
assert_eq!(alloc.spill_count, 0);
assert_eq!(alloc.total_instrs, 0);
}
#[test]
fn test_reg_alloc_alloc_gpr() {
let mut alloc = AArch64RegAlloc::new();
let preg0 = alloc.alloc_gpr(100);
assert_eq!(preg0, X0);
let preg1 = alloc.alloc_gpr(101);
assert_eq!(preg1, X1);
assert_eq!(alloc.allocated_count(), 2);
}
#[test]
fn test_reg_alloc_alloc_fpr() {
let mut alloc = AArch64RegAlloc::new();
let preg = alloc.alloc_fpr(200);
assert_eq!(preg, V0);
assert_eq!(alloc.allocated_count(), 1);
}
#[test]
fn test_reg_alloc_same_vreg_returns_same_preg() {
let mut alloc = AArch64RegAlloc::new();
let p1 = alloc.alloc_gpr(42);
let p2 = alloc.alloc_gpr(42);
assert_eq!(p1, p2);
}
#[test]
fn test_reg_alloc_free_and_reuse() {
let mut alloc = AArch64RegAlloc::new();
let p = alloc.alloc_gpr(1);
alloc.free_reg(p);
let p2 = alloc.alloc_gpr(2);
assert_eq!(p2, X28); assert_eq!(p2, X0);
}
#[test]
fn test_reg_alloc_get_assignment() {
let mut alloc = AArch64RegAlloc::new();
alloc.alloc_gpr(55);
assert_eq!(alloc.get_assignment(55), Some(X0));
assert_eq!(alloc.get_assignment(99), None);
}
#[test]
fn test_reg_alloc_run_on_instrs() {
let mut alloc = AArch64RegAlloc::new();
let instrs = vec![
MachineInstr::new(A64Opcode::NOP),
MachineInstr::new(A64Opcode::NOP),
];
let result = alloc.run(&instrs);
assert_eq!(result.len(), 2);
assert_eq!(alloc.total_instrs, 2);
}
#[test]
fn test_reg_alloc_exhaust_gprs_triggers_spill() {
let mut alloc = AArch64RegAlloc::new();
for i in 0..28 {
alloc.alloc_gpr(i);
}
assert_eq!(alloc.spill_count, 0);
alloc.alloc_gpr(28);
assert_eq!(alloc.spill_count, 1);
}
#[test]
fn test_reg_alloc_spill_slot_assignment() {
let mut alloc = AArch64RegAlloc::new();
for i in 0..28 {
alloc.alloc_gpr(i);
}
alloc.alloc_gpr(28);
assert!(!alloc.spills.is_empty());
assert_eq!(alloc.spills[0].vreg, 28);
}
#[test]
fn test_reg_alloc_insert_spill_store() {
let mut alloc = AArch64RegAlloc::new();
alloc.alloc_gpr(10);
let mut instrs = vec![MachineInstr::new(A64Opcode::NOP)];
alloc.insert_spill_store(&mut instrs, 0, 10, -16);
assert_eq!(instrs.len(), 2);
assert_eq!(instrs[0].opcode, A64Opcode::STR);
}
#[test]
fn test_reg_alloc_insert_spill_reload() {
let mut alloc = AArch64RegAlloc::new();
alloc.alloc_gpr(20);
let mut instrs = vec![MachineInstr::new(A64Opcode::NOP)];
alloc.insert_spill_reload(&mut instrs, 0, 20, -24);
assert_eq!(instrs.len(), 2);
assert_eq!(instrs[1].opcode, A64Opcode::LDR);
}
#[test]
fn test_peephole_new() {
let peep = AArch64Peephole::new();
assert_eq!(peep.opt_count, 0);
}
#[test]
fn test_peephole_eliminate_redundant_mov() {
let mut peep = AArch64Peephole::new();
let mut instrs = vec![
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(X0))
.with_use(MachineOperand::Reg(X0)), MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X1))
.with_uses(vec![MachineOperand::Reg(X0), MachineOperand::Imm(1)]),
];
peep.optimize(&mut instrs);
assert_eq!(instrs.len(), 1); assert!(peep.opt_count >= 1);
}
#[test]
fn test_peephole_fold_mov_into_operand() {
let mut peep = AArch64Peephole::new();
let mut instrs = vec![
MachineInstr::new(A64Opcode::MOV)
.with_def(MachineOperand::Reg(X1))
.with_use(MachineOperand::Reg(X2)),
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X3))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Imm(5)]),
];
peep.optimize(&mut instrs);
assert_eq!(instrs.len(), 1); let add = &instrs[0];
if let MachineOperand::Reg(r) = &add.uses[0] {
assert_eq!(*r, X2);
} else {
panic!("Expected register operand");
}
assert!(peep.opt_count >= 1);
}
#[test]
fn test_peephole_convert_mov_add_to_add_imm() {
let mut peep = AArch64Peephole::new();
let mut instrs = vec![
MachineInstr::new(A64Opcode::MOVZ)
.with_def(MachineOperand::Reg(X2))
.with_use(MachineOperand::Imm(42)),
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X3))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Reg(X2)]),
];
peep.optimize(&mut instrs);
assert_eq!(instrs.len(), 1);
let add = &instrs[0];
if let MachineOperand::Imm(imm) = &add.uses[1] {
assert_eq!(*imm, 42);
} else {
panic!("Expected immediate operand");
}
}
#[test]
fn test_peephole_merge_str_str_into_stp() {
let mut peep = AArch64Peephole::new();
let mut instrs = vec![
MachineInstr::new(A64Opcode::STR).with_uses(vec![
MachineOperand::Reg(X0),
MachineOperand::Mem {
base: SP,
index: None,
disp: 0,
scale: 1,
},
]),
MachineInstr::new(A64Opcode::STR).with_uses(vec![
MachineOperand::Reg(X1),
MachineOperand::Mem {
base: SP,
index: None,
disp: 8,
scale: 1,
},
]),
];
peep.optimize(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, A64Opcode::STP);
assert!(peep.opt_count >= 1);
}
#[test]
fn test_peephole_no_merge_different_bases() {
let mut peep = AArch64Peephole::new();
let mut instrs = vec![
MachineInstr::new(A64Opcode::STR).with_uses(vec![
MachineOperand::Reg(X0),
MachineOperand::Mem {
base: X1,
index: None,
disp: 0,
scale: 1,
},
]),
MachineInstr::new(A64Opcode::STR).with_uses(vec![
MachineOperand::Reg(X2),
MachineOperand::Mem {
base: X3,
index: None,
disp: 8,
scale: 1,
},
]),
];
peep.optimize(&mut instrs);
assert_eq!(instrs.len(), 2);
}
#[test]
fn test_peephole_preserve_non_matching() {
let mut peep = AArch64Peephole::new();
let mut instrs = vec![
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Reg(X2)]),
MachineInstr::new(A64Opcode::SUB)
.with_def(MachineOperand::Reg(X3))
.with_uses(vec![MachineOperand::Reg(X4), MachineOperand::Imm(1)]),
];
peep.optimize(&mut instrs);
assert_eq!(instrs.len(), 2);
assert_eq!(instrs[0].opcode, A64Opcode::ADD);
assert_eq!(instrs[1].opcode, A64Opcode::SUB);
}
#[test]
fn test_peephole_empty_input() {
let mut peep = AArch64Peephole::new();
let mut instrs: Vec<MachineInstr> = vec![];
peep.optimize(&mut instrs);
assert!(instrs.is_empty());
}
#[test]
fn test_isel_indexed_load() {
let mut isel = AArch64ISel::new();
let mi = isel.select_indexed_load(5, 10, 8, false);
assert_eq!(mi.opcode, A64Opcode::LDR);
assert!(mi.has_def());
}
#[test]
fn test_isel_scaled_mem() {
let mut isel = AArch64ISel::new();
let mem = isel.try_match_scaled_mem(1, 2, 3);
match mem {
MachineOperand::Mem {
base,
index,
disp,
scale,
} => {
assert_eq!(disp, 0);
assert_eq!(scale, 8); assert!(index.is_some());
}
_ => panic!("Expected Mem operand"),
}
}
#[test]
fn test_isel_ext_load_signed_byte() {
let mut isel = AArch64ISel::new();
let mi = isel.select_ext_load(1, 2, true, 8);
assert_eq!(mi.opcode, A64Opcode::LDRSB);
}
#[test]
fn test_isel_ext_load_unsigned_half() {
let mut isel = AArch64ISel::new();
let mi = isel.select_ext_load(1, 2, false, 16);
assert_eq!(mi.opcode, A64Opcode::LDRH);
}
#[test]
fn test_isel_ext_load_signed_word() {
let mut isel = AArch64ISel::new();
let mi = isel.select_ext_load(1, 2, true, 32);
assert_eq!(mi.opcode, A64Opcode::LDRSW);
}
#[test]
fn test_isel_legalize_small_imm() {
let mut isel = AArch64ISel::new();
let instrs = isel.legalize_large_imm(1, 42);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, A64Opcode::MOVZ);
}
#[test]
fn test_isel_legalize_negative_imm() {
let mut isel = AArch64ISel::new();
let instrs = isel.legalize_large_imm(1, -1);
assert!(!instrs.is_empty());
}
#[test]
fn test_isel_legalize_large_64bit() {
let mut isel = AArch64ISel::new();
let instrs = isel.legalize_large_imm(1, 0x123456789ABC);
assert!(instrs.len() >= 1);
}
#[test]
fn test_isel_legalize_zero() {
let mut isel = AArch64ISel::new();
let instrs = isel.legalize_large_imm(1, 0);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, A64Opcode::MOV);
assert_eq!(instrs[0].uses[0], MachineOperand::Reg(XZR));
}
#[test]
fn test_encoder_asm_printer_roundtrip() {
let mut enc = AArch64MCEncoder::new();
let instrs = vec![
MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X0))
.with_uses(vec![MachineOperand::Reg(X1), MachineOperand::Imm(3)]),
MachineInstr::new(A64Opcode::RET).with_use(MachineOperand::Reg(X30)),
];
let blob = enc.encode_function(&instrs);
assert_eq!(blob.len(), 8);
let mut printer = AArch64AsmPrinter::new();
let output = printer.print_function("roundtrip", &instrs);
assert!(!output.is_empty());
}
#[test]
fn test_encode_many_instructions() {
let mut enc = AArch64MCEncoder::new();
let instrs: Vec<MachineInstr> = (0..100)
.map(|_| MachineInstr::new(A64Opcode::NOP))
.collect();
let blob = enc.encode_function(&instrs);
assert_eq!(blob.len(), 400);
}
#[test]
fn test_full_compilation_pipeline_with_encoder() {
let ir = build_add_ir();
let mut backend = AArch64Deep::new().with_frame(32);
let code = backend.compile(&ir);
let mut enc = AArch64MCEncoder::new();
let blob = enc.encode_function(&code);
assert!(!blob.is_empty());
assert_eq!(blob.len() % 4, 0);
let mut printer = AArch64AsmPrinter::new();
let asm = printer.print_function("pipeline_test", &code);
assert!(asm.contains("add"));
}
#[test]
fn test_reg_alloc_with_isel_output() {
let ir = build_add_ir();
let mut isel = AArch64ISel::new();
let code = isel.select(&ir);
let mut alloc = AArch64RegAlloc::new();
let result = alloc.run(&code);
assert!(!result.is_empty());
assert!(alloc.total_instrs >= 1);
}
#[test]
fn test_peephole_on_isel_output() {
let ir = build_add_ir();
let mut isel = AArch64ISel::new();
let mut code = isel.select(&ir);
let mut peep = AArch64Peephole::new();
let len_before = code.len();
peep.optimize(&mut code);
assert!(code.len() <= len_before);
}
#[test]
fn test_mc_inst_roundtrip_opcode_field() {
let inst = MCInst::new(0x8B010000, A64Opcode::ADD);
let field = inst.opcode_field();
assert_eq!(field, 0x22C);
}
#[test]
fn test_mc_inst_roundtrip_all_fields() {
let inst = MCInst::new(0x8B020003, A64Opcode::ADD);
assert_eq!(inst.rd(), 3);
assert_eq!(inst.rn(), 0);
assert_eq!(inst.rm(), 2);
}
#[test]
fn test_stress_1000_add_instructions() {
let mut ir = Vec::new();
ir.push(IRInst::new(IROpcode::Const, 0, "i64").with_imm(0));
for i in 1..=1000 {
ir.push(IRInst::binary(
IROpcode::Add,
i as u32,
(i - 1) as u32,
(i - 1) as u32,
"i64",
));
}
ir.push(IRInst::unary(IROpcode::Ret, 1001, 1000, "i64"));
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
let add_count = code.iter().filter(|mi| mi.opcode == A64Opcode::ADD).count();
assert_eq!(add_count, 1000);
}
#[test]
fn test_stress_mixed_operations() {
let mut ir = Vec::new();
ir.push(IRInst::new(IROpcode::Const, 0, "i64").with_imm(1));
for i in 1..=200 {
let op = match i % 5 {
0 => IROpcode::Add,
1 => IROpcode::Sub,
2 => IROpcode::And,
3 => IROpcode::Or,
_ => IROpcode::Xor,
};
ir.push(IRInst::binary(op, i as u32, (i - 1) as u32, 0, "i64"));
}
ir.push(IRInst::unary(IROpcode::Ret, 201, 200, "i64"));
let mut backend = AArch64Deep::new();
let code = backend.compile(&ir);
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::ADD));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::SUB));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::AND));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::ORR));
assert!(code.iter().any(|mi| mi.opcode == A64Opcode::EOR));
}
#[test]
fn test_default_impls() {
let _isel = AArch64ISel::default();
let _backend = AArch64Deep::default();
let _cc = AArch64CallingConvention::default();
let _info = AArch64InstrInfo::default();
let _enc = AArch64MCEncoder::default();
let _printer = AArch64AsmPrinter::default();
let _alloc = AArch64RegAlloc::default();
let _peep = AArch64Peephole::default();
}
#[test]
fn test_opcode_count_equals_instr_info_count() {
let info = AArch64InstrInfo::new();
assert!(info.len() >= 150);
}
#[test]
fn test_all_opcode_variants_have_category() {
let ops = [
A64Opcode::ADD,
A64Opcode::SUB,
A64Opcode::MUL,
A64Opcode::SDIV,
A64Opcode::AND,
A64Opcode::B,
A64Opcode::RET,
A64Opcode::FADD,
A64Opcode::ADDv,
A64Opcode::ADD_sve,
A64Opcode::NOP,
];
for &op in &ops {
let _cat = op.category(); }
}
#[test]
fn test_machine_instr_builder_consistency() {
let mi = MachineInstr::new(A64Opcode::ADD)
.with_def(MachineOperand::Reg(X0))
.with_use(MachineOperand::Reg(X1))
.with_use(MachineOperand::Imm(5))
.with_comment("test");
assert_eq!(mi.opcode, A64Opcode::ADD);
assert!(mi.has_def());
assert_eq!(mi.uses.len(), 2);
assert!(mi.comment.is_some());
}
#[test]
fn test_all_cond_codes_invert_twice_identity() {
let conds = [
A64Cond::EQ,
A64Cond::NE,
A64Cond::CS,
A64Cond::CC,
A64Cond::MI,
A64Cond::PL,
A64Cond::VS,
A64Cond::VC,
A64Cond::HI,
A64Cond::LS,
A64Cond::GE,
A64Cond::LT,
A64Cond::GT,
A64Cond::LE,
A64Cond::AL,
A64Cond::NV,
];
for &c in &conds {
assert_eq!(c.invert().invert(), c, "Double invert failed for {:?}", c);
}
}
}