#![allow(non_upper_case_globals, dead_code, non_snake_case)]
use crate::codegen::{
MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, PhysReg, VirtReg,
};
use crate::x86::x86_instr_info::{X86InstrDesc, X86InstrInfo, X86Opcode};
use crate::x86::X86Subtarget;
use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt;
pub const X86_FIXUP_MAX_SCAN: usize = 32;
pub const X86_IMM32_THRESHOLD: i64 = 0xFFFF_FFFF;
pub const X86_REX_PREFIX: u8 = 0x40;
pub const X86_REX_W: u8 = 0x48;
pub const X86_REX_R: u8 = 0x44;
pub const X86_REX_X: u8 = 0x42;
pub const X86_REX_B: u8 = 0x41;
pub const X86_PREFIX_CS: u8 = 0x2E;
pub const X86_PREFIX_DS: u8 = 0x3E;
pub const X86_PREFIX_ES: u8 = 0x26;
pub const X86_PREFIX_SS: u8 = 0x36;
pub const X86_PREFIX_FS: u8 = 0x64;
pub const X86_PREFIX_GS: u8 = 0x65;
pub const X86_PREFIX_OPSIZE: u8 = 0x66;
pub const X86_PREFIX_REP: u8 = 0xF3;
pub const X86_PREFIX_REPNE: u8 = 0xF2;
pub const X86_PREFIX_LOCK: u8 = 0xF0;
pub const X86_PREFIX_ADDRSIZE: u8 = 0x67;
const RAX: PhysReg = 0;
const RCX: PhysReg = 1;
const RDX: PhysReg = 2;
const RBX: PhysReg = 3;
const RSP: PhysReg = 4;
const RBP: PhysReg = 5;
const RSI: PhysReg = 6;
const RDI: PhysReg = 7;
const R8: PhysReg = 8;
const R9: PhysReg = 9;
const R10: PhysReg = 10;
const R11: PhysReg = 11;
const R12: PhysReg = 12;
const R13: PhysReg = 13;
const R14: PhysReg = 14;
const R15: PhysReg = 15;
const EAX: PhysReg = 16;
const ECX: PhysReg = 17;
const EDX: PhysReg = 18;
const EBX: PhysReg = 19;
const ESP: PhysReg = 20;
const EBP: PhysReg = 21;
const ESI: PhysReg = 22;
const EDI: PhysReg = 23;
const R8D: PhysReg = 24;
const R9D: PhysReg = 25;
const R10D: PhysReg = 26;
const R11D: PhysReg = 27;
const R12D: PhysReg = 28;
const R13D: PhysReg = 29;
const R14D: PhysReg = 30;
const R15D: PhysReg = 31;
const AX: PhysReg = 32;
const CX: PhysReg = 33;
const DX: PhysReg = 34;
const BX: PhysReg = 35;
const SP: PhysReg = 36;
const BP: PhysReg = 37;
const SI: PhysReg = 38;
const DI: PhysReg = 39;
const AL: PhysReg = 40;
const CL: PhysReg = 41;
const DL: PhysReg = 42;
const BL: PhysReg = 43;
const FLAGS: PhysReg = 48;
fn opc(op: X86Opcode) -> u32 {
op as u32
}
fn is_phys_reg(op: &MachineOperand, reg: PhysReg) -> bool {
matches!(op, MachineOperand::PhysReg(r) if *r == reg)
}
fn is_any_phys_reg(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::PhysReg(_))
}
fn is_any_reg(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::Reg(_) | MachineOperand::PhysReg(_))
}
fn get_phys_reg(op: &MachineOperand) -> Option<PhysReg> {
match op {
MachineOperand::PhysReg(r) => Some(*r),
_ => None,
}
}
fn get_any_reg(op: &MachineOperand) -> Option<u32> {
match op {
MachineOperand::Reg(r) => Some(*r),
MachineOperand::PhysReg(r) => Some(*r),
_ => None,
}
}
fn get_imm(op: &MachineOperand) -> Option<i64> {
match op {
MachineOperand::Imm(v) => Some(*v),
_ => None,
}
}
fn same_reg(a: &MachineOperand, b: &MachineOperand) -> bool {
match (a, b) {
(MachineOperand::Reg(ra), MachineOperand::Reg(rb)) => ra == rb,
(MachineOperand::PhysReg(ra), MachineOperand::PhysReg(rb)) => ra == rb,
_ => false,
}
}
fn is_zero_imm(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::Imm(0))
}
fn is_one_imm(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::Imm(1))
}
fn fits_i8(v: i64) -> bool {
v >= -128 && v <= 127
}
fn fits_i32(v: i64) -> bool {
v >= -2_147_483_648 && v <= 2_147_483_647
}
fn fits_u32(v: i64) -> bool {
v >= 0 && v <= 4_294_967_295
}
fn is_gpr64(reg: PhysReg) -> bool {
reg <= R15
}
fn is_gpr32(reg: PhysReg) -> bool {
reg >= EAX && reg <= R15D
}
fn is_gpr16(reg: PhysReg) -> bool {
reg >= AX && reg <= DI
}
fn is_gpr8(reg: PhysReg) -> bool {
reg >= AL && reg <= 47
}
fn reg_bits(reg: PhysReg) -> u32 {
if reg <= R15 {
64
} else if reg <= R15D {
32
} else if reg <= DI {
16
} else {
8
}
}
fn gpr64_to_gpr32(reg64: PhysReg) -> PhysReg {
match reg64 {
RAX => EAX,
RCX => ECX,
RDX => EDX,
RBX => EBX,
RSP => ESP,
RBP => EBP,
RSI => ESI,
RDI => EDI,
R8 => R8D,
R9 => R9D,
R10 => R10D,
R11 => R11D,
R12 => R12D,
R13 => R13D,
R14 => R14D,
R15 => R15D,
_ => reg64,
}
}
fn are_related_regs(a: PhysReg, b: PhysReg) -> bool {
fn base(r: PhysReg) -> usize {
match r {
RAX | EAX | AX | AL => 0,
RCX | ECX | CX | CL => 1,
RDX | EDX | DX | DL => 2,
RBX | EBX | BX | BL => 3,
RSP | ESP | SP => 4,
RBP | EBP | BP => 5,
RSI | ESI | SI => 6,
RDI | EDI | DI => 7,
R8 | R8D => 8,
R9 | R9D => 9,
R10 | R10D => 10,
R11 | R11D => 11,
R12 | R12D => 12,
R13 | R13D => 13,
R14 | R14D => 14,
R15 | R15D => 15,
_ => r as _,
}
}
base(a) == base(b)
}
fn reg_name(reg: PhysReg) -> &'static str {
match reg {
RAX => "rax",
RCX => "rcx",
RDX => "rdx",
RBX => "rbx",
RSP => "rsp",
RBP => "rbp",
RSI => "rsi",
RDI => "rdi",
R8 => "r8",
R9 => "r9",
R10 => "r10",
R11 => "r11",
R12 => "r12",
R13 => "r13",
R14 => "r14",
R15 => "r15",
EAX => "eax",
ECX => "ecx",
EDX => "edx",
EBX => "ebx",
ESP => "esp",
EBP => "ebp",
ESI => "esi",
EDI => "edi",
R8D => "r8d",
R9D => "r9d",
R10D => "r10d",
R11D => "r11d",
R12D => "r12d",
R13D => "r13d",
R14D => "r14d",
R15D => "r15d",
AX => "ax",
CX => "cx",
DX => "dx",
BX => "bx",
SP => "sp",
BP => "bp",
SI => "si",
DI => "di",
AL => "al",
CL => "cl",
DL => "dl",
BL => "bl",
_ => "?",
}
}
#[derive(Debug, Clone)]
pub struct X86InstrFixup {
pub subtarget: X86Subtarget,
pub is_64_bit: bool,
pub opt_for_size: bool,
pub enable_cmp_to_test: bool,
pub enable_xor32_zeroing: bool,
pub enable_32bit_implicit_zero: bool,
pub enable_lea_to_mov: bool,
pub enable_add_sub_to_lea: bool,
pub enable_inc_dec_to_add_sub: bool,
pub enable_rex_optimization: bool,
pub enable_segment_override_removal: bool,
pub enable_prefix_removal: bool,
pub enable_size_minimization: bool,
pub prefer_and_over_mov: bool,
pub aggressive: bool,
pub stats: FixupStats,
}
#[derive(Debug, Clone, Default)]
pub struct FixupStats {
pub cmp_to_test: usize,
pub xor32_zeroing: usize,
pub gpr32_implicit_zero: usize,
pub lea_to_mov: usize,
pub add_sub_to_lea: usize,
pub inc_dec_to_add_sub: usize,
pub rex_removed: usize,
pub rex_added: usize,
pub segment_override_removed: usize,
pub prefix_removed: usize,
pub size_saved_bytes: usize,
pub total_fixups: usize,
}
impl FixupStats {
pub fn merge(&mut self, other: &FixupStats) {
self.cmp_to_test += other.cmp_to_test;
self.xor32_zeroing += other.xor32_zeroing;
self.gpr32_implicit_zero += other.gpr32_implicit_zero;
self.lea_to_mov += other.lea_to_mov;
self.add_sub_to_lea += other.add_sub_to_lea;
self.inc_dec_to_add_sub += other.inc_dec_to_add_sub;
self.rex_removed += other.rex_removed;
self.rex_added += other.rex_added;
self.segment_override_removed += other.segment_override_removed;
self.prefix_removed += other.prefix_removed;
self.size_saved_bytes += other.size_saved_bytes;
self.total_fixups += other.total_fixups;
}
pub fn summary(&self) -> String {
format!(
"X86InstrFixup: total={}, cmp_to_test={}, xor32={}, lea_to_mov={}, \
add_sub_to_lea={}, inc_dec={}, rex_rem={}, size_saved={}B",
self.total_fixups,
self.cmp_to_test,
self.xor32_zeroing,
self.lea_to_mov,
self.add_sub_to_lea,
self.inc_dec_to_add_sub,
self.rex_removed,
self.size_saved_bytes,
)
}
pub fn made_progress(&self) -> bool {
self.total_fixups > 0
}
}
impl X86InstrFixup {
pub fn new(subtarget: &X86Subtarget) -> Self {
let is_64_bit = subtarget.is_64_bit;
let opt_for_size = subtarget.opt_for_size;
Self {
subtarget: subtarget.clone(),
is_64_bit,
opt_for_size,
enable_cmp_to_test: true,
enable_xor32_zeroing: is_64_bit,
enable_32bit_implicit_zero: is_64_bit,
enable_lea_to_mov: true,
enable_add_sub_to_lea: !opt_for_size,
enable_inc_dec_to_add_sub: true,
enable_rex_optimization: is_64_bit,
enable_segment_override_removal: true,
enable_prefix_removal: true,
enable_size_minimization: true,
prefer_and_over_mov: false,
aggressive: false,
stats: FixupStats::default(),
}
}
pub fn set_opt_for_size(&mut self, v: bool) {
self.opt_for_size = v;
}
pub fn set_enable_cmp_to_test(&mut self, v: bool) {
self.enable_cmp_to_test = v;
}
pub fn set_enable_xor32_zeroing(&mut self, v: bool) {
self.enable_xor32_zeroing = v;
}
pub fn set_enable_32bit_implicit_zero(&mut self, v: bool) {
self.enable_32bit_implicit_zero = v;
}
pub fn set_enable_lea_to_mov(&mut self, v: bool) {
self.enable_lea_to_mov = v;
}
pub fn set_enable_add_sub_to_lea(&mut self, v: bool) {
self.enable_add_sub_to_lea = v;
}
pub fn set_enable_inc_dec_to_add_sub(&mut self, v: bool) {
self.enable_inc_dec_to_add_sub = v;
}
pub fn set_enable_rex_optimization(&mut self, v: bool) {
self.enable_rex_optimization = v;
}
pub fn set_enable_segment_override_removal(&mut self, v: bool) {
self.enable_segment_override_removal = v;
}
pub fn set_enable_prefix_removal(&mut self, v: bool) {
self.enable_prefix_removal = v;
}
pub fn set_enable_size_minimization(&mut self, v: bool) {
self.enable_size_minimization = v;
}
pub fn set_prefer_and_over_mov(&mut self, v: bool) {
self.prefer_and_over_mov = v;
}
pub fn set_aggressive(&mut self, v: bool) {
self.aggressive = v;
}
}
impl X86InstrFixup {
pub fn try_cmp_to_test(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.enable_cmp_to_test {
return None;
}
if instr.opcode != opc(X86Opcode::CMP) {
return None;
}
if instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if !is_any_reg(dst) {
return None;
}
if !is_zero_imm(src) {
return None;
}
let mut test_instr = MachineInstr::new(opc(X86Opcode::TEST));
test_instr.operands.push(dst.clone());
test_instr.operands.push(dst.clone());
if self.is_64_bit {
if let Some(reg) = get_phys_reg(dst) {
if is_gpr64(reg) {
let reg32 = gpr64_to_gpr32(reg);
let mut test32 = MachineInstr::new(opc(X86Opcode::TEST));
test32.operands.push(MachineOperand::PhysReg(reg32));
test32.operands.push(MachineOperand::PhysReg(reg32));
return Some(test32);
}
}
}
Some(test_instr)
}
pub fn try_cmp_imm_size_reduction(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if instr.opcode != opc(X86Opcode::CMP) {
return None;
}
if instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if !is_any_reg(dst) {
return None;
}
if let Some(imm) = get_imm(src) {
if fits_i8(imm) && imm != 0 {
}
}
None }
}
impl X86InstrFixup {
pub fn try_xor32_zeroing(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.enable_xor32_zeroing || !self.is_64_bit {
return None;
}
if instr.opcode != opc(X86Opcode::XOR) {
return None;
}
if instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if !same_reg(dst, src) {
return None;
}
if let Some(reg) = get_phys_reg(dst) {
if is_gpr64(reg) {
let reg32 = gpr64_to_gpr32(reg);
let mut xor32 = MachineInstr::new(opc(X86Opcode::XOR));
xor32.operands.push(MachineOperand::PhysReg(reg32));
xor32.operands.push(MachineOperand::PhysReg(reg32));
return Some(xor32);
}
}
None
}
pub fn try_xor16_to_xor32(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.is_64_bit {
return None;
}
if instr.opcode != opc(X86Opcode::XOR) {
return None;
}
if instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if !same_reg(dst, src) {
return None;
}
if let Some(reg) = get_phys_reg(dst) {
if is_gpr16(reg) {
let reg32 = match reg {
AX => EAX,
CX => ECX,
DX => EDX,
BX => EBX,
SP => ESP,
BP => EBP,
SI => ESI,
DI => EDI,
_ => return None,
};
if self.aggressive {
let mut xor32 = MachineInstr::new(opc(X86Opcode::XOR));
xor32.operands.push(MachineOperand::PhysReg(reg32));
xor32.operands.push(MachineOperand::PhysReg(reg32));
return Some(xor32);
}
}
}
None
}
}
impl X86InstrFixup {
pub fn try_32bit_implicit_zero(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.enable_32bit_implicit_zero || !self.is_64_bit {
return None;
}
if instr.operands.is_empty() {
return None;
}
let is_add_sub = instr.opcode == opc(X86Opcode::ADD) || instr.opcode == opc(X86Opcode::SUB);
let is_logical = instr.opcode == opc(X86Opcode::AND)
|| instr.opcode == opc(X86Opcode::OR)
|| instr.opcode == opc(X86Opcode::XOR);
let is_mov = instr.opcode == opc(X86Opcode::MOV);
let is_shift = instr.opcode == opc(X86Opcode::SHL)
|| instr.opcode == opc(X86Opcode::SHR)
|| instr.opcode == opc(X86Opcode::SAR);
if !is_add_sub && !is_logical && !is_mov && !is_shift {
return None;
}
let dst = &instr.operands[0];
if let Some(dst_reg) = get_phys_reg(dst) {
if is_gpr64(dst_reg) {
let reg32 = gpr64_to_gpr32(dst_reg);
if is_mov && instr.operands.len() >= 2 {
if let Some(imm) = get_imm(&instr.operands[1]) {
if fits_u32(imm) {
let mut mov32 = MachineInstr::new(opc(X86Opcode::MOV));
mov32.operands.push(MachineOperand::PhysReg(reg32));
mov32.operands.push(MachineOperand::Imm(imm));
return Some(mov32);
}
}
}
if is_add_sub && instr.operands.len() >= 2 {
let src = &instr.operands[1];
if let Some(imm) = get_imm(src) {
if fits_i32(imm) {
let mut op32 = MachineInstr::new(instr.opcode);
op32.operands.push(MachineOperand::PhysReg(reg32));
if fits_i8(imm) {
op32.operands.push(MachineOperand::Imm(imm)); } else {
op32.operands.push(MachineOperand::Imm(imm)); }
return Some(op32);
}
}
if is_any_phys_reg(src) {
if let Some(src_reg) = get_phys_reg(src) {
if is_gpr64(src_reg) {
let src32 = gpr64_to_gpr32(src_reg);
if are_related_regs(dst_reg, src_reg) || self.aggressive {
let mut op32 = MachineInstr::new(instr.opcode);
op32.operands.push(MachineOperand::PhysReg(reg32));
op32.operands.push(MachineOperand::PhysReg(src32));
return Some(op32);
}
}
}
}
}
if is_logical && instr.operands.len() >= 2 {
let src = &instr.operands[1];
if is_any_phys_reg(src) {
if let Some(src_reg) = get_phys_reg(src) {
if are_related_regs(dst_reg, src_reg) {
let src32 = gpr64_to_gpr32(src_reg);
let mut op32 = MachineInstr::new(instr.opcode);
op32.operands.push(MachineOperand::PhysReg(reg32));
op32.operands.push(MachineOperand::PhysReg(src32));
return Some(op32);
}
}
}
}
}
}
None
}
pub fn try_eliminate_redundant_zero_extend(&self, _instr: &MachineInstr) -> Option<()> {
None }
}
impl X86InstrFixup {
pub fn try_lea_to_mov(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.enable_lea_to_mov {
return None;
}
if instr.opcode != opc(X86Opcode::LEA) {
return None;
}
if instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if is_any_phys_reg(src) && is_any_reg(dst) {
if same_reg(dst, src) {
return Some(MachineInstr::new(0));
} else {
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(dst.clone());
mov.operands.push(src.clone());
return Some(mov);
}
}
None
}
pub fn try_lea_split(&self, instr: &MachineInstr) -> Option<(MachineInstr, MachineInstr)> {
if instr.opcode != opc(X86Opcode::LEA) {
return None;
}
if instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
None
}
}
impl X86InstrFixup {
pub fn try_add_sub_to_lea(
&self,
first: &MachineInstr,
second: &MachineInstr,
) -> Option<MachineInstr> {
if !self.enable_add_sub_to_lea {
return None;
}
let is_add = first.opcode == opc(X86Opcode::ADD);
let is_sub = first.opcode == opc(X86Opcode::SUB);
if !is_add && !is_sub {
return None;
}
if first.operands.len() < 2 {
return None;
}
let first_dst = &first.operands[0];
let first_src = &first.operands[1];
if second.opcode != opc(X86Opcode::MOV) {
return None;
}
if second.operands.len() < 2 {
return None;
}
let second_dst = &second.operands[0];
let second_src = &second.operands[1];
if !same_reg(first_dst, second_src) {
return None;
}
let imm = get_imm(first_src)?;
if !fits_i32(if is_sub { -imm } else { imm }) {
return None;
}
let lea_disp: i64 = if is_sub { -imm } else { imm };
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(second_dst.clone());
lea.operands.push(first_dst.clone());
lea.operands.push(MachineOperand::Imm(lea_disp));
Some(lea)
}
pub fn try_fold_add_into_addressing(
&self,
_add_instr: &MachineInstr,
_user_instr: &MachineInstr,
) -> Option<MachineInstr> {
None }
}
impl X86InstrFixup {
pub fn try_inc_dec_to_add_sub(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.enable_inc_dec_to_add_sub {
return None;
}
if self.opt_for_size && !self.aggressive {
return None;
}
let is_inc = instr.opcode == opc(X86Opcode::INC);
let is_dec = instr.opcode == opc(X86Opcode::DEC);
if !is_inc && !is_dec {
return None;
}
if instr.operands.is_empty() {
return None;
}
let dst = &instr.operands[0];
if !is_any_reg(dst) {
return None;
}
let new_opcode = if is_inc {
opc(X86Opcode::ADD)
} else {
opc(X86Opcode::SUB)
};
if self.is_64_bit {
if let Some(reg) = get_phys_reg(dst) {
if is_gpr64(reg) {
let reg32 = gpr64_to_gpr32(reg);
let mut op = MachineInstr::new(new_opcode);
op.operands.push(MachineOperand::PhysReg(reg32));
op.operands.push(MachineOperand::Imm(1));
return Some(op);
}
}
}
let mut op = MachineInstr::new(new_opcode);
op.operands.push(dst.clone());
op.operands.push(MachineOperand::Imm(1));
Some(op)
}
pub fn is_cf_live_after(&self, bb: &MachineBasicBlock, instr_idx: usize) -> bool {
let n = bb.instructions.len();
let limit = (instr_idx + 1 + X86_FIXUP_MAX_SCAN).min(n);
for j in (instr_idx + 1)..limit {
let next = &bb.instructions[j];
let op = next.opcode;
if op == opc(X86Opcode::ADC)
|| op == opc(X86Opcode::SBB)
|| op == opc(X86Opcode::RCL)
|| op == opc(X86Opcode::RCR)
|| op == opc(X86Opcode::JB)
|| op == opc(X86Opcode::JAE)
|| op == opc(X86Opcode::JBE)
|| op == opc(X86Opcode::JA)
|| op == opc(X86Opcode::SETB)
|| op == opc(X86Opcode::SETAE)
|| op == opc(X86Opcode::SETBE)
|| op == opc(X86Opcode::SETA)
|| op == opc(X86Opcode::CMOVB)
|| op == opc(X86Opcode::CMOVAE)
|| op == opc(X86Opcode::CMOVBE)
|| op == opc(X86Opcode::CMOVA)
{
return true;
}
if self.instr_defines_flags(next) {
return false; }
if op == opc(X86Opcode::RET) || op == opc(X86Opcode::JMP) || op == opc(X86Opcode::CALL)
{
return false;
}
}
false }
fn instr_defines_flags(&self, instr: &MachineInstr) -> bool {
let op = instr.opcode;
op == opc(X86Opcode::ADD)
|| op == opc(X86Opcode::SUB)
|| op == opc(X86Opcode::ADC)
|| op == opc(X86Opcode::SBB)
|| op == opc(X86Opcode::AND)
|| op == opc(X86Opcode::OR)
|| op == opc(X86Opcode::XOR)
|| op == opc(X86Opcode::CMP)
|| op == opc(X86Opcode::TEST)
|| op == opc(X86Opcode::INC)
|| op == opc(X86Opcode::DEC)
|| op == opc(X86Opcode::NEG)
|| op == opc(X86Opcode::MUL)
|| op == opc(X86Opcode::IMUL)
|| op == opc(X86Opcode::SHL)
|| op == opc(X86Opcode::SHR)
|| op == opc(X86Opcode::SAR)
}
}
impl X86InstrFixup {
pub fn compute_rex_requirements(&self, instr: &MachineInstr) -> u8 {
if !self.is_64_bit || !self.enable_rex_optimization {
return 0;
}
let mut rex: u8 = X86_REX_PREFIX;
let mut needs_w = false;
let mut needs_r = false;
let mut needs_x = false;
let mut needs_b = false;
for op in &instr.operands {
if let Some(reg) = get_phys_reg(op) {
if is_gpr64(reg) {
needs_w = true;
}
if reg >= R8 && reg <= R15 {
needs_r = true;
}
if reg >= R8D && reg <= R15D {
needs_r = true;
}
if reg >= R8 && reg <= R15 || reg >= R8D && reg <= R15D {
needs_b = true;
}
}
}
if !needs_w && !needs_r && !needs_x && !needs_b {
return 0;
}
if needs_w {
rex |= 0x08;
} if needs_r {
rex |= 0x04;
} if needs_x {
rex |= 0x02;
} if needs_b {
rex |= 0x01;
}
rex
}
pub fn can_remove_rex(&self, instr: &MachineInstr) -> bool {
if !self.is_64_bit {
return false;
}
for op in &instr.operands {
if let Some(reg) = get_phys_reg(op) {
if is_gpr64(reg) || reg >= R8 && reg <= R15 || reg >= R8D && reg <= R15D {
return false; }
}
}
true }
pub fn should_add_rex(&self, instr: &MachineInstr) -> bool {
if !self.is_64_bit {
return false;
}
for op in &instr.operands {
if let Some(reg) = get_phys_reg(op) {
if reg >= R8 && reg <= R15 || reg >= R8D && reg <= R15D {
return true;
}
if reg >= 48 && reg <= 51 {
return true;
}
}
}
false
}
}
impl X86InstrFixup {
pub fn is_segment_override_redundant(&self, seg_prefix: u8) -> bool {
if !self.is_64_bit {
return false; }
match seg_prefix {
X86_PREFIX_CS | X86_PREFIX_DS | X86_PREFIX_ES | X86_PREFIX_SS => {
true
}
X86_PREFIX_FS | X86_PREFIX_GS => {
false
}
_ => false,
}
}
pub fn can_remove_segment_override(&self, _instr: &MachineInstr) -> Option<u8> {
if !self.enable_segment_override_removal {
return None;
}
None }
}
impl X86InstrFixup {
pub fn detect_redundant_prefixes(&self, _instr: &MachineInstr) -> Vec<u8> {
Vec::new()
}
pub fn can_remove_66_prefix(&self, instr: &MachineInstr) -> bool {
if !self.is_64_bit {
return false;
}
for op in &instr.operands {
if let Some(reg) = get_phys_reg(op) {
if is_gpr16(reg) {
return false; }
}
}
true
}
pub fn can_remove_67_prefix(&self, _instr: &MachineInstr) -> bool {
self.is_64_bit }
}
impl X86InstrFixup {
pub fn minimize_instruction_size(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.enable_size_minimization {
return None;
}
let original_size = self.estimate_instruction_size(instr);
if let Some(test) = self.try_cmp_to_test(instr) {
let new_size = self.estimate_instruction_size(&test);
if new_size < original_size {
return Some(test);
}
}
if let Some(xor32) = self.try_xor32_zeroing(instr) {
let new_size = self.estimate_instruction_size(&xor32);
if new_size < original_size {
return Some(xor32);
}
}
if !self.opt_for_size || self.aggressive {
if let Some(repl) = self.try_inc_dec_to_add_sub(instr) {
let new_size = self.estimate_instruction_size(&repl);
if new_size <= original_size || self.aggressive {
return Some(repl);
}
}
}
if let Some(repl) = self.try_32bit_implicit_zero(instr) {
let new_size = self.estimate_instruction_size(&repl);
if new_size < original_size {
return Some(repl);
}
}
if let Some(repl) = self.try_lea_to_mov(instr) {
if repl.opcode == 0 {
return Some(repl);
}
let new_size = self.estimate_instruction_size(&repl);
if new_size < original_size {
return Some(repl);
}
}
None
}
fn estimate_instruction_size(&self, instr: &MachineInstr) -> usize {
let op = instr.opcode;
let mut size: usize = 1;
if self.is_64_bit {
if self.should_add_rex(instr) || !self.can_remove_rex(instr) {
size += 1; }
}
if op == opc(X86Opcode::MOV) {
if instr.operands.len() >= 2 {
if matches!(instr.operands[1], MachineOperand::Imm(_)) {
if let Some(imm) = get_imm(&instr.operands[1]) {
if let Some(reg) = get_phys_reg(&instr.operands[0]) {
if is_gpr32(reg) || (is_gpr64(reg) && fits_i32(imm)) {
size += if fits_i8(imm) { 1 } else { 4 }; } else if is_gpr64(reg) {
size += 8; }
}
}
return size + 1; }
if is_any_reg(&instr.operands[0]) && is_any_reg(&instr.operands[1]) {
return size + 1; }
}
}
if op == opc(X86Opcode::XOR) || op == opc(X86Opcode::AND) || op == opc(X86Opcode::OR) {
if instr.operands.len() >= 2 && same_reg(&instr.operands[0], &instr.operands[1]) {
return size + 1; }
}
if op == opc(X86Opcode::CMP) {
if instr.operands.len() >= 2 && is_zero_imm(&instr.operands[1]) {
return size + 2; }
return size + 2; }
if op == opc(X86Opcode::TEST) {
if instr.operands.len() >= 2 && same_reg(&instr.operands[0], &instr.operands[1]) {
return size + 1; }
}
if op == opc(X86Opcode::ADD) || op == opc(X86Opcode::SUB) {
if instr.operands.len() >= 2 {
if let Some(imm) = get_imm(&instr.operands[1]) {
if fits_i8(imm) {
size += 1;
}
else {
size += 4;
} }
return size + 1;
}
}
if op == opc(X86Opcode::LEA) {
return size + 3; }
if op == opc(X86Opcode::INC) || op == opc(X86Opcode::DEC) {
return size + 1;
}
if op == opc(X86Opcode::PUSH) || op == opc(X86Opcode::POP) {
if instr.operands.len() >= 1 && is_any_phys_reg(&instr.operands[0]) {
return 1; }
return 2;
}
if op == opc(X86Opcode::CALL) {
return 5;
}
if op == opc(X86Opcode::RET) {
return 1;
}
if op == opc(X86Opcode::JMP) {
return 2;
}
size + 4 }
}
impl X86InstrFixup {
pub fn fixup_block(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
if n == 0 {
return;
}
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let mut skip: usize = 0;
let mut i = 0;
while i < n {
if skip > 0 {
skip -= 1;
new_instrs.push(bb.instructions[i].clone());
i += 1;
continue;
}
let instr = &bb.instructions[i];
let mut fixed = false;
if let Some(repl) = self.try_cmp_to_test(instr) {
let old_size = self.estimate_instruction_size(instr) as isize;
let new_size = self.estimate_instruction_size(&repl) as isize;
new_instrs.push(repl);
self.stats.cmp_to_test += 1;
self.stats.total_fixups += 1;
if new_size < old_size {
self.stats.size_saved_bytes += (old_size - new_size) as usize;
}
fixed = true;
}
if !fixed {
if let Some(repl) = self.try_xor32_zeroing(instr) {
let old_size = self.estimate_instruction_size(instr) as isize;
let new_size = self.estimate_instruction_size(&repl) as isize;
new_instrs.push(repl);
self.stats.xor32_zeroing += 1;
self.stats.total_fixups += 1;
if new_size < old_size {
self.stats.size_saved_bytes += (old_size - new_size) as usize;
}
fixed = true;
}
}
if !fixed {
if let Some(repl) = self.try_32bit_implicit_zero(instr) {
let old_size = self.estimate_instruction_size(instr) as isize;
let new_size = self.estimate_instruction_size(&repl) as isize;
new_instrs.push(repl);
self.stats.gpr32_implicit_zero += 1;
self.stats.total_fixups += 1;
if new_size < old_size {
self.stats.size_saved_bytes += (old_size - new_size) as usize;
}
fixed = true;
}
}
if !fixed {
if let Some(repl) = self.try_lea_to_mov(instr) {
if repl.opcode == 0 {
self.stats.lea_to_mov += 1;
self.stats.total_fixups += 1;
self.stats.size_saved_bytes += self.estimate_instruction_size(instr);
fixed = true;
} else {
let old_size = self.estimate_instruction_size(instr) as isize;
let new_size = self.estimate_instruction_size(&repl) as isize;
new_instrs.push(repl);
self.stats.lea_to_mov += 1;
self.stats.total_fixups += 1;
if new_size < old_size {
self.stats.size_saved_bytes += (old_size - new_size) as usize;
}
fixed = true;
}
}
}
if !fixed && i + 1 < n {
if let Some(repl) = self.try_add_sub_to_lea(instr, &bb.instructions[i + 1]) {
let old_size = self.estimate_instruction_size(instr) as isize
+ self.estimate_instruction_size(&bb.instructions[i + 1]) as isize;
let new_size = self.estimate_instruction_size(&repl) as isize;
new_instrs.push(repl);
skip = 1;
self.stats.add_sub_to_lea += 1;
self.stats.total_fixups += 1;
if new_size < old_size {
self.stats.size_saved_bytes += (old_size - new_size) as usize;
}
fixed = true;
}
}
if !fixed {
if (instr.opcode == opc(X86Opcode::INC) || instr.opcode == opc(X86Opcode::DEC))
&& !self.is_cf_live_after(bb, i)
{
if let Some(repl) = self.try_inc_dec_to_add_sub(instr) {
new_instrs.push(repl);
self.stats.inc_dec_to_add_sub += 1;
self.stats.total_fixups += 1;
fixed = true;
}
}
}
if !fixed {
if let Some(repl) = self.minimize_instruction_size(instr) {
let old_size = self.estimate_instruction_size(instr) as isize;
let new_size = self.estimate_instruction_size(&repl) as isize;
if repl.opcode == 0 {
self.stats.total_fixups += 1;
self.stats.size_saved_bytes += old_size as usize;
} else {
new_instrs.push(repl);
self.stats.total_fixups += 1;
if new_size < old_size {
self.stats.size_saved_bytes += (old_size - new_size) as usize;
}
}
fixed = true;
}
}
if !fixed {
new_instrs.push(instr.clone());
}
i += 1;
}
bb.instructions = new_instrs;
}
pub fn fixup_function(&mut self, mf: &mut MachineFunction) {
self.stats = FixupStats::default();
for block in &mut mf.blocks {
self.fixup_block(block);
}
}
pub fn fixup_for_size(&mut self, mf: &mut MachineFunction) {
let saved_inc_dec = self.enable_inc_dec_to_add_sub;
let saved_add_sub_lea = self.enable_add_sub_to_lea;
self.enable_inc_dec_to_add_sub = false; self.enable_add_sub_to_lea = false; self.fixup_function(mf);
self.enable_inc_dec_to_add_sub = saved_inc_dec;
self.enable_add_sub_to_lea = saved_add_sub_lea;
}
pub fn fixup_for_speed(&mut self, mf: &mut MachineFunction) {
self.fixup_function(mf);
}
}
impl X86InstrFixup {
pub fn count_rex_prefixes(&self, instr: &MachineInstr) -> usize {
let rex = self.compute_rex_requirements(instr);
if rex == 0 {
0
} else {
1
}
}
pub fn count_rex_savings(&self, instr: &MachineInstr) -> usize {
if !self.is_64_bit {
return 0;
}
let mut has_64bit_operand = false;
for op in &instr.operands {
if let Some(reg) = get_phys_reg(op) {
if is_gpr64(reg) {
has_64bit_operand = true;
}
}
}
if has_64bit_operand {
0
} else {
1
}
}
pub fn can_use_imm8(&self, instr: &MachineInstr) -> bool {
if instr.operands.len() < 2 {
return false;
}
if let Some(imm) = get_imm(&instr.operands[1]) {
return fits_i8(imm);
}
false
}
pub fn imm8_savings(&self, instr: &MachineInstr) -> usize {
if self.can_use_imm8(instr) {
3 } else {
0
}
}
}
impl X86InstrFixup {
pub fn compute_global_liveness(&self, mf: &MachineFunction) -> HashMap<String, HashSet<u32>> {
let mut live_out: HashMap<String, HashSet<u32>> = HashMap::new();
for block in &mf.blocks {
live_out.insert(block.name.clone(), HashSet::new());
}
let mut changed = true;
let mut iter = 0;
while changed && iter < 10 {
changed = false;
iter += 1;
for block in &mf.blocks {
let mut new_live_out: HashSet<u32> = HashSet::new();
for &succ_id in &block.successors {
if let Some(succ_block) = mf.blocks.iter().find(|b| b.id == succ_id) {
if let Some(succ_live) = live_out.get(&succ_block.name) {
new_live_out.extend(succ_live);
}
}
}
let old = live_out.get(&block.name).cloned().unwrap_or_default();
if new_live_out != old {
live_out.insert(block.name.clone(), new_live_out);
changed = true;
}
}
}
live_out
}
pub fn is_cf_live_out_of_block(&self, mf: &MachineFunction, block_name: &str) -> bool {
let liveness = self.compute_global_liveness(mf);
if let Some(live) = liveness.get(block_name) {
live.contains(&FLAGS)
} else {
false
}
}
}
impl X86InstrFixup {
pub fn convert_dead_and_to_test(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for i in 0..n {
let instr = &bb.instructions[i];
if instr.opcode == opc(X86Opcode::AND)
&& instr.operands.len() >= 2
&& same_reg(&instr.operands[0], &instr.operands[1])
{
let dst_reg = get_any_reg(&instr.operands[0]);
let mut result_dead = false;
if let Some(dst) = dst_reg {
let mut overwritten_before_use = false;
let limit = (i + 1 + X86_FIXUP_MAX_SCAN).min(n);
for j in (i + 1)..limit {
let next = &bb.instructions[j];
if next.operands.iter().any(|op| get_any_reg(op) == Some(dst)) {
break; }
if next.def == Some(dst)
|| next.operands.first().and_then(|o| get_any_reg(o)) == Some(dst)
{
overwritten_before_use = true;
break;
}
}
if overwritten_before_use || i == n - 1 {
result_dead = true;
}
}
if result_dead {
let mut test = MachineInstr::new(opc(X86Opcode::TEST));
test.operands.push(instr.operands[0].clone());
test.operands.push(instr.operands[1].clone());
new_instrs.push(test);
self.stats.cmp_to_test += 1; self.stats.total_fixups += 1;
continue;
}
}
new_instrs.push(instr.clone());
}
bb.instructions = new_instrs;
}
pub fn convert_dead_or_to_test(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for i in 0..n {
let instr = &bb.instructions[i];
if instr.opcode == opc(X86Opcode::OR)
&& instr.operands.len() >= 2
&& same_reg(&instr.operands[0], &instr.operands[1])
{
let dst_reg = get_any_reg(&instr.operands[0]);
let mut result_dead = false;
if let Some(dst) = dst_reg {
let limit = (i + 1 + X86_FIXUP_MAX_SCAN).min(n);
for j in (i + 1)..limit {
let next = &bb.instructions[j];
if next.operands.iter().any(|op| get_any_reg(op) == Some(dst)) {
break;
}
if next.def == Some(dst)
|| next.operands.first().and_then(|o| get_any_reg(o)) == Some(dst)
{
result_dead = true;
break;
}
}
if i == n - 1 {
result_dead = true;
}
}
if result_dead {
let mut test = MachineInstr::new(opc(X86Opcode::TEST));
test.operands.push(instr.operands[0].clone());
test.operands.push(instr.operands[1].clone());
new_instrs.push(test);
self.stats.cmp_to_test += 1;
self.stats.total_fixups += 1;
continue;
}
}
new_instrs.push(instr.clone());
}
bb.instructions = new_instrs;
}
pub fn run_specialized_fixups(&mut self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
self.convert_dead_and_to_test(block);
self.convert_dead_or_to_test(block);
}
}
}
impl X86InstrFixup {
pub fn fixup_report(&self) -> String {
format!(
"=== X86 Instruction Fixup Report ===\n Target: {}\n 64-bit: {}\n Opt size: {}\n CMP→TEST: {}\n XOR32: {}\n GPR32 ext: {}\n LEA→MOV: {}\n ADD/SUB→LEA: {}\n INC/DEC: {}\n REX removed: {}\n Seg override removed: {}\n Prefix removed: {}\n Size saved: {}B\n\n {}\n",
self.subtarget.cpu, self.is_64_bit, self.opt_for_size,
self.enable_cmp_to_test, self.enable_xor32_zeroing,
self.enable_32bit_implicit_zero, self.enable_lea_to_mov,
self.enable_add_sub_to_lea, self.enable_inc_dec_to_add_sub,
self.stats.rex_removed, self.stats.segment_override_removed,
self.stats.prefix_removed, self.stats.size_saved_bytes,
self.stats.summary()
)
}
pub fn reset_stats(&mut self) {
self.stats = FixupStats::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen::{MachineBasicBlock, MachineFunction};
fn make_sub() -> X86Subtarget {
X86Subtarget::new("x86_64-unknown-linux-gnu", "skylake", "+sse2,+avx2")
}
fn make_bb(name: &str) -> MachineBasicBlock {
MachineBasicBlock {
name: name.to_string(),
instructions: Vec::new(),
successors: Vec::new(),
}
}
#[test]
fn test_creation() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
assert!(fix.is_64_bit);
assert!(fix.enable_cmp_to_test);
assert!(fix.enable_xor32_zeroing);
assert_eq!(fix.stats.total_fixups, 0);
}
#[test]
fn test_cmp_to_test_zero() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::CMP));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(0));
let result = fix.try_cmp_to_test(&instr);
assert!(result.is_some());
let test = result.unwrap();
assert_eq!(test.opcode, opc(X86Opcode::TEST));
assert!(matches!(&test.operands[0], MachineOperand::PhysReg(r) if *r == EAX));
}
#[test]
fn test_cmp_nonzero_not_converted() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::CMP));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(5));
let result = fix.try_cmp_to_test(&instr);
assert!(result.is_none());
}
#[test]
fn test_xor32_zeroing() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::XOR));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RAX));
let result = fix.try_xor32_zeroing(&instr);
assert!(result.is_some());
let xor32 = result.unwrap();
assert!(matches!(&xor32.operands[0], MachineOperand::PhysReg(r) if *r == EAX));
}
#[test]
fn test_xor32_different_regs_not_converted() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::XOR));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RBX));
let result = fix.try_xor32_zeroing(&instr);
assert!(result.is_none());
}
#[test]
fn test_lea_to_mov_no_arithmetic() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::LEA));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RBX));
let result = fix.try_lea_to_mov(&instr);
assert!(result.is_some());
let mov = result.unwrap();
assert_eq!(mov.opcode, opc(X86Opcode::MOV));
}
#[test]
fn test_lea_same_reg_to_nop() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::LEA));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RAX));
let result = fix.try_lea_to_mov(&instr);
assert!(result.is_some());
let nop = result.unwrap();
assert_eq!(nop.opcode, 0); }
#[test]
fn test_inc_to_add() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::INC));
instr.operands.push(MachineOperand::PhysReg(RAX));
let result = fix.try_inc_dec_to_add_sub(&instr);
assert!(result.is_some());
let add = result.unwrap();
assert_eq!(add.opcode, opc(X86Opcode::ADD));
assert!(matches!(&add.operands[0], MachineOperand::PhysReg(r) if *r == EAX));
assert!(matches!(&add.operands[1], MachineOperand::Imm(1)));
}
#[test]
fn test_dec_to_sub() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::DEC));
instr.operands.push(MachineOperand::PhysReg(RBX));
let result = fix.try_inc_dec_to_add_sub(&instr);
assert!(result.is_some());
let sub_instr = result.unwrap();
assert_eq!(sub_instr.opcode, opc(X86Opcode::SUB));
}
#[test]
fn test_is_cf_live_no_consumer() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut bb = make_bb("test");
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::PhysReg(RAX));
add.operands.push(MachineOperand::Imm(1));
bb.instructions.push(add);
assert!(!fix.is_cf_live_after(&bb, 0));
}
#[test]
fn test_is_cf_live_with_adc() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut bb = make_bb("test");
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::PhysReg(RAX));
add.operands.push(MachineOperand::Imm(1));
bb.instructions.push(add);
let mut adc = MachineInstr::new(opc(X86Opcode::ADC));
adc.operands.push(MachineOperand::PhysReg(RBX));
adc.operands.push(MachineOperand::Imm(0));
bb.instructions.push(adc);
assert!(fix.is_cf_live_after(&bb, 0));
}
#[test]
fn test_compute_rex_requirements() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RBX));
let rex = fix.compute_rex_requirements(&instr);
assert!(rex & 0x08 != 0); }
#[test]
fn test_no_rex_for_32bit() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(EAX));
instr.operands.push(MachineOperand::PhysReg(ECX));
let rex = fix.compute_rex_requirements(&instr);
assert_eq!(rex, 0); }
#[test]
fn test_size_estimate_cmp_vs_test() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut cmp = MachineInstr::new(opc(X86Opcode::CMP));
cmp.operands.push(MachineOperand::PhysReg(EAX));
cmp.operands.push(MachineOperand::Imm(0));
let cmp_size = fix.estimate_instruction_size(&cmp);
let mut test_instr = MachineInstr::new(opc(X86Opcode::TEST));
test_instr.operands.push(MachineOperand::PhysReg(EAX));
test_instr.operands.push(MachineOperand::PhysReg(EAX));
let test_size = fix.estimate_instruction_size(&test_instr);
assert!(test_size < cmp_size); }
#[test]
fn test_size_estimate_xor_64_vs_32() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut xor64 = MachineInstr::new(opc(X86Opcode::XOR));
xor64.operands.push(MachineOperand::PhysReg(RAX));
xor64.operands.push(MachineOperand::PhysReg(RAX));
let size64 = fix.estimate_instruction_size(&xor64);
let mut xor32 = MachineInstr::new(opc(X86Opcode::XOR));
xor32.operands.push(MachineOperand::PhysReg(EAX));
xor32.operands.push(MachineOperand::PhysReg(EAX));
let size32 = fix.estimate_instruction_size(&xor32);
assert!(size32 < size64); }
#[test]
fn test_add_sub_to_lea() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::PhysReg(RBX));
add.operands.push(MachineOperand::Imm(8));
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::PhysReg(RAX));
mov.operands.push(MachineOperand::PhysReg(RBX));
let result = fix.try_add_sub_to_lea(&add, &mov);
assert!(result.is_some());
let lea = result.unwrap();
assert_eq!(lea.opcode, opc(X86Opcode::LEA));
}
#[test]
fn test_32bit_implicit_zero_mov_imm() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::PhysReg(RAX));
mov.operands.push(MachineOperand::Imm(0x42));
let result = fix.try_32bit_implicit_zero(&mov);
assert!(result.is_some());
let mov32 = result.unwrap();
assert!(matches!(&mov32.operands[0], MachineOperand::PhysReg(r) if *r == EAX));
}
#[test]
fn test_stats_merge() {
let mut s1 = FixupStats::default();
s1.cmp_to_test = 5;
s1.total_fixups = 5;
let mut s2 = FixupStats::default();
s2.xor32_zeroing = 3;
s2.total_fixups = 3;
s1.merge(&s2);
assert_eq!(s1.cmp_to_test, 5);
assert_eq!(s1.xor32_zeroing, 3);
assert_eq!(s1.total_fixups, 8);
}
#[test]
fn test_fixup_report() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let report = fix.fixup_report();
assert!(report.contains("skylake"));
assert!(report.contains("X86 Instruction Fixup"));
}
#[test]
fn test_instr_defines_flags() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
assert!(fix.instr_defines_flags(&MachineInstr::new(opc(X86Opcode::ADD))));
assert!(fix.instr_defines_flags(&MachineInstr::new(opc(X86Opcode::CMP))));
assert!(fix.instr_defines_flags(&MachineInstr::new(opc(X86Opcode::TEST))));
assert!(!fix.instr_defines_flags(&MachineInstr::new(opc(X86Opcode::MOV))));
}
#[test]
fn test_can_use_imm8() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::ADD));
instr.operands.push(MachineOperand::PhysReg(EAX));
instr.operands.push(MachineOperand::Imm(5));
assert!(fix.can_use_imm8(&instr));
let mut instr2 = MachineInstr::new(opc(X86Opcode::ADD));
instr2.operands.push(MachineOperand::PhysReg(EAX));
instr2.operands.push(MachineOperand::Imm(1000));
assert!(!fix.can_use_imm8(&instr2));
}
#[test]
fn test_fixup_block_cmp_to_test() {
let sub = make_sub();
let mut fix = X86InstrFixup::new(&sub);
let mut bb = make_bb("test");
let mut cmp = MachineInstr::new(opc(X86Opcode::CMP));
cmp.operands.push(MachineOperand::PhysReg(RAX));
cmp.operands.push(MachineOperand::Imm(0));
bb.instructions.push(cmp);
fix.fixup_block(&mut bb);
assert_eq!(fix.stats.cmp_to_test, 1);
assert!(bb.instructions[0].opcode == opc(X86Opcode::TEST));
}
#[test]
fn test_fixup_block_lea_to_mov() {
let sub = make_sub();
let mut fix = X86InstrFixup::new(&sub);
let mut bb = make_bb("test");
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(MachineOperand::PhysReg(RAX));
lea.operands.push(MachineOperand::PhysReg(RBX));
bb.instructions.push(lea);
fix.fixup_block(&mut bb);
assert_eq!(fix.stats.lea_to_mov, 1);
}
#[test]
fn test_can_remove_rex() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(EAX));
instr.operands.push(MachineOperand::PhysReg(ECX));
assert!(fix.can_remove_rex(&instr));
let mut instr64 = MachineInstr::new(opc(X86Opcode::MOV));
instr64.operands.push(MachineOperand::PhysReg(RAX));
instr64.operands.push(MachineOperand::PhysReg(RCX));
assert!(!fix.can_remove_rex(&instr64));
}
#[test]
fn test_size_minimization_prefers_smaller() {
let sub = make_sub();
let fix = X86InstrFixup::new(&sub);
let mut cmp = MachineInstr::new(opc(X86Opcode::CMP));
cmp.operands.push(MachineOperand::PhysReg(EAX));
cmp.operands.push(MachineOperand::Imm(0));
let result = fix.minimize_instruction_size(&cmp);
assert!(result.is_some());
let optimized = result.unwrap();
assert_eq!(optimized.opcode, opc(X86Opcode::TEST));
}
}