use crate::ir::*;
pub fn optimize_instruction(instr: &mut Instruction, arch: Arch, level: OptLevel) -> bool {
if !matches!(arch, Arch::X86 | Arch::X86_64) || level == OptLevel::None {
return false;
}
let mut changed = false;
if level == OptLevel::Aggressive {
changed |= try_zero_idiom(instr);
}
changed |= try_mov_imm32_narrow(instr, arch);
changed |= try_rex_elimination(instr, arch);
changed |= try_test_conversion(instr);
changed
}
fn try_zero_idiom(instr: &mut Instruction) -> bool {
if instr.mnemonic != "mov" {
return false;
}
if instr.operands.len() != 2 {
return false;
}
let dst_reg = match &instr.operands[0] {
Operand::Register(r) => *r,
_ => return false,
};
let is_zero = matches!(&instr.operands[1], Operand::Immediate(0));
if !is_zero {
return false;
}
let bits = dst_reg.size_bits();
if bits != 32 && bits != 64 {
return false;
}
let xor_reg = if bits == 64 {
dst_reg.to_32bit()
} else {
Some(dst_reg)
};
if let Some(r32) = xor_reg {
instr.mnemonic = Mnemonic::from("xor");
instr.operands =
OperandList::from(alloc::vec![Operand::Register(r32), Operand::Register(r32)]);
instr.size_hint = None;
return true;
}
false
}
fn try_mov_imm32_narrow(instr: &mut Instruction, arch: Arch) -> bool {
if arch != Arch::X86_64 {
return false;
}
if instr.mnemonic != "mov" {
return false;
}
if instr.operands.len() != 2 {
return false;
}
let dst_reg = match &instr.operands[0] {
Operand::Register(r) => *r,
_ => return false,
};
if dst_reg.size_bits() != 64 {
return false;
}
let imm = match &instr.operands[1] {
Operand::Immediate(v) => *v,
_ => return false,
};
if !(0..=0xFFFF_FFFF).contains(&imm) {
return false;
}
if let Some(r32) = dst_reg.to_32bit() {
instr.operands[0] = Operand::Register(r32);
return true;
}
false
}
fn try_rex_elimination(instr: &mut Instruction, arch: Arch) -> bool {
if arch != Arch::X86_64 {
return false;
}
if instr.mnemonic != "and" {
return false;
}
if instr.operands.len() != 2 {
return false;
}
let dst_reg = match &instr.operands[0] {
Operand::Register(r) => *r,
_ => return false,
};
if dst_reg.size_bits() != 64 {
return false;
}
let imm = match &instr.operands[1] {
Operand::Immediate(v) => *v,
_ => return false,
};
if !(0..=0xFFFF_FFFF).contains(&imm) {
return false;
}
if let Some(r32) = dst_reg.to_32bit() {
instr.operands[0] = Operand::Register(r32);
return true;
}
false
}
fn try_test_conversion(instr: &mut Instruction) -> bool {
if instr.mnemonic != "and" {
return false;
}
if instr.operands.len() != 2 {
return false;
}
let r1 = match &instr.operands[0] {
Operand::Register(r) => r,
_ => return false,
};
let r2 = match &instr.operands[1] {
Operand::Register(r) => r,
_ => return false,
};
if r1 == r2 {
instr.mnemonic = Mnemonic::from("test");
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Span;
fn make_instr(mnemonic: &str, ops: Vec<Operand>) -> Instruction {
Instruction {
mnemonic: Mnemonic::from(mnemonic),
operands: OperandList::from(ops),
size_hint: None,
prefixes: PrefixList::new(),
opmask: None,
zeroing: false,
broadcast: None,
span: Span::dummy(),
}
}
#[test]
fn zero_idiom_mov_eax_0() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(0)],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Aggressive
));
assert_eq!(instr.mnemonic, "xor");
assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
assert_eq!(instr.operands[1], Operand::Register(Register::Eax));
}
#[test]
fn zero_idiom_mov_rax_0() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(0)],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Aggressive
));
assert_eq!(instr.mnemonic, "xor");
assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
}
#[test]
fn zero_idiom_mov_r12_0() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::R12), Operand::Immediate(0)],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Aggressive
));
assert_eq!(instr.mnemonic, "xor");
assert_eq!(instr.operands[0], Operand::Register(Register::R12d));
}
#[test]
fn zero_idiom_not_applied_nonzero() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(1)],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Aggressive
));
assert_eq!(instr.mnemonic, "mov");
}
#[test]
fn zero_idiom_not_applied_8bit() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Al), Operand::Immediate(0)],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Aggressive
));
assert_eq!(instr.mnemonic, "mov");
}
#[test]
fn mov_imm32_narrow_rax_1() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(1)],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
assert_eq!(instr.operands[1], Operand::Immediate(1));
}
#[test]
fn mov_imm32_narrow_rax_max_u32() {
let mut instr = make_instr(
"mov",
alloc::vec![
Operand::Register(Register::Rax),
Operand::Immediate(0xFFFF_FFFF),
],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
}
#[test]
fn mov_imm32_narrow_not_applied_negative() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(-1)],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
}
#[test]
fn mov_imm32_narrow_not_applied_large() {
let mut instr = make_instr(
"mov",
alloc::vec![
Operand::Register(Register::Rax),
Operand::Immediate(0x1_0000_0000),
],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
}
#[test]
fn test_conversion_and_self() {
let mut instr = make_instr(
"and",
alloc::vec![
Operand::Register(Register::Eax),
Operand::Register(Register::Eax),
],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
assert_eq!(instr.mnemonic, "test");
}
#[test]
fn test_conversion_not_applied_different_regs() {
let mut instr = make_instr(
"and",
alloc::vec![
Operand::Register(Register::Eax),
Operand::Register(Register::Ebx),
],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
assert_eq!(instr.mnemonic, "and");
}
#[test]
fn rex_elim_and_rax_0xff() {
let mut instr = make_instr(
"and",
alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(0xFF)],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
assert_eq!(instr.mnemonic, "and");
assert_eq!(instr.operands[0], Operand::Register(Register::Eax));
assert_eq!(instr.operands[1], Operand::Immediate(0xFF));
}
#[test]
fn rex_elim_and_r12_u32_max() {
let mut instr = make_instr(
"and",
alloc::vec![
Operand::Register(Register::R12),
Operand::Immediate(0xFFFF_FFFF),
],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
assert_eq!(instr.operands[0], Operand::Register(Register::R12d));
}
#[test]
fn rex_elim_and_not_applied_negative() {
let mut instr = make_instr(
"and",
alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(-1)],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
}
#[test]
fn rex_elim_and_not_applied_large() {
let mut instr = make_instr(
"and",
alloc::vec![
Operand::Register(Register::Rax),
Operand::Immediate(0x1_0000_0000),
],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
}
#[test]
fn rex_elim_not_applied_to_add() {
let mut instr = make_instr(
"and", alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(5)],
);
assert!(optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
let mut instr2 = make_instr(
"add",
alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(5)],
);
assert!(!optimize_instruction(
&mut instr2,
Arch::X86_64,
OptLevel::Size
));
}
#[test]
fn zero_idiom_not_applied_at_default_level() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(0)],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::Size
));
assert_eq!(instr.mnemonic, "mov");
}
#[test]
fn opt_level_none_disables_everything() {
let mut instr = make_instr(
"mov",
alloc::vec![Operand::Register(Register::Rax), Operand::Immediate(1)],
);
assert!(!optimize_instruction(
&mut instr,
Arch::X86_64,
OptLevel::None
));
assert_eq!(instr.operands[0], Operand::Register(Register::Rax));
}
#[test]
fn rex_elim_not_applied_32bit_arch() {
let mut instr = make_instr(
"and",
alloc::vec![Operand::Register(Register::Eax), Operand::Immediate(0xFF)],
);
assert!(!optimize_instruction(&mut instr, Arch::X86, OptLevel::Size));
}
}