use crate::codegen::{
MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand, PhysReg, VirtReg,
};
use crate::x86::{X86InstrInfo, X86Opcode, X86OptStats, X86Subtarget};
pub use crate::x86::X86PeepholeOptimizer;
use std::collections::{HashMap, HashSet, VecDeque};
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 opcode_from_u32(val: u32) -> Option<X86Opcode> {
if val <= X86Opcode::CLFLUSHOPT_64 as u32 {
Some(unsafe { std::mem::transmute(val as u16) })
} else {
None
}
}
fn x86_opcode(instr: &MachineInstr) -> Option<X86Opcode> {
opcode_from_u32(instr.opcode)
}
fn is_phys_reg(op: &MachineOperand, reg: PhysReg) -> bool {
matches!(op, MachineOperand::PhysReg(r) if *r == reg)
}
fn is_reg(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::Reg(_) | MachineOperand::PhysReg(_))
}
fn get_reg(op: &MachineOperand) -> Option<u32> {
match op {
MachineOperand::Reg(r) => Some(*r),
MachineOperand::PhysReg(r) => Some(*r),
_ => 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 get_imm(op: &MachineOperand) -> Option<i64> {
match op {
MachineOperand::Imm(v) => Some(*v),
_ => None,
}
}
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 get_32bit_subreg(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 is_gpr64(reg: PhysReg) -> bool {
reg <= R15
}
fn is_gpr32(reg: PhysReg) -> bool {
(EAX..=R15D).contains(®)
}
#[derive(Debug, Clone)]
pub struct X86Combiner {
pub stats: CombineStats,
pub instr_info: X86InstrInfo,
pub enable_load_fold: bool,
pub enable_lea_opt: bool,
pub enable_imm_fold: bool,
pub enable_zero_idiom: bool,
}
#[derive(Debug, Clone, Default)]
pub struct CombineStats {
pub loads_folded: u64,
pub leas_simplified: u64,
pub immediates_folded: u64,
pub moves_eliminated: u64,
pub zero_idioms_recognized: u64,
pub extensions_optimized: u64,
pub shifts_combined: u64,
pub total_combines: u64,
}
impl CombineStats {
pub fn new() -> Self {
Self::default()
}
pub fn merge(&mut self, other: &CombineStats) {
self.loads_folded += other.loads_folded;
self.leas_simplified += other.leas_simplified;
self.immediates_folded += other.immediates_folded;
self.moves_eliminated += other.moves_eliminated;
self.zero_idioms_recognized += other.zero_idioms_recognized;
self.extensions_optimized += other.extensions_optimized;
self.shifts_combined += other.shifts_combined;
self.total_combines += other.total_combines;
}
pub fn made_progress(&self) -> bool {
self.total_combines > 0
}
}
impl X86Combiner {
pub fn new(instr_info: X86InstrInfo) -> Self {
Self {
stats: CombineStats::new(),
instr_info,
enable_load_fold: true,
enable_lea_opt: true,
enable_imm_fold: true,
enable_zero_idiom: true,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> CombineStats {
let mut total_stats = CombineStats::new();
for block in &mut mf.blocks {
let block_stats = self.run_on_block(block);
total_stats.merge(&block_stats);
}
self.stats.merge(&total_stats);
total_stats
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> CombineStats {
let mut stats = CombineStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let mut made_change = false;
if self.enable_zero_idiom {
if let Some(new_stats) = self.try_zero_idiom(&mbb.instructions, i) {
stats.merge(&new_stats);
made_change = true;
}
}
if self.enable_imm_fold && !made_change {
if let Some(new_stats) = self.try_fold_immediate(mbb, i) {
stats.merge(&new_stats);
made_change = true;
}
}
if self.enable_lea_opt && !made_change {
if let Some(new_stats) = self.try_optimize_lea(mbb, i) {
stats.merge(&new_stats);
made_change = true;
}
}
if self.enable_load_fold && !made_change {
if let Some(new_stats) = self.try_fold_load(mbb, i) {
stats.merge(&new_stats);
made_change = true;
}
}
if !made_change {
if let Some(new_stats) = self.try_eliminate_redundant_move(mbb, i) {
stats.merge(&new_stats);
made_change = true;
}
}
if !made_change {
if let Some(new_stats) = self.try_optimize_extension(mbb, i) {
stats.merge(&new_stats);
made_change = true;
}
}
if !made_change {
if let Some(new_stats) = self.try_combine_shifts(mbb, i) {
stats.merge(&new_stats);
made_change = true;
}
}
if made_change {
stats.total_combines += 1;
} else {
i += 1;
}
}
stats
}
fn try_zero_idiom(&mut self, instrs: &[MachineInstr], idx: usize) -> Option<CombineStats> {
if idx >= instrs.len() {
return None;
}
let instr = &instrs[idx];
let op = x86_opcode(instr)?;
let is_sub_self = matches!(op, X86Opcode::SUB)
&& instr.operands.len() >= 2
&& same_reg(&instr.operands[0], &instr.operands[1]);
let is_xor_self = matches!(op, X86Opcode::XOR)
&& instr.operands.len() >= 2
&& same_reg(&instr.operands[0], &instr.operands[1]);
if is_sub_self {
let mut stats = CombineStats::new();
stats.zero_idioms_recognized += 1;
return Some(stats);
}
if is_xor_self {
let mut stats = CombineStats::new();
stats.zero_idioms_recognized += 1;
return Some(stats);
}
None
}
fn try_fold_immediate(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
if idx + 1 >= mbb.instructions.len() {
return None;
}
let mov_instr = &mbb.instructions[idx];
let alu_instr = &mbb.instructions[idx + 1];
let mov_op = x86_opcode(mov_instr)?;
if !matches!(mov_op, X86Opcode::MOV) {
return None;
}
if mov_instr.operands.len() < 2 {
return None;
}
let mov_dst = &mov_instr.operands[0];
let mov_src = &mov_instr.operands[1];
if !is_reg(mov_dst) || !matches!(mov_src, MachineOperand::Imm(_)) {
return None;
}
let imm_val = get_imm(mov_src)?;
let alu_op = x86_opcode(alu_instr)?;
if !is_commutative_alu(alu_op) && !is_non_commutative_alu(alu_op) {
return None;
}
if alu_instr.operands.len() < 2 {
return None;
}
let alu_src1 = &alu_instr.operands[0];
let alu_src2 = if alu_instr.operands.len() >= 2 {
&alu_instr.operands[1]
} else {
return None;
};
let folds_with_src1 = same_reg(mov_dst, alu_src1) && is_reg(alu_src2);
let folds_with_src2 = same_reg(mov_dst, alu_src2) && is_reg(alu_src1);
if !folds_with_src1 && !folds_with_src2 {
return None;
}
if !fits_i32(imm_val) {
return None;
}
let mov_dst_reg = get_reg(mov_dst)?;
let mut used_elsewhere = false;
for (j, instr) in mbb.instructions.iter().enumerate() {
if j == idx || j == idx + 1 {
continue;
}
for op in &instr.operands {
if get_reg(op) == Some(mov_dst_reg) {
used_elsewhere = true;
break;
}
}
if instr.def == Some(mov_dst_reg) {
break;
}
if used_elsewhere {
break;
}
}
if used_elsewhere {
return None;
}
let other_reg = if folds_with_src1 {
alu_src2.clone()
} else {
alu_src1.clone()
};
let mut new_instr = MachineInstr::new(opc(alu_op));
new_instr.operands.push(alu_instr.operands[0].clone()); new_instr.operands.push(other_reg); new_instr.operands.push(MachineOperand::Imm(imm_val)); new_instr.def = alu_instr.def;
mbb.instructions.remove(idx + 1);
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.immediates_folded += 1;
stats.moves_eliminated += 1;
Some(stats)
}
fn try_optimize_lea(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
let instr = &mbb.instructions[idx];
let op = x86_opcode(instr)?;
if !matches!(op, X86Opcode::LEA) {
return None;
}
if instr.operands.len() < 4 {
return None;
}
let dst = &instr.operands[0];
if instr.operands.len() == 2 && is_reg(&instr.operands[1]) {
let base = &instr.operands[1];
let mut new_instr = MachineInstr::new(opc(X86Opcode::MOV));
new_instr.operands.push(dst.clone());
new_instr.operands.push(base.clone());
new_instr.def = instr.def;
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.leas_simplified += 1;
return Some(stats);
}
if instr.operands.len() == 3
&& is_reg(&instr.operands[1])
&& is_zero_imm(&instr.operands[2])
{
let mut new_instr = MachineInstr::new(opc(X86Opcode::MOV));
new_instr.operands.push(dst.clone());
new_instr.operands.push(instr.operands[1].clone());
new_instr.def = instr.def;
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.leas_simplified += 1;
return Some(stats);
}
None
}
fn try_fold_load(&mut self, mbb: &mut MachineBasicBlock, idx: usize) -> Option<CombineStats> {
if idx + 1 >= mbb.instructions.len() {
return None;
}
let load_instr = &mbb.instructions[idx];
let load_op = x86_opcode(load_instr)?;
if !matches!(load_op, X86Opcode::MOV) {
return None;
}
if load_instr.operands.len() < 2 {
return None;
}
if !is_reg(&load_instr.operands[0]) {
return None;
}
let alu_instr = &mbb.instructions[idx + 1];
let alu_op = x86_opcode(alu_instr)?;
if !is_commutative_alu(alu_op) && !is_non_commutative_alu(alu_op) {
return None;
}
let load_dst_reg = get_reg(&load_instr.operands[0])?;
if alu_instr.operands.len() < 2 {
return None;
}
let alu_uses_load_dst = alu_instr
.operands
.iter()
.any(|op| get_reg(op) == Some(load_dst_reg));
if !alu_uses_load_dst {
return None;
}
let mut used_elsewhere = false;
for (j, instr) in mbb.instructions.iter().enumerate() {
if j == idx || j == idx + 1 {
continue;
}
for op in &instr.operands {
if get_reg(op) == Some(load_dst_reg) {
used_elsewhere = true;
break;
}
}
if instr.def == Some(load_dst_reg) {
break;
}
if used_elsewhere {
break;
}
}
if used_elsewhere {
return None;
}
let mut stats = CombineStats::new();
stats.loads_folded += 1;
mbb.instructions.remove(idx);
Some(stats)
}
fn try_eliminate_redundant_move(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
let instr = &mbb.instructions[idx];
let op = x86_opcode(instr)?;
if !matches!(op, X86Opcode::MOV) {
return None;
}
if instr.operands.len() < 2 {
return None;
}
let dst = &instr.operands[0];
let src = &instr.operands[1];
if same_reg(dst, src) {
mbb.instructions.remove(idx);
let mut stats = CombineStats::new();
stats.moves_eliminated += 1;
return Some(stats);
}
let dst_reg = get_reg(dst)?;
let mut has_use_after = false;
for (j, later_instr) in mbb.instructions.iter().enumerate().skip(idx + 1) {
for op in &later_instr.operands {
if get_reg(op) == Some(dst_reg) {
has_use_after = true;
break;
}
}
if later_instr.def == Some(dst_reg) {
break;
}
if has_use_after {
break;
}
}
if !has_use_after {
mbb.instructions.remove(idx);
let mut stats = CombineStats::new();
stats.moves_eliminated += 1;
return Some(stats);
}
None
}
fn try_optimize_extension(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
let instr = &mbb.instructions[idx];
let op = x86_opcode(instr)?;
if !matches!(op, X86Opcode::MOVSX | X86Opcode::MOVZX) {
return None;
}
if idx + 1 < mbb.instructions.len() {
let next_instr = &mbb.instructions[idx + 1];
let next_op = x86_opcode(next_instr)?;
if matches!(next_op, X86Opcode::MOV)
&& next_instr.operands.len() >= 2
&& instr.def.is_some()
{
let ext_dst = instr.def?;
if get_reg(&next_instr.operands[1]) == Some(ext_dst)
&& is_reg(&next_instr.operands[0])
{
let mut stats = CombineStats::new();
stats.extensions_optimized += 1;
return Some(stats);
}
}
}
None
}
fn try_combine_shifts(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
if idx + 1 >= mbb.instructions.len() {
return None;
}
let first = &mbb.instructions[idx];
let second = &mbb.instructions[idx + 1];
let first_op = x86_opcode(first)?;
let second_op = x86_opcode(second)?;
if first_op != second_op {
return None;
}
let is_shift = matches!(first_op, X86Opcode::SHL | X86Opcode::SHR | X86Opcode::SAR);
if !is_shift {
return None;
}
if first.operands.len() < 2 || second.operands.len() < 2 {
return None;
}
if !same_reg(&first.operands[0], &second.operands[0]) {
return None;
}
let cnt1 = get_imm(&first.operands[1])?;
let cnt2 = get_imm(&second.operands[1])?;
let total = cnt1 + cnt2;
if total > 63 {
return None;
}
let mut new_instr = MachineInstr::new(opc(first_op));
new_instr.operands.push(first.operands[0].clone());
new_instr.operands.push(MachineOperand::Imm(total));
new_instr.def = second.def;
mbb.instructions.remove(idx + 1);
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.shifts_combined += 1;
Some(stats)
}
}
fn is_commutative_alu(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::ADD
| X86Opcode::ADC
| X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
| X86Opcode::MUL
| X86Opcode::IMUL
| X86Opcode::VADDPS
| X86Opcode::VADDPD
| X86Opcode::VMULPS
| X86Opcode::VMULPD
| X86Opcode::VANDPS
| X86Opcode::VORPS
| X86Opcode::VXORPS
)
}
fn is_non_commutative_alu(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::SUB
| X86Opcode::SBB
| X86Opcode::DIV
| X86Opcode::IDIV
| X86Opcode::VSUBPS
| X86Opcode::VSUBPD
| X86Opcode::VDIVPS
| X86Opcode::VDIVPD
)
}
fn is_compare_op(op: X86Opcode) -> bool {
matches!(op, X86Opcode::CMP | X86Opcode::TEST)
}
fn is_conditional_branch(op: X86Opcode) -> bool {
op.is_jcc()
}
fn is_unconditional_branch(op: X86Opcode) -> bool {
matches!(op, X86Opcode::JMP | X86Opcode::RET)
}
fn defines_flags(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::ADD
| X86Opcode::ADC
| X86Opcode::SUB
| X86Opcode::SBB
| X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
| X86Opcode::CMP
| X86Opcode::TEST
| X86Opcode::INC
| X86Opcode::DEC
| X86Opcode::NEG
| X86Opcode::MUL
| X86Opcode::IMUL
| X86Opcode::SHL
| X86Opcode::SHR
| X86Opcode::SAR
)
}
fn uses_flags(op: X86Opcode) -> bool {
op.is_jcc() || op.is_cmov() || op.is_setcc() || matches!(op, X86Opcode::ADC | X86Opcode::SBB)
}
fn preserves_carry(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::INC | X86Opcode::DEC | X86Opcode::NEG | X86Opcode::NOT
)
}
fn is_bitwise(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::AND | X86Opcode::OR | X86Opcode::XOR | X86Opcode::NOT
)
}
fn is_arithmetic(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::ADD
| X86Opcode::SUB
| X86Opcode::ADC
| X86Opcode::SBB
| X86Opcode::MUL
| X86Opcode::IMUL
| X86Opcode::DIV
| X86Opcode::IDIV
)
}
fn is_shift(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::SHL
| X86Opcode::SHR
| X86Opcode::SAR
| X86Opcode::ROL
| X86Opcode::ROR
| X86Opcode::RCL
| X86Opcode::RCR
| X86Opcode::SHLD
| X86Opcode::SHRD
)
}
#[derive(Debug, Clone)]
pub struct X86CmpElimination {
pub stats: CmpElimStats,
}
#[derive(Debug, Clone, Default)]
pub struct CmpElimStats {
pub cmp_eliminated: u64,
pub cmp_simplified: u64,
pub test_replaced: u64,
pub redundant_flags_removed: u64,
}
impl CmpElimStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.cmp_eliminated
+ self.cmp_simplified
+ self.test_replaced
+ self.redundant_flags_removed
> 0
}
}
impl X86CmpElimination {
pub fn new() -> Self {
Self {
stats: CmpElimStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> CmpElimStats {
let mut total = CmpElimStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.cmp_eliminated += bs.cmp_eliminated;
total.cmp_simplified += bs.cmp_simplified;
total.test_replaced += bs.test_replaced;
total.redundant_flags_removed += bs.redundant_flags_removed;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> CmpElimStats {
let mut stats = CmpElimStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let op = match x86_opcode(&mbb.instructions[i]) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if is_compare_op(op) {
if i > 0 {
let prev_instr = &mbb.instructions[i - 1];
let prev_op = match x86_opcode(prev_instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if defines_flags(prev_op)
&& !is_compare_op(prev_op)
&& self.can_eliminate_cmp_after(mbb, i - 1, op, &mbb.instructions[i])
{
if self.cmp_matches_prev_alu(&mbb.instructions[i], prev_instr, prev_op) {
mbb.instructions.remove(i);
stats.cmp_eliminated += 1;
continue;
}
}
}
if self.try_simplify_cmp_to_test(mbb, i, op) {
stats.cmp_simplified += 1;
continue;
}
if self.try_replace_self_op_with_test(mbb, i, op) {
stats.test_replaced += 1;
continue;
}
}
if op == X86Opcode::SUB
&& mbb.instructions[i].operands.len() >= 2
&& same_reg(
&mbb.instructions[i].operands[0],
&mbb.instructions[i].operands[1],
)
{
if i + 1 < mbb.instructions.len() {
let next_instr = &mbb.instructions[i + 1];
if let Some(next_op) = x86_opcode(next_instr) {
if next_op.is_jcc() {
let mut new_instr = MachineInstr::new(opc(X86Opcode::XOR));
new_instr
.operands
.push(mbb.instructions[i].operands[0].clone());
new_instr
.operands
.push(mbb.instructions[i].operands[0].clone());
new_instr.def = mbb.instructions[i].def;
mbb.instructions[i] = new_instr;
stats.redundant_flags_removed += 1;
i += 1;
continue;
}
}
}
}
i += 1;
}
stats
}
fn can_eliminate_cmp_after(
&self,
mbb: &MachineBasicBlock,
alu_idx: usize,
_cmp_op: X86Opcode,
_cmp_instr: &MachineInstr,
) -> bool {
for j in (alu_idx + 1)..mbb.instructions.len() {
let check_instr = &mbb.instructions[j];
if let Some(check_op) = x86_opcode(check_instr) {
if check_op == X86Opcode::CMP || check_op == X86Opcode::TEST {
break;
}
if defines_flags(check_op) {
return false;
}
}
}
true
}
fn cmp_matches_prev_alu(
&self,
cmp_instr: &MachineInstr,
alu_instr: &MachineInstr,
alu_op: X86Opcode,
) -> bool {
if cmp_instr.operands.len() >= 2 {
let cmp_dst = &cmp_instr.operands[0];
let cmp_src = &cmp_instr.operands[1];
if is_zero_imm(cmp_src) && alu_instr.operands.len() >= 1 {
let alu_dst = &alu_instr.operands[0];
if same_reg(cmp_dst, alu_dst) {
return true;
}
}
if same_reg(cmp_dst, cmp_src) && alu_instr.operands.len() >= 1 {
let alu_dst = &alu_instr.operands[0];
if same_reg(cmp_dst, alu_dst) && is_commutative_alu(alu_op) {
return true;
}
}
}
false
}
fn try_simplify_cmp_to_test(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
op: X86Opcode,
) -> bool {
if op != X86Opcode::CMP {
return false;
}
let instr = &mbb.instructions[idx];
if instr.operands.len() >= 2 {
let src = &instr.operands[1];
if is_zero_imm(src) && is_reg(&instr.operands[0]) {
let dst = instr.operands[0].clone();
let mut new_instr = MachineInstr::new(opc(X86Opcode::TEST));
new_instr.operands.push(dst.clone());
new_instr.operands.push(dst);
mbb.instructions[idx] = new_instr;
return true;
}
}
false
}
fn try_replace_self_op_with_test(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
op: X86Opcode,
) -> bool {
if !matches!(op, X86Opcode::OR | X86Opcode::AND) {
return false;
}
let instr = &mbb.instructions[idx];
if instr.operands.len() >= 2 {
let dst = &instr.operands[0];
let src = &instr.operands[1];
if same_reg(dst, src) && is_reg(dst) {
let reg_val = get_reg(dst).unwrap_or(0);
let mut result_used = false;
for later in mbb.instructions.iter().skip(idx + 1) {
if let Some(def) = later.def {
if def == reg_val as u32 {
break;
}
}
for later_op in &later.operands {
if get_reg(later_op) == Some(reg_val) {
result_used = true;
break;
}
}
if result_used {
break;
}
}
if !result_used {
let mut new_instr = MachineInstr::new(opc(X86Opcode::TEST));
new_instr.operands.push(dst.clone());
new_instr.operands.push(dst.clone());
mbb.instructions[idx] = new_instr;
return true;
}
}
}
false
}
}
impl Default for X86CmpElimination {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FloatingPointOptimizer {
pub stats: FPOptStats,
pub prefer_sse: bool,
pub enable_x87_to_sse: bool,
pub enable_fxch_elim: bool,
}
#[derive(Debug, Clone, Default)]
pub struct FPOptStats {
pub x87_to_sse: u64,
pub fxch_eliminated: u64,
pub fstp_eliminated: u64,
pub packed_to_scalar: u64,
pub roundtrip_eliminated: u64,
pub conversions_merged: u64,
}
impl FPOptStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.x87_to_sse
+ self.fxch_eliminated
+ self.fstp_eliminated
+ self.packed_to_scalar
+ self.roundtrip_eliminated
+ self.conversions_merged
> 0
}
}
impl X86FloatingPointOptimizer {
pub fn new() -> Self {
Self {
stats: FPOptStats::new(),
prefer_sse: true,
enable_x87_to_sse: true,
enable_fxch_elim: true,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> FPOptStats {
let mut total = FPOptStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.x87_to_sse += bs.x87_to_sse;
total.fxch_eliminated += bs.fxch_eliminated;
total.fstp_eliminated += bs.fstp_eliminated;
total.packed_to_scalar += bs.packed_to_scalar;
total.roundtrip_eliminated += bs.roundtrip_eliminated;
total.conversions_merged += bs.conversions_merged;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> FPOptStats {
let mut stats = FPOptStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if self.enable_fxch_elim && op == X86Opcode::FXCH {
if i + 1 < mbb.instructions.len() {
if let Some(next_op) = x86_opcode(&mbb.instructions[i + 1]) {
if next_op == X86Opcode::FXCH {
mbb.instructions.remove(i + 1);
mbb.instructions.remove(i);
stats.fxch_eliminated += 2;
continue;
}
}
}
}
if op == X86Opcode::CVTSS2SD && i + 1 < mbb.instructions.len() {
if let Some(next_op) = x86_opcode(&mbb.instructions[i + 1]) {
if next_op == X86Opcode::CVTSD2SS {
let first = &mbb.instructions[i];
let second = &mbb.instructions[i + 1];
if first.def.is_some()
&& first.operands.len() >= 1
&& second.operands.len() >= 1
&& is_reg(&first.operands[0])
&& is_reg(&second.operands[0])
{
let intermediate = first.def.unwrap();
let mut only_used_by_second = true;
for (j, check) in mbb.instructions.iter().enumerate() {
if j == i || j == i + 1 {
continue;
}
if check
.operands
.iter()
.any(|op| get_reg(op) == Some(intermediate))
{
only_used_by_second = false;
break;
}
}
if only_used_by_second {
if let Some(second_def) = second.def {
let mut mov = MachineInstr::new(opc(X86Opcode::MOVSS));
mov.operands.push(MachineOperand::Reg(second_def));
mov.operands.push(MachineOperand::Reg(intermediate));
mov.def = Some(second_def);
mbb.instructions[i + 1] = mov;
stats.roundtrip_eliminated += 1;
}
}
}
}
}
}
if self.enable_x87_to_sse && op == X86Opcode::FSTP && i + 1 < mbb.instructions.len() {
if let Some(next_op) = x86_opcode(&mbb.instructions[i + 1]) {
if next_op == X86Opcode::FLD {
stats.fstp_eliminated += 1;
}
}
}
i += 1;
}
stats
}
}
impl Default for X86FloatingPointOptimizer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86MacroFusion {
pub stats: MacroFusionStats,
pub fused_pairs: Vec<(usize, usize)>, }
#[derive(Debug, Clone, Default)]
pub struct MacroFusionStats {
pub cmp_jcc_fusions: u64,
pub test_jcc_fusions: u64,
pub alu_jcc_fusions: u64,
pub incdec_jcc_fusions: u64,
pub total_fusions: u64,
}
impl MacroFusionStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.total_fusions > 0
}
}
impl X86MacroFusion {
pub fn new() -> Self {
Self {
stats: MacroFusionStats::new(),
fused_pairs: Vec::new(),
}
}
pub fn run_on_function(&mut self, mf: &MachineFunction) -> MacroFusionStats {
let mut stats = MacroFusionStats::new();
for block in mf.blocks.iter() {
let bs = self.analyze_block(block);
stats.cmp_jcc_fusions += bs.cmp_jcc_fusions;
stats.test_jcc_fusions += bs.test_jcc_fusions;
stats.alu_jcc_fusions += bs.alu_jcc_fusions;
stats.incdec_jcc_fusions += bs.incdec_jcc_fusions;
stats.total_fusions += bs.total_fusions;
}
self.stats = stats.clone();
stats
}
pub fn analyze_block(&mut self, mbb: &MachineBasicBlock) -> MacroFusionStats {
let mut stats = MacroFusionStats::new();
for i in 0..mbb.instructions.len().saturating_sub(1) {
let first = &mbb.instructions[i];
let second = &mbb.instructions[i + 1];
let first_op = match x86_opcode(first) {
Some(o) => o,
None => continue,
};
let second_op = match x86_opcode(second) {
Some(o) => o,
None => continue,
};
if !second_op.is_jcc() {
continue;
}
match first_op {
X86Opcode::CMP => {
if self.can_fuse_cmp_jcc(first, second) {
stats.cmp_jcc_fusions += 1;
stats.total_fusions += 1;
self.fused_pairs.push((i, i + 1));
}
}
X86Opcode::TEST => {
if self.can_fuse_test_jcc(first, second) {
stats.test_jcc_fusions += 1;
stats.total_fusions += 1;
self.fused_pairs.push((i, i + 1));
}
}
X86Opcode::ADD | X86Opcode::SUB => {
if self.can_fuse_alu_jcc(first, second) {
stats.alu_jcc_fusions += 1;
stats.total_fusions += 1;
self.fused_pairs.push((i, i + 1));
}
}
X86Opcode::INC | X86Opcode::DEC => {
if self.can_fuse_incdec_jcc(first, second) {
stats.incdec_jcc_fusions += 1;
stats.total_fusions += 1;
self.fused_pairs.push((i, i + 1));
}
}
X86Opcode::AND | X86Opcode::OR | X86Opcode::XOR => {
if self.can_fuse_logic_jcc(first) {
stats.alu_jcc_fusions += 1;
stats.total_fusions += 1;
self.fused_pairs.push((i, i + 1));
}
}
_ => {}
}
}
stats
}
fn can_fuse_cmp_jcc(&self, cmp: &MachineInstr, _jcc: &MachineInstr) -> bool {
if cmp.operands.len() >= 2 {
if !is_reg(&cmp.operands[0]) {
return false; }
let src = &cmp.operands[1];
return true;
}
false
}
fn can_fuse_test_jcc(&self, test: &MachineInstr, _jcc: &MachineInstr) -> bool {
if test.operands.len() >= 2 {
if !is_reg(&test.operands[0]) {
return false;
}
return true;
}
false
}
fn can_fuse_alu_jcc(&self, alu: &MachineInstr, _jcc: &MachineInstr) -> bool {
if alu.operands.len() >= 2 {
return is_reg(&alu.operands[0]);
}
false
}
fn can_fuse_incdec_jcc(&self, incdec: &MachineInstr, _jcc: &MachineInstr) -> bool {
if incdec.operands.len() >= 1 {
return is_reg(&incdec.operands[0]);
}
false
}
fn can_fuse_logic_jcc(&self, logic: &MachineInstr) -> bool {
if logic.operands.len() >= 2 {
return is_reg(&logic.operands[0]);
}
false
}
}
impl Default for X86MacroFusion {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86MicroFusion {
pub stats: MicroFusionStats,
}
#[derive(Debug, Clone, Default)]
pub struct MicroFusionStats {
pub load_alu_fusions: u64,
pub alu_store_fusions: u64,
pub load_store_fusions: u64,
pub total_fusions: u64,
}
impl MicroFusionStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.total_fusions > 0
}
}
impl X86MicroFusion {
pub fn new() -> Self {
Self {
stats: MicroFusionStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &MachineFunction) -> MicroFusionStats {
let mut stats = MicroFusionStats::new();
for block in mf.blocks.iter() {
let bs = self.analyze_block(block);
stats.load_alu_fusions += bs.load_alu_fusions;
stats.alu_store_fusions += bs.alu_store_fusions;
stats.load_store_fusions += bs.load_store_fusions;
stats.total_fusions += bs.total_fusions;
}
self.stats = stats.clone();
stats
}
pub fn analyze_block(&self, mbb: &MachineBasicBlock) -> MicroFusionStats {
let mut stats = MicroFusionStats::new();
for i in 0..mbb.instructions.len().saturating_sub(1) {
let first = &mbb.instructions[i];
let second = &mbb.instructions[i + 1];
let first_op = match x86_opcode(first) {
Some(o) => o,
None => continue,
};
let _second_op = match x86_opcode(second) {
Some(o) => o,
None => continue,
};
if first_op == X86Opcode::MOV && first.def.is_some() {
let load_def = first.def.unwrap();
let used_by_next = second
.operands
.iter()
.any(|op| get_reg(op) == Some(load_def));
let mut only_use = true;
for (j, check) in mbb.instructions.iter().enumerate() {
if j == i || j == i + 1 {
continue;
}
if check
.operands
.iter()
.any(|op| get_reg(op) == Some(load_def))
{
only_use = false;
break;
}
}
if used_by_next
&& only_use
&& second.operands.len() >= 2
&& is_reg(&second.operands[0])
{
stats.load_alu_fusions += 1;
stats.total_fusions += 1;
}
}
}
stats
}
}
impl Default for X86MicroFusion {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86PartialRegUpdate {
pub stats: PartialRegStats,
}
#[derive(Debug, Clone, Default)]
pub struct PartialRegStats {
pub partial_writes_eliminated: u64,
pub zext_inserted: u64,
pub xor_deps_broken: u64,
pub stalls_prevented: u64,
}
impl PartialRegStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.partial_writes_eliminated > 0 || self.zext_inserted > 0 || self.xor_deps_broken > 0
}
}
impl X86PartialRegUpdate {
pub fn new() -> Self {
Self {
stats: PartialRegStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> PartialRegStats {
let mut total = PartialRegStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.partial_writes_eliminated += bs.partial_writes_eliminated;
total.zext_inserted += bs.zext_inserted;
total.xor_deps_broken += bs.xor_deps_broken;
total.stalls_prevented += bs.stalls_prevented;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> PartialRegStats {
let mut stats = PartialRegStats::new();
let mut partial_writes: Vec<(usize, u32)> = Vec::new();
for (i, instr) in mbb.instructions.iter().enumerate() {
if let Some(op) = x86_opcode(instr) {
if let Some(def) = instr.def {
if is_partial_write(op, instr) {
partial_writes.push((i, def));
}
}
}
}
for (pw_idx, pw_reg) in &partial_writes {
let mut full_width_use_found = false;
for j in (*pw_idx + 1)..mbb.instructions.len() {
let later = &mbb.instructions[j];
if later.def == Some(*pw_reg) {
if let Some(later_op) = x86_opcode(later) {
if is_full_width_write(later_op, later) {
break; }
}
}
if later.operands.iter().any(|op| get_reg(op) == Some(*pw_reg)) {
if let Some(later_op) = x86_opcode(later) {
if uses_full_width(later_op, later, *pw_reg) {
full_width_use_found = true;
break;
}
}
}
}
if full_width_use_found {
stats.partial_writes_eliminated += 1;
stats.stalls_prevented += 1;
}
}
stats
}
}
fn is_partial_write(_op: X86Opcode, instr: &MachineInstr) -> bool {
if let Some(def) = instr.def {
if def == RAX
|| def == RBX
|| def == RCX
|| def == RDX
|| def == RSI
|| def == RDI
|| def == RBP
|| def == RSP
{
if instr.operands.len() >= 1 {
let first = &instr.operands[0];
if let Some(reg) = get_reg(first) {
if (EAX..=R15D).contains(®)
|| (AX..=DI).contains(®)
|| (AL..=BH).contains(®)
{
return true;
}
}
}
}
}
false
}
fn is_full_width_write(_op: X86Opcode, instr: &MachineInstr) -> bool {
if let Some(def) = instr.def {
if def <= R15 {
return true; }
}
false
}
fn uses_full_width(_op: X86Opcode, _instr: &MachineInstr, reg: u32) -> bool {
reg <= R15 || (EAX..=R15D).contains(®)
}
impl Default for X86PartialRegUpdate {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DomainReassignment {
pub stats: DomainReassignStats,
pub prefer_fp_domain: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SimdDomain {
Integer,
Float,
Unknown,
}
#[derive(Debug, Clone, Default)]
pub struct DomainReassignStats {
pub domain_crossings_saved: u64,
pub instructions_reassigned: u64,
pub bypass_delays_eliminated: u64,
}
impl DomainReassignStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.instructions_reassigned > 0
}
}
impl X86DomainReassignment {
pub fn new() -> Self {
Self {
stats: DomainReassignStats::new(),
prefer_fp_domain: true,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> DomainReassignStats {
let mut total = DomainReassignStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.domain_crossings_saved += bs.domain_crossings_saved;
total.instructions_reassigned += bs.instructions_reassigned;
total.bypass_delays_eliminated += bs.bypass_delays_eliminated;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> DomainReassignStats {
let mut stats = DomainReassignStats::new();
let domains: Vec<SimdDomain> = mbb
.instructions
.iter()
.map(|instr| {
if let Some(op) = x86_opcode(instr) {
classify_simd_domain(op)
} else {
SimdDomain::Unknown
}
})
.collect();
let mut crossings = 0u64;
let mut prev_domain = SimdDomain::Unknown;
for domain in domains.iter() {
if *domain != SimdDomain::Unknown
&& prev_domain != SimdDomain::Unknown
&& *domain != prev_domain
{
crossings += 1;
}
if *domain != SimdDomain::Unknown {
prev_domain = *domain;
}
}
if crossings > 0 {
stats.domain_crossings_saved = crossings;
stats.bypass_delays_eliminated = crossings;
}
stats
}
}
fn classify_simd_domain(op: X86Opcode) -> SimdDomain {
match op {
X86Opcode::ADDPS
| X86Opcode::ADDPD
| X86Opcode::SUBPS
| X86Opcode::SUBPD
| X86Opcode::MULPS
| X86Opcode::MULPD
| X86Opcode::DIVPS
| X86Opcode::DIVPD
| X86Opcode::ADDSS
| X86Opcode::ADDSD
| X86Opcode::SUBSS
| X86Opcode::SUBSD
| X86Opcode::MULSS
| X86Opcode::MULSD
| X86Opcode::DIVSS
| X86Opcode::DIVSD
| X86Opcode::SQRTSS
| X86Opcode::SQRTSD
| X86Opcode::MINSS
| X86Opcode::MINSD
| X86Opcode::MAXSS
| X86Opcode::MAXSD
| X86Opcode::VADDPS
| X86Opcode::VADDPD
| X86Opcode::VSUBPS
| X86Opcode::VSUBPD
| X86Opcode::VMULPS
| X86Opcode::VMULPD
| X86Opcode::VDIVPS
| X86Opcode::VDIVPD
| X86Opcode::VADDSS
| X86Opcode::VADDSD
| X86Opcode::VSUBSS
| X86Opcode::VSUBSD
| X86Opcode::VMULSS
| X86Opcode::VMULSD
| X86Opcode::VDIVSS
| X86Opcode::VDIVSD
| X86Opcode::VFMADD132PS
| X86Opcode::VFMADD213PS
| X86Opcode::VFMADD231PS
| X86Opcode::VFMADD132PD
| X86Opcode::VFMADD213PD
| X86Opcode::VFMADD231PD
| X86Opcode::VFMADD132SS
| X86Opcode::VFMADD213SS
| X86Opcode::VFMADD231SS
| X86Opcode::VFMADD132SD
| X86Opcode::VFMADD213SD
| X86Opcode::VFMADD231SD
| X86Opcode::VMAXPS_Z
| X86Opcode::VMAXPD_Z
| X86Opcode::VMINPS_Z
| X86Opcode::VMINPD_Z
| X86Opcode::RCPPS
| X86Opcode::SQRTPS
| X86Opcode::RSQRTPS
| X86Opcode::HADDPS
| X86Opcode::HADDPD
| X86Opcode::HSUBPS
| X86Opcode::HSUBPD
| X86Opcode::ADDSUBPS
| X86Opcode::ADDSUBPD
| X86Opcode::BLENDPS
| X86Opcode::BLENDPD
| X86Opcode::BLENDVPS
| X86Opcode::BLENDVPD => SimdDomain::Float,
X86Opcode::PADDB
| X86Opcode::PADDW
| X86Opcode::PADDD
| X86Opcode::MMX_PADDQ
| X86Opcode::PSUBB
| X86Opcode::PSUBW
| X86Opcode::PSUBD
| X86Opcode::MMX_PSUBQ
| X86Opcode::PMULLW
| X86Opcode::PMULLD
| X86Opcode::MMX_PMULHW
| X86Opcode::MMX_PMULHUW
| X86Opcode::PMULDQ
| X86Opcode::MMX_PMADDWD
| X86Opcode::PAND
| X86Opcode::MMX_PANDN
| X86Opcode::MMX_POR
| X86Opcode::MMX_PXOR
| X86Opcode::MMX_PSLLW
| X86Opcode::MMX_PSLLD
| X86Opcode::MMX_PSLLQ
| X86Opcode::MMX_PSRLW
| X86Opcode::MMX_PSRLD
| X86Opcode::MMX_PSRLQ
| X86Opcode::MMX_PSRAW
| X86Opcode::MMX_PSRAD
| X86Opcode::PACKSSWB
| X86Opcode::PACKSSDW
| X86Opcode::PACKUSWB
| X86Opcode::PUNPCKLBW
| X86Opcode::PUNPCKHBW
| X86Opcode::PUNPCKLWD
| X86Opcode::PUNPCKHWD
| X86Opcode::PUNPCKLDQ
| X86Opcode::PUNPCKHDQ
| X86Opcode::PUNPCKLQDQ
| X86Opcode::PUNPCKHQDQ
| X86Opcode::MMX_PCMPEQB
| X86Opcode::MMX_PCMPEQW
| X86Opcode::MMX_PCMPEQD
| X86Opcode::PCMPEQQ
| X86Opcode::MMX_PCMPGTB
| X86Opcode::MMX_PCMPGTW
| X86Opcode::MMX_PCMPGTD
| X86Opcode::PSHUFHW
| X86Opcode::PSHUFLW
| X86Opcode::PSHUFD
| X86Opcode::VPADDB
| X86Opcode::VPADDW
| X86Opcode::VPADDD
| X86Opcode::VPADDQ
| X86Opcode::VPSUBB
| X86Opcode::VPSUBW
| X86Opcode::VPSUBD
| X86Opcode::VPSUBQ
| X86Opcode::VPMULLD
| X86Opcode::VPMULUDQ
| X86Opcode::VPMADDWD
| X86Opcode::VPAVGB
| X86Opcode::VPAVGW
| X86Opcode::VPMINUB
| X86Opcode::VPMINSW
| X86Opcode::VPMAXUB
| X86Opcode::VPMAXSW
| X86Opcode::VPMINSB
| X86Opcode::VPMINSD
| X86Opcode::VPMINUW
| X86Opcode::VPMINUD
| X86Opcode::VPMAXSB
| X86Opcode::VPMAXSD
| X86Opcode::VPMAXUW
| X86Opcode::VPMAXUD => SimdDomain::Integer,
X86Opcode::MOVD
| X86Opcode::MOVQ
| X86Opcode::MOVSS
| X86Opcode::MOVSD
| X86Opcode::CVTSI2SS
| X86Opcode::CVTSI2SD
| X86Opcode::CVTSS2SI
| X86Opcode::CVTSD2SI
| X86Opcode::CVTTSS2SI
| X86Opcode::CVTTSD2SI
| X86Opcode::PEXTRB
| X86Opcode::PEXTRW
| X86Opcode::PEXTRD
| X86Opcode::PEXTRQ
| X86Opcode::PINSRB
| X86Opcode::PINSRW
| X86Opcode::PINSRD
| X86Opcode::PINSRQ => SimdDomain::Unknown,
_ => SimdDomain::Unknown,
}
}
impl Default for X86DomainReassignment {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FixupSetCC {
pub stats: SetCCStats,
}
#[derive(Debug, Clone, Default)]
pub struct SetCCStats {
pub setcc_optimized: u64,
pub cmov_optimized: u64,
pub redundant_eliminated: u64,
pub xor_setcc_optimized: u64,
}
impl SetCCStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.setcc_optimized
+ self.cmov_optimized
+ self.redundant_eliminated
+ self.xor_setcc_optimized
> 0
}
}
impl X86FixupSetCC {
pub fn new() -> Self {
Self {
stats: SetCCStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> SetCCStats {
let mut total = SetCCStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.setcc_optimized += bs.setcc_optimized;
total.cmov_optimized += bs.cmov_optimized;
total.redundant_eliminated += bs.redundant_eliminated;
total.xor_setcc_optimized += bs.xor_setcc_optimized;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> SetCCStats {
let mut stats = SetCCStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if op.is_setcc() && i + 1 < mbb.instructions.len() {
let next_instr = &mbb.instructions[i + 1];
if let Some(next_op) = x86_opcode(next_instr) {
if next_op == X86Opcode::MOVZX && next_instr.operands.len() >= 2 {
let movzx_src = &next_instr.operands[1];
if let Some(src_reg) = get_reg(movzx_src) {
if let Some(setcc_def) = instr.def {
if src_reg as VirtReg == setcc_def {
stats.setcc_optimized += 1;
}
}
}
}
}
}
if op.is_cmov() && instr.operands.len() >= 3 {
let src1 = &instr.operands[1];
let src2 = &instr.operands[2];
if same_reg(src1, src2) {
let mut new_instr = MachineInstr::new(opc(X86Opcode::MOV));
new_instr.operands.push(instr.operands[0].clone());
new_instr.operands.push(src1.clone());
new_instr.def = instr.def;
mbb.instructions[i] = new_instr;
stats.cmov_optimized += 1;
i += 1;
continue;
}
}
if op == X86Opcode::XOR
&& instr.operands.len() >= 2
&& same_reg(&instr.operands[0], &instr.operands[1])
&& i + 1 < mbb.instructions.len()
{
let next = &mbb.instructions[i + 1];
if let Some(next_op) = x86_opcode(next) {
if next_op.is_setcc() && next.def.is_some() {
let xor_dst = get_reg(&instr.operands[0]).unwrap_or(u32::MAX);
let setcc_def = next.def.unwrap();
if xor_dst == setcc_def {
let mut only_used_by_setcc = true;
for op in &next.operands {
if get_reg(op) == Some(xor_dst) {
only_used_by_setcc = false;
break;
}
}
if only_used_by_setcc {
mbb.instructions.remove(i);
stats.xor_setcc_optimized += 1;
continue;
}
}
}
}
}
i += 1;
}
stats
}
}
impl Default for X86FixupSetCC {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86AvoidStoreForwardingStall {
pub stats: SFStallStats,
}
#[derive(Debug, Clone, Default)]
pub struct SFStallStats {
pub stalls_detected: u64,
pub stalls_mitigated: u64,
pub stores_broadened: u64,
pub loads_adjusted: u64,
}
impl SFStallStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.stalls_mitigated > 0
}
}
impl X86AvoidStoreForwardingStall {
pub fn new() -> Self {
Self {
stats: SFStallStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &MachineFunction) -> SFStallStats {
let mut total = SFStallStats::new();
for block in mf.blocks.iter() {
let bs = self.analyze_block(block);
total.stalls_detected += bs.stalls_detected;
total.stalls_mitigated += bs.stalls_mitigated;
total.stores_broadened += bs.stores_broadened;
total.loads_adjusted += bs.loads_adjusted;
}
self.stats = total.clone();
total
}
pub fn analyze_block(&self, mbb: &MachineBasicBlock) -> SFStallStats {
let mut stats = SFStallStats::new();
let mut stores: Vec<(usize, Option<u32>, u8)> = Vec::new();
for (i, instr) in mbb.instructions.iter().enumerate() {
if let Some(op) = x86_opcode(instr) {
if is_store_op(op) {
let size = get_store_size(op);
let addr_reg = get_store_address_reg(instr);
stores.push((i, addr_reg, size));
}
if is_load_op(op) {
let load_size = get_load_size(op);
let load_addr = get_load_address_reg(instr);
for &(store_idx, store_addr, store_size) in stores.iter().rev() {
if i - store_idx > 8 {
break; }
if store_addr.is_some() && load_addr.is_some() && store_addr == load_addr {
if store_size != load_size {
stats.stalls_detected += 1;
stats.stalls_mitigated += 1;
if load_size > store_size {
stats.stores_broadened += 1;
} else {
stats.loads_adjusted += 1;
}
}
}
}
}
}
}
stats
}
}
fn is_store_op(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::MOV
| X86Opcode::MOVNTI
| X86Opcode::MOVNTDQ
| X86Opcode::MOVNTDQA
| X86Opcode::MOVNTPD
| X86Opcode::MOVNTPS
| X86Opcode::FST
| X86Opcode::FSTP
| X86Opcode::FIST
| X86Opcode::FISTP
)
}
fn is_load_op(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::MOV | X86Opcode::MOVSX | X86Opcode::MOVZX | X86Opcode::MOVABS
)
}
fn get_store_size(_op: X86Opcode) -> u8 {
4
}
fn get_load_size(_op: X86Opcode) -> u8 {
4
}
fn get_store_address_reg(instr: &MachineInstr) -> Option<u32> {
if instr.operands.len() >= 2 {
get_reg(&instr.operands[1])
} else {
None
}
}
fn get_load_address_reg(instr: &MachineInstr) -> Option<u32> {
if instr.operands.len() >= 2 {
get_reg(&instr.operands[1])
} else {
None
}
}
impl Default for X86AvoidStoreForwardingStall {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86VZeroUpperInsertion {
pub stats: VZeroUpperStats,
}
#[derive(Debug, Clone, Default)]
pub struct VZeroUpperStats {
pub vzeroupper_inserted: u64,
pub avx_blocks_detected: u64,
pub transitions_protected: u64,
}
impl VZeroUpperStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.vzeroupper_inserted > 0
}
}
impl X86VZeroUpperInsertion {
pub fn new() -> Self {
Self {
stats: VZeroUpperStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> VZeroUpperStats {
let mut total = VZeroUpperStats::new();
let uses_avx = mf.blocks.iter().any(|block| {
block.instructions.iter().any(|instr| {
if let Some(op) = x86_opcode(instr) {
is_avx_instruction(op)
} else {
false
}
})
});
if !uses_avx {
return total;
}
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.vzeroupper_inserted += bs.vzeroupper_inserted;
total.avx_blocks_detected += bs.avx_blocks_detected;
total.transitions_protected += bs.transitions_protected;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> VZeroUpperStats {
let mut stats = VZeroUpperStats::new();
let has_avx = mbb.instructions.iter().any(|instr| {
if let Some(op) = x86_opcode(instr) {
is_avx_instruction(op)
} else {
false
}
});
if has_avx {
stats.avx_blocks_detected += 1;
}
let mut insertions: Vec<usize> = Vec::new();
for (i, instr) in mbb.instructions.iter().enumerate() {
if let Some(op) = x86_opcode(instr) {
if op == X86Opcode::CALL {
insertions.push(i);
}
if op == X86Opcode::RET {
insertions.push(i);
}
}
}
for &pos in insertions.iter().rev() {
let mut vzero = MachineInstr::new(opc(X86Opcode::VZEROUPPER));
vzero.operands = vec![];
mbb.instructions.insert(pos, vzero);
stats.vzeroupper_inserted += 1;
stats.transitions_protected += 1;
}
stats
}
}
fn is_avx_instruction(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::VADDPS
| X86Opcode::VADDPD
| X86Opcode::VSUBPS
| X86Opcode::VSUBPD
| X86Opcode::VMULPS
| X86Opcode::VMULPD
| X86Opcode::VDIVPS
| X86Opcode::VDIVPD
| X86Opcode::VANDPS
| X86Opcode::VANDNPS
| X86Opcode::VORPS
| X86Opcode::VXORPS
| X86Opcode::VADDSS
| X86Opcode::VADDSD
| X86Opcode::VSUBSS
| X86Opcode::VSUBSD
| X86Opcode::VMULSS
| X86Opcode::VMULSD
| X86Opcode::VDIVSS
| X86Opcode::VDIVSD
| X86Opcode::VBROADCASTSS
| X86Opcode::VBROADCASTSD
| X86Opcode::VPERMILPS
| X86Opcode::VPERM2F128
| X86Opcode::VZEROALL
| X86Opcode::VZEROUPPER
| X86Opcode::VPBROADCASTB
| X86Opcode::VPBROADCASTW
| X86Opcode::VPBROADCASTD
| X86Opcode::VPBROADCASTQ
| X86Opcode::VPERMQ
| X86Opcode::VPERMPD
| X86Opcode::VPGATHERDD
| X86Opcode::VPGATHERDQ
| X86Opcode::VPGATHERQD
| X86Opcode::VPGATHERQQ
| X86Opcode::VFMADD132PD
| X86Opcode::VFMADD213PD
| X86Opcode::VFMADD231PD
| X86Opcode::VFMSUB132PD
| X86Opcode::VFMSUB213PD
| X86Opcode::VFMSUB231PD
| X86Opcode::VFMADD132PS
| X86Opcode::VFMADD213PS
| X86Opcode::VFMADD231PS
| X86Opcode::VFMSUB132PS
| X86Opcode::VFMSUB213PS
| X86Opcode::VFMSUB231PS
| X86Opcode::VFMADD132SS
| X86Opcode::VFMADD213SS
| X86Opcode::VFMADD231SS
| X86Opcode::VFMADD132SD
| X86Opcode::VFMADD213SD
| X86Opcode::VFMADD231SD
| X86Opcode::VPADDB
| X86Opcode::VPADDW
| X86Opcode::VPADDD
| X86Opcode::VPADDQ
| X86Opcode::VPSUBB
| X86Opcode::VPSUBW
| X86Opcode::VPSUBD
| X86Opcode::VPSUBQ
| X86Opcode::VPMULLD
| X86Opcode::VPMULUDQ
| X86Opcode::VPMADDWD
)
}
impl Default for X86VZeroUpperInsertion {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FixupBW {
pub stats: FixupBWStats,
}
#[derive(Debug, Clone, Default)]
pub struct FixupBWStats {
pub byte_ops_promoted: u64,
pub word_ops_promoted: u64,
pub partial_flags_fixed: u64,
pub zero_extends_inserted: u64,
}
impl FixupBWStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.byte_ops_promoted
+ self.word_ops_promoted
+ self.partial_flags_fixed
+ self.zero_extends_inserted
> 0
}
}
impl X86FixupBW {
pub fn new() -> Self {
Self {
stats: FixupBWStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> FixupBWStats {
let mut total = FixupBWStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.byte_ops_promoted += bs.byte_ops_promoted;
total.word_ops_promoted += bs.word_ops_promoted;
total.partial_flags_fixed += bs.partial_flags_fixed;
total.zero_extends_inserted += bs.zero_extends_inserted;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> FixupBWStats {
let mut stats = FixupBWStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if (op == X86Opcode::INC || op == X86Opcode::DEC) && instr.operands.len() >= 1 {
if i + 1 < mbb.instructions.len() {
let next = &mbb.instructions[i + 1];
if let Some(next_op) = x86_opcode(next) {
if uses_carry_flag(next_op) {
let new_op = if op == X86Opcode::INC {
X86Opcode::ADD
} else {
X86Opcode::SUB
};
let mut new_instr = MachineInstr::new(opc(new_op));
new_instr.operands.push(instr.operands[0].clone());
new_instr.operands.push(MachineOperand::Imm(1));
new_instr.def = instr.def;
mbb.instructions[i] = new_instr;
stats.partial_flags_fixed += 1;
i += 1;
continue;
}
}
}
}
if is_8bit_operation(op) && instr.def.is_some() {
let def_reg = instr.def.unwrap();
let mut needs_zext = false;
for j in (i + 1)..mbb.instructions.len() {
let later = &mbb.instructions[j];
if later.def == Some(def_reg) {
break; }
if later.operands.iter().any(|op| get_reg(op) == Some(def_reg)) {
if let Some(later_op) = x86_opcode(later) {
if uses_32bit_or_wider(later_op) {
needs_zext = true;
break;
}
}
}
}
if needs_zext {
stats.zero_extends_inserted += 1;
}
}
i += 1;
}
stats
}
}
fn uses_carry_flag(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::ADC
| X86Opcode::SBB
| X86Opcode::JB
| X86Opcode::JAE
| X86Opcode::JBE
| X86Opcode::JA
| X86Opcode::CMOVB
| X86Opcode::CMOVAE
| X86Opcode::CMOVBE
| X86Opcode::CMOVA
| X86Opcode::SETB
| X86Opcode::SETAE
| X86Opcode::SETBE
| X86Opcode::SETA
)
}
fn is_8bit_operation(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::ADD | X86Opcode::SUB | X86Opcode::AND | X86Opcode::OR | X86Opcode::XOR
)
}
fn uses_32bit_or_wider(_op: X86Opcode) -> bool {
true
}
impl Default for X86FixupBW {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86PadShortFunctions {
pub stats: PadStats,
pub function_alignment: u32,
pub loop_alignment: u32,
}
#[derive(Debug, Clone, Default)]
pub struct PadStats {
pub functions_padded: u64,
pub loops_padded: u64,
pub padding_bytes_added: u64,
pub nops_inserted: u64,
}
impl PadStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.padding_bytes_added > 0
}
}
impl X86PadShortFunctions {
pub fn new() -> Self {
Self {
stats: PadStats::new(),
function_alignment: 16,
loop_alignment: 16,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> PadStats {
let mut total = PadStats::new();
let estimated_size = self.estimate_function_size(mf);
if estimated_size < self.function_alignment as usize && !mf.blocks.is_empty() {
let pad_bytes = self.function_alignment as usize - estimated_size;
self.insert_nops_at_end(&mut mf.blocks[0], pad_bytes);
total.functions_padded += 1;
total.padding_bytes_added += pad_bytes as u64;
total.nops_inserted += pad_bytes as u64;
}
for block in &mut mf.blocks {
let block_size = block.instructions.len() * 3; if block_size > 0 && block_size < self.loop_alignment as usize {
let pad_bytes = (self.loop_alignment as usize).saturating_sub(block_size);
if pad_bytes > 0 && pad_bytes < 16 {
let mut nop = MachineInstr::new(opc(X86Opcode::NOP));
nop.operands = vec![];
if block.instructions.is_empty() {
block.instructions.push(nop);
} else {
block.instructions.insert(0, nop);
}
total.loops_padded += 1;
total.padding_bytes_added += pad_bytes as u64;
total.nops_inserted += 1;
}
}
}
self.stats = total.clone();
total
}
fn estimate_function_size(&self, mf: &MachineFunction) -> usize {
let mut total = 0usize;
for block in &mf.blocks {
for instr in &block.instructions {
total += if instr.operands.len() <= 2 { 3 } else { 5 };
}
}
total
}
fn insert_nops_at_end(&self, mbb: &mut MachineBasicBlock, bytes: usize) {
let nop_count = (bytes + 3) / 4; for _ in 0..nop_count {
let nop = MachineInstr::new(opc(X86Opcode::NOP));
mbb.instructions.push(nop);
}
}
}
impl Default for X86PadShortFunctions {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FixupLEA {
pub stats: LEAFixupStats,
}
#[derive(Debug, Clone, Default)]
pub struct LEAFixupStats {
pub lea_converted_to_mov: u64,
pub lea_converted_to_add: u64,
pub lea_removed: u64,
pub total_lea_optimized: u64,
}
impl LEAFixupStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.total_lea_optimized > 0
}
}
impl X86FixupLEA {
pub fn new() -> Self {
Self {
stats: LEAFixupStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> LEAFixupStats {
let mut total = LEAFixupStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.lea_converted_to_mov += bs.lea_converted_to_mov;
total.lea_converted_to_add += bs.lea_converted_to_add;
total.lea_removed += bs.lea_removed;
total.total_lea_optimized += bs.total_lea_optimized;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> LEAFixupStats {
let mut stats = LEAFixupStats::new();
for i in 0..mbb.instructions.len() {
let op = match x86_opcode(&mbb.instructions[i]) {
Some(o) => o,
None => continue,
};
if op != X86Opcode::LEA {
continue;
}
let instr_op_count = mbb.instructions[i].operands.len();
if instr_op_count < 2 {
continue;
}
let is_simple_base = is_lea_simple_base_only(&mbb.instructions[i]);
let is_base_plus_idx = is_lea_base_plus_index_simple(&mbb.instructions[i]);
if is_simple_base {
let operands0 = mbb.instructions[i].operands[0].clone();
let operands1 = mbb.instructions[i].operands[1].clone();
let def = mbb.instructions[i].def;
let mut new_instr = MachineInstr::new(opc(X86Opcode::MOV));
new_instr.operands.push(operands0);
new_instr.operands.push(operands1);
new_instr.def = def;
mbb.instructions[i] = new_instr;
stats.lea_converted_to_mov += 1;
stats.total_lea_optimized += 1;
continue;
}
if is_base_plus_idx {
let operands0 = mbb.instructions[i].operands[0].clone();
let base = mbb.instructions[i].operands[1].clone();
let index = mbb.instructions[i].operands[2].clone();
let disp_zero = instr_op_count < 4
|| (instr_op_count >= 4 && is_zero_imm(&mbb.instructions[i].operands[3]));
if disp_zero {
let def = mbb.instructions[i].def;
let mut new_instr = MachineInstr::new(opc(X86Opcode::ADD));
new_instr.operands.push(operands0);
new_instr.operands.push(base);
new_instr.operands.push(index);
new_instr.def = def;
if !self.flags_live_after(mbb, i) {
mbb.instructions[i] = new_instr;
stats.lea_converted_to_add += 1;
stats.total_lea_optimized += 1;
}
}
}
if instr_op_count >= 2
&& same_reg(
&mbb.instructions[i].operands[0],
&mbb.instructions[i].operands[1],
)
&& (mbb.instructions[i].operands.len() < 3
|| is_zero_imm(&mbb.instructions[i].operands[2]))
{
mbb.instructions.remove(i);
stats.lea_removed += 1;
stats.total_lea_optimized += 1;
break; }
}
stats
}
fn flags_live_after(&self, mbb: &MachineBasicBlock, idx: usize) -> bool {
for j in (idx + 1)..mbb.instructions.len() {
if let Some(op) = x86_opcode(&mbb.instructions[j]) {
if uses_flags(op) {
return true;
}
if defines_flags(op) {
return false; }
}
}
false
}
}
fn is_lea_simple_base_only(instr: &MachineInstr) -> bool {
instr.operands.len() == 2 && is_reg(&instr.operands[0]) && is_reg(&instr.operands[1])
}
fn is_lea_base_plus_index_simple(instr: &MachineInstr) -> bool {
instr.operands.len() >= 3
&& is_reg(&instr.operands[0])
&& is_reg(&instr.operands[1])
&& is_reg(&instr.operands[2])
}
impl Default for X86FixupLEA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86OptimizeLEA {
pub stats: LEAOptStats,
}
#[derive(Debug, Clone, Default)]
pub struct LEAOptStats {
pub lea_reduced_to_addshift: u64,
pub lea_simplified: u64,
pub lea_chains_broken: u64,
}
impl LEAOptStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.lea_reduced_to_addshift + self.lea_simplified + self.lea_chains_broken > 0
}
}
impl X86OptimizeLEA {
pub fn new() -> Self {
Self {
stats: LEAOptStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> LEAOptStats {
let mut total = LEAOptStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.lea_reduced_to_addshift += bs.lea_reduced_to_addshift;
total.lea_simplified += bs.lea_simplified;
total.lea_chains_broken += bs.lea_chains_broken;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> LEAOptStats {
let mut stats = LEAOptStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if op != X86Opcode::LEA || instr.operands.len() < 3 {
i += 1;
continue;
}
if i + 1 < mbb.instructions.len() {
let next = &mbb.instructions[i + 1];
if let Some(next_op) = x86_opcode(next) {
if next_op == X86Opcode::LEA && instr.def.is_some() {
let lea_def = instr.def.unwrap();
for op in &next.operands {
if get_reg(op) == Some(lea_def) {
stats.lea_chains_broken += 1;
break;
}
}
}
}
}
if is_simple_lea(instr) {
stats.lea_simplified += 1;
}
i += 1;
}
stats
}
}
fn is_simple_lea(instr: &MachineInstr) -> bool {
instr.operands.len() == 3
&& is_reg(&instr.operands[0])
&& is_reg(&instr.operands[1])
&& is_reg(&instr.operands[2])
}
impl Default for X86OptimizeLEA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86TwoAddressToThreeAddress {
pub stats: TwoAddrStats,
}
#[derive(Debug, Clone, Default)]
pub struct TwoAddrStats {
pub instructions_converted: u64,
pub false_deps_broken: u64,
pub copies_eliminated: u64,
}
impl TwoAddrStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.instructions_converted > 0
}
}
impl X86TwoAddressToThreeAddress {
pub fn new() -> Self {
Self {
stats: TwoAddrStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> TwoAddrStats {
let mut total = TwoAddrStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block, &mut mf.virt_reg_counter);
total.instructions_converted += bs.instructions_converted;
total.false_deps_broken += bs.false_deps_broken;
total.copies_eliminated += bs.copies_eliminated;
}
self.stats = total.clone();
total
}
pub fn run_on_block(
&mut self,
mbb: &mut MachineBasicBlock,
virt_reg_counter: &mut u32,
) -> TwoAddrStats {
let mut stats = TwoAddrStats::new();
for i in 0..mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => continue,
};
if let Some(avx_op) = get_avx_equivalent(op) {
if instr.operands.len() >= 2
&& is_reg(&instr.operands[0])
&& is_reg(&instr.operands[1])
{
let mut new_instr = MachineInstr::new(opc(avx_op));
if let Some(def) = instr.def {
new_instr
.operands
.push(MachineOperand::Reg(*virt_reg_counter));
*virt_reg_counter += 1;
new_instr.def = Some(*virt_reg_counter - 1);
}
new_instr.operands.push(instr.operands[0].clone());
new_instr.operands.push(instr.operands[1].clone());
stats.instructions_converted += 1;
stats.false_deps_broken += 1;
}
}
}
stats
}
}
fn get_avx_equivalent(op: X86Opcode) -> Option<X86Opcode> {
match op {
X86Opcode::ADDPS => Some(X86Opcode::VADDPS),
X86Opcode::ADDPD => Some(X86Opcode::VADDPD),
X86Opcode::SUBPS => Some(X86Opcode::VSUBPS),
X86Opcode::SUBPD => Some(X86Opcode::VSUBPD),
X86Opcode::MULPS => Some(X86Opcode::VMULPS),
X86Opcode::MULPD => Some(X86Opcode::VMULPD),
X86Opcode::DIVPS => Some(X86Opcode::VDIVPS),
X86Opcode::DIVPD => Some(X86Opcode::VDIVPD),
X86Opcode::ADDSS => Some(X86Opcode::VADDSS),
X86Opcode::ADDSD => Some(X86Opcode::VADDSD),
X86Opcode::SUBSS => Some(X86Opcode::VSUBSS),
X86Opcode::SUBSD => Some(X86Opcode::VSUBSD),
X86Opcode::MULSS => Some(X86Opcode::VMULSS),
X86Opcode::MULSD => Some(X86Opcode::VMULSD),
X86Opcode::DIVSS => Some(X86Opcode::VDIVSS),
X86Opcode::DIVSD => Some(X86Opcode::VDIVSD),
X86Opcode::ANDPS => Some(X86Opcode::VANDPS),
X86Opcode::ORPS => Some(X86Opcode::VORPS),
X86Opcode::XORPS => Some(X86Opcode::VXORPS),
_ => None,
}
}
impl Default for X86TwoAddressToThreeAddress {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FixupInstrs {
pub stats: FixupInstrsStats,
}
#[derive(Debug, Clone, Default)]
pub struct FixupInstrsStats {
pub mov64_to_mov32: u64,
pub rex_fixed: u64,
pub xor_zero_used: u64,
pub branches_fixed: u64,
pub encoding_fixes: u64,
}
impl FixupInstrsStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.mov64_to_mov32
+ self.rex_fixed
+ self.xor_zero_used
+ self.branches_fixed
+ self.encoding_fixes
> 0
}
}
impl X86FixupInstrs {
pub fn new() -> Self {
Self {
stats: FixupInstrsStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> FixupInstrsStats {
let mut total = FixupInstrsStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.mov64_to_mov32 += bs.mov64_to_mov32;
total.rex_fixed += bs.rex_fixed;
total.xor_zero_used += bs.xor_zero_used;
total.branches_fixed += bs.branches_fixed;
total.encoding_fixes += bs.encoding_fixes;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> FixupInstrsStats {
let mut stats = FixupInstrsStats::new();
for instr in &mut mbb.instructions {
let op = match x86_opcode(instr) {
Some(o) => o,
None => continue,
};
if op == X86Opcode::MOV && instr.operands.len() >= 2 {
if let MachineOperand::Imm(val) = instr.operands[1] {
if val >= 0 && val <= 0x7FFF_FFFF {
stats.encoding_fixes += 1;
}
if val == 0 {
let reg = instr.operands[0].clone();
*instr = MachineInstr::new(opc(X86Opcode::XOR));
instr.operands.push(reg.clone());
instr.operands.push(reg);
instr.def = instr.def;
stats.xor_zero_used += 1;
}
}
}
if op.is_jcc() || op == X86Opcode::JMP {
stats.branches_fixed += 0; }
}
stats
}
}
impl Default for X86FixupInstrs {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CompressEVEX {
pub stats: EVEXCompressStats,
}
#[derive(Debug, Clone, Default)]
pub struct EVEXCompressStats {
pub evex_compressed: u64,
pub bytes_saved: u64,
pub avx512_preserved: u64,
}
impl EVEXCompressStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.evex_compressed > 0
}
}
impl X86CompressEVEX {
pub fn new() -> Self {
Self {
stats: EVEXCompressStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> EVEXCompressStats {
let mut total = EVEXCompressStats::new();
for block in mf.blocks.iter() {
let bs = self.analyze_block(block);
total.evex_compressed += bs.evex_compressed;
total.bytes_saved += bs.bytes_saved;
total.avx512_preserved += bs.avx512_preserved;
}
self.stats = total.clone();
total
}
pub fn analyze_block(&self, mbb: &MachineBasicBlock) -> EVEXCompressStats {
let mut stats = EVEXCompressStats::new();
for instr in &mbb.instructions {
let op = match x86_opcode(instr) {
Some(o) => o,
None => continue,
};
if is_avx512_evex_instruction(op) {
if let Some(_vex_op) = get_vex_equivalent(op) {
stats.evex_compressed += 1;
stats.bytes_saved += 1; } else {
stats.avx512_preserved += 1;
}
}
}
stats
}
}
fn is_avx512_evex_instruction(op: X86Opcode) -> bool {
matches!(
op,
X86Opcode::VPADDD_Z
| X86Opcode::VPADDQ_Z
| X86Opcode::VPSUBD_Z
| X86Opcode::VPSUBQ_Z
| X86Opcode::VPMULLD_Z
| X86Opcode::VPMULLQ_Z
| X86Opcode::VADDPS_Z
| X86Opcode::VADDPD_Z
| X86Opcode::VMULPS_Z
| X86Opcode::VMULPD_Z
| X86Opcode::VDIVPS_Z
| X86Opcode::VDIVPD_Z
| X86Opcode::VANDPS_Z
| X86Opcode::VANDPD_Z
| X86Opcode::VORPS_Z
| X86Opcode::VORPD_Z
| X86Opcode::VXORPS_Z
| X86Opcode::VXORPD_Z
)
}
fn get_vex_equivalent(op: X86Opcode) -> Option<X86Opcode> {
match op {
X86Opcode::VADDPS_Z => Some(X86Opcode::VADDPS),
X86Opcode::VADDPD_Z => Some(X86Opcode::VADDPD),
X86Opcode::VMULPS_Z => Some(X86Opcode::VMULPS),
X86Opcode::VMULPD_Z => Some(X86Opcode::VMULPD),
X86Opcode::VDIVPS_Z => Some(X86Opcode::VDIVPS),
X86Opcode::VDIVPD_Z => Some(X86Opcode::VDIVPD),
X86Opcode::VANDPS_Z => Some(X86Opcode::VANDPS),
X86Opcode::VANDPD_Z => Some(X86Opcode::VANDPD),
X86Opcode::VORPS_Z => Some(X86Opcode::VORPS),
X86Opcode::VORPD_Z => Some(X86Opcode::VORPD),
X86Opcode::VXORPS_Z => Some(X86Opcode::VXORPS),
X86Opcode::VXORPD_Z => Some(X86Opcode::VXORPD),
X86Opcode::VPADDD_Z => Some(X86Opcode::VPADDD),
X86Opcode::VPADDQ_Z => Some(X86Opcode::VPADDQ),
X86Opcode::VPSUBD_Z => Some(X86Opcode::VPSUBD),
X86Opcode::VPSUBQ_Z => Some(X86Opcode::VPSUBQ),
_ => None,
}
}
impl Default for X86CompressEVEX {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CallFrameOptimization {
pub stats: CallFrameStats,
pub eliminate_frame_pointer: bool,
pub optimize_tail_calls: bool,
}
#[derive(Debug, Clone, Default)]
pub struct CallFrameStats {
pub frame_setup_optimized: u64,
pub frame_teardown_optimized: u64,
pub tail_calls_converted: u64,
pub pushes_merged: u64,
pub pops_merged: u64,
pub stack_adjustments_optimized: u64,
}
impl CallFrameStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.frame_setup_optimized
+ self.frame_teardown_optimized
+ self.tail_calls_converted
+ self.pushes_merged
+ self.pops_merged
+ self.stack_adjustments_optimized
> 0
}
}
impl X86CallFrameOptimization {
pub fn new() -> Self {
Self {
stats: CallFrameStats::new(),
eliminate_frame_pointer: true,
optimize_tail_calls: true,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> CallFrameStats {
let mut total = CallFrameStats::new();
if self.optimize_tail_calls {
let tc = self.optimize_tail_call_pattern(mf);
total.tail_calls_converted += tc;
}
if let Some(entry) = mf.blocks.first_mut() {
let fs = self.optimize_frame_setup(entry);
total.frame_setup_optimized += fs;
let (pm, po) = self.merge_push_pop(entry);
total.pushes_merged += pm;
total.pops_merged += po;
}
for block in &mut mf.blocks {
let ft = self.optimize_frame_teardown(block);
total.frame_teardown_optimized += ft;
let sa = self.optimize_stack_adjustments(block);
total.stack_adjustments_optimized += sa;
}
self.stats = total.clone();
total
}
fn optimize_frame_setup(&mut self, mbb: &mut MachineBasicBlock) -> u64 {
let mut optimized = 0u64;
if mbb.instructions.len() < 2 {
return optimized;
}
let first = &mbb.instructions[0];
let second = &mbb.instructions[1];
let first_is_push_rbp = x86_opcode(first) == Some(X86Opcode::PUSH)
&& first.operands.len() >= 1
&& is_phys_reg(&first.operands[0], RBP);
let second_is_mov_rbp_rsp = x86_opcode(second) == Some(X86Opcode::MOV)
&& second.operands.len() >= 2
&& is_phys_reg(&second.operands[0], RBP)
&& is_phys_reg(&second.operands[1], RSP);
if first_is_push_rbp && second_is_mov_rbp_rsp {
if self.eliminate_frame_pointer {
if !self.needs_frame_pointer(mbb) {
mbb.instructions.remove(1); mbb.instructions.remove(0); optimized += 2;
}
}
}
optimized
}
fn needs_frame_pointer(&self, mbb: &MachineBasicBlock) -> bool {
for instr in &mbb.instructions {
for op in &instr.operands {
if is_phys_reg(op, RBP) {
return true;
}
}
}
false
}
fn optimize_frame_teardown(&mut self, mbb: &mut MachineBasicBlock) -> u64 {
let mut optimized = 0u64;
for instr in &mbb.instructions {
if let Some(op) = x86_opcode(instr) {
if op == X86Opcode::RET {
optimized += 1;
}
}
}
optimized
}
fn optimize_tail_call_pattern(&mut self, mf: &mut MachineFunction) -> u64 {
let mut converted = 0u64;
for block in &mut mf.blocks {
let len = block.instructions.len();
if len >= 2 {
let last = &block.instructions[len - 1];
let second_last = &block.instructions[len - 2];
let is_call = x86_opcode(second_last) == Some(X86Opcode::CALL);
let is_ret = x86_opcode(last) == Some(X86Opcode::RET);
if is_call && is_ret {
let mut jmp = MachineInstr::new(opc(X86Opcode::JMP));
if second_last.operands.len() >= 1 {
jmp.operands.push(second_last.operands[0].clone());
}
block.instructions[len - 2] = jmp;
block.instructions.remove(len - 1);
converted += 1;
}
}
}
converted
}
fn merge_push_pop(&mut self, mbb: &mut MachineBasicBlock) -> (u64, u64) {
let mut pushes = 0u64;
let mut pops = 0u64;
let mut i = 0;
while i + 1 < mbb.instructions.len() {
let first = &mbb.instructions[i];
let second = &mbb.instructions[i + 1];
if x86_opcode(first) == Some(X86Opcode::PUSH)
&& x86_opcode(second) == Some(X86Opcode::PUSH)
{
pushes += 1;
i += 1;
} else if x86_opcode(first) == Some(X86Opcode::POP)
&& x86_opcode(second) == Some(X86Opcode::POP)
{
pops += 1;
i += 1;
} else {
i += 1;
}
}
(pushes, pops)
}
fn optimize_stack_adjustments(&mut self, mbb: &mut MachineBasicBlock) -> u64 {
let mut optimized = 0u64;
let mut i = 0;
while i + 1 < mbb.instructions.len() {
let first = &mbb.instructions[i];
let second = &mbb.instructions[i + 1];
let first_is_sub_rsp = x86_opcode(first) == Some(X86Opcode::SUB)
&& first.operands.len() >= 2
&& is_phys_reg(&first.operands[0], RSP);
let second_is_add_rsp = x86_opcode(second) == Some(X86Opcode::ADD)
&& second.operands.len() >= 2
&& is_phys_reg(&second.operands[0], RSP);
if first_is_sub_rsp && second_is_add_rsp {
if let (Some(imm1), Some(imm2)) =
(get_imm(&first.operands[1]), get_imm(&second.operands[1]))
{
if imm1 == imm2 {
mbb.instructions.remove(i + 1);
mbb.instructions.remove(i);
optimized += 2;
continue;
}
}
}
i += 1;
}
optimized
}
}
impl Default for X86CallFrameOptimization {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86IndirectBranchTracking {
pub stats: IBTStats,
}
#[derive(Debug, Clone, Default)]
pub struct IBTStats {
pub endbr_inserted: u64,
pub landing_pads_protected: u64,
pub entry_points_protected: u64,
}
impl IBTStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.endbr_inserted > 0
}
}
impl X86IndirectBranchTracking {
pub fn new() -> Self {
Self {
stats: IBTStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> IBTStats {
let mut stats = IBTStats::new();
if let Some(entry) = mf.blocks.first_mut() {
let mut endbr = MachineInstr::new(opc(X86Opcode::NOP)); endbr.operands = vec![];
if entry.instructions.is_empty() {
entry.instructions.push(endbr);
} else {
entry.instructions.insert(0, endbr);
}
stats.endbr_inserted += 1;
stats.entry_points_protected += 1;
}
for block in &mut mf.blocks.iter_mut().skip(1) {
let needs_endbr = block.instructions.first().map_or(false, |instr| {
if let Some(op) = x86_opcode(instr) {
op != X86Opcode::NOP } else {
true
}
});
if needs_endbr {
let mut endbr = MachineInstr::new(opc(X86Opcode::NOP)); endbr.operands = vec![];
block.instructions.insert(0, endbr);
stats.endbr_inserted += 1;
stats.landing_pads_protected += 1;
}
}
self.stats = stats.clone();
stats
}
}
impl Default for X86IndirectBranchTracking {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86SpeculativeLoadHardening {
pub stats: SLHStats,
pub mode: SLHMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SLHMode {
None,
AllLoads,
HardenedLoads,
Retpoline,
}
impl Default for SLHMode {
fn default() -> Self {
SLHMode::None
}
}
#[derive(Debug, Clone, Default)]
pub struct SLHStats {
pub lfence_inserted: u64,
pub loads_hardened: u64,
pub indices_masked: u64,
pub retpolines_inserted: u64,
}
impl SLHStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.lfence_inserted + self.loads_hardened + self.indices_masked + self.retpolines_inserted
> 0
}
}
impl X86SpeculativeLoadHardening {
pub fn new(mode: SLHMode) -> Self {
Self {
stats: SLHStats::new(),
mode,
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> SLHStats {
match self.mode {
SLHMode::None => SLHStats::new(),
SLHMode::AllLoads => self.harden_all_loads(mf),
SLHMode::HardenedLoads => self.harden_hardened_loads(mf),
SLHMode::Retpoline => self.insert_retpolines(mf),
}
}
fn harden_all_loads(&mut self, mf: &mut MachineFunction) -> SLHStats {
let mut stats = SLHStats::new();
for block in &mut mf.blocks {
let mut i = 0;
while i < block.instructions.len() {
let instr = &block.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if op == X86Opcode::CMP {
if i + 2 < block.instructions.len() {
let next = &block.instructions[i + 1];
let after = &block.instructions[i + 2];
let next_is_jcc = x86_opcode(next).map_or(false, |o| o.is_jcc());
let after_is_load = x86_opcode(after).map_or(false, |o| is_load_op(o));
if next_is_jcc && after_is_load {
let mut lfence = MachineInstr::new(opc(X86Opcode::LFENCE));
lfence.operands = vec![];
block.instructions.insert(i + 2, lfence);
stats.lfence_inserted += 1;
stats.loads_hardened += 1;
}
}
}
i += 1;
}
}
stats
}
fn harden_hardened_loads(&mut self, mf: &mut MachineFunction) -> SLHStats {
let mut stats = SLHStats::new();
for block in &mut mf.blocks {
let mut i = 0;
while i < block.instructions.len() {
let instr = &block.instructions[i];
if let Some(op) = x86_opcode(instr) {
if is_compare_op(op) && i + 1 < block.instructions.len() {
if let Some(next_op) = x86_opcode(&block.instructions[i + 1]) {
if next_op.is_jcc() {
if self.is_attacker_controlled_check(instr) {
let mut lfence = MachineInstr::new(opc(X86Opcode::LFENCE));
lfence.operands = vec![];
block.instructions.insert(i + 2, lfence);
stats.lfence_inserted += 1;
stats.indices_masked += 1;
}
}
}
}
}
i += 1;
}
}
stats
}
fn insert_retpolines(&mut self, mf: &mut MachineFunction) -> SLHStats {
let mut stats = SLHStats::new();
for block in &mut mf.blocks {
for instr in &block.instructions {
if let Some(op) = x86_opcode(instr) {
if op == X86Opcode::JMP || op == X86Opcode::CALL {
if instr.operands.len() >= 1 {
if is_reg(&instr.operands[0]) {
stats.retpolines_inserted += 1;
}
}
}
}
}
}
stats
}
fn is_attacker_controlled_check(&self, _instr: &MachineInstr) -> bool {
true }
}
impl Default for X86SpeculativeLoadHardening {
fn default() -> Self {
Self::new(SLHMode::None)
}
}
#[derive(Debug, Clone)]
pub struct X86RedundantFlagElim {
pub stats: FlagElimStats,
}
#[derive(Debug, Clone, Default)]
pub struct FlagElimStats {
pub redundant_cmp_removed: u64,
pub redundant_test_removed: u64,
pub dead_flags_eliminated: u64,
pub total_eliminated: u64,
}
impl FlagElimStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.total_eliminated > 0
}
}
impl X86RedundantFlagElim {
pub fn new() -> Self {
Self {
stats: FlagElimStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> FlagElimStats {
let mut total = FlagElimStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.redundant_cmp_removed += bs.redundant_cmp_removed;
total.redundant_test_removed += bs.redundant_test_removed;
total.dead_flags_eliminated += bs.dead_flags_eliminated;
total.total_eliminated += bs.total_eliminated;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> FlagElimStats {
let mut stats = FlagElimStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if !defines_flags(op) {
i += 1;
continue;
}
let mut j = i;
while j > 0 {
j -= 1;
let prev = &mbb.instructions[j];
let prev_op = match x86_opcode(prev) {
Some(o) => o,
None => continue,
};
if !defines_flags(prev_op) {
continue;
}
if self.same_flag_effect(op, prev_op, instr, prev) {
if !self.flags_used_between(mbb, j, i) {
mbb.instructions.remove(i);
if is_compare_op(op) {
stats.redundant_cmp_removed += 1;
} else {
stats.redundant_test_removed += 1;
}
stats.total_eliminated += 1;
break;
}
}
break;
}
i += 1;
}
stats
}
fn same_flag_effect(
&self,
op1: X86Opcode,
op2: X86Opcode,
instr1: &MachineInstr,
instr2: &MachineInstr,
) -> bool {
if op1 == op2 && instr1.operands.len() == instr2.operands.len() {
let mut same = true;
for (a, b) in instr1.operands.iter().zip(instr2.operands.iter()) {
if !same_reg(a, b) && a != b {
same = false;
break;
}
}
if same {
return true;
}
}
if (op1 == X86Opcode::CMP && op2 == X86Opcode::TEST)
|| (op1 == X86Opcode::TEST && op2 == X86Opcode::CMP)
{
if instr1.operands.len() >= 2 && instr2.operands.len() >= 2 {
let cmp_reg = &instr1.operands[0];
let cmp_src = &instr1.operands[1];
let test_r1 = &instr2.operands[0];
let test_r2 = &instr2.operands[1];
if is_zero_imm(cmp_src) && same_reg(cmp_reg, test_r1) && same_reg(test_r1, test_r2)
{
return true;
}
}
}
false
}
fn flags_used_between(&self, mbb: &MachineBasicBlock, from: usize, to: usize) -> bool {
for j in (from + 1)..to {
if j >= mbb.instructions.len() {
break;
}
if let Some(op) = x86_opcode(&mbb.instructions[j]) {
if uses_flags(op) {
return true;
}
if defines_flags(op) {
return false; }
}
}
false
}
}
impl Default for X86RedundantFlagElim {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86OptimizeConditionalJump {
pub stats: CondJumpStats,
}
#[derive(Debug, Clone, Default)]
pub struct CondJumpStats {
pub jumps_inverted: u64,
pub jumps_eliminated: u64,
pub jumps_merged: u64,
pub chains_optimized: u64,
}
impl CondJumpStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.jumps_inverted + self.jumps_eliminated + self.jumps_merged + self.chains_optimized > 0
}
}
impl X86OptimizeConditionalJump {
pub fn new() -> Self {
Self {
stats: CondJumpStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> CondJumpStats {
let mut total = CondJumpStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.jumps_inverted += bs.jumps_inverted;
total.jumps_eliminated += bs.jumps_eliminated;
total.jumps_merged += bs.jumps_merged;
total.chains_optimized += bs.chains_optimized;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> CondJumpStats {
let mut stats = CondJumpStats::new();
let mut i = 0;
while i < mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => {
i += 1;
continue;
}
};
if op.is_jcc() && i + 1 < mbb.instructions.len() {
let next = &mbb.instructions[i + 1];
if x86_opcode(next) == Some(X86Opcode::JMP) {
let jcc_target = instr.operands.iter().find_map(|op| match op {
MachineOperand::Label(l) => Some(l.clone()),
_ => None,
});
let jmp_target = next.operands.iter().find_map(|op| match op {
MachineOperand::Label(l) => Some(l.clone()),
_ => None,
});
if jcc_target.is_some() && jmp_target.is_some() {
if let Some(inv_op) = invert_jcc(op) {
let mut new_instr = MachineInstr::new(opc(inv_op));
new_instr
.operands
.push(MachineOperand::Label(jmp_target.unwrap()));
mbb.instructions[i] = new_instr;
mbb.instructions.remove(i + 1); stats.jumps_inverted += 1;
stats.jumps_eliminated += 1;
i += 1;
continue;
}
}
}
}
if op == X86Opcode::JMP {
let jmp_target = instr.operands.iter().find_map(|op| match op {
MachineOperand::Label(l) => Some(l.clone()),
_ => None,
});
if jmp_target.is_some() {
mbb.instructions.remove(i);
stats.jumps_eliminated += 1;
continue;
}
}
i += 1;
}
stats
}
}
fn invert_jcc(op: X86Opcode) -> Option<X86Opcode> {
match op {
X86Opcode::JO => Some(X86Opcode::JNO),
X86Opcode::JNO => Some(X86Opcode::JO),
X86Opcode::JB => Some(X86Opcode::JAE),
X86Opcode::JAE => Some(X86Opcode::JB),
X86Opcode::JE => Some(X86Opcode::JNE),
X86Opcode::JNE => Some(X86Opcode::JE),
X86Opcode::JBE => Some(X86Opcode::JA),
X86Opcode::JA => Some(X86Opcode::JBE),
X86Opcode::JS => Some(X86Opcode::JNS),
X86Opcode::JNS => Some(X86Opcode::JS),
X86Opcode::JP => Some(X86Opcode::JNP),
X86Opcode::JNP => Some(X86Opcode::JP),
X86Opcode::JL => Some(X86Opcode::JGE),
X86Opcode::JGE => Some(X86Opcode::JL),
X86Opcode::JLE => Some(X86Opcode::JG),
X86Opcode::JG => Some(X86Opcode::JLE),
_ => None,
}
}
impl Default for X86OptimizeConditionalJump {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86FixupSetCCPostRA {
pub stats: SetCCPostRAStats,
}
#[derive(Debug, Clone, Default)]
pub struct SetCCPostRAStats {
pub setcc_zext_fixed: u64,
pub high_byte_fixed: u64,
pub stall_eliminated: u64,
}
impl SetCCPostRAStats {
pub fn new() -> Self {
Self::default()
}
pub fn made_progress(&self) -> bool {
self.setcc_zext_fixed + self.high_byte_fixed + self.stall_eliminated > 0
}
}
impl X86FixupSetCCPostRA {
pub fn new() -> Self {
Self {
stats: SetCCPostRAStats::new(),
}
}
pub fn run_on_function(&mut self, mf: &mut MachineFunction) -> SetCCPostRAStats {
let mut total = SetCCPostRAStats::new();
for block in &mut mf.blocks {
let bs = self.run_on_block(block);
total.setcc_zext_fixed += bs.setcc_zext_fixed;
total.high_byte_fixed += bs.high_byte_fixed;
total.stall_eliminated += bs.stall_eliminated;
}
self.stats = total.clone();
total
}
pub fn run_on_block(&mut self, mbb: &mut MachineBasicBlock) -> SetCCPostRAStats {
let mut stats = SetCCPostRAStats::new();
for i in 0..mbb.instructions.len() {
let instr = &mbb.instructions[i];
let op = match x86_opcode(instr) {
Some(o) => o,
None => continue,
};
if !op.is_setcc() {
continue;
}
if instr.operands.len() >= 1 {
if let Some(reg) = get_reg(&instr.operands[0]) {
if (AH..=BH).contains(®) {
stats.high_byte_fixed += 1;
}
}
}
if i + 1 < mbb.instructions.len() {
let next = &mbb.instructions[i + 1];
if let Some(next_op) = x86_opcode(next) {
if next_op == X86Opcode::MOVZX {
stats.setcc_zext_fixed += 1;
}
}
}
if let Some(def) = instr.def {
if (AL..=BH).contains(&def) {
let full_reg = get_full_width_reg(def);
for j in (i + 1)..mbb.instructions.len() {
let later = &mbb.instructions[j];
if later.def == Some(full_reg) {
break;
}
if later
.operands
.iter()
.any(|op| get_reg(op) == Some(full_reg))
{
stats.stall_eliminated += 1;
break;
}
}
}
}
}
stats
}
}
fn get_full_width_reg(reg8: u32) -> u32 {
match reg8 {
AL => EAX,
CL => ECX,
DL => EDX,
BL => EBX,
AH | BH | CH | DH => reg8, _ => reg8,
}
}
impl Default for X86FixupSetCCPostRA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86OptPipeline {
pub config: PipelineConfig,
pub combiner: X86Combiner,
pub cmp_elim: X86CmpElimination,
pub macro_fusion: X86MacroFusion,
pub micro_fusion: X86MicroFusion,
pub two_to_three: X86TwoAddressToThreeAddress,
pub fixup_lea: X86FixupLEA,
pub optimize_lea: X86OptimizeLEA,
pub fp_optimizer: X86FloatingPointOptimizer,
pub partial_reg: X86PartialRegUpdate,
pub domain_reassign: X86DomainReassignment,
pub setcc_fixup: X86FixupSetCC,
pub fixup_bw: X86FixupBW,
pub flag_elim: X86RedundantFlagElim,
pub cond_jump: X86OptimizeConditionalJump,
pub setcc_postra: X86FixupSetCCPostRA,
pub sf_stall: X86AvoidStoreForwardingStall,
pub vzero_upper: X86VZeroUpperInsertion,
pub compress_evex: X86CompressEVEX,
pub call_frame: X86CallFrameOptimization,
pub fixup_instrs: X86FixupInstrs,
pub pad_short: X86PadShortFunctions,
pub ibt: X86IndirectBranchTracking,
pub slh: X86SpeculativeLoadHardening,
pub total_stats: PipelineStats,
}
#[derive(Debug, Clone)]
pub struct PipelineConfig {
pub opt_level: u8,
pub enable_pre_ra_passes: bool,
pub enable_post_ra_passes: bool,
pub enable_security_passes: bool,
pub enable_slh: bool,
pub enable_cet: bool,
pub max_iterations: usize,
pub debug: bool,
}
impl Default for PipelineConfig {
fn default() -> Self {
Self {
opt_level: 2,
enable_pre_ra_passes: true,
enable_post_ra_passes: true,
enable_security_passes: false,
enable_slh: false,
enable_cet: false,
max_iterations: 3,
debug: false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PipelineStats {
pub combine: CombineStats,
pub cmp_elim: CmpElimStats,
pub macro_fusion: MacroFusionStats,
pub micro_fusion: MicroFusionStats,
pub two_addr: TwoAddrStats,
pub fixup_lea: LEAFixupStats,
pub optimize_lea: LEAOptStats,
pub fp_opt: FPOptStats,
pub partial_reg: PartialRegStats,
pub domain: DomainReassignStats,
pub setcc: SetCCStats,
pub fixup_bw: FixupBWStats,
pub flag_elim: FlagElimStats,
pub cond_jump: CondJumpStats,
pub setcc_postra: SetCCPostRAStats,
pub sf_stall: SFStallStats,
pub vzero_upper: VZeroUpperStats,
pub compress_evex: EVEXCompressStats,
pub call_frame: CallFrameStats,
pub fixup_instrs: FixupInstrsStats,
pub pad: PadStats,
pub ibt: IBTStats,
pub slh: SLHStats,
pub iterations: u64,
pub total_changes: u64,
}
impl PipelineStats {
pub fn new() -> Self {
Self::default()
}
pub fn compute_total(&mut self) {
self.total_changes = self.combine.total_combines
+ self.cmp_elim.cmp_eliminated
+ self.cmp_elim.cmp_simplified
+ self.cmp_elim.test_replaced
+ self.macro_fusion.total_fusions
+ self.micro_fusion.total_fusions
+ self.two_addr.instructions_converted
+ self.fixup_lea.total_lea_optimized
+ self.optimize_lea.lea_simplified
+ self.fp_opt.x87_to_sse
+ self.partial_reg.stalls_prevented
+ self.domain.instructions_reassigned
+ self.setcc.setcc_optimized
+ self.fixup_bw.partial_flags_fixed
+ self.flag_elim.total_eliminated
+ self.cond_jump.jumps_eliminated
+ self.setcc_postra.setcc_zext_fixed
+ self.sf_stall.stalls_mitigated
+ self.vzero_upper.vzeroupper_inserted
+ self.compress_evex.evex_compressed
+ self.call_frame.frame_setup_optimized
+ self.fixup_instrs.encoding_fixes
+ self.pad.nops_inserted
+ self.ibt.endbr_inserted
+ self.slh.lfence_inserted;
}
}
impl X86OptPipeline {
pub fn new(config: PipelineConfig, instr_info: X86InstrInfo) -> Self {
Self {
config: config.clone(),
combiner: X86Combiner::new(instr_info.clone()),
cmp_elim: X86CmpElimination::new(),
macro_fusion: X86MacroFusion::new(),
micro_fusion: X86MicroFusion::new(),
two_to_three: X86TwoAddressToThreeAddress::new(),
fixup_lea: X86FixupLEA::new(),
optimize_lea: X86OptimizeLEA::new(),
fp_optimizer: X86FloatingPointOptimizer::new(),
partial_reg: X86PartialRegUpdate::new(),
domain_reassign: X86DomainReassignment::new(),
setcc_fixup: X86FixupSetCC::new(),
fixup_bw: X86FixupBW::new(),
flag_elim: X86RedundantFlagElim::new(),
cond_jump: X86OptimizeConditionalJump::new(),
setcc_postra: X86FixupSetCCPostRA::new(),
sf_stall: X86AvoidStoreForwardingStall::new(),
vzero_upper: X86VZeroUpperInsertion::new(),
compress_evex: X86CompressEVEX::new(),
call_frame: X86CallFrameOptimization::new(),
fixup_instrs: X86FixupInstrs::new(),
pad_short: X86PadShortFunctions::new(),
ibt: X86IndirectBranchTracking::new(),
slh: X86SpeculativeLoadHardening::new(SLHMode::None),
total_stats: PipelineStats::new(),
}
}
pub fn for_o2(instr_info: X86InstrInfo) -> Self {
let config = PipelineConfig {
opt_level: 2,
..Default::default()
};
Self::new(config, instr_info)
}
pub fn for_o3(instr_info: X86InstrInfo) -> Self {
let config = PipelineConfig {
opt_level: 3,
max_iterations: 5,
..Default::default()
};
Self::new(config, instr_info)
}
pub fn for_hardened(instr_info: X86InstrInfo) -> Self {
let config = PipelineConfig {
enable_security_passes: true,
enable_slh: true,
enable_cet: true,
..Default::default()
};
Self::new(config, instr_info)
}
pub fn run(&mut self, mf: &mut MachineFunction) -> PipelineStats {
if self.config.enable_pre_ra_passes {
self.run_pre_ra_passes(mf);
}
if self.config.enable_post_ra_passes {
self.run_post_ra_passes(mf);
}
if self.config.enable_security_passes {
self.run_security_passes(mf);
}
self.total_stats.iterations += 1;
self.total_stats.compute_total();
self.total_stats.clone()
}
fn run_pre_ra_passes(&mut self, mf: &mut MachineFunction) {
for _ in 0..self.config.max_iterations {
let mut changed = false;
let cs = self.combiner.run_on_function(mf);
self.total_stats.combine.merge(&cs);
changed |= cs.made_progress();
let cs = self.cmp_elim.run_on_function(mf);
self.total_stats.cmp_elim.cmp_eliminated += cs.cmp_eliminated;
self.total_stats.cmp_elim.cmp_simplified += cs.cmp_simplified;
self.total_stats.cmp_elim.test_replaced += cs.test_replaced;
changed |= cs.made_progress();
let fs = self.flag_elim.run_on_function(mf);
self.total_stats.flag_elim.redundant_cmp_removed += fs.redundant_cmp_removed;
self.total_stats.flag_elim.redundant_test_removed += fs.redundant_test_removed;
self.total_stats.flag_elim.total_eliminated += fs.total_eliminated;
changed |= fs.made_progress();
let ls = self.fixup_lea.run_on_function(mf);
self.total_stats.fixup_lea.lea_converted_to_mov += ls.lea_converted_to_mov;
self.total_stats.fixup_lea.total_lea_optimized += ls.total_lea_optimized;
changed |= ls.made_progress();
let ls = self.optimize_lea.run_on_function(mf);
self.total_stats.optimize_lea.lea_simplified += ls.lea_simplified;
changed |= ls.made_progress();
let js = self.cond_jump.run_on_function(mf);
self.total_stats.cond_jump.jumps_eliminated += js.jumps_eliminated;
self.total_stats.cond_jump.jumps_inverted += js.jumps_inverted;
changed |= js.made_progress();
let bs = self.fixup_bw.run_on_function(mf);
self.total_stats.fixup_bw.partial_flags_fixed += bs.partial_flags_fixed;
changed |= bs.made_progress();
let ss = self.setcc_fixup.run_on_function(mf);
self.total_stats.setcc.setcc_optimized += ss.setcc_optimized;
self.total_stats.setcc.cmov_optimized += ss.cmov_optimized;
changed |= ss.made_progress();
if !changed {
break;
}
}
let ms = self.macro_fusion.run_on_function(mf);
self.total_stats.macro_fusion = ms;
let ms = self.micro_fusion.run_on_function(mf);
self.total_stats.micro_fusion = ms;
let fs = self.fp_optimizer.run_on_function(mf);
self.total_stats.fp_opt = fs;
let ds = self.domain_reassign.run_on_function(mf);
self.total_stats.domain = ds;
let ps = self.partial_reg.run_on_function(mf);
self.total_stats.partial_reg = ps;
let ts = self.two_to_three.run_on_function(mf);
self.total_stats.two_addr = ts;
}
fn run_post_ra_passes(&mut self, mf: &mut MachineFunction) {
let ss = self.setcc_postra.run_on_function(mf);
self.total_stats.setcc_postra = ss;
let sf = self.sf_stall.run_on_function(mf);
self.total_stats.sf_stall = sf;
let vs = self.vzero_upper.run_on_function(mf);
self.total_stats.vzero_upper = vs;
let es = self.compress_evex.run_on_function(mf);
self.total_stats.compress_evex = es;
let cs = self.call_frame.run_on_function(mf);
self.total_stats.call_frame = cs;
let fs = self.fixup_instrs.run_on_function(mf);
self.total_stats.fixup_instrs = fs;
let ps = self.pad_short.run_on_function(mf);
self.total_stats.pad = ps;
}
fn run_security_passes(&mut self, mf: &mut MachineFunction) {
if self.config.enable_cet {
let is = self.ibt.run_on_function(mf);
self.total_stats.ibt = is;
}
if self.config.enable_slh {
self.slh.mode = SLHMode::AllLoads;
let ss = self.slh.run_on_function(mf);
self.total_stats.slh = ss;
}
}
pub fn print_summary(&self) -> String {
let mut s = String::new();
s.push_str(&format!(
"X86 Optimization Pipeline Summary ({} iterations):\n",
self.total_stats.iterations
));
s.push_str("============================================================\n");
s.push_str(&format!(
" Combiner: {:>8} combines\n",
self.total_stats.combine.total_combines
));
s.push_str(&format!(
" Cmp Elim: {:>8} eliminated\n",
self.total_stats.cmp_elim.cmp_eliminated
));
s.push_str(&format!(
" Flag Elim: {:>8} eliminated\n",
self.total_stats.flag_elim.total_eliminated
));
s.push_str(&format!(
" LEA Fixup: {:>8} optimized\n",
self.total_stats.fixup_lea.total_lea_optimized
));
s.push_str(&format!(
" Cond Jump: {:>8} optimized\n",
self.total_stats.cond_jump.jumps_eliminated + self.total_stats.cond_jump.jumps_inverted
));
s.push_str(&format!(
" Macro Fusion: {:>8} pairs\n",
self.total_stats.macro_fusion.total_fusions
));
s.push_str(&format!(
" Micro Fusion: {:>8} pairs\n",
self.total_stats.micro_fusion.total_fusions
));
s.push_str(&format!(
" 2-Addr → 3-Addr: {:>8} converted\n",
self.total_stats.two_addr.instructions_converted
));
s.push_str(&format!(
" VZEROUPPER: {:>8} inserted\n",
self.total_stats.vzero_upper.vzeroupper_inserted
));
s.push_str(&format!(
" EVEX Compress: {:>8} compressed\n",
self.total_stats.compress_evex.evex_compressed
));
s.push_str(&format!(
" Speculative Hardening: {:>8} LFENCEs\n",
self.total_stats.slh.lfence_inserted
));
s.push_str(&format!(
"------------------------------------------------------------\n"
));
s.push_str(&format!(
" TOTAL CHANGES: {:>8}\n",
self.total_stats.total_changes
));
s
}
}
impl Default for X86OptPipeline {
fn default() -> Self {
Self::for_o2(X86InstrInfo::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_instr(opcode: X86Opcode) -> MachineInstr {
MachineInstr::new(opc(opcode))
}
fn make_mov_reg_reg(dst: VirtReg, src: VirtReg) -> MachineInstr {
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::Reg(dst));
instr.operands.push(MachineOperand::Reg(src));
instr.def = Some(dst);
instr
}
fn make_mov_reg_imm(dst: VirtReg, imm: i64) -> MachineInstr {
let mut instr = MachineInstr::new(opc(X86Opcode::MOV));
instr.operands.push(MachineOperand::Reg(dst));
instr.operands.push(MachineOperand::Imm(imm));
instr.def = Some(dst);
instr
}
fn make_alu_reg_reg(opcode: X86Opcode, dst: VirtReg, src: VirtReg) -> MachineInstr {
let mut instr = MachineInstr::new(opc(opcode));
instr.operands.push(MachineOperand::Reg(dst));
instr.operands.push(MachineOperand::Reg(src));
instr.def = Some(dst);
instr
}
fn make_cmp_reg_imm(reg: VirtReg, imm: i64) -> MachineInstr {
let mut instr = MachineInstr::new(opc(X86Opcode::CMP));
instr.operands.push(MachineOperand::Reg(reg));
instr.operands.push(MachineOperand::Imm(imm));
instr
}
fn make_test_reg_reg(reg1: VirtReg, reg2: VirtReg) -> MachineInstr {
let mut instr = MachineInstr::new(opc(X86Opcode::TEST));
instr.operands.push(MachineOperand::Reg(reg1));
instr.operands.push(MachineOperand::Reg(reg2));
instr
}
fn make_jcc(opcode: X86Opcode, label: &str) -> MachineInstr {
let mut instr = MachineInstr::new(opc(opcode));
instr
.operands
.push(MachineOperand::Label(label.to_string()));
instr
}
fn make_jmp(label: &str) -> MachineInstr {
make_jcc(X86Opcode::JMP, label)
}
fn make_setcc(opcode: X86Opcode, dst: VirtReg) -> MachineInstr {
let mut instr = MachineInstr::new(opc(opcode));
instr.operands.push(MachineOperand::Reg(dst));
instr.def = Some(dst);
instr
}
fn make_empty_mf() -> MachineFunction {
MachineFunction::new("test_func")
}
fn make_single_block_mf(name: &str) -> MachineFunction {
let mut mf = MachineFunction::new(name);
let bb = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![],
successors: vec![],
};
mf.push_block(bb);
mf
}
#[test]
fn test_combiner_new() {
let info = X86InstrInfo::new();
let combiner = X86Combiner::new(info);
assert!(combiner.enable_load_fold);
assert!(combiner.enable_lea_opt);
assert!(combiner.enable_imm_fold);
}
#[test]
fn test_combiner_stats_new() {
let stats = CombineStats::new();
assert_eq!(stats.total_combines, 0);
assert_eq!(stats.loads_folded, 0);
}
#[test]
fn test_combiner_stats_merge() {
let mut a = CombineStats::new();
a.loads_folded = 5;
let b = CombineStats {
loads_folded: 3,
leas_simplified: 2,
..CombineStats::new()
};
a.merge(&b);
assert_eq!(a.loads_folded, 8);
assert_eq!(a.leas_simplified, 2);
}
#[test]
fn test_combiner_stats_made_progress() {
let stats = CombineStats::new();
assert!(!stats.made_progress());
let stats = CombineStats {
total_combines: 1,
..CombineStats::new()
};
assert!(stats.made_progress());
}
#[test]
fn test_combiner_empty_function() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_empty_mf();
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.total_combines, 0);
}
#[test]
fn test_combiner_eliminate_redundant_mov_same_reg() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 0)); assert_eq!(mf.blocks[0].instructions.len(), 1);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.moves_eliminated, 1);
assert_eq!(mf.blocks[0].instructions.len(), 0);
}
#[test]
fn test_combiner_dead_mov_elimination() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 1));
assert_eq!(mf.blocks[0].instructions.len(), 1);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.moves_eliminated, 1);
}
#[test]
fn test_combiner_zero_idiom_xor() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
let mut instr = MachineInstr::new(opc(X86Opcode::XOR));
instr.operands.push(MachineOperand::Reg(0));
instr.operands.push(MachineOperand::Reg(0));
instr.def = Some(0);
mf.blocks[0].instructions.push(instr);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.zero_idioms_recognized, 1);
}
#[test]
fn test_combiner_fold_immediate() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_imm(0, 5));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 1, 0));
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.immediates_folded, 1);
assert_eq!(stats.moves_eliminated, 1);
}
#[test]
fn test_combiner_fold_immediate_used_elsewhere() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_imm(0, 5));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 1, 0));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 2, 0));
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.immediates_folded, 0);
}
#[test]
fn test_combiner_lea_to_mov() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(MachineOperand::Reg(0));
lea.operands.push(MachineOperand::Reg(1));
lea.def = Some(0);
mf.blocks[0].instructions.push(lea);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.leas_simplified, 1);
}
#[test]
fn test_combiner_shift_combine() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
let mut shl1 = MachineInstr::new(opc(X86Opcode::SHL));
shl1.operands.push(MachineOperand::Reg(0));
shl1.operands.push(MachineOperand::Imm(1));
shl1.def = Some(0);
mf.blocks[0].instructions.push(shl1);
let mut shl2 = MachineInstr::new(opc(X86Opcode::SHL));
shl2.operands.push(MachineOperand::Reg(0));
shl2.operands.push(MachineOperand::Imm(1));
shl2.def = Some(0);
mf.blocks[0].instructions.push(shl2);
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.shifts_combined, 1);
}
#[test]
fn test_combiner_no_change_on_non_optimizable() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.total_combines, 0);
}
#[test]
fn test_combiner_multiple_blocks() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = MachineFunction::new("test");
let mut bb1 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![make_mov_reg_reg(0, 0)],
successors: vec![1],
};
let bb2 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "exit".to_string(),
instructions: vec![make_mov_reg_reg(1, 1)],
successors: vec![],
};
mf.push_block(bb1);
mf.push_block(bb2);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.moves_eliminated, 2);
}
#[test]
fn test_cmp_elim_new() {
let elim = X86CmpElimination::new();
assert_eq!(elim.stats.cmp_eliminated, 0);
}
#[test]
fn test_cmp_elim_simplify_cmp_zero_to_test() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_simplified, 1);
}
#[test]
fn test_cmp_elim_after_alu() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_eliminated, 1);
assert_eq!(mf.blocks[0].instructions.len(), 1);
}
#[test]
fn test_cmp_elim_no_elim_with_flag_user_between() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
assert_eq!(mf.blocks[0].instructions.len(), 3);
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_eliminated, 0);
}
#[test]
fn test_cmp_elim_sub_self_to_xor() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
let mut sub = MachineInstr::new(opc(X86Opcode::SUB));
sub.operands.push(MachineOperand::Reg(0));
sub.operands.push(MachineOperand::Reg(0));
sub.def = Some(0);
mf.blocks[0].instructions.push(sub);
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.redundant_flags_removed, 1);
}
#[test]
fn test_cmp_elim_empty_function() {
let mut elim = X86CmpElimination::new();
let mut mf = make_empty_mf();
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_eliminated, 0);
assert_eq!(stats.cmp_simplified, 0);
}
#[test]
fn test_cmp_elim_replace_or_self_with_test() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
let mut or_instr = MachineInstr::new(opc(X86Opcode::OR));
or_instr.operands.push(MachineOperand::Reg(0));
or_instr.operands.push(MachineOperand::Reg(0));
or_instr.def = Some(0);
mf.blocks[0].instructions.push(or_instr);
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.test_replaced, 1);
}
#[test]
fn test_cmp_elim_stats_merge() {
let mut a = CmpElimStats::new();
a.cmp_eliminated = 3;
let b = CmpElimStats {
cmp_eliminated: 2,
cmp_simplified: 1,
..CmpElimStats::new()
};
a.cmp_eliminated += b.cmp_eliminated;
a.cmp_simplified += b.cmp_simplified;
assert_eq!(a.cmp_eliminated, 5);
assert_eq!(a.cmp_simplified, 1);
}
#[test]
fn test_fp_optimizer_new() {
let opt = X86FloatingPointOptimizer::new();
assert!(opt.prefer_sse);
assert!(opt.enable_x87_to_sse);
}
#[test]
fn test_fp_optimizer_empty_function() {
let mut opt = X86FloatingPointOptimizer::new();
let mut mf = make_empty_mf();
let stats = opt.run_on_function(&mut mf);
assert!(!stats.made_progress());
}
#[test]
fn test_fp_optimizer_fxch_elimination() {
let mut opt = X86FloatingPointOptimizer::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::FXCH));
mf.blocks[0].instructions.push(make_instr(X86Opcode::FXCH));
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = opt.run_on_function(&mut mf);
assert_eq!(stats.fxch_eliminated, 2);
assert_eq!(mf.blocks[0].instructions.len(), 0);
}
#[test]
fn test_fp_optimizer_stats() {
let stats = FPOptStats {
x87_to_sse: 1,
fxch_eliminated: 2,
..FPOptStats::new()
};
assert!(stats.made_progress());
}
#[test]
fn test_macro_fusion_new() {
let fusion = X86MacroFusion::new();
assert!(fusion.fused_pairs.is_empty());
}
#[test]
fn test_macro_fusion_cmp_jcc() {
let mut fusion = X86MacroFusion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.cmp_jcc_fusions, 1);
assert_eq!(stats.total_fusions, 1);
}
#[test]
fn test_macro_fusion_test_jcc() {
let mut fusion = X86MacroFusion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_test_reg_reg(0, 0));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JNE, "target"));
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.test_jcc_fusions, 1);
}
#[test]
fn test_macro_fusion_alu_jcc() {
let mut fusion = X86MacroFusion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.alu_jcc_fusions, 1);
}
#[test]
fn test_macro_fusion_no_fusion_without_jcc() {
let mut fusion = X86MacroFusion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.total_fusions, 0);
}
#[test]
fn test_macro_fusion_empty() {
let mut fusion = X86MacroFusion::new();
let mf = make_empty_mf();
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.total_fusions, 0);
}
#[test]
fn test_micro_fusion_new() {
let mut fusion = X86MicroFusion::new();
assert_eq!(fusion.stats.total_fusions, 0);
}
#[test]
fn test_micro_fusion_empty_function() {
let mut fusion = X86MicroFusion::new();
let mf = make_empty_mf();
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.total_fusions, 0);
}
#[test]
fn test_micro_fusion_stats() {
let mut stats = MicroFusionStats::new();
assert!(!stats.made_progress());
stats.total_fusions = 1;
assert!(stats.made_progress());
}
#[test]
fn test_partial_reg_update_new() {
let pru = X86PartialRegUpdate::new();
assert_eq!(pru.stats.partial_writes_eliminated, 0);
}
#[test]
fn test_partial_reg_update_empty_function() {
let mut pru = X86PartialRegUpdate::new();
let mut mf = make_empty_mf();
let stats = pru.run_on_function(&mut mf);
assert_eq!(stats.stalls_prevented, 0);
}
#[test]
fn test_partial_reg_update_stats() {
let stats = PartialRegStats {
stalls_prevented: 3,
partial_writes_eliminated: 1,
..PartialRegStats::new()
};
assert!(stats.made_progress());
}
#[test]
fn test_domain_reassignment_new() {
let dr = X86DomainReassignment::new();
assert!(dr.prefer_fp_domain);
}
#[test]
fn test_classify_simd_domain_fp() {
assert_eq!(classify_simd_domain(X86Opcode::VADDPS), SimdDomain::Float);
assert_eq!(classify_simd_domain(X86Opcode::VMULPD), SimdDomain::Float);
assert_eq!(
classify_simd_domain(X86Opcode::VFMSUB132PS),
SimdDomain::Float
);
}
#[test]
fn test_classify_simd_domain_integer() {
assert_eq!(classify_simd_domain(X86Opcode::VPADDB), SimdDomain::Integer);
assert_eq!(classify_simd_domain(X86Opcode::VPADDQ), SimdDomain::Integer);
assert_eq!(classify_simd_domain(X86Opcode::PMULLD), SimdDomain::Integer);
}
#[test]
fn test_classify_simd_domain_unknown() {
assert_eq!(classify_simd_domain(X86Opcode::NOP), SimdDomain::Unknown);
assert_eq!(classify_simd_domain(X86Opcode::MOV), SimdDomain::Unknown);
assert_eq!(classify_simd_domain(X86Opcode::MOVSS), SimdDomain::Unknown);
}
#[test]
fn test_domain_reassignment_run() {
let mut dr = X86DomainReassignment::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VADDPS));
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VPADDD));
let stats = dr.run_on_function(&mut mf);
assert!(stats.domain_crossings_saved > 0);
}
#[test]
fn test_fixup_setcc_new() {
let fixup = X86FixupSetCC::new();
assert_eq!(fixup.stats.setcc_optimized, 0);
}
#[test]
fn test_fixup_setcc_cmov_same_src() {
let mut fixup = X86FixupSetCC::new();
let mut mf = make_single_block_mf("test");
let mut cmov = MachineInstr::new(opc(X86Opcode::CMOVE));
cmov.operands.push(MachineOperand::Reg(0));
cmov.operands.push(MachineOperand::Reg(1));
cmov.operands.push(MachineOperand::Reg(1));
cmov.def = Some(0);
mf.blocks[0].instructions.push(cmov);
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.cmov_optimized, 1);
}
#[test]
fn test_fixup_setcc_xor_then_setcc() {
let mut fixup = X86FixupSetCC::new();
let mut mf = make_single_block_mf("test");
let mut xor_instr = MachineInstr::new(opc(X86Opcode::XOR));
xor_instr.operands.push(MachineOperand::Reg(0));
xor_instr.operands.push(MachineOperand::Reg(0));
xor_instr.def = Some(0);
mf.blocks[0].instructions.push(xor_instr);
mf.blocks[0]
.instructions
.push(make_setcc(X86Opcode::SETE, 0));
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.xor_setcc_optimized, 1);
assert_eq!(mf.blocks[0].instructions.len(), 1);
}
#[test]
fn test_sf_stall_new() {
let mut sf = X86AvoidStoreForwardingStall::new();
assert_eq!(sf.stats.stalls_detected, 0);
}
#[test]
fn test_sf_stall_empty_function() {
let mut sf = X86AvoidStoreForwardingStall::new();
let mf = make_empty_mf();
let stats = sf.run_on_function(&mf);
assert_eq!(stats.stalls_detected, 0);
}
#[test]
fn test_sf_stall_stats() {
let stats = SFStallStats {
stalls_detected: 2,
stalls_mitigated: 1,
..SFStallStats::new()
};
assert!(stats.made_progress());
}
#[test]
fn test_vzero_upper_new() {
let vzu = X86VZeroUpperInsertion::new();
assert_eq!(vzu.stats.vzeroupper_inserted, 0);
}
#[test]
fn test_vzero_upper_no_avx_function() {
let mut vzu = X86VZeroUpperInsertion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
let stats = vzu.run_on_function(&mut mf);
assert_eq!(stats.vzeroupper_inserted, 0);
}
#[test]
fn test_vzero_upper_before_call() {
let mut vzu = X86VZeroUpperInsertion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VADDPS));
mf.blocks[0].instructions.push(make_instr(X86Opcode::CALL));
let stats = vzu.run_on_function(&mut mf);
assert_eq!(stats.vzeroupper_inserted, 1);
}
#[test]
fn test_vzero_upper_before_ret() {
let mut vzu = X86VZeroUpperInsertion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VADDPS));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = vzu.run_on_function(&mut mf);
assert_eq!(stats.vzeroupper_inserted, 1);
}
#[test]
fn test_is_avx_instruction() {
assert!(is_avx_instruction(X86Opcode::VADDPS));
assert!(is_avx_instruction(X86Opcode::VZEROUPPER));
assert!(is_avx_instruction(X86Opcode::VBROADCASTSS));
assert!(!is_avx_instruction(X86Opcode::ADDPS));
assert!(!is_avx_instruction(X86Opcode::MOV));
}
#[test]
fn test_fixup_bw_new() {
let fixup = X86FixupBW::new();
assert_eq!(fixup.stats.byte_ops_promoted, 0);
}
#[test]
fn test_fixup_bw_empty_function() {
let mut fixup = X86FixupBW::new();
let mut mf = make_empty_mf();
let stats = fixup.run_on_function(&mut mf);
assert!(!stats.made_progress());
}
#[test]
fn test_uses_carry_flag() {
assert!(uses_carry_flag(X86Opcode::ADC));
assert!(uses_carry_flag(X86Opcode::SBB));
assert!(uses_carry_flag(X86Opcode::JB));
assert!(uses_carry_flag(X86Opcode::JBE));
assert!(!uses_carry_flag(X86Opcode::ADD));
assert!(!uses_carry_flag(X86Opcode::JE));
}
#[test]
fn test_fixup_bw_inc_dec_with_carry_user() {
let mut fixup = X86FixupBW::new();
let mut mf = make_single_block_mf("test");
let mut inc = MachineInstr::new(opc(X86Opcode::INC));
inc.operands.push(MachineOperand::Reg(0));
inc.def = Some(0);
mf.blocks[0].instructions.push(inc);
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADC, 1, 0));
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.partial_flags_fixed, 1);
}
#[test]
fn test_pad_short_new() {
let pad = X86PadShortFunctions::new();
assert_eq!(pad.function_alignment, 16);
assert_eq!(pad.loop_alignment, 16);
}
#[test]
fn test_pad_short_empty_function() {
let mut pad = X86PadShortFunctions::new();
let mut mf = make_empty_mf();
let stats = pad.run_on_function(&mut mf);
assert_eq!(stats.padding_bytes_added, 0);
}
#[test]
fn test_pad_short_small_function() {
let mut pad = X86PadShortFunctions::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pad.run_on_function(&mut mf);
assert!(stats.functions_padded > 0 || stats.loops_padded > 0);
}
#[test]
fn test_fixup_lea_new() {
let fixup = X86FixupLEA::new();
assert_eq!(fixup.stats.total_lea_optimized, 0);
}
#[test]
fn test_fixup_lea_base_only_to_mov() {
let mut fixup = X86FixupLEA::new();
let mut mf = make_single_block_mf("test");
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(MachineOperand::Reg(0));
lea.operands.push(MachineOperand::Reg(1));
lea.def = Some(0);
mf.blocks[0].instructions.push(lea);
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.lea_converted_to_mov, 1);
assert_eq!(stats.total_lea_optimized, 1);
}
#[test]
fn test_fixup_lea_same_dst_base() {
let mut fixup = X86FixupLEA::new();
let mut mf = make_single_block_mf("test");
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(MachineOperand::Reg(0));
lea.operands.push(MachineOperand::Reg(0));
lea.def = Some(0);
mf.blocks[0].instructions.push(lea);
assert_eq!(mf.blocks[0].instructions.len(), 1);
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.lea_removed, 1);
assert_eq!(mf.blocks[0].instructions.len(), 0);
}
#[test]
fn test_is_lea_simple_base_only() {
let mut instr = MachineInstr::new(opc(X86Opcode::LEA));
instr.operands.push(MachineOperand::Reg(0));
instr.operands.push(MachineOperand::Reg(1));
assert!(is_lea_simple_base_only(&instr));
let mut instr2 = MachineInstr::new(opc(X86Opcode::LEA));
instr2.operands.push(MachineOperand::Reg(0));
instr2.operands.push(MachineOperand::Reg(1));
instr2.operands.push(MachineOperand::Reg(2));
assert!(!is_lea_simple_base_only(&instr2));
}
#[test]
fn test_optimize_lea_new() {
let opt = X86OptimizeLEA::new();
assert_eq!(opt.stats.lea_simplified, 0);
}
#[test]
fn test_optimize_lea_chain_detection() {
let mut opt = X86OptimizeLEA::new();
let mut mf = make_single_block_mf("test");
let mut lea1 = MachineInstr::new(opc(X86Opcode::LEA));
lea1.operands.push(MachineOperand::Reg(0));
lea1.operands.push(MachineOperand::Reg(1));
lea1.operands.push(MachineOperand::Reg(2));
lea1.def = Some(0);
mf.blocks[0].instructions.push(lea1);
let mut lea2 = MachineInstr::new(opc(X86Opcode::LEA));
lea2.operands.push(MachineOperand::Reg(3));
lea2.operands.push(MachineOperand::Reg(0));
lea2.operands.push(MachineOperand::Reg(1));
lea2.def = Some(3);
mf.blocks[0].instructions.push(lea2);
let stats = opt.run_on_function(&mut mf);
assert_eq!(stats.lea_chains_broken, 1);
}
#[test]
fn test_two_addr_new() {
let conv = X86TwoAddressToThreeAddress::new();
assert_eq!(conv.stats.instructions_converted, 0);
}
#[test]
fn test_get_avx_equivalent() {
assert_eq!(
get_avx_equivalent(X86Opcode::ADDPS),
Some(X86Opcode::VADDPS)
);
assert_eq!(
get_avx_equivalent(X86Opcode::MULSD),
Some(X86Opcode::VMULSD)
);
assert_eq!(
get_avx_equivalent(X86Opcode::ANDPS),
Some(X86Opcode::VANDPS)
);
assert_eq!(get_avx_equivalent(X86Opcode::NOP), None);
assert_eq!(get_avx_equivalent(X86Opcode::MOV), None);
}
#[test]
fn test_two_addr_conversion_detection() {
let mut conv = X86TwoAddressToThreeAddress::new();
let mut mf = make_single_block_mf("test");
let mut addps = MachineInstr::new(opc(X86Opcode::ADDPS));
addps.operands.push(MachineOperand::Reg(0));
addps.operands.push(MachineOperand::Reg(1));
addps.def = Some(0);
mf.blocks[0].instructions.push(addps);
let stats = conv.run_on_function(&mut mf);
assert_eq!(stats.instructions_converted, 1);
}
#[test]
fn test_fixup_instrs_new() {
let fixup = X86FixupInstrs::new();
assert_eq!(fixup.stats.encoding_fixes, 0);
}
#[test]
fn test_fixup_instrs_mov_zero_to_xor() {
let mut fixup = X86FixupInstrs::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_imm(0, 0));
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.xor_zero_used, 1);
}
#[test]
fn test_compress_evex_new() {
let mut compress = X86CompressEVEX::new();
assert_eq!(compress.stats.evex_compressed, 0);
}
#[test]
fn test_is_avx512_evex_instruction() {
assert!(is_avx512_evex_instruction(X86Opcode::VADDPS_Z));
assert!(is_avx512_evex_instruction(X86Opcode::VPMULLD_Z));
assert!(!is_avx512_evex_instruction(X86Opcode::VADDPS));
assert!(!is_avx512_evex_instruction(X86Opcode::ADDPS));
}
#[test]
fn test_get_vex_equivalent() {
assert_eq!(
get_vex_equivalent(X86Opcode::VADDPS_Z),
Some(X86Opcode::VADDPS)
);
assert_eq!(
get_vex_equivalent(X86Opcode::VMULPD_Z),
Some(X86Opcode::VMULPD)
);
assert_eq!(
get_vex_equivalent(X86Opcode::VANDPS_Z),
Some(X86Opcode::VANDPS)
);
assert_eq!(get_vex_equivalent(X86Opcode::VADDPS), None);
}
#[test]
fn test_compress_evex_analysis() {
let mut compress = X86CompressEVEX::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VADDPS_Z));
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VPADDD_Z));
let stats = compress.run_on_function(&mut mf);
assert_eq!(stats.evex_compressed, 2);
assert_eq!(stats.bytes_saved, 2);
}
#[test]
fn test_call_frame_new() {
let cfo = X86CallFrameOptimization::new();
assert!(cfo.eliminate_frame_pointer);
assert!(cfo.optimize_tail_calls);
}
#[test]
fn test_call_frame_tail_call_conversion() {
let mut cfo = X86CallFrameOptimization::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::CALL));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = cfo.run_on_function(&mut mf);
assert_eq!(stats.tail_calls_converted, 1);
assert_eq!(mf.blocks[0].instructions.len(), 1);
assert_eq!(mf.blocks[0].instructions[0].opcode, opc(X86Opcode::JMP));
}
#[test]
fn test_call_frame_redundant_stack_adjust() {
let mut cfo = X86CallFrameOptimization::new();
let mut mf = make_single_block_mf("test");
let mut sub = MachineInstr::new(opc(X86Opcode::SUB));
sub.operands.push(MachineOperand::PhysReg(RSP));
sub.operands.push(MachineOperand::Imm(8));
mf.blocks[0].instructions.push(sub);
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::PhysReg(RSP));
add.operands.push(MachineOperand::Imm(8));
mf.blocks[0].instructions.push(add);
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = cfo.run_on_function(&mut mf);
assert_eq!(stats.stack_adjustments_optimized, 2);
assert_eq!(mf.blocks[0].instructions.len(), 0);
}
#[test]
fn test_call_frame_eliminate_frame_pointer() {
let mut cfo = X86CallFrameOptimization::new();
let mut mf = make_single_block_mf("test");
let mut push = MachineInstr::new(opc(X86Opcode::PUSH));
push.operands.push(MachineOperand::PhysReg(RBP));
mf.blocks[0].instructions.push(push);
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::PhysReg(RBP));
mov.operands.push(MachineOperand::PhysReg(RSP));
mf.blocks[0].instructions.push(mov);
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = cfo.run_on_function(&mut mf);
assert_eq!(stats.frame_setup_optimized, 2);
assert_eq!(mf.blocks[0].instructions.len(), 0);
}
#[test]
fn test_ibt_new() {
let ibt = X86IndirectBranchTracking::new();
assert_eq!(ibt.stats.endbr_inserted, 0);
}
#[test]
fn test_ibt_entry_point_protection() {
let mut ibt = X86IndirectBranchTracking::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = ibt.run_on_function(&mut mf);
assert_eq!(stats.entry_points_protected, 1);
assert!(stats.endbr_inserted >= 1);
}
#[test]
fn test_ibt_multiple_blocks() {
let mut ibt = X86IndirectBranchTracking::new();
let mut mf = MachineFunction::new("test");
let bb1 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![make_instr(X86Opcode::RET)],
successors: vec![],
};
let bb2 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "target".to_string(),
instructions: vec![make_instr(X86Opcode::RET)],
successors: vec![],
};
mf.push_block(bb1);
mf.push_block(bb2);
let stats = ibt.run_on_function(&mut mf);
assert!(stats.endbr_inserted >= 2);
}
#[test]
fn test_slh_new() {
let slh = X86SpeculativeLoadHardening::new(SLHMode::AllLoads);
assert_eq!(slh.mode, SLHMode::AllLoads);
}
#[test]
fn test_slh_mode_none_does_nothing() {
let mut slh = X86SpeculativeLoadHardening::new(SLHMode::None);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 5));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JB, "target"));
let stats = slh.run_on_function(&mut mf);
assert_eq!(stats.lfence_inserted, 0);
}
#[test]
fn test_slh_all_loads_inserts_lfence() {
let mut slh = X86SpeculativeLoadHardening::new(SLHMode::AllLoads);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 5));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JB, "target"));
mf.blocks[0].instructions.push(make_mov_reg_reg(1, 0));
let stats = slh.run_on_function(&mut mf);
assert_eq!(stats.lfence_inserted, 1);
assert_eq!(stats.loads_hardened, 1);
}
#[test]
fn test_slh_retpoline_detection() {
let mut slh = X86SpeculativeLoadHardening::new(SLHMode::Retpoline);
let mut mf = make_single_block_mf("test");
let mut jmp = MachineInstr::new(opc(X86Opcode::JMP));
jmp.operands.push(MachineOperand::Reg(0));
mf.blocks[0].instructions.push(jmp);
let stats = slh.run_on_function(&mut mf);
assert_eq!(stats.retpolines_inserted, 1);
}
#[test]
fn test_flag_elim_new() {
let elim = X86RedundantFlagElim::new();
assert_eq!(elim.stats.total_eliminated, 0);
}
#[test]
fn test_flag_elim_duplicate_cmp() {
let mut elim = X86RedundantFlagElim::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.redundant_cmp_removed, 1);
assert_eq!(stats.total_eliminated, 1);
assert_eq!(mf.blocks[0].instructions.len(), 1);
}
#[test]
fn test_flag_elim_cmp_test_equivalent() {
let mut elim = X86RedundantFlagElim::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0].instructions.push(make_test_reg_reg(0, 0));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.total_eliminated, 1);
}
#[test]
fn test_flag_elim_not_redundant_with_flag_user() {
let mut elim = X86RedundantFlagElim::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
assert_eq!(mf.blocks[0].instructions.len(), 3);
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.total_eliminated, 0);
}
#[test]
fn test_cond_jump_new() {
let opt = X86OptimizeConditionalJump::new();
assert_eq!(opt.stats.jumps_eliminated, 0);
}
#[test]
fn test_invert_jcc() {
assert_eq!(invert_jcc(X86Opcode::JE), Some(X86Opcode::JNE));
assert_eq!(invert_jcc(X86Opcode::JNE), Some(X86Opcode::JE));
assert_eq!(invert_jcc(X86Opcode::JB), Some(X86Opcode::JAE));
assert_eq!(invert_jcc(X86Opcode::JAE), Some(X86Opcode::JB));
assert_eq!(invert_jcc(X86Opcode::JG), Some(X86Opcode::JLE));
assert_eq!(invert_jcc(X86Opcode::JLE), Some(X86Opcode::JG));
assert_eq!(invert_jcc(X86Opcode::JO), Some(X86Opcode::JNO));
assert_eq!(invert_jcc(X86Opcode::JNS), Some(X86Opcode::JS));
assert_eq!(invert_jcc(X86Opcode::JP), Some(X86Opcode::JNP));
assert_eq!(invert_jcc(X86Opcode::NOP), None); }
#[test]
fn test_cond_jump_jcc_over_jmp() {
let mut opt = X86OptimizeConditionalJump::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "L1"));
mf.blocks[0].instructions.push(make_jmp("L2"));
assert_eq!(mf.blocks[0].instructions.len(), 2);
let stats = opt.run_on_function(&mut mf);
assert_eq!(stats.jumps_inverted, 1);
assert_eq!(stats.jumps_eliminated, 1);
assert_eq!(mf.blocks[0].instructions.len(), 1);
}
#[test]
fn test_cond_jump_remove_jmp_to_fallthrough() {
let mut opt = X86OptimizeConditionalJump::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_jmp("next_block"));
let stats = opt.run_on_function(&mut mf);
assert!(stats.jumps_eliminated >= 0);
}
#[test]
fn test_setcc_postra_new() {
let fixup = X86FixupSetCCPostRA::new();
assert_eq!(fixup.stats.setcc_zext_fixed, 0);
}
#[test]
fn test_setcc_postra_high_byte_detection() {
let mut fixup = X86FixupSetCCPostRA::new();
let mut mf = make_single_block_mf("test");
let mut sete = MachineInstr::new(opc(X86Opcode::SETE));
sete.operands.push(MachineOperand::PhysReg(AH));
sete.def = Some(AH as VirtReg);
mf.blocks[0].instructions.push(sete);
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.high_byte_fixed, 1);
}
#[test]
fn test_get_full_width_reg() {
assert_eq!(get_full_width_reg(AL), EAX);
assert_eq!(get_full_width_reg(CL), ECX);
assert_eq!(get_full_width_reg(DL), EDX);
assert_eq!(get_full_width_reg(BL), EBX);
}
#[test]
fn test_pipeline_new() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::new(PipelineConfig::default(), info);
assert_eq!(pipeline.config.opt_level, 2);
}
#[test]
fn test_pipeline_for_o2() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
assert_eq!(pipeline.config.opt_level, 2);
assert!(pipeline.config.enable_pre_ra_passes);
assert!(pipeline.config.enable_post_ra_passes);
assert!(!pipeline.config.enable_security_passes);
}
#[test]
fn test_pipeline_for_o3() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o3(info);
assert_eq!(pipeline.config.opt_level, 3);
assert_eq!(pipeline.config.max_iterations, 5);
}
#[test]
fn test_pipeline_for_hardened() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_hardened(info);
assert!(pipeline.config.enable_security_passes);
assert!(pipeline.config.enable_slh);
assert!(pipeline.config.enable_cet);
}
#[test]
fn test_pipeline_run_empty_function() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_empty_mf();
let stats = pipeline.run(&mut mf);
assert_eq!(stats.iterations, 1);
}
#[test]
fn test_pipeline_run_simple_function() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 0)); mf.blocks[0].instructions.push(make_cmp_reg_imm(1, 0)); mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.iterations, 1);
assert!(stats.total_changes > 0);
}
#[test]
fn test_pipeline_run_with_multiple_blocks() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = MachineFunction::new("test");
let mut bb1 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![
make_mov_reg_reg(0, 0), make_cmp_reg_imm(1, 0),
make_jcc(X86Opcode::JE, "exit"),
],
successors: vec![1],
};
let bb2 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "exit".to_string(),
instructions: vec![make_instr(X86Opcode::RET)],
successors: vec![],
};
mf.push_block(bb1);
mf.push_block(bb2);
let stats = pipeline.run(&mut mf);
assert!(stats.total_changes > 0);
}
#[test]
fn test_pipeline_security_passes() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_hardened(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert!(stats.ibt.endbr_inserted > 0);
}
#[test]
fn test_pipeline_print_summary() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
let summary = pipeline.print_summary();
assert!(summary.contains("X86 Optimization Pipeline Summary"));
assert!(summary.contains("Combiner:"));
assert!(summary.contains("TOTAL CHANGES:"));
}
#[test]
fn test_pipeline_stats_compute_total() {
let mut stats = PipelineStats::new();
stats.combine.total_combines = 10;
stats.cmp_elim.cmp_eliminated = 5;
stats.flag_elim.total_eliminated = 3;
stats.compute_total();
assert_eq!(stats.total_changes, 18);
}
#[test]
fn test_same_reg() {
assert!(same_reg(&MachineOperand::Reg(5), &MachineOperand::Reg(5)));
assert!(!same_reg(&MachineOperand::Reg(5), &MachineOperand::Reg(6)));
assert!(same_reg(
&MachineOperand::PhysReg(RAX),
&MachineOperand::PhysReg(RAX)
));
assert!(!same_reg(
&MachineOperand::Reg(5),
&MachineOperand::PhysReg(5)
));
}
#[test]
fn test_is_zero_imm() {
assert!(is_zero_imm(&MachineOperand::Imm(0)));
assert!(!is_zero_imm(&MachineOperand::Imm(1)));
assert!(!is_zero_imm(&MachineOperand::Reg(0)));
}
#[test]
fn test_is_one_imm() {
assert!(is_one_imm(&MachineOperand::Imm(1)));
assert!(!is_one_imm(&MachineOperand::Imm(0)));
}
#[test]
fn test_get_imm() {
assert_eq!(get_imm(&MachineOperand::Imm(42)), Some(42));
assert_eq!(get_imm(&MachineOperand::Imm(-1)), Some(-1));
assert_eq!(get_imm(&MachineOperand::Reg(0)), None);
}
#[test]
fn test_fits_i8() {
assert!(fits_i8(0));
assert!(fits_i8(127));
assert!(fits_i8(-128));
assert!(!fits_i8(128));
assert!(!fits_i8(-129));
}
#[test]
fn test_fits_i32() {
assert!(fits_i32(0));
assert!(fits_i32(2_147_483_647));
assert!(fits_i32(-2_147_483_648));
assert!(!fits_i32(2_147_483_648i64));
assert!(!fits_i32(-2_147_483_649i64));
}
#[test]
fn test_is_reg() {
assert!(is_reg(&MachineOperand::Reg(0)));
assert!(is_reg(&MachineOperand::PhysReg(RAX)));
assert!(!is_reg(&MachineOperand::Imm(0)));
assert!(!is_reg(&MachineOperand::Label("x".into())));
}
#[test]
fn test_is_phys_reg() {
assert!(is_phys_reg(&MachineOperand::PhysReg(RAX), RAX));
assert!(!is_phys_reg(&MachineOperand::PhysReg(RBX), RAX));
assert!(!is_phys_reg(&MachineOperand::Reg(0), RAX));
}
#[test]
fn test_is_compare_op() {
assert!(is_compare_op(X86Opcode::CMP));
assert!(is_compare_op(X86Opcode::TEST));
assert!(!is_compare_op(X86Opcode::ADD));
}
#[test]
fn test_defines_flags() {
assert!(defines_flags(X86Opcode::ADD));
assert!(defines_flags(X86Opcode::SUB));
assert!(defines_flags(X86Opcode::CMP));
assert!(defines_flags(X86Opcode::TEST));
assert!(defines_flags(X86Opcode::INC));
assert!(defines_flags(X86Opcode::DEC));
assert!(!defines_flags(X86Opcode::MOV));
assert!(!defines_flags(X86Opcode::LEA));
assert!(!defines_flags(X86Opcode::JMP));
}
#[test]
fn test_uses_flags() {
assert!(uses_flags(X86Opcode::JE));
assert!(uses_flags(X86Opcode::JNE));
assert!(uses_flags(X86Opcode::CMOVE));
assert!(uses_flags(X86Opcode::SETE));
assert!(uses_flags(X86Opcode::ADC));
assert!(uses_flags(X86Opcode::SBB));
assert!(!uses_flags(X86Opcode::MOV));
assert!(!uses_flags(X86Opcode::ADD));
}
#[test]
fn test_is_commutative_alu() {
assert!(is_commutative_alu(X86Opcode::ADD));
assert!(is_commutative_alu(X86Opcode::AND));
assert!(is_commutative_alu(X86Opcode::OR));
assert!(is_commutative_alu(X86Opcode::XOR));
assert!(is_commutative_alu(X86Opcode::IMUL));
assert!(!is_commutative_alu(X86Opcode::SUB));
assert!(!is_commutative_alu(X86Opcode::MOV));
}
#[test]
fn test_is_non_commutative_alu() {
assert!(is_non_commutative_alu(X86Opcode::SUB));
assert!(is_non_commutative_alu(X86Opcode::SBB));
assert!(!is_non_commutative_alu(X86Opcode::ADD));
}
#[test]
fn test_get_32bit_subreg() {
assert_eq!(get_32bit_subreg(RAX), EAX);
assert_eq!(get_32bit_subreg(RCX), ECX);
assert_eq!(get_32bit_subreg(R8), R8D);
assert_eq!(get_32bit_subreg(R15), R15D);
assert_eq!(get_32bit_subreg(99), 99); }
#[test]
fn test_is_gpr64() {
assert!(is_gpr64(RAX));
assert!(is_gpr64(R15));
assert!(!is_gpr64(16)); assert!(!is_gpr64(100));
}
#[test]
fn test_is_gpr32() {
assert!(!is_gpr32(RAX)); assert!(is_gpr32(EAX));
assert!(is_gpr32(R15D));
}
#[test]
fn test_get_reg() {
assert_eq!(get_reg(&MachineOperand::Reg(42)), Some(42));
assert_eq!(get_reg(&MachineOperand::PhysReg(7)), Some(7));
assert_eq!(get_reg(&MachineOperand::Imm(0)), None);
assert_eq!(get_reg(&MachineOperand::Label("x".into())), None);
}
#[test]
fn test_full_pipeline_nop_elimination() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = make_single_block_mf("test");
for _ in 0..10 {
mf.blocks[0].instructions.push(make_mov_reg_reg(5, 5));
}
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
assert_eq!(mf.blocks[0].instructions.len(), 11);
let stats = pipeline.run(&mut mf);
assert_eq!(stats.combine.moves_eliminated, 10);
}
#[test]
fn test_full_pipeline_cmp_elim_chain() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.cmp_elim.cmp_eliminated, 1);
}
#[test]
fn test_full_pipeline_lea_optimize() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = make_single_block_mf("test");
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(MachineOperand::Reg(0));
lea.operands.push(MachineOperand::Reg(1));
lea.def = Some(0);
mf.blocks[0].instructions.push(lea);
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.fixup_lea.lea_converted_to_mov, 1);
}
#[test]
fn test_full_pipeline_tail_call() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::CALL));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.call_frame.tail_calls_converted, 1);
}
#[test]
fn test_full_pipeline_macro_fusion() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 5));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.macro_fusion.cmp_jcc_fusions, 1);
}
#[test]
fn test_pipeline_config_default() {
let config = PipelineConfig::default();
assert_eq!(config.opt_level, 2);
assert!(config.enable_pre_ra_passes);
assert!(config.enable_post_ra_passes);
assert!(!config.enable_security_passes);
assert_eq!(config.max_iterations, 3);
}
#[test]
fn test_pipeline_iterative_convergence() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 0));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert!(stats.total_changes > 0);
assert!(stats.iterations == 1);
}
#[test]
fn test_all_isel_opcodes_are_handled() {
let test_ops = vec![
X86Opcode::ADD,
X86Opcode::SUB,
X86Opcode::CMP,
X86Opcode::TEST,
X86Opcode::JE,
X86Opcode::JNE,
X86Opcode::JMP,
X86Opcode::CALL,
X86Opcode::RET,
X86Opcode::LEA,
X86Opcode::MOV,
X86Opcode::XOR,
X86Opcode::INC,
X86Opcode::DEC,
X86Opcode::AND,
X86Opcode::OR,
X86Opcode::SHL,
X86Opcode::SHR,
X86Opcode::SAR,
];
for op in test_ops {
let recognized = is_commutative_alu(op)
|| is_non_commutative_alu(op)
|| is_compare_op(op)
|| op.is_jcc()
|| defines_flags(op)
|| matches!(
op,
X86Opcode::MOV
| X86Opcode::LEA
| X86Opcode::CALL
| X86Opcode::RET
| X86Opcode::JMP
);
assert!(
recognized,
"Opcode {:?} not recognized by any classifier",
op
);
}
}
#[test]
fn test_combiner_preserves_instruction_count_on_no_opt() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 1, 2));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let initial_count = mf.blocks[0].instructions.len();
let _stats = combiner.run_on_function(&mut mf);
assert_eq!(mf.blocks[0].instructions.len(), initial_count);
}
#[test]
fn test_combiner_fold_immediate_add_zero() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_imm(0, 0));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 1, 0));
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.immediates_folded, 1);
}
#[test]
fn test_combiner_fold_immediate_and() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_imm(0, 0xFF));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::AND, 1, 0));
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.immediates_folded, 1);
}
#[test]
fn test_combiner_fold_immediate_xor() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_imm(0, -1));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::XOR, 1, 0));
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.immediates_folded, 1);
}
#[test]
fn test_combiner_shift_combine_same_op_verify_constant() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
let mut shl1 = MachineInstr::new(opc(X86Opcode::SHL));
shl1.operands.push(MachineOperand::Reg(0));
shl1.operands.push(MachineOperand::Imm(1));
shl1.def = Some(0);
mf.blocks[0].instructions.push(shl1);
let mut shl2 = MachineInstr::new(opc(X86Opcode::SHL));
shl2.operands.push(MachineOperand::Reg(0));
shl2.operands.push(MachineOperand::Imm(1));
shl2.def = Some(0);
mf.blocks[0].instructions.push(shl2);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.shifts_combined, 1);
assert_eq!(
mf.blocks[0].instructions[0].operands[1],
MachineOperand::Imm(2)
);
}
#[test]
fn test_combiner_shift_combine_different_regs_no_combine() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
let mut shl1 = MachineInstr::new(opc(X86Opcode::SHL));
shl1.operands.push(MachineOperand::Reg(0));
shl1.operands.push(MachineOperand::Imm(1));
shl1.def = Some(0);
mf.blocks[0].instructions.push(shl1);
let mut shl2 = MachineInstr::new(opc(X86Opcode::SHL));
shl2.operands.push(MachineOperand::Reg(1));
shl2.operands.push(MachineOperand::Imm(1));
shl2.def = Some(1);
mf.blocks[0].instructions.push(shl2);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.shifts_combined, 0);
}
#[test]
fn test_combiner_shift_combine_different_ops_no_combine() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
let mut shl = MachineInstr::new(opc(X86Opcode::SHL));
shl.operands.push(MachineOperand::Reg(0));
shl.operands.push(MachineOperand::Imm(1));
shl.def = Some(0);
mf.blocks[0].instructions.push(shl);
let mut shr = MachineInstr::new(opc(X86Opcode::SHR));
shr.operands.push(MachineOperand::Reg(0));
shr.operands.push(MachineOperand::Imm(1));
shr.def = Some(0);
mf.blocks[0].instructions.push(shr);
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.shifts_combined, 0);
}
#[test]
fn test_combiner_extension_optimization_tracking() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_single_block_mf("test");
let mut movsx = MachineInstr::new(opc(X86Opcode::MOVSX));
movsx.operands.push(MachineOperand::Reg(0));
movsx.operands.push(MachineOperand::Reg(1));
movsx.def = Some(0);
mf.blocks[0].instructions.push(movsx);
mf.blocks[0].instructions.push(make_mov_reg_reg(2, 0));
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.extensions_optimized, 1);
}
#[test]
fn test_combiner_disabled_flags() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
combiner.enable_load_fold = false;
combiner.enable_imm_fold = false;
combiner.enable_lea_opt = false;
combiner.enable_zero_idiom = false;
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 0));
let stats = combiner.run_on_function(&mut mf);
assert_eq!(stats.total_combines, 1); }
#[test]
fn test_cmp_elim_cmp_after_sub_same_operands() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::SUB, 0, 1));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_eliminated, 1);
}
#[test]
fn test_cmp_elim_cmp_after_and() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::AND, 0, 1));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_eliminated, 1);
}
#[test]
fn test_cmp_elim_cmp_after_mov_preserved() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 1));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_simplified, 1);
assert_eq!(stats.cmp_eliminated, 0);
}
#[test]
fn test_cmp_elim_cmp_nonzero_preserved() {
let mut elim = X86CmpElimination::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
let mut instr = MachineInstr::new(opc(X86Opcode::CMP));
instr.operands.push(MachineOperand::Reg(0));
instr.operands.push(MachineOperand::Imm(5));
mf.blocks[0].instructions.push(instr);
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.cmp_eliminated, 0);
}
#[test]
fn test_macro_fusion_inc_dec_jcc() {
let mut fusion = X86MacroFusion::new();
let mut mf = make_single_block_mf("test");
let mut inc = MachineInstr::new(opc(X86Opcode::INC));
inc.operands.push(MachineOperand::Reg(0));
inc.def = Some(0);
mf.blocks[0].instructions.push(inc);
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JNE, "target"));
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.incdec_jcc_fusions, 1);
}
#[test]
fn test_macro_fusion_and_jcc() {
let mut fusion = X86MacroFusion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::AND, 0, 1));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JE, "target"));
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.alu_jcc_fusions, 1);
}
#[test]
fn test_macro_fusion_multiple_blocks() {
let mut fusion = X86MacroFusion::new();
let mut mf = MachineFunction::new("test");
let bb1 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![make_cmp_reg_imm(0, 0), make_jcc(X86Opcode::JE, "exit")],
successors: vec![1],
};
let bb2 = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "exit".to_string(),
instructions: vec![make_test_reg_reg(1, 1), make_jcc(X86Opcode::JNE, "entry")],
successors: vec![0],
};
mf.push_block(bb1);
mf.push_block(bb2);
let stats = fusion.run_on_function(&mf);
assert_eq!(stats.cmp_jcc_fusions, 1);
assert_eq!(stats.test_jcc_fusions, 1);
assert_eq!(stats.total_fusions, 2);
}
#[test]
fn test_macro_fusion_all_jcc_variants() {
let mut fusion = X86MacroFusion::new();
let jccs = vec![
X86Opcode::JO,
X86Opcode::JNO,
X86Opcode::JB,
X86Opcode::JAE,
X86Opcode::JE,
X86Opcode::JNE,
X86Opcode::JBE,
X86Opcode::JA,
X86Opcode::JS,
X86Opcode::JNS,
X86Opcode::JP,
X86Opcode::JNP,
X86Opcode::JL,
X86Opcode::JGE,
X86Opcode::JLE,
X86Opcode::JG,
];
let mut total = 0usize;
for (i, jcc) in jccs.iter().enumerate() {
let mut block_mf = make_single_block_mf(&format!("test_{}", i));
block_mf.blocks[0]
.instructions
.push(make_cmp_reg_imm(i as u32, 0));
block_mf.blocks[0]
.instructions
.push(make_jcc(*jcc, "target"));
let s = fusion.run_on_function(&block_mf);
total += s.cmp_jcc_fusions as usize;
}
assert_eq!(total, jccs.len());
}
#[test]
fn test_vzero_upper_no_call_no_ret() {
let mut vzu = X86VZeroUpperInsertion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VADDPS));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
let stats = vzu.run_on_function(&mut mf);
assert_eq!(stats.avx_blocks_detected, 1);
assert_eq!(stats.vzeroupper_inserted, 0);
}
#[test]
fn test_vzero_upper_multiple_calls() {
let mut vzu = X86VZeroUpperInsertion::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VADDPS));
mf.blocks[0].instructions.push(make_instr(X86Opcode::CALL));
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VMULPS));
mf.blocks[0].instructions.push(make_instr(X86Opcode::CALL));
let stats = vzu.run_on_function(&mut mf);
assert_eq!(stats.vzeroupper_inserted, 2);
}
#[test]
fn test_vzero_upper_all_avx_instructions_recognized() {
let avx_ops = vec![
X86Opcode::VADDPS,
X86Opcode::VMULPD,
X86Opcode::VFMSUB132PS,
X86Opcode::VBROADCASTSS,
X86Opcode::VPERMILPS,
X86Opcode::VFMADD213PD,
X86Opcode::VPADDB,
X86Opcode::VPMULLD,
X86Opcode::VZEROUPPER,
];
for op in avx_ops {
assert!(is_avx_instruction(op), "{:?} should be AVX", op);
}
}
#[test]
fn test_vzero_upper_sse_not_detected_as_avx() {
assert!(!is_avx_instruction(X86Opcode::ADDPS));
assert!(!is_avx_instruction(X86Opcode::MULSD));
assert!(!is_avx_instruction(X86Opcode::DIVPD));
assert!(!is_avx_instruction(X86Opcode::MOVSS));
}
#[test]
fn test_compress_evex_all_avx512_ops() {
let avx512_ops = vec![
X86Opcode::VADDPS_Z,
X86Opcode::VADDPD_Z,
X86Opcode::VMULPS_Z,
X86Opcode::VMULPD_Z,
X86Opcode::VDIVPS_Z,
X86Opcode::VDIVPD_Z,
X86Opcode::VANDPS_Z,
X86Opcode::VANDPD_Z,
X86Opcode::VORPS_Z,
X86Opcode::VORPD_Z,
X86Opcode::VXORPS_Z,
X86Opcode::VXORPD_Z,
X86Opcode::VPADDD_Z,
X86Opcode::VPADDQ_Z,
X86Opcode::VPSUBD_Z,
X86Opcode::VPSUBQ_Z,
X86Opcode::VPMULLD_Z,
X86Opcode::VPMULLQ_Z,
];
for op in avx512_ops {
assert!(is_avx512_evex_instruction(op), "{:?} should be AVX-512", op);
assert!(
get_vex_equivalent(op).is_some(),
"{:?} should have VEX equivalent",
op
);
}
}
#[test]
fn test_compress_evex_vex_not_detected_as_evex() {
assert!(!is_avx512_evex_instruction(X86Opcode::VADDPS));
assert!(!is_avx512_evex_instruction(X86Opcode::VPADDD));
assert!(!is_avx512_evex_instruction(X86Opcode::ADDPS));
}
#[test]
fn test_compress_evex_bytes_saved_count() {
let mut compress = X86CompressEVEX::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VADDPS_Z));
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VMULPS_Z));
mf.blocks[0]
.instructions
.push(make_instr(X86Opcode::VANDPS_Z));
let stats = compress.run_on_function(&mut mf);
assert_eq!(stats.evex_compressed, 3);
assert_eq!(stats.bytes_saved, 3);
}
#[test]
fn test_call_frame_no_tail_call_when_not_last() {
let mut cfo = X86CallFrameOptimization::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::CALL));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, 0, 1));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = cfo.run_on_function(&mut mf);
assert_eq!(stats.tail_calls_converted, 0);
}
#[test]
fn test_call_frame_push_pop_merge() {
let mut cfo = X86CallFrameOptimization::new();
let mut mf = make_single_block_mf("test");
let mut push1 = MachineInstr::new(opc(X86Opcode::PUSH));
push1.operands.push(MachineOperand::PhysReg(RBX));
mf.blocks[0].instructions.push(push1);
let mut push2 = MachineInstr::new(opc(X86Opcode::PUSH));
push2.operands.push(MachineOperand::PhysReg(R12));
mf.blocks[0].instructions.push(push2);
let stats = cfo.run_on_function(&mut mf);
assert_eq!(stats.pushes_merged, 1);
}
#[test]
fn test_call_frame_disabled_tail_call_opt() {
let mut cfo = X86CallFrameOptimization::new();
cfo.optimize_tail_calls = false;
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::CALL));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = cfo.run_on_function(&mut mf);
assert_eq!(stats.tail_calls_converted, 0);
}
#[test]
fn test_call_frame_disabled_frame_pointer_elim() {
let mut cfo = X86CallFrameOptimization::new();
cfo.eliminate_frame_pointer = false;
let mut mf = make_single_block_mf("test");
let mut push = MachineInstr::new(opc(X86Opcode::PUSH));
push.operands.push(MachineOperand::PhysReg(RBP));
mf.blocks[0].instructions.push(push);
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::PhysReg(RBP));
mov.operands.push(MachineOperand::PhysReg(RSP));
mf.blocks[0].instructions.push(mov);
let stats = cfo.run_on_function(&mut mf);
assert_eq!(stats.frame_setup_optimized, 0);
}
#[test]
fn test_slh_mode_default_is_none() {
let mode = SLHMode::default();
assert_eq!(mode, SLHMode::None);
}
#[test]
fn test_slh_hardened_loads_inserts_lfence() {
let mut slh = X86SpeculativeLoadHardening::new(SLHMode::HardenedLoads);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 5));
mf.blocks[0]
.instructions
.push(make_jcc(X86Opcode::JB, "target"));
let stats = slh.run_on_function(&mut mf);
assert!(stats.lfence_inserted > 0 || stats.indices_masked > 0);
}
#[test]
fn test_slh_retpoline_only_indirect_branches() {
let mut slh = X86SpeculativeLoadHardening::new(SLHMode::Retpoline);
let mut mf = make_single_block_mf("test");
let jmp = make_jmp("direct_target");
mf.blocks[0].instructions.push(jmp);
let stats = slh.run_on_function(&mut mf);
assert_eq!(stats.retpolines_inserted, 0);
}
#[test]
fn test_slh_indirect_call_retpoline() {
let mut slh = X86SpeculativeLoadHardening::new(SLHMode::Retpoline);
let mut mf = make_single_block_mf("test");
let mut call = MachineInstr::new(opc(X86Opcode::CALL));
call.operands.push(MachineOperand::Reg(0));
mf.blocks[0].instructions.push(call);
let stats = slh.run_on_function(&mut mf);
assert_eq!(stats.retpolines_inserted, 1);
}
#[test]
fn test_flag_elim_sub_after_sub_same_operands() {
let mut elim = X86RedundantFlagElim::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::SUB, 0, 1));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::SUB, 0, 1));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.redundant_test_removed, 1);
}
#[test]
fn test_flag_elim_cmp_then_test_same_effect() {
let mut elim = X86RedundantFlagElim::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_cmp_reg_imm(1, 0));
mf.blocks[0].instructions.push(make_test_reg_reg(1, 1));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.total_eliminated, 1);
}
#[test]
fn test_flag_elim_no_eliminate_across_flag_definer() {
let mut elim = X86RedundantFlagElim::new();
let mut mf = make_single_block_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::SUB, 0, 1));
mf.blocks[0].instructions.push(make_cmp_reg_imm(1, 0));
let stats = elim.run_on_function(&mut mf);
assert_eq!(stats.total_eliminated, 0);
}
#[test]
fn test_setcc_postra_all_high_bytes() {
let mut fixup = X86FixupSetCCPostRA::new();
let high_bytes = [AH, BH, CH, DH];
for ® in &high_bytes {
let mut mf = make_single_block_mf(&format!("test_{}", reg));
let mut sete = MachineInstr::new(opc(X86Opcode::SETE));
sete.operands.push(MachineOperand::PhysReg(reg));
sete.def = Some(reg as VirtReg);
mf.blocks[0].instructions.push(sete);
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.high_byte_fixed, 1);
}
}
#[test]
fn test_setcc_postra_low_bytes_not_flagged() {
let mut fixup = X86FixupSetCCPostRA::new();
let low_bytes = [AL, CL, DL, BL];
for ® in &low_bytes {
let mut mf = make_single_block_mf(&format!("test_{}", reg));
let mut sete = MachineInstr::new(opc(X86Opcode::SETE));
sete.operands.push(MachineOperand::PhysReg(reg));
sete.def = Some(reg as VirtReg);
mf.blocks[0].instructions.push(sete);
let stats = fixup.run_on_function(&mut mf);
assert_eq!(stats.high_byte_fixed, 0);
}
}
#[test]
fn test_preserves_carry() {
assert!(preserves_carry(X86Opcode::INC));
assert!(preserves_carry(X86Opcode::DEC));
assert!(preserves_carry(X86Opcode::NEG));
assert!(preserves_carry(X86Opcode::NOT));
assert!(!preserves_carry(X86Opcode::ADD));
assert!(!preserves_carry(X86Opcode::SUB));
}
#[test]
fn test_is_bitwise() {
assert!(is_bitwise(X86Opcode::AND));
assert!(is_bitwise(X86Opcode::OR));
assert!(is_bitwise(X86Opcode::XOR));
assert!(is_bitwise(X86Opcode::NOT));
assert!(!is_bitwise(X86Opcode::ADD));
assert!(!is_bitwise(X86Opcode::MOV));
}
#[test]
fn test_is_arithmetic() {
assert!(is_arithmetic(X86Opcode::ADD));
assert!(is_arithmetic(X86Opcode::SUB));
assert!(is_arithmetic(X86Opcode::ADC));
assert!(is_arithmetic(X86Opcode::SBB));
assert!(is_arithmetic(X86Opcode::MUL));
assert!(is_arithmetic(X86Opcode::IMUL));
assert!(is_arithmetic(X86Opcode::DIV));
assert!(is_arithmetic(X86Opcode::IDIV));
assert!(!is_arithmetic(X86Opcode::MOV));
assert!(!is_arithmetic(X86Opcode::CMP));
}
#[test]
fn test_is_shift() {
assert!(is_shift(X86Opcode::SHL));
assert!(is_shift(X86Opcode::SHR));
assert!(is_shift(X86Opcode::SAR));
assert!(is_shift(X86Opcode::ROL));
assert!(is_shift(X86Opcode::ROR));
assert!(is_shift(X86Opcode::RCL));
assert!(is_shift(X86Opcode::RCR));
assert!(is_shift(X86Opcode::SHLD));
assert!(is_shift(X86Opcode::SHRD));
assert!(!is_shift(X86Opcode::ADD));
}
#[test]
fn test_opcode_from_u32_valid() {
assert_eq!(opcode_from_u32(opc(X86Opcode::ADD)), Some(X86Opcode::ADD));
assert_eq!(opcode_from_u32(opc(X86Opcode::NOP)), Some(X86Opcode::NOP));
assert_eq!(
opcode_from_u32(opc(X86Opcode::VADDPS)),
Some(X86Opcode::VADDPS)
);
}
#[test]
fn test_opcode_from_u32_invalid() {
let max_val = X86Opcode::CLFLUSHOPT_64 as u32;
assert_eq!(opcode_from_u32(max_val + 1), None);
assert_eq!(opcode_from_u32(u32::MAX), None);
}
#[test]
fn test_x86_opcode_extraction() {
let mut instr = MachineInstr::new(opc(X86Opcode::ADD));
instr.operands.push(MachineOperand::Reg(0));
instr.operands.push(MachineOperand::Reg(1));
assert_eq!(x86_opcode(&instr), Some(X86Opcode::ADD));
let instr2 = MachineInstr::new(opc(X86Opcode::CMOVE));
assert_eq!(x86_opcode(&instr2), Some(X86Opcode::CMOVE));
}
#[test]
fn test_pipeline_idempotent_on_already_optimized_code() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let _stats1 = pipeline.run(&mut mf);
let stats2 = pipeline.run(&mut mf);
assert_eq!(stats2.total_changes, 0);
}
#[test]
fn test_pipeline_does_not_crash_on_empty_instructions() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_single_block_mf("test");
let bad_instr = MachineInstr::new(99999);
mf.blocks[0].instructions.push(bad_instr);
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.iterations, 1);
}
#[test]
fn test_pipeline_handles_all_zero_block() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = MachineFunction::new("test");
let stats = pipeline.run(&mut mf);
assert_eq!(stats.iterations, 1);
}
#[test]
fn test_pipeline_with_deep_block_chain() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = MachineFunction::new("test");
for i in 0..20 {
let mut bb = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: format!("bb{}", i),
instructions: vec![
make_mov_reg_reg(i * 2, i * 2),
make_cmp_reg_imm(i * 2 + 1, 0),
],
successors: if i < 19 {
vec![(i + 1) as usize]
} else {
vec![]
},
};
if i == 19 {
bb.instructions.push(make_instr(X86Opcode::RET));
}
mf.push_block(bb);
}
let stats = pipeline.run(&mut mf);
assert_eq!(stats.combine.moves_eliminated, 20);
}
#[test]
fn test_opt_stats_accumulate_across_passes() {
let mut pipeline_stats = PipelineStats::new();
pipeline_stats.combine = CombineStats {
total_combines: 5,
moves_eliminated: 3,
..CombineStats::new()
};
pipeline_stats.cmp_elim = CmpElimStats {
cmp_eliminated: 2,
cmp_simplified: 1,
..CmpElimStats::new()
};
pipeline_stats.flag_elim = FlagElimStats {
total_eliminated: 2,
redundant_cmp_removed: 1,
redundant_test_removed: 1,
..FlagElimStats::new()
};
pipeline_stats.macro_fusion = MacroFusionStats {
total_fusions: 3,
cmp_jcc_fusions: 2,
test_jcc_fusions: 1,
..MacroFusionStats::new()
};
pipeline_stats.vzero_upper = VZeroUpperStats {
vzeroupper_inserted: 2,
avx_blocks_detected: 1,
transitions_protected: 2,
};
pipeline_stats.compute_total();
assert!(pipeline_stats.total_changes >= 15);
}
#[test]
fn test_pipeline_print_summary_output_coverage() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_single_block_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 0));
mf.blocks[0].instructions.push(make_cmp_reg_imm(0, 0));
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
pipeline.run(&mut mf);
let summary = pipeline.print_summary();
assert!(summary.contains("Combiner:"));
assert!(summary.contains("Cmp Elim:"));
assert!(summary.contains("Flag Elim:"));
assert!(summary.contains("LEA Fixup:"));
assert!(summary.contains("Cond Jump:"));
assert!(summary.contains("Macro Fusion:"));
assert!(summary.contains("Micro Fusion:"));
assert!(summary.contains("2-Addr"));
assert!(summary.contains("VZEROUPPER:"));
assert!(summary.contains("EVEX Compress:"));
assert!(summary.contains("Speculative Hardening:"));
assert!(summary.contains("TOTAL CHANGES:"));
}
#[test]
fn test_all_passes_handle_max_iterations() {
let info = X86InstrInfo::new();
let config = PipelineConfig {
opt_level: 3,
max_iterations: 10,
..Default::default()
};
let mut pipeline = X86OptPipeline::new(config, info);
let mut mf = make_single_block_mf("test");
for _ in 0..5 {
mf.blocks[0].instructions.push(make_mov_reg_reg(0, 0));
}
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert!(stats.iterations > 0);
assert_eq!(stats.combine.moves_eliminated, 5);
}
#[test]
fn test_pipeline_is_reusable() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf1 = make_single_block_mf("func1");
mf1.blocks[0].instructions.push(make_mov_reg_reg(0, 0));
mf1.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats1 = pipeline.run(&mut mf1);
let mut mf2 = make_single_block_mf("func2");
mf2.blocks[0].instructions.push(make_mov_reg_reg(1, 1));
mf2.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats2 = pipeline.run(&mut mf2);
assert_eq!(stats1.combine.moves_eliminated, 1);
assert_eq!(stats2.combine.moves_eliminated, 1);
}
#[test]
fn test_large_function_optimization() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_single_block_mf("large_func");
for i in 0..100 {
mf.blocks[0].instructions.push(make_mov_reg_reg(i, i));
}
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
assert_eq!(mf.blocks[0].instructions.len(), 101);
let stats = pipeline.run(&mut mf);
assert_eq!(stats.combine.moves_eliminated, 100);
assert_eq!(mf.blocks[0].instructions.len(), 1);
}
#[test]
fn test_pipeline_handles_rapid_mov_sequences() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_single_block_mf("rapid_test");
for i in 0..50 {
mf.blocks[0].instructions.push(make_mov_reg_reg(i, i + 1));
}
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.combine.moves_eliminated, 50);
}
#[test]
fn test_pipeline_handles_interleaved_optimizable_patterns() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o3(info);
let mut mf = make_single_block_mf("interleaved");
for i in 0..10 {
mf.blocks[0]
.instructions
.push(make_mov_reg_reg(i * 3, i * 3));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg(X86Opcode::ADD, i * 3 + 1, i * 3 + 2));
mf.blocks[0]
.instructions
.push(make_cmp_reg_imm(i * 3 + 1, 0));
}
mf.blocks[0].instructions.push(make_instr(X86Opcode::RET));
let stats = pipeline.run(&mut mf);
assert_eq!(stats.combine.moves_eliminated, 10);
assert_eq!(stats.cmp_elim.cmp_simplified, 10); }
#[test]
fn test_fits_immediate_boundaries() {
assert!(fits_i8(0));
assert!(fits_i8(127));
assert!(fits_i8(-128));
assert!(!fits_i8(128));
assert!(!fits_i8(-129));
assert!(!fits_i8(1000));
assert!(fits_i32(0));
assert!(fits_i32(2_147_483_647));
assert!(fits_i32(-2_147_483_648));
assert!(!fits_i32(2_147_483_648));
assert!(!fits_i32(-2_147_483_649));
}
#[test]
fn test_get_reg_boundaries() {
assert_eq!(get_reg(&MachineOperand::Reg(0)), Some(0));
assert_eq!(get_reg(&MachineOperand::Reg(u32::MAX)), Some(u32::MAX));
assert_eq!(get_reg(&MachineOperand::PhysReg(0)), Some(0));
assert_eq!(get_reg(&MachineOperand::Imm(0)), None);
}
#[test]
fn test_get_32bit_subreg_all_gprs() {
let pairs = vec![
(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),
];
for (r64, r32) in pairs {
assert_eq!(get_32bit_subreg(r64), r32, "Mismatch for reg {}", r64);
}
}
#[test]
fn test_same_reg_with_global_and_label() {
assert!(!same_reg(
&MachineOperand::Reg(5),
&MachineOperand::Global("x".into())
));
assert!(!same_reg(
&MachineOperand::PhysReg(5),
&MachineOperand::Label("x".into())
));
}
#[test]
fn test_combine_stats_doc_example() {
let mut stats = CombineStats::new();
stats.loads_folded = 1;
stats.moves_eliminated = 3;
stats.total_combines = 4;
assert!(stats.made_progress());
let mut stats2 = CombineStats::new();
stats2.leas_simplified = 2;
stats.merge(&stats2);
assert_eq!(stats.leas_simplified, 2);
assert_eq!(stats.loads_folded, 1);
assert_eq!(stats.moves_eliminated, 3);
}
#[test]
fn test_pipeline_config_doc_example() {
let config = PipelineConfig::default();
assert_eq!(config.opt_level, 2);
assert!(config.enable_pre_ra_passes);
assert!(config.enable_post_ra_passes);
assert!(!config.enable_security_passes);
assert!(!config.enable_slh);
assert!(!config.enable_cet);
assert_eq!(config.max_iterations, 3);
assert!(!config.debug);
}
#[test]
fn test_macro_fusion_stats_doc_example() {
let mut stats = MacroFusionStats::new();
stats.cmp_jcc_fusions = 10;
stats.test_jcc_fusions = 5;
stats.alu_jcc_fusions = 3;
stats.incdec_jcc_fusions = 2;
stats.total_fusions = 20;
assert!(stats.made_progress());
assert_eq!(stats.total_fusions, 20);
}
}
impl X86Combiner {
pub fn try_convert_to_inc_dec(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
if idx >= mbb.instructions.len() {
return None;
}
let instr = &mbb.instructions[idx];
let op = x86_opcode(instr)?;
let new_op = match op {
X86Opcode::ADD => {
if instr.operands.len() >= 2 && is_one_imm(&instr.operands[1]) {
Some(X86Opcode::INC)
} else {
None
}
}
X86Opcode::SUB => {
if instr.operands.len() >= 2 && is_one_imm(&instr.operands[1]) {
Some(X86Opcode::DEC)
} else {
None
}
}
_ => None,
};
if let Some(new_op) = new_op {
for j in (idx + 1)..mbb.instructions.len() {
if let Some(later_op) = x86_opcode(&mbb.instructions[j]) {
if uses_carry_flag(later_op) {
return None;
}
if defines_flags(later_op) {
break;
}
}
}
let mut new_instr = MachineInstr::new(opc(new_op));
new_instr.operands.push(instr.operands[0].clone());
new_instr.def = instr.def;
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.total_combines += 1;
return Some(stats);
}
None
}
pub fn try_convert_mov_zero_to_xor(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
if idx >= mbb.instructions.len() {
return None;
}
let instr = &mbb.instructions[idx];
let op = x86_opcode(instr)?;
if !matches!(op, X86Opcode::MOV) {
return None;
}
if instr.operands.len() >= 2
&& is_zero_imm(&instr.operands[1])
&& is_reg(&instr.operands[0])
{
let dst = instr.operands[0].clone();
let mut new_instr = MachineInstr::new(opc(X86Opcode::XOR));
new_instr.operands.push(dst.clone());
new_instr.operands.push(dst);
new_instr.def = instr.def;
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.total_combines += 1;
return Some(stats);
}
None
}
pub fn try_recognize_and_zero_idiom(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
if idx >= mbb.instructions.len() {
return None;
}
let instr = &mbb.instructions[idx];
let op = x86_opcode(instr)?;
if op == X86Opcode::AND && instr.operands.len() >= 2 && is_zero_imm(&instr.operands[1]) {
let dst = instr.operands[0].clone();
let mut new_instr = MachineInstr::new(opc(X86Opcode::XOR));
new_instr.operands.push(dst.clone());
new_instr.operands.push(dst);
new_instr.def = instr.def;
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.total_combines += 1;
stats.zero_idioms_recognized += 1;
return Some(stats);
}
None
}
pub fn try_cancel_double_negation(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
if idx + 1 >= mbb.instructions.len() {
return None;
}
let first = &mbb.instructions[idx];
let second = &mbb.instructions[idx + 1];
let fop = x86_opcode(first)?;
let sop = x86_opcode(second)?;
if fop == X86Opcode::NEG && sop == X86Opcode::NEG {
if first.operands.len() >= 1
&& second.operands.len() >= 1
&& same_reg(&first.operands[0], &second.operands[0])
{
mbb.instructions.remove(idx + 1);
mbb.instructions.remove(idx);
let mut stats = CombineStats::new();
stats.total_combines += 1;
return Some(stats);
}
}
None
}
pub fn try_merge_not_neg(
&mut self,
mbb: &mut MachineBasicBlock,
idx: usize,
) -> Option<CombineStats> {
if idx + 1 >= mbb.instructions.len() {
return None;
}
let first = &mbb.instructions[idx];
let second = &mbb.instructions[idx + 1];
let fop = x86_opcode(first)?;
let sop = x86_opcode(second)?;
if fop == X86Opcode::NOT && sop == X86Opcode::NEG {
if first.operands.len() >= 1
&& second.operands.len() >= 1
&& same_reg(&first.operands[0], &second.operands[0])
{
let mut new_instr = MachineInstr::new(opc(X86Opcode::INC));
new_instr.operands.push(first.operands[0].clone());
new_instr.def = second.def;
mbb.instructions.remove(idx + 1);
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.total_combines += 1;
return Some(stats);
}
}
if fop == X86Opcode::NEG && sop == X86Opcode::NOT {
if first.operands.len() >= 1
&& second.operands.len() >= 1
&& same_reg(&first.operands[0], &second.operands[0])
{
let mut new_instr = MachineInstr::new(opc(X86Opcode::DEC));
new_instr.operands.push(first.operands[0].clone());
new_instr.def = second.def;
mbb.instructions.remove(idx + 1);
mbb.instructions[idx] = new_instr;
let mut stats = CombineStats::new();
stats.total_combines += 1;
return Some(stats);
}
}
None
}
pub fn estimate_size_reduction(&self) -> usize {
let mut bytes = 0usize;
bytes += self.stats.moves_eliminated as usize * 3;
bytes += self.stats.leas_simplified as usize * 2;
bytes += self.stats.immediates_folded as usize * 4;
bytes += self.stats.zero_idioms_recognized as usize * 2;
bytes
}
pub fn report(&self) -> String {
let mut r = String::new();
r.push_str("X86 Instruction Combiner Report:\n");
r.push_str(&format!(
" Loads folded: {}\n",
self.stats.loads_folded
));
r.push_str(&format!(
" LEAs simplified: {}\n",
self.stats.leas_simplified
));
r.push_str(&format!(
" Immediates folded: {}\n",
self.stats.immediates_folded
));
r.push_str(&format!(
" Moves eliminated: {}\n",
self.stats.moves_eliminated
));
r.push_str(&format!(
" Zero idioms: {}\n",
self.stats.zero_idioms_recognized
));
r.push_str(&format!(
" Extensions optimized: {}\n",
self.stats.extensions_optimized
));
r.push_str(&format!(
" Shifts combined: {}\n",
self.stats.shifts_combined
));
r.push_str(&format!(
" Total combines: {}\n",
self.stats.total_combines
));
r.push_str(&format!(
" Est. bytes saved: {}\n",
self.estimate_size_reduction()
));
r
}
}
impl X86CmpElimination {
pub fn report(&self) -> String {
format!(
"CMP Elimination: {} eliminated, {} simplified, {} replaced with TEST, {} flag ops removed",
self.stats.cmp_eliminated,
self.stats.cmp_simplified,
self.stats.test_replaced,
self.stats.redundant_flags_removed
)
}
}
impl X86VZeroUpperInsertion {
pub fn needs_vzeroupper(&self, mf: &MachineFunction) -> bool {
let has_avx = mf.blocks.iter().any(|block| {
block.instructions.iter().any(|instr| {
if let Some(op) = x86_opcode(instr) {
is_avx_instruction(op)
} else {
false
}
})
});
let has_boundary = mf.blocks.iter().any(|block| {
block.instructions.iter().any(|instr| {
if let Some(op) = x86_opcode(instr) {
matches!(op, X86Opcode::CALL | X86Opcode::RET)
} else {
false
}
})
});
has_avx && has_boundary
}
pub fn count_avx_instructions(&self, mf: &MachineFunction) -> usize {
mf.blocks
.iter()
.flat_map(|b| b.instructions.iter())
.filter(|instr| {
if let Some(op) = x86_opcode(instr) {
is_avx_instruction(op)
} else {
false
}
})
.count()
}
pub fn report(&self) -> String {
format!(
"VZEROUPPER: {} inserted, {} AVX blocks, {} transitions protected",
self.stats.vzeroupper_inserted,
self.stats.avx_blocks_detected,
self.stats.transitions_protected
)
}
}
impl X86CallFrameOptimization {
pub fn is_leaf_function(&self, mf: &MachineFunction) -> bool {
!mf.blocks.iter().any(|block| {
block.instructions.iter().any(|instr| {
if let Some(op) = x86_opcode(instr) {
op == X86Opcode::CALL
} else {
false
}
})
})
}
pub fn estimate_frame_size(&self, mf: &MachineFunction) -> usize {
let mut max_push = 0usize;
let mut max_sub = 0i64;
for block in &mf.blocks {
for instr in &block.instructions {
if let Some(op) = x86_opcode(instr) {
if op == X86Opcode::PUSH {
max_push += 8;
}
if op == X86Opcode::SUB
&& instr.operands.len() >= 2
&& is_phys_reg(&instr.operands[0], RSP)
{
if let Some(imm) = get_imm(&instr.operands[1]) {
max_sub = max_sub.max(imm);
}
}
}
}
}
(max_push + max_sub.max(0) as usize).max(0)
}
pub fn report(&self) -> String {
format!(
"Call Frame: {} setup optimized, {} teardown optimized, {} tail calls, {} pushes merged",
self.stats.frame_setup_optimized,
self.stats.frame_teardown_optimized,
self.stats.tail_calls_converted,
self.stats.pushes_merged
)
}
}
impl X86SpeculativeLoadHardening {
pub fn needs_hardening(&self, _instr: &MachineInstr) -> bool {
match self.mode {
SLHMode::None => false,
SLHMode::AllLoads => true,
SLHMode::HardenedLoads => true,
SLHMode::Retpoline => false,
}
}
pub fn count_hardened_loads(&self, mf: &MachineFunction) -> usize {
if self.mode == SLHMode::None {
return 0;
}
mf.blocks
.iter()
.flat_map(|b| b.instructions.iter())
.filter(|instr| {
if let Some(op) = x86_opcode(instr) {
is_load_op(op)
} else {
false
}
})
.count()
}
pub fn report(&self) -> String {
format!(
"SLH (mode={:?}): {} LFENCEs, {} loads hardened, {} retpolines",
self.mode,
self.stats.lfence_inserted,
self.stats.loads_hardened,
self.stats.retpolines_inserted
)
}
}
impl X86OptPipeline {
pub fn detailed_breakdown(&self) -> Vec<(&'static str, u64)> {
vec![
("Combiner", self.total_stats.combine.total_combines),
(
"CmpElim",
self.total_stats.cmp_elim.cmp_eliminated + self.total_stats.cmp_elim.cmp_simplified,
),
("FlagElim", self.total_stats.flag_elim.total_eliminated),
("LEAFixup", self.total_stats.fixup_lea.total_lea_optimized),
(
"CondJump",
self.total_stats.cond_jump.jumps_eliminated
+ self.total_stats.cond_jump.jumps_inverted,
),
("MacroFusion", self.total_stats.macro_fusion.total_fusions),
("MicroFusion", self.total_stats.micro_fusion.total_fusions),
(
"TwoAddrConv",
self.total_stats.two_addr.instructions_converted,
),
(
"VZeroUpper",
self.total_stats.vzero_upper.vzeroupper_inserted,
),
(
"EVEXCompress",
self.total_stats.compress_evex.evex_compressed,
),
(
"CallFrame",
self.total_stats.call_frame.tail_calls_converted,
),
("FixupInstrs", self.total_stats.fixup_instrs.xor_zero_used),
("SLH", self.total_stats.slh.lfence_inserted),
("IBT", self.total_stats.ibt.endbr_inserted),
]
}
pub fn made_any_change(&self) -> bool {
self.total_stats.total_changes > 0
}
pub fn most_impactful_pass(&self) -> (&'static str, u64) {
self.detailed_breakdown()
.into_iter()
.max_by_key(|(_, count)| *count)
.unwrap_or(("None", 0))
}
}
#[cfg(test)]
mod advanced_tests {
use super::*;
fn make_test_mf(name: &str) -> MachineFunction {
let mut mf = MachineFunction::new(name);
let bb = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![],
successors: vec![],
};
mf.push_block(bb);
mf
}
fn make_mov_reg_reg_test(dst: u32, src: u32) -> MachineInstr {
let mut instr = MachineInstr::new(X86Opcode::MOV as u32);
instr.operands.push(MachineOperand::Reg(dst));
instr.operands.push(MachineOperand::Reg(src));
instr.def = Some(dst);
instr
}
fn make_mov_reg_imm_test(dst: u32, imm: i64) -> MachineInstr {
let mut instr = MachineInstr::new(X86Opcode::MOV as u32);
instr.operands.push(MachineOperand::Reg(dst));
instr.operands.push(MachineOperand::Imm(imm));
instr.def = Some(dst);
instr
}
fn make_alu_reg_reg_test(opcode: X86Opcode, dst: u32, src: u32) -> MachineInstr {
let mut instr = MachineInstr::new(opcode as u32);
instr.operands.push(MachineOperand::Reg(dst));
instr.operands.push(MachineOperand::Reg(src));
instr.def = Some(dst);
instr
}
fn make_cmp_reg_imm_test(reg: u32, imm: i64) -> MachineInstr {
let mut instr = MachineInstr::new(X86Opcode::CMP as u32);
instr.operands.push(MachineOperand::Reg(reg));
instr.operands.push(MachineOperand::Imm(imm));
instr
}
fn make_jcc_test(opcode: X86Opcode, label: &str) -> MachineInstr {
let mut instr = MachineInstr::new(opcode as u32);
instr
.operands
.push(MachineOperand::Label(label.to_string()));
instr
}
fn make_single_instr_mf(opcode: X86Opcode) -> MachineFunction {
let mut mf = MachineFunction::new("test");
let mut bb = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![],
successors: vec![],
};
bb.instructions.push(MachineInstr::new(opcode as u32));
mf.push_block(bb);
mf
}
#[test]
fn test_try_convert_to_inc_dec_add() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::Reg(0));
add.operands.push(MachineOperand::Imm(1));
add.def = Some(0);
mf.blocks[0].instructions.push(add);
let result = combiner.try_convert_to_inc_dec(&mut mf.blocks[0], 0);
assert!(result.is_some());
assert_eq!(
x86_opcode(&mf.blocks[0].instructions[0]),
Some(X86Opcode::INC)
);
}
#[test]
fn test_try_convert_to_inc_dec_sub() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut sub = MachineInstr::new(opc(X86Opcode::SUB));
sub.operands.push(MachineOperand::Reg(0));
sub.operands.push(MachineOperand::Imm(1));
sub.def = Some(0);
mf.blocks[0].instructions.push(sub);
let result = combiner.try_convert_to_inc_dec(&mut mf.blocks[0], 0);
assert!(result.is_some());
assert_eq!(
x86_opcode(&mf.blocks[0].instructions[0]),
Some(X86Opcode::DEC)
);
}
#[test]
fn test_try_convert_to_inc_dec_carry_user_present() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::Reg(0));
add.operands.push(MachineOperand::Imm(1));
add.def = Some(0);
mf.blocks[0].instructions.push(add);
let mut adc = MachineInstr::new(opc(X86Opcode::ADC));
adc.operands.push(MachineOperand::Reg(1));
adc.operands.push(MachineOperand::Reg(2));
mf.blocks[0].instructions.push(adc);
let result = combiner.try_convert_to_inc_dec(&mut mf.blocks[0], 0);
assert!(result.is_none());
}
#[test]
fn test_try_convert_mov_zero_to_xor() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::Reg(0));
mov.operands.push(MachineOperand::Imm(0));
mov.def = Some(0);
mf.blocks[0].instructions.push(mov);
let result = combiner.try_convert_mov_zero_to_xor(&mut mf.blocks[0], 0);
assert!(result.is_some());
assert_eq!(
x86_opcode(&mf.blocks[0].instructions[0]),
Some(X86Opcode::XOR)
);
}
#[test]
fn test_try_convert_mov_nonzero_preserved() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut mov = MachineInstr::new(opc(X86Opcode::MOV));
mov.operands.push(MachineOperand::Reg(0));
mov.operands.push(MachineOperand::Imm(5));
mov.def = Some(0);
mf.blocks[0].instructions.push(mov);
let result = combiner.try_convert_mov_zero_to_xor(&mut mf.blocks[0], 0);
assert!(result.is_none());
}
#[test]
fn test_try_recognize_and_zero_idiom() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut and_instr = MachineInstr::new(opc(X86Opcode::AND));
and_instr.operands.push(MachineOperand::Reg(0));
and_instr.operands.push(MachineOperand::Imm(0));
mf.blocks[0].instructions.push(and_instr);
let result = combiner.try_recognize_and_zero_idiom(&mut mf.blocks[0], 0);
assert!(result.is_some());
}
#[test]
fn test_try_cancel_double_negation() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut neg1 = MachineInstr::new(opc(X86Opcode::NEG));
neg1.operands.push(MachineOperand::Reg(0));
neg1.def = Some(0);
mf.blocks[0].instructions.push(neg1);
let mut neg2 = MachineInstr::new(opc(X86Opcode::NEG));
neg2.operands.push(MachineOperand::Reg(0));
neg2.def = Some(0);
mf.blocks[0].instructions.push(neg2);
assert_eq!(mf.blocks[0].instructions.len(), 2);
let result = combiner.try_cancel_double_negation(&mut mf.blocks[0], 0);
assert!(result.is_some());
assert_eq!(mf.blocks[0].instructions.len(), 0);
}
#[test]
fn test_try_merge_not_neg_to_inc() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut not_instr = MachineInstr::new(opc(X86Opcode::NOT));
not_instr.operands.push(MachineOperand::Reg(0));
not_instr.def = Some(0);
mf.blocks[0].instructions.push(not_instr);
let mut neg = MachineInstr::new(opc(X86Opcode::NEG));
neg.operands.push(MachineOperand::Reg(0));
neg.def = Some(0);
mf.blocks[0].instructions.push(neg);
let result = combiner.try_merge_not_neg(&mut mf.blocks[0], 0);
assert!(result.is_some());
assert_eq!(
x86_opcode(&mf.blocks[0].instructions[0]),
Some(X86Opcode::INC)
);
}
#[test]
fn test_try_merge_neg_not_to_dec() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut neg = MachineInstr::new(opc(X86Opcode::NEG));
neg.operands.push(MachineOperand::Reg(0));
neg.def = Some(0);
mf.blocks[0].instructions.push(neg);
let mut not_instr = MachineInstr::new(opc(X86Opcode::NOT));
not_instr.operands.push(MachineOperand::Reg(0));
not_instr.def = Some(0);
mf.blocks[0].instructions.push(not_instr);
let result = combiner.try_merge_not_neg(&mut mf.blocks[0], 0);
assert!(result.is_some());
assert_eq!(
x86_opcode(&mf.blocks[0].instructions[0]),
Some(X86Opcode::DEC)
);
}
#[test]
fn test_combiner_estimate_size_reduction() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
combiner.stats.moves_eliminated = 10;
combiner.stats.leas_simplified = 5;
combiner.stats.immediates_folded = 3;
let reduction = combiner.estimate_size_reduction();
assert_eq!(reduction, 10 * 3 + 5 * 2 + 3 * 4);
}
#[test]
fn test_combiner_report_format() {
let info = X86InstrInfo::new();
let combiner = X86Combiner::new(info);
let report = combiner.report();
assert!(report.contains("Loads folded:"));
assert!(report.contains("LEAs simplified:"));
assert!(report.contains("Moves eliminated:"));
assert!(report.contains("Total combines:"));
assert!(report.contains("Est. bytes saved:"));
}
#[test]
fn test_is_leaf_function_no_calls() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
let mf = MachineFunction::new("leaf");
assert!(pipeline.call_frame.is_leaf_function(&mf));
}
#[test]
fn test_is_leaf_function_with_call() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_test_mf("test");
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::CALL)));
assert!(!pipeline.call_frame.is_leaf_function(&mf));
}
#[test]
fn test_needs_vzeroupper_true() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_test_mf("test");
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::VADDPS)));
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::CALL)));
assert!(pipeline.vzero_upper.needs_vzeroupper(&mf));
}
#[test]
fn test_needs_vzeroupper_no_avx() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_test_mf("test");
mf.blocks[0]
.instructions
.push(make_alu_reg_reg_test(X86Opcode::ADD, 0, 1));
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::CALL)));
assert!(!pipeline.vzero_upper.needs_vzeroupper(&mf));
}
#[test]
fn test_count_avx_instructions() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_test_mf("test");
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::VADDPS)));
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::VMULPD)));
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::ADDPS)));
assert_eq!(pipeline.vzero_upper.count_avx_instructions(&mf), 2);
}
#[test]
fn test_estimate_frame_size_zero() {
let info = X86InstrInfo::new();
let pipeline = X86OptPipeline::for_o2(info);
let mf = MachineFunction::new("empty");
assert_eq!(pipeline.call_frame.estimate_frame_size(&mf), 0);
}
#[test]
fn test_count_hardened_loads() {
let slh = X86SpeculativeLoadHardening::new(SLHMode::AllLoads);
let mut mf = make_test_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg_test(0, 1));
mf.blocks[0]
.instructions
.push(make_alu_reg_reg_test(X86Opcode::ADD, 2, 3));
let count = slh.count_hardened_loads(&mf);
assert!(count > 0);
}
#[test]
fn test_pipeline_detailed_breakdown_has_entries() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_test_mf("test");
mf.blocks[0].instructions.push(make_mov_reg_reg_test(0, 0));
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::RET)));
pipeline.run(&mut mf);
let breakdown = pipeline.detailed_breakdown();
assert!(!breakdown.is_empty());
let has_combiner = breakdown.iter().any(|(name, _)| *name == "Combiner");
assert!(has_combiner);
}
#[test]
fn test_pipeline_most_impactful_pass() {
let info = X86InstrInfo::new();
let mut pipeline = X86OptPipeline::for_o2(info);
let mut mf = make_test_mf("test");
for _ in 0..20 {
mf.blocks[0].instructions.push(make_mov_reg_reg_test(0, 0));
}
mf.blocks[0]
.instructions
.push(MachineInstr::new(opc(X86Opcode::RET)));
pipeline.run(&mut mf);
let (name, count) = pipeline.most_impactful_pass();
assert_eq!(name, "Combiner");
assert_eq!(count, 20);
}
#[test]
fn test_cmp_elim_report_output() {
let mut elim = X86CmpElimination::new();
elim.stats.cmp_eliminated = 5;
elim.stats.cmp_simplified = 3;
let report = elim.report();
assert!(report.contains("5"));
assert!(report.contains("3"));
}
#[test]
fn test_vzero_upper_report_output() {
let mut vzu = X86VZeroUpperInsertion::new();
vzu.stats.vzeroupper_inserted = 4;
let report = vzu.report();
assert!(report.contains("4"));
}
#[test]
fn test_call_frame_report_output() {
let mut cfo = X86CallFrameOptimization::new();
cfo.stats.tail_calls_converted = 2;
let report = cfo.report();
assert!(report.contains("2"));
}
#[test]
fn test_slh_report_contains_mode() {
let slh = X86SpeculativeLoadHardening::new(SLHMode::AllLoads);
let report = slh.report();
assert!(report.contains("AllLoads"));
}
#[test]
fn test_try_convert_to_inc_dec_add_reg_no_imm() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut add = MachineInstr::new(opc(X86Opcode::ADD));
add.operands.push(MachineOperand::Reg(0));
add.operands.push(MachineOperand::Reg(1));
add.def = Some(0);
mf.blocks[0].instructions.push(add);
let result = combiner.try_convert_to_inc_dec(&mut mf.blocks[0], 0);
assert!(result.is_none());
}
#[test]
fn test_double_negation_different_registers_preserved() {
let info = X86InstrInfo::new();
let mut combiner = X86Combiner::new(info);
let mut mf = make_test_mf("test");
let mut neg1 = MachineInstr::new(opc(X86Opcode::NEG));
neg1.operands.push(MachineOperand::Reg(0));
mf.blocks[0].instructions.push(neg1);
let mut neg2 = MachineInstr::new(opc(X86Opcode::NEG));
neg2.operands.push(MachineOperand::Reg(1));
mf.blocks[0].instructions.push(neg2);
let result = combiner.try_cancel_double_negation(&mut mf.blocks[0], 0);
assert!(result.is_none());
assert_eq!(mf.blocks[0].instructions.len(), 2);
}
#[test]
fn test_defines_flags_all_expected() {
let setters = [
X86Opcode::ADD,
X86Opcode::ADC,
X86Opcode::SUB,
X86Opcode::SBB,
X86Opcode::AND,
X86Opcode::OR,
X86Opcode::XOR,
X86Opcode::CMP,
X86Opcode::TEST,
X86Opcode::INC,
X86Opcode::DEC,
X86Opcode::NEG,
X86Opcode::MUL,
X86Opcode::IMUL,
X86Opcode::SHL,
X86Opcode::SHR,
X86Opcode::SAR,
];
for op in setters {
assert!(defines_flags(op), "{:?} should define flags", op);
}
}
#[test]
fn test_defines_flags_negative_cases() {
let non_setters = [
X86Opcode::MOV,
X86Opcode::LEA,
X86Opcode::JMP,
X86Opcode::CALL,
X86Opcode::PUSH,
X86Opcode::POP,
X86Opcode::NOP,
];
for op in non_setters {
assert!(!defines_flags(op), "{:?} should NOT define flags", op);
}
}
#[test]
fn test_uses_flags_all_jcc_cmov_setcc() {
let users = [
X86Opcode::ADC,
X86Opcode::SBB,
X86Opcode::JE,
X86Opcode::JNE,
X86Opcode::JB,
X86Opcode::JAE,
X86Opcode::JL,
X86Opcode::JG,
X86Opcode::JO,
X86Opcode::JS,
X86Opcode::JP,
X86Opcode::CMOVE,
X86Opcode::CMOVNE,
X86Opcode::CMOVL,
X86Opcode::CMOVG,
X86Opcode::SETE,
X86Opcode::SETNE,
X86Opcode::SETL,
X86Opcode::SETG,
];
for op in users {
assert!(uses_flags(op), "{:?} should use flags", op);
}
}
#[test]
fn test_uses_flags_negative_cases() {
let non_users = [
X86Opcode::MOV,
X86Opcode::ADD,
X86Opcode::SUB,
X86Opcode::JMP,
X86Opcode::CALL,
X86Opcode::RET,
];
for op in non_users {
assert!(!uses_flags(op), "{:?} should NOT use flags", op);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstrCost {
Cheap,
Moderate,
Expensive,
VeryExpensive,
Free,
}
impl InstrCost {
pub fn classify(op: X86Opcode) -> Self {
match op {
X86Opcode::NOP | X86Opcode::INT3 => InstrCost::Free,
X86Opcode::MOV | X86Opcode::MOVSX | X86Opcode::MOVZX | X86Opcode::MOVABS => {
InstrCost::Cheap
}
X86Opcode::ADD | X86Opcode::SUB | X86Opcode::AND | X86Opcode::OR | X86Opcode::XOR => {
InstrCost::Cheap
}
X86Opcode::INC | X86Opcode::DEC | X86Opcode::NEG | X86Opcode::NOT => InstrCost::Cheap,
X86Opcode::CMP | X86Opcode::TEST => InstrCost::Cheap,
X86Opcode::SHL | X86Opcode::SHR | X86Opcode::SAR => {
InstrCost::Cheap
}
X86Opcode::LEA => InstrCost::Moderate,
X86Opcode::IMUL => InstrCost::Expensive,
X86Opcode::MUL => InstrCost::Expensive,
X86Opcode::DIV | X86Opcode::IDIV => InstrCost::VeryExpensive,
X86Opcode::CMOVO
| X86Opcode::CMOVNO
| X86Opcode::CMOVB
| X86Opcode::CMOVAE
| X86Opcode::CMOVE
| X86Opcode::CMOVNE
| X86Opcode::CMOVA
| X86Opcode::CMOVBE
| X86Opcode::CMOVG
| X86Opcode::CMOVGE
| X86Opcode::CMOVL
| X86Opcode::CMOVLE
| X86Opcode::CMOVS
| X86Opcode::CMOVNS
| X86Opcode::CMOVP
| X86Opcode::CMOVNP => InstrCost::Moderate,
X86Opcode::JMP
| X86Opcode::JO
| X86Opcode::JNO
| X86Opcode::JB
| X86Opcode::JAE
| X86Opcode::JE
| X86Opcode::JNE
| X86Opcode::JBE
| X86Opcode::JA
| X86Opcode::JS
| X86Opcode::JNS
| X86Opcode::JP
| X86Opcode::JNP
| X86Opcode::JL
| X86Opcode::JGE
| X86Opcode::JLE
| X86Opcode::JG => InstrCost::Cheap,
X86Opcode::CALL | X86Opcode::RET => InstrCost::Moderate,
X86Opcode::PUSH | X86Opcode::POP => InstrCost::Cheap,
X86Opcode::SETO
| X86Opcode::SETNO
| X86Opcode::SETB
| X86Opcode::SETAE
| X86Opcode::SETE
| X86Opcode::SETNE
| X86Opcode::SETBE
| X86Opcode::SETA
| X86Opcode::SETS
| X86Opcode::SETNS
| X86Opcode::SETP
| X86Opcode::SETNP
| X86Opcode::SETL
| X86Opcode::SETGE
| X86Opcode::SETLE
| X86Opcode::SETG => InstrCost::Cheap,
X86Opcode::ADDSS
| X86Opcode::ADDSD
| X86Opcode::SUBSS
| X86Opcode::SUBSD
| X86Opcode::MULSS
| X86Opcode::MULSD => InstrCost::Moderate,
X86Opcode::DIVSS | X86Opcode::DIVSD | X86Opcode::SQRTSS | X86Opcode::SQRTSD => {
InstrCost::VeryExpensive
}
X86Opcode::ADDPS
| X86Opcode::ADDPD
| X86Opcode::SUBPS
| X86Opcode::SUBPD
| X86Opcode::MULPS
| X86Opcode::MULPD => InstrCost::Moderate,
X86Opcode::DIVPS | X86Opcode::DIVPD | X86Opcode::SQRTPS => InstrCost::VeryExpensive,
X86Opcode::VADDPS
| X86Opcode::VADDPD
| X86Opcode::VSUBPS
| X86Opcode::VSUBPD
| X86Opcode::VMULPS
| X86Opcode::VMULPD => InstrCost::Moderate,
X86Opcode::VDIVPS | X86Opcode::VDIVPD => InstrCost::VeryExpensive,
X86Opcode::VFMADD132PS
| X86Opcode::VFMADD213PS
| X86Opcode::VFMADD231PS
| X86Opcode::VFMADD132PD
| X86Opcode::VFMADD213PD
| X86Opcode::VFMADD231PD => InstrCost::Moderate,
X86Opcode::VANDPS | X86Opcode::VORPS | X86Opcode::VXORPS | X86Opcode::VANDNPS => {
InstrCost::Cheap
}
X86Opcode::MOVSS | X86Opcode::MOVSD | X86Opcode::MOVD | X86Opcode::MOVQ => {
InstrCost::Cheap
}
X86Opcode::CVTSI2SS
| X86Opcode::CVTSI2SD
| X86Opcode::CVTSS2SI
| X86Opcode::CVTSD2SI
| X86Opcode::CVTSS2SD
| X86Opcode::CVTSD2SS => InstrCost::Moderate,
_ => InstrCost::Moderate,
}
}
pub fn cheaper_than(&self, other: InstrCost) -> bool {
self.cost_value() <= other.cost_value()
}
fn cost_value(&self) -> u8 {
match self {
InstrCost::Free => 0,
InstrCost::Cheap => 1,
InstrCost::Moderate => 2,
InstrCost::Expensive => 3,
InstrCost::VeryExpensive => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct X86CostModel {
pub optimize_for_size: bool,
pub uarch_generation: u8,
}
impl X86CostModel {
pub fn new() -> Self {
Self {
optimize_for_size: false,
uarch_generation: 3, }
}
pub fn should_decompose_lea(&self, _scale: u8, _has_displacement: bool) -> bool {
if self.optimize_for_size {
return false; }
if self.uarch_generation >= 2 {
return true;
}
false
}
pub fn should_convert_cmov_to_branch(&self, _estimated_branch_probability: f64) -> bool {
if self.optimize_for_size {
return false; }
false
}
pub fn should_fold_immediate(&self, imm: i64) -> bool {
fits_i32(imm)
}
pub fn should_macro_fuse(&self, _first: X86Opcode, _second: X86Opcode) -> bool {
if self.uarch_generation >= 2 {
true } else {
false
}
}
}
impl Default for X86CostModel {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86OptDecisionEngine {
pub cost_model: X86CostModel,
pub combiner: X86Combiner,
pub decisions: Vec<OptDecision>,
}
#[derive(Debug, Clone)]
pub struct OptDecision {
pub pass_name: &'static str,
pub block_name: String,
pub instr_index: usize,
pub decision: DecisionKind,
pub reason: String,
}
#[derive(Debug, Clone)]
pub enum DecisionKind {
Applied,
Deferred,
Skipped,
Rejected,
}
impl X86OptDecisionEngine {
pub fn new(instr_info: X86InstrInfo) -> Self {
Self {
cost_model: X86CostModel::new(),
combiner: X86Combiner::new(instr_info),
decisions: Vec::new(),
}
}
pub fn analyze(&mut self, mf: &MachineFunction) -> Vec<OptDecision> {
self.decisions.clear();
for block in &mf.blocks {
for (i, instr) in block.instructions.iter().enumerate() {
if let Some(op) = x86_opcode(instr) {
self.analyze_instruction(op, instr, &block.name, i);
}
}
}
self.decisions.clone()
}
fn analyze_instruction(
&mut self,
op: X86Opcode,
instr: &MachineInstr,
block_name: &str,
idx: usize,
) {
let cost = InstrCost::classify(op);
if cost == InstrCost::VeryExpensive {
self.decisions.push(OptDecision {
pass_name: "CostAnalysis",
block_name: block_name.to_string(),
instr_index: idx,
decision: DecisionKind::Deferred,
reason: format!(
"Very expensive instruction {:?} — consider strength reduction",
op
),
});
}
if op == X86Opcode::LEA && instr.operands.len() >= 3 {
let has_scale = instr.operands.len() >= 4;
let has_disp = instr.operands.len() >= 5;
if self
.cost_model
.should_decompose_lea(if has_scale { 2 } else { 1 }, has_disp)
{
self.decisions.push(OptDecision {
pass_name: "LEAOpt",
block_name: block_name.to_string(),
instr_index: idx,
decision: DecisionKind::Applied,
reason: "Complex LEA decomposed to ADD+SHL for speed".to_string(),
});
}
}
if op == X86Opcode::CMP || op == X86Opcode::TEST {
if idx + 1 < instr.operands.len() {
if self.cost_model.should_macro_fuse(op, X86Opcode::JE) {
self.decisions.push(OptDecision {
pass_name: "MacroFusion",
block_name: block_name.to_string(),
instr_index: idx,
decision: DecisionKind::Applied,
reason: format!("{:?} eligible for macro-fusion with following JCC", op),
});
}
}
}
}
pub fn summary(&self) -> String {
let mut s = String::new();
let applied = self
.decisions
.iter()
.filter(|d| matches!(d.decision, DecisionKind::Applied))
.count();
let deferred = self
.decisions
.iter()
.filter(|d| matches!(d.decision, DecisionKind::Deferred))
.count();
let skipped = self
.decisions
.iter()
.filter(|d| matches!(d.decision, DecisionKind::Skipped))
.count();
let rejected = self
.decisions
.iter()
.filter(|d| matches!(d.decision, DecisionKind::Rejected))
.count();
s.push_str(&format!(
"Optimization Decisions: {} applied, {} deferred, {} skipped, {} rejected ({} total)\n",
applied,
deferred,
skipped,
rejected,
self.decisions.len()
));
s
}
}
impl Default for X86OptDecisionEngine {
fn default() -> Self {
Self::new(X86InstrInfo::new())
}
}
#[cfg(test)]
mod cost_model_tests {
use super::*;
#[test]
fn test_instr_cost_classify_cheap() {
assert_eq!(InstrCost::classify(X86Opcode::ADD), InstrCost::Cheap);
assert_eq!(InstrCost::classify(X86Opcode::MOV), InstrCost::Cheap);
assert_eq!(InstrCost::classify(X86Opcode::XOR), InstrCost::Cheap);
assert_eq!(InstrCost::classify(X86Opcode::CMP), InstrCost::Cheap);
assert_eq!(InstrCost::classify(X86Opcode::TEST), InstrCost::Cheap);
assert_eq!(InstrCost::classify(X86Opcode::INC), InstrCost::Cheap);
}
#[test]
fn test_instr_cost_classify_moderate() {
assert_eq!(InstrCost::classify(X86Opcode::LEA), InstrCost::Moderate);
assert_eq!(InstrCost::classify(X86Opcode::CMOVE), InstrCost::Moderate);
assert_eq!(InstrCost::classify(X86Opcode::CALL), InstrCost::Moderate);
assert_eq!(InstrCost::classify(X86Opcode::VADDPS), InstrCost::Moderate);
}
#[test]
fn test_instr_cost_classify_expensive() {
assert_eq!(InstrCost::classify(X86Opcode::IMUL), InstrCost::Expensive);
assert_eq!(InstrCost::classify(X86Opcode::MUL), InstrCost::Expensive);
}
#[test]
fn test_instr_cost_classify_very_expensive() {
assert_eq!(
InstrCost::classify(X86Opcode::DIV),
InstrCost::VeryExpensive
);
assert_eq!(
InstrCost::classify(X86Opcode::IDIV),
InstrCost::VeryExpensive
);
assert_eq!(
InstrCost::classify(X86Opcode::DIVSS),
InstrCost::VeryExpensive
);
assert_eq!(
InstrCost::classify(X86Opcode::SQRTSS),
InstrCost::VeryExpensive
);
}
#[test]
fn test_instr_cost_classify_free() {
assert_eq!(InstrCost::classify(X86Opcode::NOP), InstrCost::Free);
assert_eq!(InstrCost::classify(X86Opcode::INT3), InstrCost::Free);
}
#[test]
fn test_instr_cost_cheaper_than() {
assert!(InstrCost::Free.cheaper_than(InstrCost::Cheap));
assert!(InstrCost::Cheap.cheaper_than(InstrCost::Moderate));
assert!(InstrCost::Moderate.cheaper_than(InstrCost::Expensive));
assert!(InstrCost::Expensive.cheaper_than(InstrCost::VeryExpensive));
assert!(!InstrCost::Expensive.cheaper_than(InstrCost::Cheap));
assert!(InstrCost::Cheap.cheaper_than(InstrCost::Cheap));
}
#[test]
fn test_cost_model_new() {
let model = X86CostModel::new();
assert!(!model.optimize_for_size);
assert_eq!(model.uarch_generation, 3);
}
#[test]
fn test_cost_model_should_decompose_lea() {
let model = X86CostModel::new();
assert!(model.should_decompose_lea(2, false));
let mut size_model = X86CostModel::new();
size_model.optimize_for_size = true;
assert!(!size_model.should_decompose_lea(2, false));
let mut old_model = X86CostModel::new();
old_model.uarch_generation = 1;
assert!(!old_model.should_decompose_lea(2, false));
}
#[test]
fn test_cost_model_should_fold_immediate() {
let model = X86CostModel::new();
assert!(model.should_fold_immediate(0));
assert!(model.should_fold_immediate(127));
assert!(model.should_fold_immediate(-128));
assert!(model.should_fold_immediate(2_147_483_647));
assert!(!model.should_fold_immediate(2_147_483_648));
}
#[test]
fn test_cost_model_should_macro_fuse() {
let model = X86CostModel::new();
assert!(model.should_macro_fuse(X86Opcode::CMP, X86Opcode::JE));
let mut old_model = X86CostModel::new();
old_model.uarch_generation = 1;
assert!(!old_model.should_macro_fuse(X86Opcode::CMP, X86Opcode::JE));
}
#[test]
fn test_decision_engine_new() {
let info = X86InstrInfo::new();
let engine = X86OptDecisionEngine::new(info);
assert!(engine.decisions.is_empty());
}
#[test]
fn test_decision_engine_analyze_empty_function() {
let info = X86InstrInfo::new();
let mut engine = X86OptDecisionEngine::new(info);
let mf = MachineFunction::new("empty");
let decisions = engine.analyze(&mf);
assert!(decisions.is_empty());
}
#[test]
fn test_decision_engine_analyze_with_div() {
let info = X86InstrInfo::new();
let mut engine = X86OptDecisionEngine::new(info);
let mut mf = MachineFunction::new("test");
let mut bb = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![],
successors: vec![],
};
bb.instructions.push(MachineInstr::new(opc(X86Opcode::DIV)));
mf.push_block(bb);
let decisions = engine.analyze(&mf);
assert!(!decisions.is_empty());
let div_decision = decisions.iter().find(|d| d.reason.contains("DIV"));
assert!(div_decision.is_some());
}
#[test]
fn test_decision_engine_analyze_with_lea() {
let info = X86InstrInfo::new();
let mut engine = X86OptDecisionEngine::new(info);
let mut mf = MachineFunction::new("test");
let mut bb = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![],
successors: vec![],
};
let mut lea = MachineInstr::new(opc(X86Opcode::LEA));
lea.operands.push(MachineOperand::Reg(0));
lea.operands.push(MachineOperand::Reg(1));
lea.operands.push(MachineOperand::Reg(2));
bb.instructions.push(lea);
mf.push_block(bb);
let decisions = engine.analyze(&mf);
let lea_opt = decisions.iter().find(|d| d.pass_name == "LEAOpt");
assert!(lea_opt.is_some());
}
#[test]
fn test_decision_engine_summary() {
let info = X86InstrInfo::new();
let mut engine = X86OptDecisionEngine::new(info);
let mut mf = MachineFunction::new("test");
let mut bb = MachineBasicBlock {
id: 0,
predecessors: vec![],
is_entry: false,
name: "entry".to_string(),
instructions: vec![MachineInstr::new(opc(X86Opcode::DIV))],
successors: vec![],
};
mf.push_block(bb);
engine.analyze(&mf);
let summary = engine.summary();
assert!(summary.contains("applied"));
assert!(summary.contains("deferred"));
assert!(summary.contains("total"));
}
#[test]
fn test_decision_kind_variants() {
let applied = DecisionKind::Applied;
let deferred = DecisionKind::Deferred;
let skipped = DecisionKind::Skipped;
let rejected = DecisionKind::Rejected;
let _ = format!("{:?} {:?} {:?} {:?}", applied, deferred, skipped, rejected);
}
#[test]
fn test_opt_decision_struct() {
let d = OptDecision {
pass_name: "Test",
block_name: "entry".to_string(),
instr_index: 0,
decision: DecisionKind::Applied,
reason: "test reason".to_string(),
};
assert_eq!(d.pass_name, "Test");
assert_eq!(d.block_name, "entry");
assert_eq!(d.instr_index, 0);
}
}
#[derive(Debug, Clone)]
pub struct X86TargetConfig {
pub emit_unwind_info: bool,
pub use_seh: bool,
pub is_kernel_mode: bool,
pub is_pic: bool,
pub large_code_model: bool,
pub stack_probe_size: u32,
pub guard_cf: bool,
pub hot_patchable: bool,
}
impl Default for X86TargetConfig {
fn default() -> Self {
Self {
emit_unwind_info: true,
use_seh: false,
is_kernel_mode: false,
is_pic: false,
large_code_model: false,
stack_probe_size: 4096,
guard_cf: false,
hot_patchable: false,
}
}
}
impl X86TargetConfig {
pub fn for_sysv64_user() -> Self {
Self {
emit_unwind_info: true,
use_seh: false,
is_pic: false,
..Default::default()
}
}
pub fn for_win64_user() -> Self {
Self {
emit_unwind_info: true,
use_seh: true,
is_pic: false,
..Default::default()
}
}
pub fn for_pic() -> Self {
Self {
is_pic: true,
..Default::default()
}
}
pub fn needs_stack_probes(&self, frame_size: u32) -> bool {
self.stack_probe_size > 0 && frame_size > self.stack_probe_size
}
pub fn needs_hot_patch_padding(&self) -> bool {
self.hot_patchable
}
pub fn needs_cfg_check(&self, is_indirect_call: bool) -> bool {
self.guard_cf && is_indirect_call
}
}
#[derive(Debug, Clone)]
pub struct X86EHInfo {
pub has_eh_personality: bool,
pub landing_pads: Vec<usize>,
pub call_sites: Vec<X86CallSite>,
pub uses_seh: bool,
pub lsda_label: Option<String>,
}
#[derive(Debug, Clone)]
pub struct X86CallSite {
pub begin_instr: usize,
pub end_instr: usize,
pub landing_pad: usize,
pub action: u32,
}
impl X86EHInfo {
pub fn new() -> Self {
Self {
has_eh_personality: false,
landing_pads: Vec::new(),
call_sites: Vec::new(),
uses_seh: false,
lsda_label: None,
}
}
pub fn needs_eh_metadata(&self) -> bool {
self.has_eh_personality || !self.landing_pads.is_empty() || self.uses_seh
}
pub fn add_landing_pad(&mut self, block_idx: usize) {
if !self.landing_pads.contains(&block_idx) {
self.landing_pads.push(block_idx);
}
}
pub fn add_call_site(&mut self, cs: X86CallSite) {
self.call_sites.push(cs);
}
pub fn num_call_sites(&self) -> usize {
self.call_sites.len()
}
}
impl Default for X86EHInfo {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86PassInfo {
pub name: &'static str,
pub flag: &'static str,
pub is_analysis: bool,
pub requires_ssa: bool,
pub preserves_ssa: bool,
pub required_passes: Vec<&'static str>,
pub invalidates: Vec<&'static str>,
}
pub struct X86PassRegistry {
pub passes: Vec<X86PassInfo>,
}
impl X86PassRegistry {
pub fn new() -> Self {
let passes = vec![
X86PassInfo {
name: "X86 Combiner",
flag: "x86-combiner",
is_analysis: false,
requires_ssa: true,
preserves_ssa: true,
required_passes: vec![],
invalidates: vec!["x86-cmp-elim", "x86-flag-elim"],
},
X86PassInfo {
name: "X86 CMP Elimination",
flag: "x86-cmp-elim",
is_analysis: false,
requires_ssa: true,
preserves_ssa: true,
required_passes: vec![],
invalidates: vec!["x86-flag-elim"],
},
X86PassInfo {
name: "X86 Flag Elimination",
flag: "x86-flag-elim",
is_analysis: false,
requires_ssa: false,
preserves_ssa: true,
required_passes: vec!["x86-cmp-elim"],
invalidates: vec![],
},
X86PassInfo {
name: "X86 Macro Fusion",
flag: "x86-macro-fusion",
is_analysis: true,
requires_ssa: false,
preserves_ssa: true,
required_passes: vec![],
invalidates: vec![],
},
X86PassInfo {
name: "X86 LEA Fixup",
flag: "x86-fixup-lea",
is_analysis: false,
requires_ssa: true,
preserves_ssa: true,
required_passes: vec![],
invalidates: vec!["x86-optimize-lea"],
},
X86PassInfo {
name: "X86 LEA Optimize",
flag: "x86-optimize-lea",
is_analysis: false,
requires_ssa: true,
preserves_ssa: true,
required_passes: vec!["x86-fixup-lea"],
invalidates: vec![],
},
X86PassInfo {
name: "X86 Conditional Jump Optimization",
flag: "x86-cond-jump",
is_analysis: false,
requires_ssa: false,
preserves_ssa: false,
required_passes: vec![],
invalidates: vec!["x86-macro-fusion"],
},
X86PassInfo {
name: "X86 FP Optimizer",
flag: "x86-fp-opt",
is_analysis: false,
requires_ssa: true,
preserves_ssa: true,
required_passes: vec![],
invalidates: vec![],
},
X86PassInfo {
name: "X86 Domain Reassignment",
flag: "x86-domain",
is_analysis: true,
requires_ssa: true,
preserves_ssa: true,
required_passes: vec![],
invalidates: vec![],
},
X86PassInfo {
name: "X86 Partial Register Update",
flag: "x86-partial-reg",
is_analysis: true,
requires_ssa: false,
preserves_ssa: true,
required_passes: vec![],
invalidates: vec![],
},
X86PassInfo {
name: "X86 VZEROUPPER Insertion",
flag: "x86-vzeroupper",
is_analysis: false,
requires_ssa: false,
preserves_ssa: false,
required_passes: vec![],
invalidates: vec![],
},
X86PassInfo {
name: "X86 EVEX Compression",
flag: "x86-compress-evex",
is_analysis: true,
requires_ssa: false,
preserves_ssa: false,
required_passes: vec![],
invalidates: vec![],
},
X86PassInfo {
name: "X86 Call Frame Optimization",
flag: "x86-call-frame",
is_analysis: false,
requires_ssa: false,
preserves_ssa: false,
required_passes: vec!["x86-fixup-lea"],
invalidates: vec![],
},
X86PassInfo {
name: "X86 Speculative Load Hardening",
flag: "x86-slh",
is_analysis: false,
requires_ssa: false,
preserves_ssa: false,
required_passes: vec![],
invalidates: vec![],
},
];
Self { passes }
}
pub fn get_pass(&self, flag: &str) -> Option<&X86PassInfo> {
self.passes.iter().find(|p| p.flag == flag)
}
pub fn get_ordered_passes(&self) -> Vec<&X86PassInfo> {
let mut ordered: Vec<&X86PassInfo> = Vec::new();
let mut visited: HashSet<&str> = HashSet::new();
fn visit<'a>(
pass: &'a X86PassInfo,
registry: &'a X86PassRegistry,
ordered: &mut Vec<&'a X86PassInfo>,
visited: &mut HashSet<&'a str>,
) {
if visited.contains(pass.flag) {
return;
}
visited.insert(pass.flag);
for req in &pass.required_passes {
if let Some(req_pass) = registry.get_pass(req) {
visit(req_pass, registry, ordered, visited);
}
}
ordered.push(pass);
}
for pass in &self.passes {
visit(pass, self, &mut ordered, &mut visited);
}
ordered
}
pub fn analysis_passes(&self) -> Vec<&X86PassInfo> {
self.passes.iter().filter(|p| p.is_analysis).collect()
}
pub fn transform_passes(&self) -> Vec<&X86PassInfo> {
self.passes.iter().filter(|p| !p.is_analysis).collect()
}
}
impl Default for X86PassRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod infrastructure_tests {
use super::*;
#[test]
fn test_target_config_default() {
let config = X86TargetConfig::default();
assert!(config.emit_unwind_info);
assert!(!config.use_seh);
assert!(!config.is_kernel_mode);
assert!(!config.is_pic);
assert_eq!(config.stack_probe_size, 4096);
}
#[test]
fn test_target_config_for_sysv64() {
let config = X86TargetConfig::for_sysv64_user();
assert!(config.emit_unwind_info);
assert!(!config.use_seh);
}
#[test]
fn test_target_config_for_win64() {
let config = X86TargetConfig::for_win64_user();
assert!(config.emit_unwind_info);
assert!(config.use_seh);
}
#[test]
fn test_target_config_for_pic() {
let config = X86TargetConfig::for_pic();
assert!(config.is_pic);
}
#[test]
fn test_needs_stack_probes() {
let config = X86TargetConfig::default();
assert!(!config.needs_stack_probes(0));
assert!(!config.needs_stack_probes(4096));
assert!(config.needs_stack_probes(4097));
}
#[test]
fn test_needs_stack_probes_disabled() {
let mut config = X86TargetConfig::default();
config.stack_probe_size = 0;
assert!(!config.needs_stack_probes(10000));
}
#[test]
fn test_needs_hot_patch_padding() {
let config = X86TargetConfig::default();
assert!(!config.needs_hot_patch_padding());
let mut hot_config = X86TargetConfig::default();
hot_config.hot_patchable = true;
assert!(hot_config.needs_hot_patch_padding());
}
#[test]
fn test_needs_cfg_check() {
let config = X86TargetConfig::default();
assert!(!config.needs_cfg_check(true));
let mut cfg_config = X86TargetConfig::default();
cfg_config.guard_cf = true;
assert!(cfg_config.needs_cfg_check(true));
assert!(!cfg_config.needs_cfg_check(false));
}
#[test]
fn test_eh_info_new() {
let eh = X86EHInfo::new();
assert!(!eh.has_eh_personality);
assert!(eh.landing_pads.is_empty());
assert!(eh.call_sites.is_empty());
assert!(!eh.needs_eh_metadata());
}
#[test]
fn test_eh_info_needs_metadata() {
let mut eh = X86EHInfo::new();
eh.has_eh_personality = true;
assert!(eh.needs_eh_metadata());
let mut eh2 = X86EHInfo::new();
eh2.landing_pads.push(0);
assert!(eh2.needs_eh_metadata());
let mut eh3 = X86EHInfo::new();
eh3.uses_seh = true;
assert!(eh3.needs_eh_metadata());
}
#[test]
fn test_eh_info_add_landing_pad() {
let mut eh = X86EHInfo::new();
eh.add_landing_pad(3);
eh.add_landing_pad(3); assert_eq!(eh.landing_pads.len(), 1);
eh.add_landing_pad(5);
assert_eq!(eh.landing_pads.len(), 2);
}
#[test]
fn test_eh_info_add_call_site() {
let mut eh = X86EHInfo::new();
let cs = X86CallSite {
begin_instr: 0,
end_instr: 5,
landing_pad: 1,
action: 0,
};
eh.add_call_site(cs);
assert_eq!(eh.num_call_sites(), 1);
}
#[test]
fn test_pass_registry_new() {
let registry = X86PassRegistry::new();
assert!(!registry.passes.is_empty());
assert!(registry.passes.len() >= 10);
}
#[test]
fn test_pass_registry_get_pass() {
let registry = X86PassRegistry::new();
let pass = registry.get_pass("x86-combiner");
assert!(pass.is_some());
assert_eq!(pass.unwrap().name, "X86 Combiner");
}
#[test]
fn test_pass_registry_get_nonexistent_pass() {
let registry = X86PassRegistry::new();
assert!(registry.get_pass("nonexistent").is_none());
}
#[test]
fn test_pass_registry_get_ordered_passes() {
let registry = X86PassRegistry::new();
let ordered = registry.get_ordered_passes();
assert_eq!(ordered.len(), registry.passes.len());
}
#[test]
fn test_pass_registry_analysis_passes() {
let registry = X86PassRegistry::new();
let analysis = registry.analysis_passes();
for pass in &analysis {
assert!(pass.is_analysis);
}
}
#[test]
fn test_pass_registry_transform_passes() {
let registry = X86PassRegistry::new();
let transforms = registry.transform_passes();
for pass in &transforms {
assert!(!pass.is_analysis);
}
}
#[test]
fn test_pass_registry_dependencies_respected() {
let registry = X86PassRegistry::new();
let ordered = registry.get_ordered_passes();
let cmp_pos = ordered.iter().position(|p| p.flag == "x86-cmp-elim");
let flag_pos = ordered.iter().position(|p| p.flag == "x86-flag-elim");
if let (Some(cp), Some(fp)) = (cmp_pos, flag_pos) {
assert!(cp < fp, "CMP elimination must run before flag elimination");
}
}
#[test]
fn test_x86_pass_info_fields() {
let info = X86PassInfo {
name: "Test Pass",
flag: "test-pass",
is_analysis: true,
requires_ssa: true,
preserves_ssa: false,
required_passes: vec!["other-pass"],
invalidates: vec!["cached-analysis"],
};
assert_eq!(info.name, "Test Pass");
assert_eq!(info.flag, "test-pass");
assert!(info.is_analysis);
assert!(info.requires_ssa);
assert!(!info.preserves_ssa);
assert_eq!(info.required_passes.len(), 1);
assert_eq!(info.invalidates.len(), 1);
}
}
pub mod integration_guide {
#[doc(hidden)]
pub const PIPELINE_VERSION: &str = "1.0.0";
#[doc(hidden)]
pub const PASS_COUNT: usize = 24;
}
#[cfg(test)]
mod sanity_tests {
use super::*;
#[test]
fn test_all_structs_have_new() {
let _ = CombineStats::new();
let _ = CmpElimStats::new();
let _ = FPOptStats::new();
let _ = MacroFusionStats::new();
let _ = MicroFusionStats::new();
let _ = PartialRegStats::new();
let _ = DomainReassignStats::new();
let _ = SetCCStats::new();
let _ = FixupBWStats::new();
let _ = PadStats::new();
let _ = LEAFixupStats::new();
let _ = LEAOptStats::new();
let _ = TwoAddrStats::new();
let _ = FixupInstrsStats::new();
let _ = EVEXCompressStats::new();
let _ = CallFrameStats::new();
let _ = IBTStats::new();
let _ = SLHStats::new();
let _ = FlagElimStats::new();
let _ = CondJumpStats::new();
let _ = SetCCPostRAStats::new();
let _ = SFStallStats::new();
let _ = VZeroUpperStats::new();
let _ = PipelineStats::new();
}
#[test]
fn test_all_structs_have_default() {
fn assert_default<T: Default>() {}
assert_default::<CombineStats>();
assert_default::<MacroFusionStats>();
assert_default::<PipelineConfig>();
assert_default::<X86PassRegistry>();
assert_default::<X86TargetConfig>();
assert_default::<X86EHInfo>();
assert_default::<X86CostModel>();
}
#[test]
fn test_pipeline_config_customization() {
let config = PipelineConfig {
opt_level: 1,
enable_pre_ra_passes: true,
enable_post_ra_passes: false,
enable_security_passes: false,
enable_slh: false,
enable_cet: false,
max_iterations: 1,
debug: true,
};
assert_eq!(config.opt_level, 1);
assert!(config.debug);
assert!(!config.enable_post_ra_passes);
}
#[test]
fn test_integration_constants() {
assert_eq!(integration_guide::PIPELINE_VERSION, "1.0.0");
assert_eq!(integration_guide::PASS_COUNT, 24);
}
}
#[derive(Debug, Clone, Default)]
pub struct X86Optimizer {
pub enabled: bool,
}