use super::riscv_instr_info::RiscVOpcode;
use super::riscv_register_info::GPR_BASE;
use crate::codegen::{MachineFunction, MachineInstr, MachineOperand};
#[derive(Debug, Clone, Default)]
pub struct RiscVOptStats {
pub nops_eliminated: usize,
pub redundant_moves_eliminated: usize,
pub compare_branches_folded: usize,
pub lui_addi_fusions: usize,
pub auipc_addi_fusions: usize,
pub branch_chains_optimized: usize,
pub dead_adds_eliminated: usize,
pub zero_shifts_removed: usize,
pub canonical_pseudos_recognized: usize,
pub total_instructions_before: usize,
pub total_instructions_after: usize,
}
impl RiscVOptStats {
pub fn new() -> Self {
Self::default()
}
pub fn total_removed(&self) -> usize {
self.total_instructions_before
.saturating_sub(self.total_instructions_after)
}
}
pub struct RiscVPeepholeOptimizer {
pub stats: RiscVOptStats,
pub is_64bit: bool,
}
impl RiscVPeepholeOptimizer {
pub fn new(is_64bit: bool) -> Self {
Self {
stats: RiscVOptStats::new(),
is_64bit,
}
}
pub fn optimize(&mut self, mf: &mut MachineFunction) -> RiscVOptStats {
self.stats.total_instructions_before = mf.blocks.iter().map(|b| b.instructions.len()).sum();
for block in &mut mf.blocks {
self.eliminate_nop_moves(&mut block.instructions);
self.fold_redundant_ops(&mut block.instructions);
self.optimize_branch_chains(&mut block.instructions);
self.eliminate_zero_shifts(&mut block.instructions);
self.eliminate_dead_adds(&mut block.instructions);
self.recognize_canonical_pseudos(&mut block.instructions);
self.fuse_lui_addi(&mut block.instructions);
self.eliminate_redundant_moves(&mut block.instructions);
}
self.stats.total_instructions_after = mf.blocks.iter().map(|b| b.instructions.len()).sum();
self.stats.clone()
}
pub fn eliminate_nop_moves(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i < instructions.len() {
let op = instructions[i].opcode;
let remove = if op == RiscVOpcode::NOP as u32 {
true
} else if op == RiscVOpcode::ADDI as u32 {
let rd = get_rd_field(&instructions[i]);
let rs1 = get_rs1_field(&instructions[i]);
rd == 0 && rs1 == 0 && get_imm_field(&instructions[i]) == Some(0)
|| (rd == rs1 && get_imm_field(&instructions[i]) == Some(0))
} else if op == RiscVOpcode::SLLI as u32 {
let rd = get_rd_field(&instructions[i]);
let rs1 = get_rs1_field(&instructions[i]);
rd == rs1 && get_imm_field(&instructions[i]) == Some(0)
} else {
false
};
if remove {
instructions.remove(i);
self.stats.nops_eliminated += 1;
} else {
i += 1;
}
}
}
pub fn fold_redundant_ops(&mut self, instructions: &mut Vec<MachineInstr>) {
for instr in instructions.iter_mut() {
let op = instr.opcode;
if op == RiscVOpcode::XORI as u32 {
if get_imm_field(instr) == Some(-1) {
let rd = get_rd_field(instr);
let rs1 = get_rs1_field(instr);
let mut not_instr = MachineInstr::new(RiscVOpcode::NOT as u32);
not_instr.operands.push(MachineOperand::Reg(to_reg_id(rd)));
not_instr.operands.push(MachineOperand::Reg(to_reg_id(rs1)));
*instr = not_instr;
self.stats.canonical_pseudos_recognized += 1;
}
}
}
}
pub fn optimize_branch_chains(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instructions.len() {
let branch_op = instructions[i].opcode;
let next_op = instructions[i + 1].opcode;
if is_conditional_branch_op(branch_op) && is_unconditional_jump_op(next_op) {
let inverted = invert_branch_op(branch_op);
if let Some(inv_op) = inverted {
let j_target = get_imm_field(&instructions[i + 1]).unwrap_or(0);
let mut new_branch = MachineInstr::new(inv_op);
for idx in 0..2.min(instructions[i].operands.len()) {
new_branch
.operands
.push(instructions[i].operands[idx].clone());
}
new_branch
.operands
.push(MachineOperand::Imm(j_target as i64));
instructions[i] = new_branch;
instructions.remove(i + 1);
self.stats.branch_chains_optimized += 1;
}
}
i += 1;
}
}
fn eliminate_zero_shifts(&mut self, instructions: &mut Vec<MachineInstr>) {
for instr in instructions.iter_mut() {
let op = instr.opcode;
let is_shift = op == RiscVOpcode::SLLI as u32
|| op == RiscVOpcode::SRLI as u32
|| op == RiscVOpcode::SRAI as u32;
if is_shift && get_imm_field(instr) == Some(0) {
let rd = get_rd_field(instr);
let rs1 = get_rs1_field(instr);
let mut mv = MachineInstr::new(RiscVOpcode::MV as u32);
mv.operands.push(MachineOperand::Reg(to_reg_id(rd)));
mv.operands.push(MachineOperand::Reg(to_reg_id(rs1)));
*instr = mv;
self.stats.zero_shifts_removed += 1;
}
}
}
fn eliminate_dead_adds(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i < instructions.len() {
let op = instructions[i].opcode;
if op == RiscVOpcode::ADDI as u32 {
let rd = get_rd_field(&instructions[i]);
let rs1 = get_rs1_field(&instructions[i]);
let imm = get_imm_field(&instructions[i]).unwrap_or(0);
if rd == 0 && rs1 == 0 && imm == 0 {
instructions.remove(i);
self.stats.dead_adds_eliminated += 1;
continue;
}
}
i += 1;
}
}
fn recognize_canonical_pseudos(&mut self, instructions: &mut Vec<MachineInstr>) {
for instr in instructions.iter_mut() {
let op = instr.opcode;
if op == RiscVOpcode::ADDI as u32 && get_imm_field(instr) == Some(0) {
let rd = get_rd_field(instr);
let rs1 = get_rs1_field(instr);
if rd != rs1 {
let mut mv = MachineInstr::new(RiscVOpcode::MV as u32);
mv.operands.push(MachineOperand::Reg(to_reg_id(rd)));
mv.operands.push(MachineOperand::Reg(to_reg_id(rs1)));
*instr = mv;
self.stats.canonical_pseudos_recognized += 1;
}
}
if op == RiscVOpcode::SUB as u32 && get_rs1_field(instr) == 0 {
let rd = get_rd_field(instr);
let rs2 = get_rs2_field(instr);
let mut neg = MachineInstr::new(RiscVOpcode::NEG as u32);
neg.operands.push(MachineOperand::Reg(to_reg_id(rd)));
neg.operands.push(MachineOperand::Reg(to_reg_id(rs2)));
*instr = neg;
self.stats.canonical_pseudos_recognized += 1;
}
if op == RiscVOpcode::SLLI as u32 && get_imm_field(instr) == Some(0) {
let rd = get_rd_field(instr);
let rs1 = get_rs1_field(instr);
if rd != rs1 {
let mut mv = MachineInstr::new(RiscVOpcode::MV as u32);
mv.operands.push(MachineOperand::Reg(to_reg_id(rd)));
mv.operands.push(MachineOperand::Reg(to_reg_id(rs1)));
*instr = mv;
self.stats.canonical_pseudos_recognized += 1;
}
}
}
}
fn fuse_lui_addi(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instructions.len() {
if instructions[i].opcode == RiscVOpcode::LUI as u32
&& instructions[i + 1].opcode == RiscVOpcode::ADDI as u32
{
let lui_rd = get_rd_field(&instructions[i]);
let addi_rd = get_rd_field(&instructions[i + 1]);
let addi_rs1 = get_rs1_field(&instructions[i + 1]);
if lui_rd == addi_rd && addi_rs1 == lui_rd {
let lui_imm = get_imm_field(&instructions[i]).unwrap_or(0) as u32;
let addi_imm = get_imm_field(&instructions[i + 1]).unwrap_or(0);
let full_imm = (lui_imm as i64) + (addi_imm as i64);
let mut li = MachineInstr::new(RiscVOpcode::LI as u32);
li.operands.push(MachineOperand::Reg(to_reg_id(lui_rd)));
li.operands.push(MachineOperand::Imm(full_imm));
instructions[i] = li;
instructions.remove(i + 1);
self.stats.lui_addi_fusions += 1;
}
}
i += 1;
}
}
pub fn eliminate_redundant_moves(&mut self, instructions: &mut Vec<MachineInstr>) {
let mut i = 0;
while i + 1 < instructions.len() {
let a_op = instructions[i].opcode;
let b_op = instructions[i + 1].opcode;
let a_is_move = a_op == RiscVOpcode::MV as u32
|| (a_op == RiscVOpcode::ADDI as u32 && get_imm_field(&instructions[i]) == Some(0));
let b_is_move = b_op == RiscVOpcode::MV as u32
|| (b_op == RiscVOpcode::ADDI as u32
&& get_imm_field(&instructions[i + 1]) == Some(0));
if a_is_move && b_is_move {
let a_rd = get_rd_field(&instructions[i]);
let a_rs1 = get_rs1_field(&instructions[i]);
let b_rs1 = get_rs1_field(&instructions[i + 1]);
if b_rs1 == a_rs1 && a_rd != a_rs1 && a_rd != b_rs1 {
if instructions[i + 1].operands.len() > 1 {
instructions[i + 1].operands[1] = MachineOperand::Reg(to_reg_id(a_rd));
}
self.stats.redundant_moves_eliminated += 1;
}
}
i += 1;
}
}
}
fn get_rd_field(mi: &MachineInstr) -> u8 {
mi.operands.first().and_then(get_reg).unwrap_or(0)
}
fn get_rs1_field(mi: &MachineInstr) -> u8 {
mi.operands.get(1).and_then(get_reg).unwrap_or(0)
}
fn get_rs2_field(mi: &MachineInstr) -> u8 {
mi.operands.get(2).and_then(get_reg).unwrap_or(0)
}
fn get_imm_field(mi: &MachineInstr) -> Option<i32> {
mi.operands.iter().find_map(|op| match op {
MachineOperand::Imm(v) => Some(*v as i32),
_ => None,
})
}
fn get_reg(op: &MachineOperand) -> Option<u8> {
match op {
MachineOperand::Reg(v) => {
let v = *v as u16;
if v >= GPR_BASE && v < GPR_BASE + 32 {
Some((v - GPR_BASE) as u8)
} else {
Some(0)
}
}
MachineOperand::PhysReg(v) => {
let v = *v as u16;
if v >= GPR_BASE && v < GPR_BASE + 32 {
Some((v - GPR_BASE) as u8)
} else {
Some(0)
}
}
_ => None,
}
}
fn to_reg_id(field: u8) -> u32 {
(GPR_BASE + field as u16) as u32
}
fn is_conditional_branch_op(op: u32) -> bool {
matches!(
op,
x if x == RiscVOpcode::BEQ as u32
|| x == RiscVOpcode::BNE as u32
|| x == RiscVOpcode::BLT as u32
|| x == RiscVOpcode::BGE as u32
|| x == RiscVOpcode::BLTU as u32
|| x == RiscVOpcode::BGEU as u32
)
}
fn is_unconditional_jump_op(op: u32) -> bool {
op == RiscVOpcode::JAL as u32 || op == RiscVOpcode::J as u32
}
fn invert_branch_op(op: u32) -> Option<u32> {
if op == RiscVOpcode::BEQ as u32 {
Some(RiscVOpcode::BNE as u32)
} else if op == RiscVOpcode::BNE as u32 {
Some(RiscVOpcode::BEQ as u32)
} else if op == RiscVOpcode::BLT as u32 {
Some(RiscVOpcode::BGE as u32)
} else if op == RiscVOpcode::BGE as u32 {
Some(RiscVOpcode::BLT as u32)
} else if op == RiscVOpcode::BLTU as u32 {
Some(RiscVOpcode::BGEU as u32)
} else if op == RiscVOpcode::BGEU as u32 {
Some(RiscVOpcode::BLTU as u32)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mi(opcode: u32, ops: Vec<MachineOperand>) -> MachineInstr {
let mut m = MachineInstr::new(opcode);
m.operands = ops;
m
}
fn reg(id: u16) -> MachineOperand {
MachineOperand::Reg(id as u32)
}
fn imm(v: i32) -> MachineOperand {
MachineOperand::Imm(v as i64)
}
#[test]
fn test_eliminate_nop() {
let mut instrs = vec![
mi(RiscVOpcode::NOP as u32, vec![]),
mi(
RiscVOpcode::ADDI as u32,
vec![reg(3005), reg(3010), imm(42)],
),
mi(RiscVOpcode::NOP as u32, vec![]),
];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.eliminate_nop_moves(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(opt.stats.nops_eliminated, 2);
}
#[test]
fn test_eliminate_addi_x0_x0_0() {
let mut instrs = vec![mi(
RiscVOpcode::ADDI as u32,
vec![reg(3000), reg(3000), imm(0)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.eliminate_nop_moves(&mut instrs);
assert_eq!(instrs.len(), 0);
}
#[test]
fn test_eliminate_self_mv() {
let mut instrs = vec![mi(
RiscVOpcode::ADDI as u32,
vec![reg(3005), reg(3005), imm(0)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.eliminate_nop_moves(&mut instrs);
assert_eq!(instrs.len(), 0);
}
#[test]
fn test_recognize_mv() {
let mut instrs = vec![mi(
RiscVOpcode::ADDI as u32,
vec![reg(3005), reg(3010), imm(0)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.recognize_canonical_pseudos(&mut instrs);
assert_eq!(instrs[0].opcode, RiscVOpcode::MV as u32);
assert_eq!(opt.stats.canonical_pseudos_recognized, 1);
}
#[test]
fn test_recognize_neg() {
let mut instrs = vec![mi(
RiscVOpcode::SUB as u32,
vec![reg(3005), reg(3000), reg(3010)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.recognize_canonical_pseudos(&mut instrs);
assert_eq!(instrs[0].opcode, RiscVOpcode::NEG as u32);
}
#[test]
fn test_recognize_not() {
let mut instrs = vec![mi(
RiscVOpcode::XORI as u32,
vec![reg(3005), reg(3010), imm(-1)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.fold_redundant_ops(&mut instrs);
assert_eq!(instrs[0].opcode, RiscVOpcode::NOT as u32);
assert_eq!(opt.stats.canonical_pseudos_recognized, 1);
}
#[test]
fn test_fuse_lui_addi() {
let mut instrs = vec![
mi(
RiscVOpcode::LUI as u32,
vec![reg(3005), imm(0x12345000u32 as i32)],
),
mi(
RiscVOpcode::ADDI as u32,
vec![reg(3005), reg(3005), imm(0x678)],
),
];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.fuse_lui_addi(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, RiscVOpcode::LI as u32);
assert_eq!(opt.stats.lui_addi_fusions, 1);
}
#[test]
fn test_invert_branch_over_jump() {
let mut instrs = vec![
mi(RiscVOpcode::BEQ as u32, vec![reg(3010), reg(3011), imm(4)]),
mi(RiscVOpcode::J as u32, vec![imm(256)]),
];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.optimize_branch_chains(&mut instrs);
assert_eq!(instrs.len(), 1);
assert_eq!(instrs[0].opcode, RiscVOpcode::BNE as u32);
assert_eq!(opt.stats.branch_chains_optimized, 1);
}
#[test]
fn test_eliminate_zero_shift() {
let mut instrs = vec![mi(
RiscVOpcode::SLLI as u32,
vec![reg(3005), reg(3010), imm(0)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.eliminate_zero_shifts(&mut instrs);
assert_eq!(instrs[0].opcode, RiscVOpcode::MV as u32);
assert_eq!(opt.stats.zero_shifts_removed, 1);
}
#[test]
fn test_redundant_move_elimination() {
let mut instrs = vec![
mi(RiscVOpcode::MV as u32, vec![reg(3005), reg(3010)]),
mi(RiscVOpcode::MV as u32, vec![reg(3006), reg(3010)]),
];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.eliminate_redundant_moves(&mut instrs);
assert_eq!(instrs[1].operands[1], MachineOperand::Reg(3005));
assert_eq!(opt.stats.redundant_moves_eliminated, 1);
}
#[test]
fn test_dead_add_elimination() {
let mut instrs = vec![mi(
RiscVOpcode::ADDI as u32,
vec![reg(3000), reg(3000), imm(0)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.eliminate_dead_adds(&mut instrs);
assert_eq!(instrs.len(), 0);
assert_eq!(opt.stats.dead_adds_eliminated, 1);
}
#[test]
fn test_stats_total_removed() {
let mut stats = RiscVOptStats::new();
stats.total_instructions_before = 10;
stats.total_instructions_after = 7;
assert_eq!(stats.total_removed(), 3);
}
#[test]
fn test_no_fuse_different_rd() {
let mut instrs = vec![
mi(RiscVOpcode::LUI as u32, vec![reg(3005), imm(0x1000)]),
mi(RiscVOpcode::ADDI as u32, vec![reg(3006), reg(3005), imm(4)]),
];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.fuse_lui_addi(&mut instrs);
assert_eq!(instrs.len(), 2); assert_eq!(opt.stats.lui_addi_fusions, 0);
}
#[test]
fn test_invert_branch_all_pairs() {
let pairs = vec![
(RiscVOpcode::BEQ, RiscVOpcode::BNE),
(RiscVOpcode::BNE, RiscVOpcode::BEQ),
(RiscVOpcode::BLT, RiscVOpcode::BGE),
(RiscVOpcode::BGE, RiscVOpcode::BLT),
(RiscVOpcode::BLTU, RiscVOpcode::BGEU),
(RiscVOpcode::BGEU, RiscVOpcode::BLTU),
];
for (orig, expected) in pairs {
assert_eq!(invert_branch_op(orig as u32), Some(expected as u32));
}
}
#[test]
fn test_c_mv_to_c_li() {
let mut instrs = vec![mi(
RiscVOpcode::SLLI as u32,
vec![reg(3005), reg(3005), imm(0)],
)];
let mut opt = RiscVPeepholeOptimizer::new(true);
opt.eliminate_nop_moves(&mut instrs);
assert_eq!(instrs.len(), 0); assert_eq!(opt.stats.nops_eliminated, 1);
}
}