#![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::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
pub const X86_MOV_FOLD_MAX_SCAN: usize = 16;
pub const X86_MOV_DEAD_MAX_SCAN: usize = 32;
pub const X86_PUSH_POP_SIZE_THRESHOLD: usize = 3;
pub const X86_ZEROING_IMM: i64 = 0;
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 AH: PhysReg = 44;
const CH: PhysReg = 45;
const DH: PhysReg = 46;
const BH: PhysReg = 47;
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_reg(op: &MachineOperand) -> Option<VirtReg> {
match op {
MachineOperand::Reg(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 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 <= BH
}
fn gpr32_to_gpr64(reg32: PhysReg) -> Option<PhysReg> {
match reg32 {
EAX => Some(RAX),
ECX => Some(RCX),
EDX => Some(RDX),
EBX => Some(RBX),
ESP => Some(RSP),
EBP => Some(RBP),
ESI => Some(RSI),
EDI => Some(RDI),
R8D => Some(R8),
R9D => Some(R9),
R10D => Some(R10),
R11D => Some(R11),
R12D => Some(R12),
R13D => Some(R13),
R14D => Some(R14),
R15D => Some(R15),
_ => None,
}
}
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 reg_base_idx(reg: PhysReg) -> usize {
match reg {
RAX | EAX | AX | AL | AH => 0,
RCX | ECX | CX | CL | CH => 1,
RDX | EDX | DX | DL | DH => 2,
RBX | EBX | BX | BL | BH => 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,
_ => reg as usize,
}
}
fn are_related_regs(a: PhysReg, b: PhysReg) -> bool {
reg_base_idx(a) == reg_base_idx(b)
}
fn is_subregister(src: PhysReg, dst: PhysReg) -> bool {
are_related_regs(src, dst) && reg_bits(src) < reg_bits(dst)
}
fn reg_bits(reg: PhysReg) -> u32 {
if reg <= R15 {
64
} else if reg <= R15D {
32
} else if reg <= DI {
16
} else {
8
}
}
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",
AH => "ah",
CH => "ch",
DH => "dh",
BH => "bh",
_ => "?",
}
}
#[derive(Debug, Clone)]
pub struct X86MOVOptimizer {
pub subtarget: X86Subtarget,
pub is_64_bit: bool,
pub prefer_32bit_zero_ext: bool,
pub enable_lea_conversion: bool,
pub enable_push_pop_conversion: bool,
pub enable_dead_mov_elim: bool,
pub enable_xor_zeroing: bool,
pub enable_mov_folding: bool,
pub prefer_and_over_mov: bool,
pub aggressive: bool,
pub stats: MOVOptimizerStats,
}
#[derive(Debug, Clone, Default)]
pub struct MOVOptimizerStats {
pub mov_same_reg_eliminated: usize,
pub mov_self_nop: usize,
pub mov_zero_to_xor: usize,
pub mov_zero_ext_to_movzx: usize,
pub mov_imm32_64bit_zero_ext: usize,
pub mov_sub_super_eliminated: usize,
pub mov_dead_eliminated: usize,
pub mov_folded_into_mem_op: usize,
pub mov_to_lea: usize,
pub mov_to_push_pop: usize,
pub mov_to_and: usize,
pub movsxd_simplified: usize,
pub mov_aliasing_fixed: usize,
pub mov_between_related_elim: usize,
pub total_movs_eliminated: usize,
}
impl MOVOptimizerStats {
pub fn merge(&mut self, other: &MOVOptimizerStats) {
self.mov_same_reg_eliminated += other.mov_same_reg_eliminated;
self.mov_self_nop += other.mov_self_nop;
self.mov_zero_to_xor += other.mov_zero_to_xor;
self.mov_zero_ext_to_movzx += other.mov_zero_ext_to_movzx;
self.mov_imm32_64bit_zero_ext += other.mov_imm32_64bit_zero_ext;
self.mov_sub_super_eliminated += other.mov_sub_super_eliminated;
self.mov_dead_eliminated += other.mov_dead_eliminated;
self.mov_folded_into_mem_op += other.mov_folded_into_mem_op;
self.mov_to_lea += other.mov_to_lea;
self.mov_to_push_pop += other.mov_to_push_pop;
self.mov_to_and += other.mov_to_and;
self.movsxd_simplified += other.movsxd_simplified;
self.mov_aliasing_fixed += other.mov_aliasing_fixed;
self.mov_between_related_elim += other.mov_between_related_elim;
self.total_movs_eliminated += other.total_movs_eliminated;
}
pub fn summary(&self) -> String {
format!("X86MOVOpt: total={}, same_reg={}, zero_to_xor={}, dead={}, folded_mem={}, to_lea={}, to_pushpop={}, sub_super={}",
self.total_movs_eliminated, self.mov_same_reg_eliminated, self.mov_zero_to_xor, self.mov_dead_eliminated,
self.mov_folded_into_mem_op, self.mov_to_lea, self.mov_to_push_pop, self.mov_sub_super_eliminated)
}
pub fn made_progress(&self) -> bool {
self.total_movs_eliminated > 0
}
}
#[derive(Debug, Clone)]
pub struct BlockLiveness {
pub live_in: HashSet<u32>,
pub live_out: HashSet<u32>,
}
impl X86MOVOptimizer {
pub fn new(subtarget: &X86Subtarget) -> Self {
let is_64_bit = subtarget.is_64_bit;
Self {
subtarget: subtarget.clone(),
is_64_bit,
prefer_32bit_zero_ext: is_64_bit,
enable_lea_conversion: true,
enable_push_pop_conversion: true,
enable_dead_mov_elim: true,
enable_xor_zeroing: true,
enable_mov_folding: true,
prefer_and_over_mov: false,
aggressive: false,
stats: MOVOptimizerStats::default(),
}
}
pub fn set_prefer_32bit_zero_ext(&mut self, e: bool) {
self.prefer_32bit_zero_ext = e;
}
pub fn set_enable_lea_conversion(&mut self, e: bool) {
self.enable_lea_conversion = e;
}
pub fn set_enable_push_pop_conversion(&mut self, e: bool) {
self.enable_push_pop_conversion = e;
}
pub fn set_enable_dead_mov_elim(&mut self, e: bool) {
self.enable_dead_mov_elim = e;
}
pub fn set_enable_xor_zeroing(&mut self, e: bool) {
self.enable_xor_zeroing = e;
}
pub fn set_enable_mov_folding(&mut self, e: bool) {
self.enable_mov_folding = e;
}
pub fn set_prefer_and_over_mov(&mut self, e: bool) {
self.prefer_and_over_mov = e;
}
pub fn set_aggressive(&mut self, e: bool) {
self.aggressive = e;
}
pub fn is_mov_instruction(&self, instr: &MachineInstr) -> bool {
let op = instr.opcode;
op == opc(X86Opcode::MOV)
|| op == opc(X86Opcode::MOVSX)
|| op == opc(X86Opcode::MOVZX)
|| op == opc(X86Opcode::MOVSXD)
|| op == opc(X86Opcode::MOVABS)
}
fn is_mov_reg_reg(&self, instr: &MachineInstr) -> bool {
instr.opcode == opc(X86Opcode::MOV)
&& instr.operands.len() >= 2
&& is_any_reg(&instr.operands[0])
&& is_any_reg(&instr.operands[1])
}
fn is_mov_reg_imm(&self, instr: &MachineInstr) -> bool {
instr.opcode == opc(X86Opcode::MOV)
&& instr.operands.len() >= 2
&& is_any_reg(&instr.operands[0])
&& matches!(instr.operands[1], MachineOperand::Imm(_))
}
fn try_eliminate_mov_same_reg(&self, instr: &MachineInstr) -> Option<()> {
if !self.is_mov_reg_reg(instr) {
return None;
}
if same_reg(&instr.operands[0], &instr.operands[1]) {
Some(())
} else {
None
}
}
fn try_eliminate_sub_super_mov(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.is_mov_reg_reg(instr) {
return None;
}
let dst_reg = get_phys_reg(&instr.operands[0]);
let src_reg = get_phys_reg(&instr.operands[1]);
match (dst_reg, src_reg) {
(Some(d), Some(s)) if d == s => Some(MachineInstr::new(0)),
(Some(d), Some(s))
if is_subregister(d, s) && reg_bits(d) == 32 && reg_bits(s) == 64 =>
{
Some(MachineInstr::new(0))
}
_ => None,
}
}
fn try_mov_zero_to_xor(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.enable_xor_zeroing || !self.is_mov_reg_imm(instr) {
return None;
}
let imm = get_imm(&instr.operands[1])?;
if imm != 0 {
return None;
}
let dst_reg = get_phys_reg(&instr.operands[0]);
match dst_reg {
Some(reg) if is_gpr64(reg) && self.is_64_bit => {
let reg32 = gpr64_to_gpr32(reg);
let mut xor_instr = MachineInstr::new(opc(X86Opcode::XOR));
xor_instr.operands.push(MachineOperand::PhysReg(reg32));
xor_instr.operands.push(MachineOperand::PhysReg(reg32));
Some(xor_instr)
}
Some(reg) if is_gpr32(reg) => {
let mut xor_instr = MachineInstr::new(opc(X86Opcode::XOR));
xor_instr.operands.push(MachineOperand::PhysReg(reg));
xor_instr.operands.push(MachineOperand::PhysReg(reg));
Some(xor_instr)
}
Some(reg) if is_gpr16(reg) => {
let mut xor_instr = MachineInstr::new(opc(X86Opcode::XOR));
xor_instr.operands.push(MachineOperand::PhysReg(reg));
xor_instr.operands.push(MachineOperand::PhysReg(reg));
Some(xor_instr)
}
_ => None,
}
}
fn try_mov_imm32_to_64bit(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if !self.prefer_32bit_zero_ext || !self.is_mov_reg_imm(instr) {
return None;
}
let dst_reg = get_phys_reg(&instr.operands[0])?;
if !is_gpr64(dst_reg) {
return None;
}
let imm = get_imm(&instr.operands[1])?;
if !fits_u32(imm) {
return None;
}
let reg32 = gpr64_to_gpr32(dst_reg);
let mut mov_instr = MachineInstr::new(opc(X86Opcode::MOV));
mov_instr.operands.push(MachineOperand::PhysReg(reg32));
mov_instr.operands.push(MachineOperand::Imm(imm));
Some(mov_instr)
}
fn try_mov_to_movzx(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if instr.opcode != opc(X86Opcode::MOV) || instr.operands.len() < 2 {
return None;
}
let dst_reg = get_phys_reg(&instr.operands[0])?;
let src_reg = get_phys_reg(&instr.operands[1])?;
if is_gpr64(dst_reg) && is_gpr8(src_reg) {
let mut movzx = MachineInstr::new(opc(X86Opcode::MOVZX));
movzx.operands.push(MachineOperand::PhysReg(dst_reg));
movzx.operands.push(MachineOperand::PhysReg(src_reg));
return Some(movzx);
}
None
}
fn try_mov_to_lea(&self, instr: &MachineInstr) -> Option<MachineInstr> {
if instr.opcode != opc(X86Opcode::MOV) || instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if is_any_reg(dst) && is_any_phys_reg(src) {
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(dst.clone());
lea.operands.push(src.clone());
return Some(lea);
}
None
}
fn is_foldable_alu_op(&self, instr: &MachineInstr) -> bool {
let op = instr.opcode;
op == opc(X86Opcode::ADD)
|| op == opc(X86Opcode::SUB)
|| op == opc(X86Opcode::AND)
|| op == opc(X86Opcode::OR)
|| op == opc(X86Opcode::XOR)
|| op == opc(X86Opcode::CMP)
|| op == opc(X86Opcode::TEST)
|| op == opc(X86Opcode::ADC)
|| op == opc(X86Opcode::SBB)
|| op == opc(X86Opcode::IMUL)
|| op == opc(X86Opcode::MOV)
}
fn try_fold_mov_into_next(
&self,
mov_instr: &MachineInstr,
next_instr: &MachineInstr,
) -> Option<(MachineInstr, usize)> {
if !self.is_mov_instruction(mov_instr) || mov_instr.operands.len() < 2 {
return None;
}
let dst = &mov_instr.operands[0];
let dst_reg = get_any_reg(dst)?;
if !self.is_foldable_alu_op(next_instr) || next_instr.operands.len() < 2 {
return None;
}
let uses_dst = next_instr.operands.iter().any(|op| match op {
MachineOperand::Reg(r) => *r == dst_reg,
MachineOperand::PhysReg(r) => *r == dst_reg,
_ => false,
});
if !uses_dst {
return None;
}
let mut folded = next_instr.clone();
for op in &mut folded.operands {
if matches!(op, MachineOperand::Reg(r) if *r == dst_reg) {
*op = mov_instr.operands[1].clone();
}
if matches!(op, MachineOperand::PhysReg(r) if *r == dst_reg) {
*op = mov_instr.operands[1].clone();
}
}
Some((folded, 1))
}
fn fold_movs_across_blocks(&mut self, mf: &mut MachineFunction) {
let mut changed = true;
let mut iter = 0;
while changed && iter < 3 {
changed = false;
let prev = self.stats.mov_folded_into_mem_op;
for block in &mut mf.blocks {
self.optimize_block(block);
}
if self.stats.mov_folded_into_mem_op > prev {
changed = true;
}
iter += 1;
}
}
fn is_sub_rsp_imm(&self, instr: &MachineInstr, expected: i64) -> bool {
instr.opcode == opc(X86Opcode::SUB)
&& instr.operands.len() >= 2
&& is_phys_reg(&instr.operands[0], RSP)
&& matches!(&instr.operands[1], MachineOperand::Imm(v) if *v == expected)
}
fn is_add_rsp_imm(&self, instr: &MachineInstr, expected: i64) -> bool {
instr.opcode == opc(X86Opcode::ADD)
&& instr.operands.len() >= 2
&& is_phys_reg(&instr.operands[0], RSP)
&& matches!(&instr.operands[1], MachineOperand::Imm(v) if *v == expected)
}
fn is_mov_to_rsp_offset(&self, instr: &MachineInstr, _off: i64) -> bool {
instr.opcode == opc(X86Opcode::MOV)
&& instr.operands.len() >= 2
&& is_any_phys_reg(&instr.operands[0])
&& is_any_reg(&instr.operands[1])
}
fn is_mov_from_rsp_offset(&self, instr: &MachineInstr, _off: i64) -> bool {
instr.opcode == opc(X86Opcode::MOV)
&& instr.operands.len() >= 2
&& is_any_reg(&instr.operands[0])
&& is_any_phys_reg(&instr.operands[1])
}
fn convert_save_restore_to_push_pop(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
if n < 2 {
return;
}
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let mut i = 0;
while i < n {
if i + 1 < n {
let a = &bb.instructions[i];
let b = &bb.instructions[i + 1];
if self.is_sub_rsp_imm(a, 8) && self.is_mov_to_rsp_offset(b, 0) {
let src = &b.operands[1];
let mut push = MachineInstr::new(opc(X86Opcode::PUSH));
push.operands.push(src.clone());
new_instrs.push(push);
self.stats.mov_to_push_pop += 1;
self.stats.total_movs_eliminated += 1;
i += 2;
continue;
}
if self.is_mov_from_rsp_offset(a, 0) && self.is_add_rsp_imm(b, 8) {
let dst = &a.operands[0];
let mut pop = MachineInstr::new(opc(X86Opcode::POP));
pop.operands.push(dst.clone());
new_instrs.push(pop);
self.stats.mov_to_push_pop += 1;
self.stats.total_movs_eliminated += 1;
i += 2;
continue;
}
}
new_instrs.push(bb.instructions[i].clone());
i += 1;
}
bb.instructions = new_instrs;
}
pub fn simplify_movsxd(&mut self, bb: &mut MachineBasicBlock) {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let n = bb.instructions.len();
let mut i = 0;
while i < n {
let instr = &bb.instructions[i];
if instr.opcode == opc(X86Opcode::MOVSXD) && instr.operands.len() >= 2 {
let dst = get_phys_reg(&instr.operands[0]);
let src = get_phys_reg(&instr.operands[1]);
if let (Some(d), Some(s)) = (dst, src) {
if are_related_regs(d, s) && is_gpr64(d) && is_gpr32(s) {
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::PhysReg(s));
mov.operands.push(MachineOperand::PhysReg(s));
new_instrs.push(mov);
self.stats.movsxd_simplified += 1;
self.stats.total_movs_eliminated += 1;
i += 1;
continue;
}
if i + 1 < n {
let next = &bb.instructions[i + 1];
if next.opcode == opc(X86Opcode::AND) && next.operands.len() >= 2 {
if let Some(imm) = get_imm(&next.operands[1]) {
if imm == 0xFFFF_FFFF_i64 {
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(instr.operands[1].clone());
mov.operands.push(instr.operands[1].clone());
new_instrs.push(mov);
self.stats.movsxd_simplified += 1;
self.stats.total_movs_eliminated += 2;
i += 2;
continue;
}
}
}
}
}
}
new_instrs.push(instr.clone());
i += 1;
}
bb.instructions = new_instrs;
}
pub fn fix_aliasing_movs(&mut self, bb: &mut MachineBasicBlock) {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &bb.instructions {
if instr.opcode == opc(X86Opcode::MOV) && instr.operands.len() >= 2 {
if let Some(dst_reg) = get_phys_reg(&instr.operands[0]) {
if is_gpr8(dst_reg) || is_gpr16(dst_reg) {
if let Some(parent64) = gpr32_to_gpr64(dst_reg) {
if self.subtarget.features.contains("avoid-partial-reg-stall")
|| self.aggressive
{
let mut xor_instr = MachineInstr::new(opc(X86Opcode::XOR));
xor_instr.operands.push(MachineOperand::PhysReg(parent64));
xor_instr.operands.push(MachineOperand::PhysReg(parent64));
new_instrs.push(xor_instr);
self.stats.mov_aliasing_fixed += 1;
}
}
}
}
}
new_instrs.push(instr.clone());
}
bb.instructions = new_instrs;
}
fn eliminate_dead_movs_intra_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 i = 0;
while i < n {
let instr = &bb.instructions[i];
if !self.is_mov_reg_reg(instr) {
new_instrs.push(instr.clone());
i += 1;
continue;
}
if let Some(dst) = get_any_reg(&instr.operands[0]) {
let mut used = false;
let limit = (i + 1 + X86_MOV_DEAD_MAX_SCAN).min(n);
for j in (i + 1)..limit {
let next = &bb.instructions[j];
let uses = next.operands.iter().any(|op| match op {
MachineOperand::Reg(r) => *r == dst,
MachineOperand::PhysReg(r) => *r == dst,
_ => false,
});
if uses {
used = true;
break;
}
if next.def.is_some() && next.def.unwrap() == dst {
break;
}
}
if !used {
self.stats.mov_dead_eliminated += 1;
self.stats.total_movs_eliminated += 1;
i += 1;
continue;
}
}
new_instrs.push(instr.clone());
i += 1;
}
bb.instructions = new_instrs;
}
fn compute_block_liveness(&self, mf: &MachineFunction) -> Vec<BlockLiveness> {
let mut result: Vec<BlockLiveness> = Vec::new();
for block in &mf.blocks {
let mut used: HashSet<u32> = HashSet::new();
let mut defined: HashSet<u32> = HashSet::new();
for instr in &block.instructions {
for op in &instr.operands {
if let Some(r) = get_any_reg(op) {
if !defined.contains(&r) {
used.insert(r);
}
}
}
if let Some(def) = instr.def {
defined.insert(def);
}
if let Some(dst) = get_any_reg(
&instr
.operands
.first()
.cloned()
.unwrap_or(MachineOperand::Imm(0)),
) {
defined.insert(dst);
}
}
result.push(BlockLiveness {
live_in: used,
live_out: defined.clone(),
});
}
result
}
fn eliminate_dead_movs_global(&mut self, mf: &mut MachineFunction) {
let liveness = self.compute_block_liveness(mf);
for (bi, live) in liveness.iter().enumerate() {
if bi >= mf.blocks.len() {
break;
}
let block = &mut mf.blocks[bi];
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &block.instructions {
if self.is_mov_reg_reg(instr) || self.is_mov_reg_imm(instr) {
if let Some(dst) = get_any_reg(&instr.operands[0]) {
if !live.live_out.contains(&dst) {
let mut used = false;
for other in &block.instructions {
if std::ptr::eq(instr, other) {
continue;
}
for op in &other.operands {
if let Some(r) = get_any_reg(op) {
if r == dst {
used = true;
break;
}
}
}
if used {
break;
}
}
if !used {
self.stats.mov_dead_eliminated += 1;
self.stats.total_movs_eliminated += 1;
continue;
}
}
}
}
new_instrs.push(instr.clone());
}
block.instructions = new_instrs;
}
}
pub fn eliminate_related_movs(&mut self, bb: &mut MachineBasicBlock) {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &bb.instructions {
if !self.is_mov_reg_reg(instr) {
new_instrs.push(instr.clone());
continue;
}
if let (Some(dst), Some(src)) = (
get_phys_reg(&instr.operands[0]),
get_phys_reg(&instr.operands[1]),
) {
if dst == src {
self.stats.mov_between_related_elim += 1;
self.stats.total_movs_eliminated += 1;
continue;
}
if are_related_regs(dst, src) && reg_bits(dst) == reg_bits(src) && dst == src {
self.stats.mov_between_related_elim += 1;
self.stats.total_movs_eliminated += 1;
continue;
}
}
new_instrs.push(instr.clone());
}
bb.instructions = new_instrs;
}
fn optimize_block(&mut self, bb: &mut MachineBasicBlock) {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let mut skip: usize = 0;
let n = bb.instructions.len();
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];
if !self.is_mov_instruction(instr) {
new_instrs.push(instr.clone());
i += 1;
continue;
}
let mut done = false;
if self.try_eliminate_mov_same_reg(instr).is_some() {
self.stats.mov_same_reg_eliminated += 1;
self.stats.total_movs_eliminated += 1;
done = true;
}
if !done {
if let Some(r) = self.try_mov_zero_to_xor(instr) {
new_instrs.push(r);
self.stats.mov_zero_to_xor += 1;
self.stats.total_movs_eliminated += 1;
done = true;
}
}
if !done && self.is_64_bit {
if let Some(r) = self.try_mov_imm32_to_64bit(instr) {
new_instrs.push(r);
self.stats.mov_imm32_64bit_zero_ext += 1;
done = true;
}
}
if !done {
if let Some(r) = self.try_eliminate_sub_super_mov(instr) {
if r.opcode != 0 {
new_instrs.push(r);
}
self.stats.mov_sub_super_eliminated += 1;
self.stats.total_movs_eliminated += 1;
done = true;
}
}
if !done && self.enable_mov_folding && i + 1 < n {
if let Some((r, c)) = self.try_fold_mov_into_next(instr, &bb.instructions[i + 1]) {
new_instrs.push(r);
skip = c;
self.stats.mov_folded_into_mem_op += 1;
self.stats.total_movs_eliminated += 1;
done = true;
}
}
if !done && self.enable_lea_conversion {
if let Some(r) = self.try_mov_to_lea(instr) {
new_instrs.push(r);
self.stats.mov_to_lea += 1;
done = true;
}
}
if !done {
if let Some(r) = self.try_mov_to_movzx(instr) {
new_instrs.push(r);
self.stats.mov_zero_ext_to_movzx += 1;
done = true;
}
}
if !done {
new_instrs.push(instr.clone());
}
i += 1;
}
bb.instructions = new_instrs;
}
pub fn optimize_function(&mut self, mf: &mut MachineFunction) {
for block in &mut mf.blocks {
self.optimize_block(block);
}
if self.enable_mov_folding {
self.fold_movs_across_blocks(mf);
}
if self.enable_dead_mov_elim {
self.eliminate_dead_movs_global(mf);
}
}
pub fn run_full_pipeline(&mut self, mf: &mut MachineFunction) {
self.stats = MOVOptimizerStats::default();
for block in &mut mf.blocks {
self.eliminate_related_movs(block);
}
for block in &mut mf.blocks {
self.optimize_block(block);
}
for block in &mut mf.blocks {
self.convert_save_restore_to_push_pop(block);
}
for block in &mut mf.blocks {
self.simplify_movsxd(block);
}
for block in &mut mf.blocks {
self.fix_aliasing_movs(block);
}
for bi in 0..mf.blocks.len() {
let mut bb = mf.blocks[bi].clone();
self.eliminate_dead_movs_intra_block(&mut bb);
mf.blocks[bi] = bb;
}
if self.enable_dead_mov_elim {
self.eliminate_dead_movs_global(mf);
}
for block in &mut mf.blocks {
self.eliminate_related_movs(block);
}
if self.enable_mov_folding {
self.fold_movs_across_blocks(mf);
}
}
pub fn estimate_instruction_size(&self, instr: &MachineInstr) -> usize {
let op = instr.opcode;
if op == opc(X86Opcode::MOV) {
if self.is_mov_reg_imm(instr) {
if let Some(imm) = get_imm(&instr.operands[1]) {
if let Some(dst) = get_phys_reg(&instr.operands[0]) {
if is_gpr64(dst) {
return if fits_i32(imm) { 7 } else { 10 };
}
if is_gpr32(dst) {
return if imm == 0 { 2 } else { 5 };
}
}
}
}
if self.is_mov_reg_reg(instr) {
return 3;
}
}
if op == opc(X86Opcode::XOR)
&& instr.operands.len() >= 2
&& same_reg(&instr.operands[0], &instr.operands[1])
{
if let Some(dst) = get_phys_reg(&instr.operands[0]) {
if is_gpr32(dst) {
return 2;
}
if is_gpr64(dst) {
return 3;
}
}
}
if op == opc(X86Opcode::LEA) {
return 4;
}
if op == opc(X86Opcode::PUSH) {
return if instr.operands.len() >= 1 && is_any_phys_reg(&instr.operands[0]) {
1
} else {
2
};
}
if op == opc(X86Opcode::POP) {
return if instr.operands.len() >= 1 && is_any_phys_reg(&instr.operands[0]) {
1
} else {
2
};
}
4
}
pub fn estimate_xor_zeroing_savings(&self, instr: &MachineInstr) -> isize {
self.estimate_instruction_size(instr) as isize - 2
}
pub fn estimate_push_pop_savings(&self) -> isize {
6
}
pub fn optimization_report(&self) -> String {
format!("=== X86 MOV Optimizer Report ===\n Target: {}\n 64-bit mode: {}\n XOR zeroing: {}\n LEA conversion: {}\n PUSH/POP: {}\n Dead MOV elim: {}\n MOV folding: {}\n\n {}\n",
self.subtarget.cpu, self.is_64_bit, self.enable_xor_zeroing, self.enable_lea_conversion, self.enable_push_pop_conversion, self.enable_dead_mov_elim, self.enable_mov_folding, self.stats.summary())
}
pub fn reset_stats(&mut self) {
self.stats = MOVOptimizerStats::default();
}
pub fn count_movs(&self, mf: &MachineFunction) -> usize {
let mut c = 0;
for block in &mf.blocks {
for instr in &block.instructions {
if self.is_mov_instruction(instr) {
c += 1;
}
}
}
c
}
pub fn count_zero_mov_candidates(&self, mf: &MachineFunction) -> usize {
let mut c = 0;
for block in &mf.blocks {
for instr in &block.instructions {
if self.is_mov_reg_imm(instr) {
if let Some(imm) = get_imm(&instr.operands[1]) {
if imm == 0 && self.enable_xor_zeroing {
c += 1;
}
}
}
}
}
c
}
}
pub struct X86PostRAMOVOptimizer {
pub enable_same_reg_elim: bool,
pub enable_xor_zeroing: bool,
pub stats: MOVOptimizerStats,
}
impl X86PostRAMOVOptimizer {
pub fn new() -> Self {
Self {
enable_same_reg_elim: true,
enable_xor_zeroing: true,
stats: MOVOptimizerStats::default(),
}
}
pub fn optimize_block(&mut self, bb: &mut MachineBasicBlock) {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &bb.instructions {
if instr.opcode != opc(X86Opcode::MOV) || instr.operands.len() < 2 {
new_instrs.push(instr.clone());
continue;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if self.enable_same_reg_elim && same_reg(dst, src) {
self.stats.mov_same_reg_eliminated += 1;
self.stats.total_movs_eliminated += 1;
continue;
}
if self.enable_xor_zeroing {
if let Some(imm) = get_imm(src) {
if imm == 0 {
if let Some(reg) = get_any_reg(dst) {
if is_gpr32(reg) || is_gpr64(reg) {
let t = if is_gpr64(reg) {
gpr64_to_gpr32(reg)
} else {
reg
};
let mut xor_instr = MachineInstr::new(opc(X86Opcode::XOR));
xor_instr.operands.push(MachineOperand::PhysReg(t));
xor_instr.operands.push(MachineOperand::PhysReg(t));
new_instrs.push(xor_instr);
self.stats.mov_zero_to_xor += 1;
self.stats.total_movs_eliminated += 1;
continue;
}
}
}
}
}
new_instrs.push(instr.clone());
}
bb.instructions = new_instrs;
}
}
pub fn can_convert_mov_to_lea(instr: &MachineInstr) -> bool {
if instr.opcode != opc(X86Opcode::MOV) {
return false;
}
if instr.operands.len() < 2 {
return false;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
is_any_reg(dst)
&& (is_any_phys_reg(src)
|| matches!(src, MachineOperand::Label(_))
|| matches!(src, MachineOperand::Global(_)))
}
pub fn can_convert_mov_to_push(prev: &MachineInstr, mov: &MachineInstr) -> bool {
prev.opcode == opc(X86Opcode::SUB)
&& prev.operands.len() >= 2
&& is_phys_reg(&prev.operands[0], RSP)
&& matches!(&prev.operands[1], MachineOperand::Imm(8))
&& mov.opcode == opc(X86Opcode::MOV)
&& mov.operands.len() >= 2
}
pub fn can_convert_mov_pop_to_pop(mov: &MachineInstr, next: &MachineInstr) -> bool {
mov.opcode == opc(X86Opcode::MOV)
&& mov.operands.len() >= 2
&& next.opcode == opc(X86Opcode::ADD)
&& next.operands.len() >= 2
&& is_phys_reg(&next.operands[0], RSP)
&& matches!(&next.operands[1], MachineOperand::Imm(8))
}
#[derive(Debug, Clone, Default)]
pub struct MOVToCMOVCandidate {
pub mov_instr_idx: usize,
pub condition: u8,
pub true_value_reg: u32,
pub false_value_reg: u32,
pub dest_reg: u32,
pub can_convert: bool,
pub estimated_savings: usize,
}
impl X86MOVOptimizer {
pub fn identify_mov_to_cmov_candidates(
&self,
bb: &MachineBasicBlock,
) -> Vec<MOVToCMOVCandidate> {
let mut candidates: Vec<MOVToCMOVCandidate> = Vec::new();
let n = bb.instructions.len();
if n < 4 {
return candidates;
}
let mut i = 0;
while i < n {
let instr = &bb.instructions[i];
if instr.opcode == opc(X86Opcode::CMP) || instr.opcode == opc(X86Opcode::TEST) {
if i + 3 < n {
let jcc = &bb.instructions[i + 1];
if self.is_conditional_jump(jcc) {
let mov_true = &bb.instructions[i + 2];
let jmp = &bb.instructions[i + 3];
if mov_true.opcode == opc(X86Opcode::MOV)
&& jmp.opcode == opc(X86Opcode::JMP)
&& i + 4 < n
{
let mov_false = &bb.instructions[i + 4];
if mov_false.opcode == opc(X86Opcode::MOV)
&& mov_true.operands.len() >= 1
&& mov_false.operands.len() >= 1
{
if let (Some(dst_t), Some(dst_f)) = (
get_any_reg(&mov_true.operands[0]),
get_any_reg(&mov_false.operands[0]),
) {
if dst_t == dst_f {
let cmov_cc = self.jcc_to_cmov_cc(jcc.opcode);
if cmov_cc != 0xFF {
candidates.push(MOVToCMOVCandidate {
mov_instr_idx: i + 2,
condition: cmov_cc,
true_value_reg: dst_t,
false_value_reg: dst_f,
dest_reg: dst_t,
can_convert: true,
estimated_savings: 1,
});
}
}
}
}
}
}
}
}
i += 1;
}
candidates
}
fn is_conditional_jump(&self, instr: &MachineInstr) -> bool {
let op = instr.opcode;
op == opc(X86Opcode::JE)
|| op == opc(X86Opcode::JNE)
|| op == opc(X86Opcode::JB)
|| op == opc(X86Opcode::JAE)
|| op == opc(X86Opcode::JBE)
|| op == opc(X86Opcode::JA)
|| op == opc(X86Opcode::JL)
|| op == opc(X86Opcode::JGE)
|| op == opc(X86Opcode::JLE)
|| op == opc(X86Opcode::JG)
|| op == opc(X86Opcode::JS)
|| op == opc(X86Opcode::JNS)
|| op == opc(X86Opcode::JO)
|| op == opc(X86Opcode::JNO)
|| op == opc(X86Opcode::JP)
|| op == opc(X86Opcode::JNP)
}
fn jcc_to_cmov_cc(&self, jcc_opcode: u32) -> u8 {
match jcc_opcode {
o if o == opc(X86Opcode::JE) => 0x04,
o if o == opc(X86Opcode::JNE) => 0x05,
o if o == opc(X86Opcode::JB) => 0x02,
o if o == opc(X86Opcode::JAE) => 0x03,
o if o == opc(X86Opcode::JBE) => 0x06,
o if o == opc(X86Opcode::JA) => 0x07,
o if o == opc(X86Opcode::JL) => 0x0C,
o if o == opc(X86Opcode::JGE) => 0x0D,
o if o == opc(X86Opcode::JLE) => 0x0E,
o if o == opc(X86Opcode::JG) => 0x0F,
o if o == opc(X86Opcode::JS) => 0x08,
o if o == opc(X86Opcode::JNS) => 0x09,
o if o == opc(X86Opcode::JO) => 0x00,
o if o == opc(X86Opcode::JNO) => 0x01,
o if o == opc(X86Opcode::JP) => 0x0A,
o if o == opc(X86Opcode::JNP) => 0x0B,
_ => 0xFF,
}
}
}
impl X86MOVOptimizer {
pub fn coalesce_mov_chains(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
if n < 2 {
return;
}
let mut last_def: HashMap<u32, usize> = HashMap::new();
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for (i, instr) in bb.instructions.iter().enumerate() {
let mut modified_instr = instr.clone();
if instr.opcode == opc(X86Opcode::MOV) && instr.operands.len() >= 2 {
let src = &instr.operands[1];
if let Some(src_reg) = get_any_reg(src) {
if let Some(&def_idx) = last_def.get(&src_reg) {
let def_instr = &bb.instructions[def_idx];
if def_instr.opcode == opc(X86Opcode::MOV) && def_instr.operands.len() >= 2
{
let orig_src = &def_instr.operands[1];
let mut overwritten = false;
if let Some(orig_reg) = get_any_reg(orig_src) {
for k in (def_idx + 1)..i {
let mid = &bb.instructions[k];
if mid.opcode == opc(X86Opcode::MOV)
&& mid.operands.len() >= 1
&& get_any_reg(&mid.operands[0]) == Some(orig_reg)
{
overwritten = true;
break;
}
if mid.def == Some(orig_reg) {
overwritten = true;
break;
}
}
if !overwritten {
modified_instr.operands[1] = orig_src.clone();
self.stats.mov_between_related_elim += 1;
}
}
}
}
}
}
if let Some(dst) = get_any_reg(
&instr
.operands
.first()
.cloned()
.unwrap_or(MachineOperand::Imm(0)),
) {
last_def.insert(dst, i);
}
if let Some(def) = instr.def {
last_def.insert(def, i);
}
new_instrs.push(modified_instr);
}
bb.instructions = new_instrs;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MOVImmEncoding {
NotApplicable,
XOR32 { reg32: PhysReg },
MOV32 { reg32: PhysReg, value: u32 },
MOV64SignExtended { reg64: PhysReg, value: i64 },
MOV64Full { reg64: PhysReg, value: u64 },
MOV16 { reg16: PhysReg, value: u16 },
MOV8 { reg8: PhysReg, value: u8 },
}
impl MOVImmEncoding {
pub fn size_bytes(&self) -> usize {
match self {
MOVImmEncoding::NotApplicable => 0,
MOVImmEncoding::XOR32 { .. } => 2,
MOVImmEncoding::MOV32 { .. } => 5,
MOVImmEncoding::MOV64SignExtended { .. } => 7,
MOVImmEncoding::MOV64Full { .. } => 10,
MOVImmEncoding::MOV16 { .. } => 4,
MOVImmEncoding::MOV8 { .. } => 3,
}
}
pub fn to_machine_instr(&self) -> Option<MachineInstr> {
match self {
MOVImmEncoding::NotApplicable => None,
MOVImmEncoding::XOR32 { reg32 } => {
let mut x = MachineInstr::new(opc(X86Opcode::XOR));
x.operands.push(MachineOperand::PhysReg(*reg32));
x.operands.push(MachineOperand::PhysReg(*reg32));
Some(x)
}
MOVImmEncoding::MOV32 { reg32, value } => {
let mut m = MachineInstr::new(opc(X86Opcode::MOV));
m.operands.push(MachineOperand::PhysReg(*reg32));
m.operands.push(MachineOperand::Imm(*value as i64));
Some(m)
}
MOVImmEncoding::MOV64SignExtended { reg64, value } => {
let mut m = MachineInstr::new(opc(X86Opcode::MOV));
m.operands.push(MachineOperand::PhysReg(*reg64));
m.operands.push(MachineOperand::Imm(*value));
Some(m)
}
MOVImmEncoding::MOV64Full { reg64, value } => {
let mut m = MachineInstr::new(opc(X86Opcode::MOVABS));
m.operands.push(MachineOperand::PhysReg(*reg64));
m.operands.push(MachineOperand::Imm(*value as i64));
Some(m)
}
MOVImmEncoding::MOV16 { reg16, value } => {
let mut m = MachineInstr::new(opc(X86Opcode::MOV));
m.operands.push(MachineOperand::PhysReg(*reg16));
m.operands.push(MachineOperand::Imm(*value as i64));
Some(m)
}
MOVImmEncoding::MOV8 { reg8, value } => {
let mut m = MachineInstr::new(opc(X86Opcode::MOV));
m.operands.push(MachineOperand::PhysReg(*reg8));
m.operands.push(MachineOperand::Imm(*value as i64));
Some(m)
}
}
}
}
impl X86MOVOptimizer {
pub fn analyze_mov_imm_encoding(&self, instr: &MachineInstr) -> MOVImmEncoding {
if !self.is_mov_reg_imm(instr) {
return MOVImmEncoding::NotApplicable;
}
let imm = get_imm(&instr.operands[1]).unwrap_or(0);
let dst_reg = get_phys_reg(&instr.operands[0]);
match dst_reg {
Some(reg) if is_gpr64(reg) => {
if imm == 0 {
MOVImmEncoding::XOR32 {
reg32: gpr64_to_gpr32(reg),
}
} else if fits_u32(imm) {
MOVImmEncoding::MOV32 {
reg32: gpr64_to_gpr32(reg),
value: imm as u32,
}
} else if fits_i32(imm) {
MOVImmEncoding::MOV64SignExtended {
reg64: reg,
value: imm,
}
} else {
MOVImmEncoding::MOV64Full {
reg64: reg,
value: imm as u64,
}
}
}
Some(reg) if is_gpr32(reg) => {
if imm == 0 {
MOVImmEncoding::XOR32 { reg32: reg }
} else {
MOVImmEncoding::MOV32 {
reg32: reg,
value: imm as u32,
}
}
}
Some(reg) if is_gpr16(reg) => MOVImmEncoding::MOV16 {
reg16: reg,
value: imm as u16,
},
Some(reg) if is_gpr8(reg) => MOVImmEncoding::MOV8 {
reg8: reg,
value: imm as u8,
},
_ => MOVImmEncoding::NotApplicable,
}
}
pub fn apply_optimal_mov_imm_encoding(&mut self, instr: &MachineInstr) -> Option<MachineInstr> {
self.analyze_mov_imm_encoding(instr).to_machine_instr()
}
}
impl X86MOVOptimizer {
pub fn detect_mov_add_lea_patterns(
&self,
instr_a: &MachineInstr,
instr_b: &MachineInstr,
) -> Option<MachineInstr> {
if instr_a.opcode == opc(X86Opcode::MOV)
&& instr_a.operands.len() >= 2
&& instr_b.opcode == opc(X86Opcode::ADD)
&& instr_b.operands.len() >= 2
{
let mov_dst = &instr_a.operands[0];
let mov_src = &instr_a.operands[1];
let add_dst = &instr_b.operands[0];
let add_src = &instr_b.operands[1];
if same_reg(mov_dst, add_dst) {
if let Some(imm) = get_imm(add_src) {
if fits_i32(imm) {
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(add_dst.clone());
lea.operands.push(mov_src.clone());
lea.operands.push(MachineOperand::Imm(imm));
return Some(lea);
}
}
}
}
if instr_a.opcode == opc(X86Opcode::MOV)
&& instr_a.operands.len() >= 2
&& instr_b.opcode == opc(X86Opcode::ADD)
&& instr_b.operands.len() >= 2
{
let mov_dst = &instr_a.operands[0];
let mov_src = &instr_a.operands[1];
let add_dst = &instr_b.operands[0];
let add_src = &instr_b.operands[1];
if same_reg(mov_dst, add_dst) && is_any_reg(add_src) {
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(add_dst.clone());
lea.operands.push(mov_src.clone());
lea.operands.push(add_src.clone());
return Some(lea);
}
}
None
}
}
impl X86MOVOptimizer {
pub fn optimize_mov_with_known_zero_upper(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
if n == 0 {
return;
}
let mut upper_zero: HashSet<u32> = HashSet::new();
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for instr in &bb.instructions {
let mut skip = false;
if instr.opcode == opc(X86Opcode::XOR)
|| instr.opcode == opc(X86Opcode::MOV)
|| instr.opcode == opc(X86Opcode::ADD)
|| instr.opcode == opc(X86Opcode::SUB)
|| instr.opcode == opc(X86Opcode::AND)
|| instr.opcode == opc(X86Opcode::OR)
{
if let Some(dst) = get_phys_reg(
&instr
.operands
.first()
.cloned()
.unwrap_or(MachineOperand::Imm(0)),
) {
if is_gpr32(dst) {
if let Some(parent64) = gpr32_to_gpr64(dst) {
upper_zero.insert(parent64);
}
}
}
}
if instr.opcode == opc(X86Opcode::MOV)
&& instr.operands.len() >= 2
&& self.is_mov_reg_imm(instr)
{
if let Some(dst) = get_phys_reg(&instr.operands[0]) {
if is_gpr64(dst) && upper_zero.contains(&dst) {
if let Some(imm) = get_imm(&instr.operands[1]) {
if fits_u32(imm) {
let reg32 = gpr64_to_gpr32(dst);
let mut mov32 = MachineInstr::new(opc(X86Opcode::MOV));
mov32.operands.push(MachineOperand::PhysReg(reg32));
mov32.operands.push(MachineOperand::Imm(imm));
new_instrs.push(mov32);
self.stats.mov_imm32_64bit_zero_ext += 1;
skip = true;
}
}
}
}
}
if instr.opcode == opc(X86Opcode::CALL)
|| instr.opcode == opc(X86Opcode::DIV)
|| instr.opcode == opc(X86Opcode::IDIV)
|| instr.opcode == opc(X86Opcode::MUL)
|| instr.opcode == opc(X86Opcode::IMUL)
{
upper_zero.clear();
}
if !skip {
new_instrs.push(instr.clone());
}
}
bb.instructions = new_instrs;
}
}
impl X86MOVOptimizer {
pub fn optimize_mov_setcc_sequence(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
if n < 2 {
return;
}
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let mut i = 0;
while i < n {
if i + 1 < n {
let first = &bb.instructions[i];
let second = &bb.instructions[i + 1];
let is_setcc = first.opcode == opc(X86Opcode::SETE)
|| first.opcode == opc(X86Opcode::SETNE)
|| first.opcode == opc(X86Opcode::SETB)
|| first.opcode == opc(X86Opcode::SETAE)
|| first.opcode == opc(X86Opcode::SETBE)
|| first.opcode == opc(X86Opcode::SETA)
|| first.opcode == opc(X86Opcode::SETL)
|| first.opcode == opc(X86Opcode::SETGE)
|| first.opcode == opc(X86Opcode::SETLE)
|| first.opcode == opc(X86Opcode::SETG)
|| first.opcode == opc(X86Opcode::SETS)
|| first.opcode == opc(X86Opcode::SETNS)
|| first.opcode == opc(X86Opcode::SETO)
|| first.opcode == opc(X86Opcode::SETNO)
|| first.opcode == opc(X86Opcode::SETP)
|| first.opcode == opc(X86Opcode::SETNP);
let is_movzx = second.opcode == opc(X86Opcode::MOVZX);
if is_setcc && is_movzx && first.operands.len() >= 1 && second.operands.len() >= 2 {
if same_reg(&first.operands[0], &second.operands[1]) {
new_instrs.push(first.clone());
self.stats.mov_zero_ext_to_movzx += 1;
self.stats.total_movs_eliminated += 1;
i += 2;
continue;
}
}
}
new_instrs.push(bb.instructions[i].clone());
i += 1;
}
bb.instructions = new_instrs;
}
}
impl X86MOVOptimizer {
pub fn fuse_mov_test_sequence(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
if n < 2 {
return;
}
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let mut i = 0;
while i < n {
if i + 1 < n {
let first = &bb.instructions[i];
let second = &bb.instructions[i + 1];
if first.opcode == opc(X86Opcode::MOV)
&& first.operands.len() >= 2
&& second.opcode == opc(X86Opcode::TEST)
&& second.operands.len() >= 2
{
let mov_dst = &first.operands[0];
let mov_src = &first.operands[1];
let test_op1 = &second.operands[0];
let test_op2 = &second.operands[1];
if same_reg(mov_dst, test_op1) && same_reg(mov_dst, test_op2) {
let mut new_test = MachineInstr::new(opc(X86Opcode::TEST));
new_test.operands.push(mov_src.clone());
new_test.operands.push(mov_src.clone());
new_instrs.push(new_test);
self.stats.mov_dead_eliminated += 1;
self.stats.total_movs_eliminated += 1;
i += 2;
continue;
}
}
}
new_instrs.push(bb.instructions[i].clone());
i += 1;
}
bb.instructions = new_instrs;
}
}
impl X86MOVOptimizer {
pub fn optimize_callee_save_restore(&mut self, bb: &mut MachineBasicBlock) {
let n = bb.instructions.len();
if n < 2 {
return;
}
let mut saved_regs: Vec<(PhysReg, i64)> = Vec::new();
let mut save_end: usize = 0;
for (i, instr) in bb.instructions.iter().enumerate() {
if instr.opcode == opc(X86Opcode::MOV) && instr.operands.len() >= 2 {
if is_any_phys_reg(&instr.operands[0]) && is_any_phys_reg(&instr.operands[1]) {
if let Some(reg) = get_phys_reg(&instr.operands[1]) {
saved_regs.push((reg, -(saved_regs.len() as i64 + 1) * 8));
save_end = i + 1;
}
}
} else {
break;
}
}
let mut restore_start: usize = n;
for i in (0..n).rev() {
let instr = &bb.instructions[i];
if instr.opcode == opc(X86Opcode::MOV) && instr.operands.len() >= 2 {
if is_any_phys_reg(&instr.operands[0]) && is_any_phys_reg(&instr.operands[1]) {
restore_start = i;
} else {
break;
}
} else if instr.opcode == opc(X86Opcode::RET) || instr.opcode == opc(X86Opcode::JMP) {
continue;
} else {
break;
}
}
if !saved_regs.is_empty() && restore_start > save_end {
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for &(reg, _) in &saved_regs {
let mut push = MachineInstr::new(opc(X86Opcode::PUSH));
push.operands.push(MachineOperand::PhysReg(reg));
new_instrs.push(push);
self.stats.mov_to_push_pop += 1;
self.stats.total_movs_eliminated += 1;
}
for i in save_end..restore_start {
new_instrs.push(bb.instructions[i].clone());
}
for &(reg, _) in saved_regs.iter().rev() {
let mut pop = MachineInstr::new(opc(X86Opcode::POP));
pop.operands.push(MachineOperand::PhysReg(reg));
new_instrs.push(pop);
self.stats.mov_to_push_pop += 1;
self.stats.total_movs_eliminated += 1;
}
for i in restore_start + saved_regs.len()..n {
if i < bb.instructions.len() {
new_instrs.push(bb.instructions[i].clone());
}
}
bb.instructions = new_instrs;
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SSALiveness {
pub live_in: BTreeSet<u32>,
pub live_out: BTreeSet<u32>,
pub live_before: Vec<BTreeSet<u32>>,
pub live_after: Vec<BTreeSet<u32>>,
}
impl X86MOVOptimizer {
pub fn compute_ssa_liveness(&self, bb: &MachineBasicBlock) -> SSALiveness {
let n = bb.instructions.len();
let mut result = SSALiveness::default();
result.live_before = vec![BTreeSet::new(); n];
result.live_after = vec![BTreeSet::new(); n];
if n == 0 {
return result;
}
let mut live: BTreeSet<u32> = BTreeSet::new();
for i in (0..n).rev() {
result.live_after[i] = live.clone();
let instr = &bb.instructions[i];
if let Some(dst) = get_any_reg(
&instr
.operands
.first()
.cloned()
.unwrap_or(MachineOperand::Imm(0)),
) {
live.remove(&dst);
}
if let Some(def) = instr.def {
live.remove(&def);
}
for op in &instr.operands {
if let Some(r) = get_any_reg(op) {
live.insert(r);
}
}
result.live_before[i] = live.clone();
}
result.live_in = live.clone();
result.live_out = BTreeSet::new();
result
}
pub fn eliminate_dead_movs_with_liveness(&mut self, bb: &mut MachineBasicBlock) {
let liveness = self.compute_ssa_liveness(bb);
let n = bb.instructions.len();
let mut new_instrs: Vec<MachineInstr> = Vec::new();
for (i, instr) in bb.instructions.iter().enumerate() {
if self.is_mov_instruction(instr) && instr.operands.len() >= 1 {
if let Some(dst) = get_any_reg(&instr.operands[0]) {
if !liveness.live_after[i].contains(&dst) {
self.stats.mov_dead_eliminated += 1;
self.stats.total_movs_eliminated += 1;
continue;
}
}
}
new_instrs.push(instr.clone());
}
bb.instructions = new_instrs;
}
}
impl X86MOVOptimizer {
pub fn detailed_report(&self, mf: &MachineFunction) -> String {
let mov_count = self.count_movs(mf);
let candidates = self.count_zero_mov_candidates(mf);
let mut report = String::new();
report.push_str("╔══════════════════════════════════════════════╗\n");
report.push_str("║ X86 MOV Optimizer — Detailed Report ║\n");
report.push_str("╠══════════════════════════════════════════════╣\n");
report.push_str(&format!("║ Target CPU: {:28} ║\n", self.subtarget.cpu));
report.push_str(&format!(
"║ 64-bit mode: {:28} ║\n",
if self.is_64_bit { "yes" } else { "no" }
));
report.push_str(&format!("║ Total MOVs: {:28} ║\n", mov_count));
report.push_str(&format!(
"║ MOVs Eliminated: {:26} ║\n",
self.stats.total_movs_eliminated
));
report.push_str(&format!("║ Zero Imm Candidates: {:22} ║\n", candidates));
report.push_str("╠══════════════════════════════════════════════╣\n");
report.push_str(&format!(
"║ Same-reg elim: {:22} ║\n",
self.stats.mov_same_reg_eliminated
));
report.push_str(&format!(
"║ Zero→XOR: {:22} ║\n",
self.stats.mov_zero_to_xor
));
report.push_str(&format!(
"║ Dead MOV elim: {:22} ║\n",
self.stats.mov_dead_eliminated
));
report.push_str(&format!(
"║ Folded into memop: {:22} ║\n",
self.stats.mov_folded_into_mem_op
));
report.push_str(&format!(
"║ MOV→LEA: {:22} ║\n",
self.stats.mov_to_lea
));
report.push_str(&format!(
"║ MOV→PUSH/POP: {:22} ║\n",
self.stats.mov_to_push_pop
));
report.push_str(&format!(
"║ Sub/super elim: {:22} ║\n",
self.stats.mov_sub_super_eliminated
));
report.push_str(&format!(
"║ MOVSXD simplified: {:22} ║\n",
self.stats.movsxd_simplified
));
report.push_str(&format!(
"║ Aliasing fixed: {:22} ║\n",
self.stats.mov_aliasing_fixed
));
report.push_str(&format!(
"║ Related reg elim: {:22} ║\n",
self.stats.mov_between_related_elim
));
report.push_str(&format!(
"║ Zero ext→MOVZX: {:22} ║\n",
self.stats.mov_zero_ext_to_movzx
));
report.push_str(&format!(
"║ imm32→64bit zext: {:22} ║\n",
self.stats.mov_imm32_64bit_zero_ext
));
report.push_str("╚══════════════════════════════════════════════╝\n");
report
}
pub fn categorize_movs(&self, mf: &MachineFunction) -> (usize, usize, usize) {
let mut reg_reg = 0;
let mut reg_imm = 0;
let mut other = 0;
for block in &mf.blocks {
for instr in &block.instructions {
if self.is_mov_instruction(instr) {
if self.is_mov_reg_reg(instr) {
reg_reg += 1;
} else if self.is_mov_reg_imm(instr) {
reg_imm += 1;
} else {
other += 1;
}
}
}
}
(reg_reg, reg_imm, other)
}
}
#[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_mf() -> MachineFunction {
MachineFunction::new("test")
}
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 opt = X86MOVOptimizer::new(&sub);
assert!(opt.is_64_bit);
assert!(opt.enable_xor_zeroing);
assert_eq!(opt.stats.total_movs_eliminated, 0);
}
#[test]
fn test_is_mov() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
assert!(opt.is_mov_instruction(&MachineInstr::new(opc(X86Opcode::MOV))));
assert!(!opt.is_mov_instruction(&MachineInstr::new(opc(X86Opcode::ADD))));
assert!(opt.is_mov_instruction(&MachineInstr::new(opc(X86Opcode::MOVSX))));
}
#[test]
fn test_mov_same_reg_elim() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RAX));
assert!(opt.try_eliminate_mov_same_reg(&instr).is_some());
}
#[test]
fn test_mov_different_reg_not_elim() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RBX));
assert!(opt.try_eliminate_mov_same_reg(&instr).is_none());
}
#[test]
fn test_mov_zero_to_xor_64bit() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(0));
let r = opt.try_mov_zero_to_xor(&instr);
assert!(r.is_some());
let x = r.unwrap();
assert_eq!(x.opcode, opc(X86Opcode::XOR));
assert!(matches!(&x.operands[0], MachineOperand::PhysReg(r2) if *r2 == EAX));
}
#[test]
fn test_mov_nonzero_not_xor() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(42));
assert!(opt.try_mov_zero_to_xor(&instr).is_none());
}
#[test]
fn test_mov_imm32_to_64bit() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(0x42));
let r = opt.try_mov_imm32_to_64bit(&instr);
assert!(r.is_some());
let c = r.unwrap();
assert!(matches!(&c.operands[0], MachineOperand::PhysReg(r2) if *r2 == EAX));
}
#[test]
fn test_reg_bits_all() {
assert_eq!(reg_bits(RAX), 64);
assert_eq!(reg_bits(EAX), 32);
assert_eq!(reg_bits(AX), 16);
assert_eq!(reg_bits(AL), 8);
}
#[test]
fn test_are_related_regs() {
assert!(are_related_regs(RAX, EAX));
assert!(are_related_regs(EAX, AX));
assert!(!are_related_regs(RAX, RBX));
}
#[test]
fn test_is_subregister() {
assert!(is_subregister(EAX, RAX));
assert!(!is_subregister(RAX, EAX));
assert!(!is_subregister(RAX, RBX));
}
#[test]
fn test_gpr64_to_32() {
assert_eq!(gpr64_to_gpr32(RAX), EAX);
assert_eq!(gpr64_to_gpr32(R8), R8D);
}
#[test]
fn test_gpr32_to_64() {
assert_eq!(gpr32_to_gpr64(EAX), Some(RAX));
assert_eq!(gpr32_to_gpr64(R8D), Some(R8));
assert_eq!(gpr32_to_gpr64(AL), None);
}
#[test]
fn test_foldable_alu() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
assert!(opt.is_foldable_alu_op(&MachineInstr::new(opc(X86Opcode::ADD))));
assert!(opt.is_foldable_alu_op(&MachineInstr::new(opc(X86Opcode::CMP))));
assert!(!opt.is_foldable_alu_op(&MachineInstr::new(opc(X86Opcode::JMP))));
}
#[test]
fn test_reg_name_all() {
assert_eq!(reg_name(RAX), "rax");
assert_eq!(reg_name(EAX), "eax");
assert_eq!(reg_name(R8), "r8");
assert_eq!(reg_name(AL), "al");
}
#[test]
fn test_post_ra_optimizer() {
let mut opt = X86PostRAMOVOptimizer::new();
let mut bb = make_bb("test");
let mut m1 = MachineInstr::new(opc(X86Opcode::MOV));
m1.operands.push(MachineOperand::PhysReg(RAX));
m1.operands.push(MachineOperand::PhysReg(RAX));
bb.instructions.push(m1);
let mut m2 = MachineInstr::new(opc(X86Opcode::MOV));
m2.operands.push(MachineOperand::PhysReg(RAX));
m2.operands.push(MachineOperand::Imm(0));
bb.instructions.push(m2);
opt.optimize_block(&mut bb);
assert_eq!(opt.stats.mov_same_reg_eliminated, 1);
assert_eq!(opt.stats.mov_zero_to_xor, 1);
assert_eq!(opt.stats.total_movs_eliminated, 2);
}
#[test]
fn test_stats_merge() {
let mut s1 = MOVOptimizerStats::default();
s1.mov_same_reg_eliminated = 3;
s1.mov_zero_to_xor = 5;
s1.total_movs_eliminated = 8;
let mut s2 = MOVOptimizerStats::default();
s2.mov_same_reg_eliminated = 2;
s2.mov_folded_into_mem_op = 4;
s2.total_movs_eliminated = 6;
s1.merge(&s2);
assert_eq!(s1.mov_same_reg_eliminated, 5);
assert_eq!(s1.mov_folded_into_mem_op, 4);
assert_eq!(s1.total_movs_eliminated, 14);
}
#[test]
fn test_estimate_size() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut xor_instr = MachineInstr::new(opc(X86Opcode::XOR));
xor_instr.operands.push(MachineOperand::PhysReg(EAX));
xor_instr.operands.push(MachineOperand::PhysReg(EAX));
assert_eq!(opt.estimate_instruction_size(&xor_instr), 2);
let mut push_instr = MachineInstr::new(opc(X86Opcode::PUSH));
push_instr.operands.push(MachineOperand::PhysReg(RAX));
assert_eq!(opt.estimate_instruction_size(&push_instr), 1);
}
#[test]
fn test_can_convert_mov_to_lea() {
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::PhysReg(RBX));
assert!(can_convert_mov_to_lea(&instr));
let add_instr = MachineInstr::new(opc(X86Opcode::ADD));
assert!(!can_convert_mov_to_lea(&add_instr));
}
#[test]
fn test_optimization_report() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let report = opt.optimization_report();
assert!(report.contains("skylake"));
assert!(report.contains("X86 MOV Optimizer"));
}
#[test]
fn test_simplify_movsxd() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut bb = make_bb("test");
let mut movsxd = MachineInstr::new(opc(X86Opcode::MOVSXD));
movsxd.operands.push(MachineOperand::PhysReg(RAX));
movsxd.operands.push(MachineOperand::PhysReg(EAX));
bb.instructions.push(movsxd);
opt.simplify_movsxd(&mut bb);
assert_eq!(opt.stats.movsxd_simplified, 1);
assert_eq!(opt.stats.total_movs_eliminated, 1);
}
#[test]
fn test_mov_imm_encoding_xor32() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(0));
let enc = opt.analyze_mov_imm_encoding(&instr);
assert_eq!(enc, MOVImmEncoding::XOR32 { reg32: EAX });
assert_eq!(enc.size_bytes(), 2);
}
#[test]
fn test_mov_imm_encoding_mov32() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(0x42));
let enc = opt.analyze_mov_imm_encoding(&instr);
assert_eq!(
enc,
MOVImmEncoding::MOV32 {
reg32: EAX,
value: 0x42
}
);
assert_eq!(enc.size_bytes(), 5);
}
#[test]
fn test_mov_imm_encoding_mov64full() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(0x1_0000_0000_i64));
let enc = opt.analyze_mov_imm_encoding(&instr);
assert!(matches!(enc, MOVImmEncoding::MOV64SignExtended { .. }));
}
#[test]
fn test_mov_coalesce_chains() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut bb = make_bb("test");
let mut mov1 = MachineInstr::new(opc(X86Opcode::MOV));
mov1.operands.push(MachineOperand::PhysReg(RCX));
mov1.operands.push(MachineOperand::PhysReg(RBX));
bb.instructions.push(mov1);
let mut mov2 = MachineInstr::new(opc(X86Opcode::MOV));
mov2.operands.push(MachineOperand::PhysReg(RAX));
mov2.operands.push(MachineOperand::PhysReg(RCX));
bb.instructions.push(mov2);
opt.coalesce_mov_chains(&mut bb);
}
#[test]
fn test_detect_mov_add_lea() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::PhysReg(RAX));
mov.operands.push(MachineOperand::PhysReg(RBX));
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::PhysReg(RAX));
add.operands.push(MachineOperand::Imm(8));
let result = opt.detect_mov_add_lea_patterns(&mov, &add);
assert!(result.is_some());
let lea = result.unwrap();
assert_eq!(lea.opcode, opc(X86Opcode::LEA));
}
#[test]
fn test_optimize_mov_setcc_sequence() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut bb = make_bb("test");
let mut sete = MachineInstr::new(opc(X86Opcode::SETE));
sete.operands.push(MachineOperand::PhysReg(AL));
bb.instructions.push(sete);
let mut movzx = MachineInstr::new(opc(X86Opcode::MOVZX));
movzx.operands.push(MachineOperand::PhysReg(EAX));
movzx.operands.push(MachineOperand::PhysReg(AL));
bb.instructions.push(movzx);
opt.optimize_mov_setcc_sequence(&mut bb);
assert_eq!(opt.stats.mov_zero_ext_to_movzx, 1);
assert_eq!(opt.stats.total_movs_eliminated, 1);
}
#[test]
fn test_fuse_mov_test() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut bb = make_bb("test");
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::PhysReg(RCX));
mov.operands.push(MachineOperand::PhysReg(RAX));
bb.instructions.push(mov);
let mut test = MachineInstr::new(opc(X86Opcode::TEST));
test.operands.push(MachineOperand::PhysReg(RCX));
test.operands.push(MachineOperand::PhysReg(RCX));
bb.instructions.push(test);
opt.fuse_mov_test_sequence(&mut bb);
assert_eq!(opt.stats.mov_dead_eliminated, 1);
assert_eq!(opt.stats.total_movs_eliminated, 1);
}
#[test]
fn test_ssa_liveness_dead_mov() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut bb = make_bb("test");
let mut mov_dead = MachineInstr::new(opc(X86Opcode::MOV));
mov_dead.operands.push(MachineOperand::PhysReg(RCX));
mov_dead.operands.push(MachineOperand::PhysReg(RBX));
bb.instructions.push(mov_dead);
let mut mov_use = MachineInstr::new(opc(X86Opcode::MOV));
mov_use.operands.push(MachineOperand::PhysReg(RAX));
mov_use.operands.push(MachineOperand::PhysReg(RCX));
bb.instructions.push(mov_use);
opt.eliminate_dead_movs_with_liveness(&mut bb);
assert_eq!(opt.stats.mov_dead_eliminated, 0); }
#[test]
fn test_detailed_report() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mf = make_mf();
let report = opt.detailed_report(&mf);
assert!(report.contains("Detailed Report"));
assert!(report.contains("skylake"));
}
#[test]
fn test_categorize_movs() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut mf = make_mf();
let mut bb = make_bb("test");
let mut mov_rr = MachineInstr::new(opc(X86Opcode::MOV));
mov_rr.operands.push(MachineOperand::PhysReg(RAX));
mov_rr.operands.push(MachineOperand::PhysReg(RBX));
bb.instructions.push(mov_rr);
let mut mov_ri = MachineInstr::new(opc(X86Opcode::MOV));
mov_ri.operands.push(MachineOperand::PhysReg(RCX));
mov_ri.operands.push(MachineOperand::Imm(42));
bb.instructions.push(mov_ri);
mf.blocks.push(bb);
let (rr, ri, other) = opt.categorize_movs(&mf);
assert_eq!(rr, 1);
assert_eq!(ri, 1);
assert_eq!(other, 0);
}
#[test]
fn test_mov_imm_encoding_to_instr() {
let enc = MOVImmEncoding::XOR32 { reg32: EAX };
let instr = enc.to_machine_instr();
assert!(instr.is_some());
let i = instr.unwrap();
assert_eq!(i.opcode, opc(X86Opcode::XOR));
assert!(matches!(&i.operands[0], MachineOperand::PhysReg(r) if *r == EAX));
}
#[test]
fn test_optimize_with_known_zero_upper() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut bb = make_bb("test");
let mut xor32 = MachineInstr::new(opc(X86Opcode::XOR));
xor32.operands.push(MachineOperand::PhysReg(EAX));
xor32.operands.push(MachineOperand::PhysReg(EAX));
bb.instructions.push(xor32);
let mut mov64 = MachineInstr::new(opc(X86Opcode::MOV));
mov64.operands.push(MachineOperand::PhysReg(RAX));
mov64.operands.push(MachineOperand::Imm(42));
bb.instructions.push(mov64);
opt.optimize_mov_with_known_zero_upper(&mut bb);
assert_eq!(opt.stats.mov_imm32_64bit_zero_ext, 1);
}
#[test]
fn test_is_conditional_jump() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let je = MachineInstr::new(opc(X86Opcode::JE));
assert!(opt.is_conditional_jump(&je));
let jne = MachineInstr::new(opc(X86Opcode::JNE));
assert!(opt.is_conditional_jump(&jne));
let jmp = MachineInstr::new(opc(X86Opcode::JMP));
assert!(!opt.is_conditional_jump(&jmp));
}
#[test]
fn test_jcc_to_cmov_cc() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
assert_eq!(opt.jcc_to_cmov_cc(opc(X86Opcode::JE)), 0x04);
assert_eq!(opt.jcc_to_cmov_cc(opc(X86Opcode::JNE)), 0x05);
assert_eq!(opt.jcc_to_cmov_cc(opc(X86Opcode::JB)), 0x02);
assert_eq!(opt.jcc_to_cmov_cc(opc(X86Opcode::JMP)), 0xFF);
}
#[test]
fn test_apply_optimal_mov_imm_encoding() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::PhysReg(RAX));
instr.operands.push(MachineOperand::Imm(0));
let result = opt.apply_optimal_mov_imm_encoding(&instr);
assert!(result.is_some());
let optimized = result.unwrap();
assert_eq!(optimized.opcode, opc(X86Opcode::XOR));
}
#[test]
fn test_identify_mov_to_cmov_candidates() {
let sub = make_sub();
let opt = X86MOVOptimizer::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::PhysReg(RBX));
bb.instructions.push(cmp);
let mut je = MachineInstr::new(opc(X86Opcode::JE));
je.operands.push(MachineOperand::Label("else".to_string()));
bb.instructions.push(je);
let mut mov_true = MachineInstr::new(opc(X86Opcode::MOV));
mov_true.operands.push(MachineOperand::PhysReg(RCX));
mov_true.operands.push(MachineOperand::Imm(1));
bb.instructions.push(mov_true);
let mut jmp = MachineInstr::new(opc(X86Opcode::JMP));
jmp.operands.push(MachineOperand::Label("end".to_string()));
bb.instructions.push(jmp);
let mut mov_false = MachineInstr::new(opc(X86Opcode::MOV));
mov_false.operands.push(MachineOperand::PhysReg(RCX));
mov_false.operands.push(MachineOperand::Imm(0));
bb.instructions.push(mov_false);
let candidates = opt.identify_mov_to_cmov_candidates(&bb);
assert_eq!(candidates.len(), 1);
assert!(candidates[0].can_convert);
}
#[test]
fn test_optimize_callee_save_restore_basic() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut bb = make_bb("test");
let mut save = MachineInstr::new(opc(X86Opcode::MOV));
save.operands.push(MachineOperand::PhysReg(RSP));
save.operands.push(MachineOperand::PhysReg(RBX));
bb.instructions.push(save);
let mut restore = MachineInstr::new(opc(X86Opcode::MOV));
restore.operands.push(MachineOperand::PhysReg(RBX));
restore.operands.push(MachineOperand::PhysReg(RSP));
bb.instructions.push(restore);
opt.optimize_callee_save_restore(&mut bb);
}
#[test]
fn test_run_full_pipeline() {
let sub = make_sub();
let mut opt = X86MOVOptimizer::new(&sub);
let mut mf = make_mf();
let mut bb = make_bb("entry");
let mut mov_nop = MachineInstr::new(opc(X86Opcode::MOV));
mov_nop.operands.push(MachineOperand::PhysReg(RAX));
mov_nop.operands.push(MachineOperand::PhysReg(RAX));
bb.instructions.push(mov_nop);
let mut mov_zero = MachineInstr::new(opc(X86Opcode::MOV));
mov_zero.operands.push(MachineOperand::PhysReg(RBX));
mov_zero.operands.push(MachineOperand::Imm(0));
bb.instructions.push(mov_zero);
let mut lea_nop = MachineInstr::new(opc(X86Opcode::LEA));
lea_nop.operands.push(MachineOperand::PhysReg(RCX));
lea_nop.operands.push(MachineOperand::PhysReg(RCX));
bb.instructions.push(lea_nop);
mf.blocks.push(bb);
opt.run_full_pipeline(&mut mf);
assert!(opt.stats.total_movs_eliminated > 0);
}
#[test]
fn test_mov_imm_encoding_all_variants() {
assert_eq!(MOVImmEncoding::NotApplicable.size_bytes(), 0);
assert_eq!(MOVImmEncoding::XOR32 { reg32: EAX }.size_bytes(), 2);
assert_eq!(
MOVImmEncoding::MOV16 {
reg16: AX,
value: 42
}
.size_bytes(),
4
);
assert_eq!(MOVImmEncoding::MOV8 { reg8: AL, value: 7 }.size_bytes(), 3);
assert_eq!(
MOVImmEncoding::MOV64Full {
reg64: RAX,
value: 0xDEADBEEF_CAFEBABE
}
.size_bytes(),
10
);
}
#[test]
fn test_count_movs_in_function() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut mf = make_mf();
let mut bb = make_bb("test");
for _ in 0..5 {
let mut m = MachineInstr::new(opc(X86Opcode::MOV));
m.operands.push(MachineOperand::PhysReg(RAX));
m.operands.push(MachineOperand::PhysReg(RBX));
bb.instructions.push(m);
}
mf.blocks.push(bb);
assert_eq!(opt.count_movs(&mf), 5);
}
#[test]
fn test_zero_candidates_count() {
let sub = make_sub();
let opt = X86MOVOptimizer::new(&sub);
let mut mf = make_mf();
let mut bb = make_bb("test");
let mut m1 = MachineInstr::new(opc(X86Opcode::MOV));
m1.operands.push(MachineOperand::PhysReg(RAX));
m1.operands.push(MachineOperand::Imm(0));
bb.instructions.push(m1);
let mut m2 = MachineInstr::new(opc(X86Opcode::MOV));
m2.operands.push(MachineOperand::PhysReg(RBX));
m2.operands.push(MachineOperand::Imm(0));
bb.instructions.push(m2);
mf.blocks.push(bb);
assert_eq!(opt.count_zero_mov_candidates(&mf), 2);
}
}
#[derive(Debug, Clone)]
pub struct X86MOVOptimizerPassManager {
pub optimizer: X86MOVOptimizer,
pub per_function_results: HashMap<String, MOVOptimizerStats>,
pub total_stats: MOVOptimizerStats,
pub callee_clobber_hints: HashMap<String, HashSet<PhysReg>>,
pub enable_ipa: bool,
}
impl X86MOVOptimizerPassManager {
pub fn new(subtarget: &X86Subtarget) -> Self {
Self {
optimizer: X86MOVOptimizer::new(subtarget),
per_function_results: HashMap::new(),
total_stats: MOVOptimizerStats::default(),
callee_clobber_hints: HashMap::new(),
enable_ipa: false,
}
}
pub fn optimize_function(&mut self, mf: &mut MachineFunction) {
self.optimizer.reset_stats();
self.optimizer.run_full_pipeline(mf);
let stats = self.optimizer.stats.clone();
self.per_function_results
.insert(mf.name.clone(), stats.clone());
self.total_stats.merge(&stats);
if self.enable_ipa {
self.collect_callee_hints(mf);
}
}
fn collect_callee_hints(&mut self, mf: &MachineFunction) {
let mut clobbered: HashSet<PhysReg> = HashSet::new();
for block in &mf.blocks {
for instr in &block.instructions {
if instr.opcode == opc(X86Opcode::MOV) && instr.operands.len() >= 1 {
if let Some(dst) = get_phys_reg(&instr.operands[0]) {
if dst == RBX || dst == RBP || (dst >= R12 && dst <= R15) {
clobbered.insert(dst);
}
}
}
if instr.opcode == opc(X86Opcode::POP) && instr.operands.len() >= 1 {
if let Some(dst) = get_phys_reg(&instr.operands[0]) {
if dst == RBX || dst == RBP || (dst >= R12 && dst <= R15) {
clobbered.insert(dst);
}
}
}
}
}
self.callee_clobber_hints.insert(mf.name.clone(), clobbered);
}
pub fn pass_report(&self) -> String {
let mut report = String::new();
report.push_str("╔══════════════════════════════════════════════════╗\n");
report.push_str("║ X86 MOV Optimizer — Pass Manager Report ║\n");
report.push_str("╠══════════════════════════════════════════════════╣\n");
report.push_str(&format!(
"║ Functions optimized: {:26} ║\n",
self.per_function_results.len()
));
report.push_str(&format!(
"║ IPA enabled: {:26} ║\n",
if self.enable_ipa { "yes" } else { "no" }
));
report.push_str("╠══════════════════════════════════════════════════╣\n");
report.push_str(&format!(
"║ Total MOVs eliminated: {:22} ║\n",
self.total_stats.total_movs_eliminated
));
report.push_str(&format!(
"║ Same-reg: {:22} ║\n",
self.total_stats.mov_same_reg_eliminated
));
report.push_str(&format!(
"║ Zero→XOR: {:22} ║\n",
self.total_stats.mov_zero_to_xor
));
report.push_str(&format!(
"║ Dead MOV elim: {:22} ║\n",
self.total_stats.mov_dead_eliminated
));
report.push_str(&format!(
"║ Folded into memop: {:22} ║\n",
self.total_stats.mov_folded_into_mem_op
));
report.push_str(&format!(
"║ MOV→LEA: {:22} ║\n",
self.total_stats.mov_to_lea
));
report.push_str(&format!(
"║ MOV→PUSH/POP: {:22} ║\n",
self.total_stats.mov_to_push_pop
));
report.push_str("╚══════════════════════════════════════════════════╝\n");
if self.enable_ipa {
report.push_str("\nCallee Clobber Hints:\n");
for (func, regs) in &self.callee_clobber_hints {
let reg_names: Vec<&str> = regs.iter().map(|r| reg_name(*r)).collect();
report.push_str(&format!(" {}: {}\n", func, reg_names.join(", ")));
}
}
report
}
}
impl X86MOVOptimizer {
pub fn analyze_mov_hotspots(&self, mf: &MachineFunction) -> Vec<(String, usize)> {
let mut hotspots: Vec<(String, usize)> = Vec::new();
for block in &mf.blocks {
let mut mov_count = 0;
for instr in &block.instructions {
if self.is_mov_instruction(instr) {
mov_count += 1;
}
}
if mov_count > 0 {
hotspots.push((block.name.clone(), mov_count));
}
}
hotspots.sort_by(|a, b| b.1.cmp(&a.1));
hotspots
}
pub fn report_mov_hotspots(&self, mf: &MachineFunction, top_n: usize) -> String {
let hotspots = self.analyze_mov_hotspots(mf);
let mut report = String::new();
report.push_str(&format!("Top {} MOV-heavy basic blocks:\n", top_n));
for (i, (name, count)) in hotspots.iter().take(top_n).enumerate() {
report.push_str(&format!(" {}. {}: {} MOVs\n", i + 1, name, count));
}
report
}
}
#[derive(Debug, Clone)]
pub struct RegisterPressureModel {
pub pressure_at: Vec<usize>,
pub max_pressure: usize,
pub allocatable_regs: HashSet<PhysReg>,
}
impl RegisterPressureModel {
pub fn new(num_instrs: usize, max_pressure: usize) -> Self {
Self {
pressure_at: vec![0; num_instrs],
max_pressure,
allocatable_regs: HashSet::new(),
}
}
pub fn can_eliminate_mov(&self, _instr_idx: usize) -> bool {
true }
pub fn record_elimination(&mut self, instr_idx: usize) {
if instr_idx < self.pressure_at.len() {
self.pressure_at[instr_idx] = self.pressure_at[instr_idx].saturating_sub(1);
}
}
}
#[derive(Debug, Clone)]
pub struct MOVOptDecision {
pub block_name: String,
pub instr_index: usize,
pub original_opcode: u32,
pub optimization_applied: String,
pub bytes_saved: isize,
}
impl X86MOVOptimizer {
pub fn log_decision(&self, decisions: &mut Vec<MOVOptDecision>, decision: MOVOptDecision) {
decisions.push(decision);
}
pub fn verify_after_optimization(&self, mf: &MachineFunction) -> Vec<String> {
let mut warnings: Vec<String> = Vec::new();
for block in &mf.blocks {
for (i, instr) in block.instructions.iter().enumerate() {
if self.is_mov_reg_reg(instr)
&& instr.operands.len() >= 2
&& same_reg(&instr.operands[0], &instr.operands[1])
{
warnings.push(format!(
"WARNING: Uneliminated MOV same-reg at {}:{} (opcode={})",
block.name, i, instr.opcode
));
}
if self.is_mov_reg_imm(instr)
&& instr.operands.len() >= 2
&& is_zero_imm(&instr.operands[1])
&& self.enable_xor_zeroing
{
warnings.push(format!(
"WARNING: Unconverted MOV-zero at {}:{} (opcode={})",
block.name, i, instr.opcode
));
}
}
}
warnings
}
}