use super::arm_instr_info::ArmOpcode;
use super::arm_mc_encoder::ArmCond;
use super::arm_register_info::*;
use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
const OP_ADD: u32 = ArmOpcode::ADD as u32;
const OP_SUB: u32 = ArmOpcode::SUB as u32;
const OP_ADDS: u32 = ArmOpcode::ADDS as u32;
const OP_SUBS: u32 = ArmOpcode::SUBS as u32;
const OP_MOV: u32 = ArmOpcode::MOV as u32;
const _OP_MOVZ: u32 = ArmOpcode::MOVZ as u32;
const _OP_MOVK: u32 = ArmOpcode::MOVK as u32;
const _OP_AND: u32 = ArmOpcode::AND as u32;
const _OP_ORR: u32 = ArmOpcode::ORR as u32;
const _OP_EOR: u32 = ArmOpcode::EOR as u32;
const OP_LDR: u32 = ArmOpcode::LDR as u32;
const OP_STR: u32 = ArmOpcode::STR as u32;
const OP_LDP: u32 = ArmOpcode::LDP as u32;
const OP_STP: u32 = ArmOpcode::STP as u32;
const OP_B: u32 = ArmOpcode::B as u32;
const OP_BL: u32 = ArmOpcode::BL as u32;
const OP_RET: u32 = ArmOpcode::RET as u32;
const OP_BR: u32 = ArmOpcode::BR as u32;
const OP_B_COND: u32 = ArmOpcode::B_COND as u32;
const OP_CBZ: u32 = ArmOpcode::CBZ as u32;
const OP_CBNZ: u32 = ArmOpcode::CBNZ as u32;
const OP_CMP: u32 = ArmOpcode::CMP as u32;
const OP_CSEL: u32 = ArmOpcode::CSEL as u32;
const _OP_NOP: u32 = ArmOpcode::NOP as u32;
const OP_ADR: u32 = ArmOpcode::ADR as u32;
#[derive(Debug, Clone, Default)]
pub struct ArmOptStats {
pub mov_nop_eliminated: usize,
pub cmp_branch_fused: usize,
pub ldr_str_paired: usize,
pub redundant_loads_eliminated: usize,
pub dead_flags_removed: usize,
pub add_zero_eliminated: usize,
pub adr_add_merged: usize,
pub total_removed: usize,
pub total_added: usize,
}
impl ArmOptStats {
pub fn net_change(&self) -> isize {
(self.total_added as isize) - (self.total_removed as isize)
}
pub fn did_work(&self) -> bool {
self.total_removed > 0 || self.total_added > 0
}
}
pub struct ArmPeepholeOptimizer {
pub is_64bit: bool,
pub stats: ArmOptStats,
}
impl ArmPeepholeOptimizer {
pub fn new(is_64bit: bool) -> Self {
Self {
is_64bit,
stats: ArmOptStats::default(),
}
}
pub fn optimize(&mut self, mf: &mut MachineFunction) -> ArmOptStats {
self.stats = ArmOptStats::default();
for bb in &mut mf.blocks {
self.eliminate_nop_moves(&mut bb.instructions);
}
for bb in &mut mf.blocks {
self.fuse_cmp_branch(&mut bb.instructions);
}
for bb in &mut mf.blocks {
self.pair_ldr_str(&mut bb.instructions);
}
for bb in &mut mf.blocks {
self.eliminate_redundant_loads(&mut bb.instructions);
}
for bb in &mut mf.blocks {
self.eliminate_add_zero(&mut bb.instructions);
}
for bb in &mut mf.blocks {
self.optimize_flag_setting(&mut bb.instructions);
}
for bb in &mut mf.blocks {
self.merge_adr_add(&mut bb.instructions);
}
self.stats.clone()
}
pub fn eliminate_nop_moves(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut to_remove = Vec::new();
for (i, mi) in instructions.iter().enumerate() {
if mi.opcode != OP_MOV {
continue;
}
if mi.operands.len() < 2 {
continue;
}
let same = match (&mi.operands[0], &mi.operands[1]) {
(MachineOperand::PhysReg(r1), MachineOperand::PhysReg(r2)) => r1 == r2,
(MachineOperand::Reg(v1), MachineOperand::Reg(v2)) => v1 == v2,
_ => false,
};
if same {
to_remove.push(i);
self.stats.mov_nop_eliminated += 1;
}
}
for i in to_remove.into_iter().rev() {
instructions.remove(i);
self.stats.total_removed += 1;
}
}
pub fn fuse_cmp_branch(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instructions.len() {
let (cmp_valid, cmp_reg, _cmp_imm) = self.match_cmp_imm_zero(&instructions[i]);
if !cmp_valid {
i += 1;
continue;
}
let bcond = &instructions[i + 1];
if bcond.opcode != OP_B_COND {
i += 2;
continue;
}
let cond = self.get_cond_from_bcond(bcond);
let target_off = self.get_branch_target(bcond);
let new_opcode = match cond {
Some(ArmCond::EQ) => OP_CBZ,
Some(ArmCond::NE) => OP_CBNZ,
_ => {
i += 2;
continue;
}
};
let mut new_mi = MachineInstr::new(new_opcode);
new_mi
.operands
.push(MachineOperand::PhysReg(cmp_reg as u32));
if let Some(off) = target_off {
new_mi.operands.push(MachineOperand::Imm(off));
} else {
if bcond.operands.len() >= 2 {
new_mi.operands.push(bcond.operands[1].clone());
}
}
instructions[i] = new_mi;
instructions.remove(i + 1); self.stats.cmp_branch_fused += 1;
self.stats.total_removed += 1; i += 1;
}
}
fn match_cmp_imm_zero(&self, mi: &MachineInstr) -> (bool, u32, i64) {
if mi.opcode != OP_CMP {
return (false, 0, 0);
}
if mi.operands.len() < 2 {
return (false, 0, 0);
}
let reg = match &mi.operands[0] {
MachineOperand::PhysReg(r) => *r,
MachineOperand::Reg(v) => *v,
_ => return (false, 0, 0),
};
let imm = match &mi.operands[1] {
MachineOperand::Imm(v) => *v,
_ => return (false, 0, 0),
};
(imm == 0, reg, imm)
}
fn get_cond_from_bcond(&self, mi: &MachineInstr) -> Option<ArmCond> {
if mi.operands.is_empty() {
return None;
}
match &mi.operands[0] {
MachineOperand::Imm(v) => {
let v = *v as u32;
match v {
0 => Some(ArmCond::EQ),
1 => Some(ArmCond::NE),
2 => Some(ArmCond::CS),
3 => Some(ArmCond::CC),
4 => Some(ArmCond::MI),
5 => Some(ArmCond::PL),
6 => Some(ArmCond::VS),
7 => Some(ArmCond::VC),
8 => Some(ArmCond::HI),
9 => Some(ArmCond::LS),
10 => Some(ArmCond::GE),
11 => Some(ArmCond::LT),
12 => Some(ArmCond::GT),
13 => Some(ArmCond::LE),
14 => Some(ArmCond::AL),
_ => None,
}
}
MachineOperand::Label(s) => ArmCond::from_mnemonic(s),
_ => None,
}
}
fn get_branch_target(&self, mi: &MachineInstr) -> Option<i64> {
for op in &mi.operands {
if let MachineOperand::Imm(v) = op {
return Some(*v);
}
}
None
}
pub fn pair_ldr_str(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instructions.len() {
let mi0 = &instructions[i];
let mi1 = &instructions[i + 1];
if mi0.opcode != mi1.opcode {
i += 1;
continue;
}
let pair_opcode = match mi0.opcode {
OP_LDR => OP_LDP,
OP_STR => OP_STP,
_ => {
i += 1;
continue;
}
};
if mi0.operands.len() < 3 || mi1.operands.len() < 3 {
i += 1;
continue;
}
let rn0 = self.get_reg_from_op(&mi0.operands[1]);
let rn1 = self.get_reg_from_op(&mi1.operands[1]);
let off0 = self.get_imm_from_op(&mi0.operands[2]);
let off1 = self.get_imm_from_op(&mi1.operands[2]);
if rn0.is_none() || rn1.is_none() || off0.is_none() || off1.is_none() {
i += 1;
continue;
}
if rn0 != rn1 {
i += 1;
continue;
}
if off1.unwrap() != off0.unwrap() + 8 {
i += 1;
continue;
}
let rt0 = self.get_reg_from_op(&mi0.operands[0]);
let rt1_val = self.get_reg_from_op(&mi1.operands[0]);
if rt0.is_none() || rt1_val.is_none() || rt0 == rt1_val {
i += 1;
continue;
}
let mut new_mi = MachineInstr::new(pair_opcode);
new_mi.operands.push(MachineOperand::PhysReg(rt0.unwrap()));
new_mi
.operands
.push(MachineOperand::PhysReg(rt1_val.unwrap()));
new_mi.operands.push(MachineOperand::PhysReg(rn0.unwrap()));
new_mi.operands.push(MachineOperand::Imm(off0.unwrap()));
instructions[i] = new_mi;
instructions.remove(i + 1);
self.stats.ldr_str_paired += 1;
self.stats.total_removed += 1;
i += 1;
}
}
pub fn eliminate_redundant_loads(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i < instructions.len() {
let mi0 = &instructions[i];
if mi0.opcode != OP_STR || mi0.operands.len() < 3 {
i += 1;
continue;
}
let mut st_data = self.get_reg_from_op(&mi0.operands[0]);
let st_base = self.get_reg_from_op(&mi0.operands[1]);
let st_off = self.get_imm_from_op(&mi0.operands[2]);
if st_data.is_none() || st_base.is_none() || st_off.is_none() {
i += 1;
continue;
}
for j in (i + 1)..instructions.len() {
let mj = &instructions[j];
if mj.opcode == OP_STR && mj.operands.len() >= 3 {
let intr_base = self.get_reg_from_op(&mj.operands[1]);
let intr_off = self.get_imm_from_op(&mj.operands[2]);
if intr_base == st_base && intr_off == st_off {
st_data = self.get_reg_from_op(&mj.operands[0]);
continue; }
}
if mj.opcode == OP_LDR && mj.operands.len() >= 3 {
let ld_base = self.get_reg_from_op(&mj.operands[1]);
let ld_off = self.get_imm_from_op(&mj.operands[2]);
if ld_base == st_base && ld_off == st_off {
let ld_dst = self.get_reg_from_op(&mj.operands[0]);
if let (Some(dst), Some(src)) = (ld_dst, st_data) {
let mut mov_mi = MachineInstr::new(OP_MOV);
mov_mi.operands.push(MachineOperand::PhysReg(dst));
mov_mi.operands.push(MachineOperand::PhysReg(src));
instructions[j] = mov_mi;
self.stats.redundant_loads_eliminated += 1;
}
break;
}
}
if mj.opcode == OP_BL
|| mj.opcode == OP_B
|| mj.opcode == OP_BR
|| mj.opcode == OP_RET
|| mj.opcode == OP_B_COND
{
break;
}
}
i += 1;
}
}
pub fn eliminate_add_zero(&mut self, instructions: &mut Vec<MachineInstr>) {
for mi in instructions.iter_mut() {
if mi.opcode != OP_ADD {
continue;
}
if mi.operands.len() < 3 {
continue;
}
let is_zero = match &mi.operands[2] {
MachineOperand::Imm(v) => *v == 0,
_ => false,
};
if !is_zero {
continue;
}
mi.opcode = OP_MOV;
mi.operands.truncate(2); self.stats.add_zero_eliminated += 1;
}
}
pub fn optimize_flag_setting(&mut self, instructions: &mut Vec<MachineInstr>) {
for i in 0..instructions.len() {
let opcode = instructions[i].opcode;
if opcode != OP_ADDS && opcode != OP_SUBS {
continue;
}
let mut flag_dead = true;
for j in (i + 1)..instructions.len() {
let next_op = instructions[j].opcode;
if next_op == OP_CMP
|| next_op == OP_B_COND
|| next_op == OP_CSEL
|| next_op == OP_CBZ
|| next_op == OP_CBNZ
{
flag_dead = false;
break;
}
if next_op == OP_ADDS || next_op == OP_SUBS || next_op == OP_CMP {
break;
}
if next_op == OP_B
|| next_op == OP_BL
|| next_op == OP_BR
|| next_op == OP_RET
|| next_op == OP_B_COND
|| next_op == OP_CBZ
|| next_op == OP_CBNZ
{
break;
}
}
if flag_dead {
instructions[i].opcode = if opcode == OP_ADDS { OP_ADD } else { OP_SUB };
self.stats.dead_flags_removed += 1;
}
}
}
pub fn merge_adr_add(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instructions.len() {
let mi0 = &instructions[i];
let mi1 = &instructions[i + 1];
if mi0.opcode != OP_ADR {
i += 1;
continue;
}
if mi1.opcode != OP_ADD {
i += 1;
continue;
}
if mi0.operands.len() < 2 || mi1.operands.len() < 3 {
i += 1;
continue;
}
let adr_dst = self.get_reg_from_op(&mi0.operands[0]);
let add_dst = self.get_reg_from_op(&mi1.operands[0]);
let add_src = self.get_reg_from_op(&mi1.operands[1]);
if adr_dst.is_none()
|| add_dst.is_none()
|| add_src.is_none()
|| adr_dst != add_dst
|| adr_dst != add_src
{
i += 1;
continue;
}
let add_imm = self.get_imm_from_op(&mi1.operands[2]);
if add_imm.is_none() {
i += 1;
continue;
}
self.stats.adr_add_merged += 1;
i += 2;
}
}
fn get_reg_from_op(&self, op: &MachineOperand) -> Option<u32> {
match op {
MachineOperand::PhysReg(r) => Some(*r),
MachineOperand::Reg(v) => Some(*v),
_ => None,
}
}
fn get_imm_from_op(&self, op: &MachineOperand) -> Option<i64> {
match op {
MachineOperand::Imm(v) => Some(*v),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_mf() -> MachineFunction {
let mut mf = MachineFunction::new("test");
let bb = MachineBasicBlock {
name: "entry".to_string(),
instructions: Vec::new(),
successors: Vec::new(),
};
mf.push_block(bb);
mf
}
fn push_instr(mf: &mut MachineFunction, mi: MachineInstr) {
mf.blocks[0].instructions.push(mi);
}
fn make_mov(rd: u32, rs: u32) -> MachineInstr {
let mut mi = MachineInstr::new(OP_MOV);
mi.operands.push(MachineOperand::PhysReg(rd));
mi.operands.push(MachineOperand::PhysReg(rs));
mi
}
fn make_add_reg(rd: u32, rn: u32, rm: u32) -> MachineInstr {
let mut mi = MachineInstr::new(OP_ADD);
mi.operands.push(MachineOperand::PhysReg(rd));
mi.operands.push(MachineOperand::PhysReg(rn));
mi.operands.push(MachineOperand::PhysReg(rm));
mi
}
fn make_add_imm(rd: u32, rn: u32, imm: i64) -> MachineInstr {
let mut mi = MachineInstr::new(OP_ADD);
mi.operands.push(MachineOperand::PhysReg(rd));
mi.operands.push(MachineOperand::PhysReg(rn));
mi.operands.push(MachineOperand::Imm(imm));
mi
}
fn make_adds(rd: u32, rn: u32, rm: u32) -> MachineInstr {
let mut mi = MachineInstr::new(OP_ADDS);
mi.operands.push(MachineOperand::PhysReg(rd));
mi.operands.push(MachineOperand::PhysReg(rn));
mi.operands.push(MachineOperand::PhysReg(rm));
mi
}
fn make_cmp_imm(rn: u32, imm: i64) -> MachineInstr {
let mut mi = MachineInstr::new(OP_CMP);
mi.operands.push(MachineOperand::PhysReg(rn));
mi.operands.push(MachineOperand::Imm(imm));
mi
}
fn make_cmp_reg(rn: u32, rm: u32) -> MachineInstr {
let mut mi = MachineInstr::new(OP_CMP);
mi.operands.push(MachineOperand::PhysReg(rn));
mi.operands.push(MachineOperand::PhysReg(rm));
mi
}
fn make_b_cond(cond: ArmCond, offset: i64) -> MachineInstr {
let mut mi = MachineInstr::new(OP_B_COND);
mi.operands.push(MachineOperand::Imm(cond as i64));
mi.operands.push(MachineOperand::Imm(offset));
mi
}
fn make_ldr(rt: u32, rn: u32, off: i64) -> MachineInstr {
let mut mi = MachineInstr::new(OP_LDR);
mi.operands.push(MachineOperand::PhysReg(rt));
mi.operands.push(MachineOperand::PhysReg(rn));
mi.operands.push(MachineOperand::Imm(off));
mi
}
fn make_str(rt: u32, rn: u32, off: i64) -> MachineInstr {
let mut mi = MachineInstr::new(OP_STR);
mi.operands.push(MachineOperand::PhysReg(rt));
mi.operands.push(MachineOperand::PhysReg(rn));
mi.operands.push(MachineOperand::Imm(off));
mi
}
#[test]
fn test_eliminate_nop_moves() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_mov(X0 as u32, X0 as u32), make_mov(X1 as u32, X2 as u32), make_mov(X3 as u32, X3 as u32), ];
opt.eliminate_nop_moves(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, OP_MOV);
assert_eq!(opt.stats.mov_nop_eliminated, 2);
}
#[test]
fn test_eliminate_nop_moves_no_effect() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_mov(X0 as u32, X1 as u32),
make_mov(X2 as u32, X3 as u32),
];
opt.eliminate_nop_moves(&mut instrs);
assert_eq!(instrs.len(), 2);
assert_eq!(opt.stats.mov_nop_eliminated, 0);
}
#[test]
fn test_fuse_cmp_beq_to_cbz() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_cmp_imm(X0 as u32, 0), make_b_cond(ArmCond::EQ, 16), make_add_reg(X1 as u32, X2 as u32, X3 as u32),
];
opt.fuse_cmp_branch(&mut instrs);
assert_eq!(instrs.len(), 2);
assert_eq!(instrs[0].opcode, OP_CBZ);
assert_eq!(opt.stats.cmp_branch_fused, 1);
}
#[test]
fn test_fuse_cmp_bne_to_cbnz() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_cmp_imm(X5 as u32, 0), make_b_cond(ArmCond::NE, 32), ];
opt.fuse_cmp_branch(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, OP_CBNZ);
assert_eq!(opt.stats.cmp_branch_fused, 1);
}
#[test]
fn test_no_fuse_cmp_nonzero() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_cmp_imm(X0 as u32, 5), make_b_cond(ArmCond::EQ, 16),
];
let orig_len = instrs.len();
opt.fuse_cmp_branch(&mut instrs);
assert_eq!(instrs.len(), orig_len);
assert_eq!(opt.stats.cmp_branch_fused, 0);
}
#[test]
fn test_no_fuse_cmp_other_cond() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_cmp_imm(X0 as u32, 0), make_b_cond(ArmCond::LT, 16), ];
let orig_len = instrs.len();
opt.fuse_cmp_branch(&mut instrs);
assert_eq!(instrs.len(), orig_len);
assert_eq!(opt.stats.cmp_branch_fused, 0);
}
#[test]
fn test_pair_ldr_to_ldp() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_ldr(X0 as u32, X1 as u32, 0), make_ldr(X2 as u32, X1 as u32, 8), ];
opt.pair_ldr_str(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, OP_LDP);
assert_eq!(opt.stats.ldr_str_paired, 1);
}
#[test]
fn test_pair_str_to_stp() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_str(X3 as u32, X4 as u32, 16), make_str(X5 as u32, X4 as u32, 24), ];
opt.pair_ldr_str(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, OP_STP);
assert_eq!(opt.stats.ldr_str_paired, 1);
}
#[test]
fn test_no_pair_nonconsecutive_offsets() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_ldr(X0 as u32, X1 as u32, 0), make_ldr(X2 as u32, X1 as u32, 16), ];
opt.pair_ldr_str(&mut instrs);
assert_eq!(instrs.len(), 2);
assert_eq!(opt.stats.ldr_str_paired, 0);
}
#[test]
fn test_eliminate_redundant_load() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_str(X0 as u32, X10 as u32, 0), make_ldr(X1 as u32, X10 as u32, 0), ];
opt.eliminate_redundant_loads(&mut instrs);
assert_eq!(instrs.len(), 2);
assert_eq!(instrs[1].opcode, OP_MOV);
assert_eq!(opt.stats.redundant_loads_eliminated, 1);
}
#[test]
fn test_no_forward_across_intervening_store() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_str(X0 as u32, X10 as u32, 0), make_str(X1 as u32, X10 as u32, 0), make_ldr(X2 as u32, X10 as u32, 0), ];
opt.eliminate_redundant_loads(&mut instrs);
assert_eq!(instrs[2].opcode, OP_MOV);
if let MachineOperand::PhysReg(dst) = instrs[2].operands[0] {
assert_eq!(dst, X2 as u32);
}
if let MachineOperand::PhysReg(src) = instrs[2].operands[1] {
assert_eq!(src, X1 as u32);
}
assert!(opt.stats.redundant_loads_eliminated >= 1);
}
#[test]
fn test_eliminate_add_zero() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_add_imm(X3 as u32, X4 as u32, 0), make_add_imm(X5 as u32, X6 as u32, 10), ];
opt.eliminate_add_zero(&mut instrs);
assert_eq!(instrs[0].opcode, OP_MOV); assert_eq!(instrs[1].opcode, OP_ADD); assert_eq!(opt.stats.add_zero_eliminated, 1);
}
#[test]
fn test_optimize_flag_setting() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_adds(X0 as u32, X1 as u32, X2 as u32), make_add_reg(X3 as u32, X4 as u32, X5 as u32), ];
opt.optimize_flag_setting(&mut instrs);
assert_eq!(instrs[0].opcode, OP_ADD);
assert_eq!(opt.stats.dead_flags_removed, 1);
}
#[test]
fn test_keep_flag_setting_when_used() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_adds(X0 as u32, X1 as u32, X2 as u32), make_b_cond(ArmCond::EQ, 8), ];
opt.optimize_flag_setting(&mut instrs);
assert_eq!(instrs[0].opcode, OP_ADDS);
assert_eq!(opt.stats.dead_flags_removed, 0);
}
#[test]
fn test_optimize_full() {
let mut mf = make_mf();
push_instr(&mut mf, make_mov(X0 as u32, X0 as u32)); push_instr(&mut mf, make_cmp_imm(X1 as u32, 0)); push_instr(&mut mf, make_b_cond(ArmCond::EQ, 16)); push_instr(&mut mf, make_add_imm(X2 as u32, X3 as u32, 0)); push_instr(&mut mf, make_adds(X4 as u32, X5 as u32, X6 as u32)); push_instr(&mut mf, make_add_reg(X7 as u32, X8 as u32, X9 as u32));
let mut opt = ArmPeepholeOptimizer::new(true);
let stats = opt.optimize(&mut mf);
let _instrs = &mf.blocks[0].instructions;
assert_eq!(stats.mov_nop_eliminated, 1);
assert_eq!(stats.cmp_branch_fused, 1);
assert_eq!(stats.add_zero_eliminated, 1);
assert_eq!(stats.dead_flags_removed, 1);
assert!(stats.did_work());
}
#[test]
fn test_optimize_no_effect() {
let mut mf = make_mf();
push_instr(&mut mf, make_add_reg(X0 as u32, X1 as u32, X2 as u32));
push_instr(&mut mf, make_add_reg(X3 as u32, X4 as u32, X5 as u32));
let mut opt = ArmPeepholeOptimizer::new(true);
let stats = opt.optimize(&mut mf);
assert!(!stats.did_work());
assert_eq!(stats.net_change(), 0);
}
#[test]
fn test_stats_net_change() {
let mut stats = ArmOptStats::default();
assert_eq!(stats.net_change(), 0);
stats.total_removed = 5;
stats.total_added = 3;
assert_eq!(stats.net_change(), -2);
}
#[test]
fn test_stats_did_work() {
let mut stats = ArmOptStats::default();
assert!(!stats.did_work());
stats.total_removed = 1;
assert!(stats.did_work());
}
#[test]
fn test_no_fuse_cmp_reg() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
make_cmp_reg(X0 as u32, X1 as u32), make_b_cond(ArmCond::EQ, 16),
];
let orig_len = instrs.len();
opt.fuse_cmp_branch(&mut instrs);
assert_eq!(instrs.len(), orig_len);
}
#[test]
fn test_adr_add_merge() {
let mut opt = ArmPeepholeOptimizer::new(true);
let mut instrs = vec![
{
let mut mi = MachineInstr::new(OP_ADR);
mi.operands.push(MachineOperand::PhysReg(X0 as u32));
mi.operands
.push(MachineOperand::Label("some_label".to_string()));
mi
},
make_add_imm(X0 as u32, X0 as u32, 16),
];
opt.merge_adr_add(&mut instrs);
assert_eq!(opt.stats.adr_add_merged, 1);
}
}