use std::collections::{HashMap, HashSet};
use synth_cfg::{BlockId, Cfg};
pub trait OptimizationPass {
fn name(&self) -> &'static str;
fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult;
}
#[derive(Debug, Clone)]
pub struct OptResult {
pub changed: bool,
pub removed_count: usize,
pub added_count: usize,
pub modified_count: usize,
}
impl OptResult {
pub fn no_change() -> Self {
Self {
changed: false,
removed_count: 0,
added_count: 0,
modified_count: 0,
}
}
pub fn merge(&mut self, other: OptResult) {
self.changed |= other.changed;
self.removed_count += other.removed_count;
self.added_count += other.added_count;
self.modified_count += other.modified_count;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Instruction {
pub id: usize,
pub opcode: Opcode,
pub block_id: BlockId,
pub is_dead: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Opcode {
Nop,
Add {
dest: Reg,
src1: Reg,
src2: Reg,
},
Sub {
dest: Reg,
src1: Reg,
src2: Reg,
},
Mul {
dest: Reg,
src1: Reg,
src2: Reg,
},
DivS {
dest: Reg,
src1: Reg,
src2: Reg,
}, DivU {
dest: Reg,
src1: Reg,
src2: Reg,
}, RemS {
dest: Reg,
src1: Reg,
src2: Reg,
}, RemU {
dest: Reg,
src1: Reg,
src2: Reg,
}, And {
dest: Reg,
src1: Reg,
src2: Reg,
},
Or {
dest: Reg,
src1: Reg,
src2: Reg,
},
Xor {
dest: Reg,
src1: Reg,
src2: Reg,
},
Shl {
dest: Reg,
src1: Reg,
src2: Reg,
}, ShrS {
dest: Reg,
src1: Reg,
src2: Reg,
}, ShrU {
dest: Reg,
src1: Reg,
src2: Reg,
}, Rotl {
dest: Reg,
src1: Reg,
src2: Reg,
}, Rotr {
dest: Reg,
src1: Reg,
src2: Reg,
}, Clz {
dest: Reg,
src: Reg,
}, Ctz {
dest: Reg,
src: Reg,
}, Popcnt {
dest: Reg,
src: Reg,
}, Extend8S {
dest: Reg,
src: Reg,
}, Extend16S {
dest: Reg,
src: Reg,
}, Eqz {
dest: Reg,
src: Reg,
}, Eq {
dest: Reg,
src1: Reg,
src2: Reg,
},
Ne {
dest: Reg,
src1: Reg,
src2: Reg,
},
LtS {
dest: Reg,
src1: Reg,
src2: Reg,
}, LtU {
dest: Reg,
src1: Reg,
src2: Reg,
}, LeS {
dest: Reg,
src1: Reg,
src2: Reg,
}, LeU {
dest: Reg,
src1: Reg,
src2: Reg,
}, GtS {
dest: Reg,
src1: Reg,
src2: Reg,
}, GtU {
dest: Reg,
src1: Reg,
src2: Reg,
}, GeS {
dest: Reg,
src1: Reg,
src2: Reg,
}, GeU {
dest: Reg,
src1: Reg,
src2: Reg,
}, Load {
dest: Reg,
addr: u32,
},
Store {
src: Reg,
addr: u32,
},
I64Load {
dest_lo: Reg,
dest_hi: Reg,
addr: u32,
},
MemLoad {
dest: Reg,
addr: Reg,
offset: u32,
},
MemStore {
src: Reg,
addr: Reg,
offset: u32,
},
MemLoadSubword {
dest: Reg,
addr: Reg,
offset: u32,
width: u32, signed: bool,
},
MemStoreSubword {
src: Reg,
addr: Reg,
offset: u32,
width: u32, },
GlobalGet {
dest: Reg,
idx: u32,
},
GlobalSet {
src: Reg,
idx: u32,
},
MemorySize {
dest: Reg,
},
MemoryGrow {
dest: Reg,
delta: Reg,
},
Branch {
target: BlockId,
},
CondBranch {
cond: Reg,
target: BlockId,
},
Call {
dest: Reg,
func_idx: u32,
},
Return {
value: Option<Reg>,
},
Label {
id: BlockId,
},
Copy {
dest: Reg,
src: Reg,
},
TeeStore {
dest: Reg,
src: Reg,
addr: u32,
},
Select {
dest: Reg,
val_true: Reg,
val_false: Reg,
cond: Reg,
},
Const {
dest: Reg,
value: i32,
},
I64Add {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Sub {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64And {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Or {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Xor {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Const {
dest_lo: Reg,
dest_hi: Reg,
value: i64,
},
I64Eq {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Ne {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LtS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GtS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LeS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GeS {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LtU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GtU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64LeU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64GeU {
dest: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Eqz {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Clz {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Ctz {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Popcnt {
dest: Reg,
src_lo: Reg,
src_hi: Reg,
},
I64Extend8S {
dest_lo: Reg,
dest_hi: Reg,
src_lo: Reg,
},
I64Extend16S {
dest_lo: Reg,
dest_hi: Reg,
src_lo: Reg,
},
I64Extend32S {
dest_lo: Reg,
dest_hi: Reg,
src_lo: Reg,
},
I64ExtendI32U {
dest_lo: Reg,
dest_hi: Reg,
src: Reg,
},
I64ExtendI32S {
dest_lo: Reg,
dest_hi: Reg,
src: Reg,
},
I32WrapI64 {
dest: Reg,
src_lo: Reg,
},
I64Mul {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64DivS {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64DivU {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64RemS {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64RemU {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Shl {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64ShrS {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64ShrU {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Rotl {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
I64Rotr {
dest_lo: Reg,
dest_hi: Reg,
src1_lo: Reg,
src1_hi: Reg,
src2_lo: Reg,
src2_hi: Reg,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Reg(pub u32);
pub struct DeadCodeElimination {
verbose: bool,
}
impl DeadCodeElimination {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn mark_reachable_blocks(&self, cfg: &Cfg) -> HashSet<BlockId> {
let mut reachable = HashSet::new();
let mut worklist = vec![cfg.entry];
while let Some(block_id) = worklist.pop() {
if reachable.contains(&block_id) {
continue;
}
reachable.insert(block_id);
if let Some(block) = cfg.block(block_id) {
for &succ in &block.successors {
if !reachable.contains(&succ) {
worklist.push(succ);
}
}
}
}
reachable
}
fn remove_unreachable(&self, cfg: &Cfg, instructions: &mut [Instruction]) -> OptResult {
let reachable = self.mark_reachable_blocks(cfg);
let mut removed = 0;
for inst in instructions.iter_mut() {
if !reachable.contains(&inst.block_id) && !inst.is_dead {
inst.is_dead = true;
removed += 1;
}
}
if self.verbose && removed > 0 {
eprintln!("DCE: Removed {} unreachable instructions", removed);
}
OptResult {
changed: removed > 0,
removed_count: removed,
added_count: 0,
modified_count: 0,
}
}
}
impl Default for DeadCodeElimination {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for DeadCodeElimination {
fn name(&self) -> &'static str {
"dead-code-elimination"
}
fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.remove_unreachable(cfg, instructions)
}
}
pub struct ConstantFolding {
verbose: bool,
}
impl ConstantFolding {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn fold_constants(&mut self, instructions: &mut [Instruction]) -> OptResult {
let mut const_values: HashMap<Reg, i32> = HashMap::new();
let mut modified = 0;
for inst in instructions.iter_mut() {
if inst.is_dead {
continue;
}
let opcode = inst.opcode.clone();
match opcode {
Opcode::Const { dest, value } => {
const_values.insert(dest, value);
}
Opcode::Add { dest, src1, src2 } => {
if let (Some(&val1), Some(&val2)) =
(const_values.get(&src1), const_values.get(&src2))
{
let result = val1.wrapping_add(val2);
inst.opcode = Opcode::Const {
dest,
value: result,
};
const_values.insert(dest, result);
modified += 1;
if self.verbose {
eprintln!(
"Folded: add {} = {} + {} -> const {} = {}",
dest.0, val1, val2, dest.0, result
);
}
}
}
Opcode::Sub { dest, src1, src2 } => {
if let (Some(&val1), Some(&val2)) =
(const_values.get(&src1), const_values.get(&src2))
{
let result = val1.wrapping_sub(val2);
inst.opcode = Opcode::Const {
dest,
value: result,
};
const_values.insert(dest, result);
modified += 1;
if self.verbose {
eprintln!(
"Folded: sub {} = {} - {} -> const {} = {}",
dest.0, val1, val2, dest.0, result
);
}
}
}
Opcode::Mul { dest, src1, src2 } => {
if let (Some(&val1), Some(&val2)) =
(const_values.get(&src1), const_values.get(&src2))
{
let result = val1.wrapping_mul(val2);
inst.opcode = Opcode::Const {
dest,
value: result,
};
const_values.insert(dest, result);
modified += 1;
if self.verbose {
eprintln!(
"Folded: mul {} = {} * {} -> const {} = {}",
dest.0, val1, val2, dest.0, result
);
}
}
}
_ => {}
}
}
if self.verbose && modified > 0 {
eprintln!("Constant folding: {} operations folded", modified);
}
OptResult {
changed: modified > 0,
removed_count: 0,
added_count: 0,
modified_count: modified,
}
}
}
impl Default for ConstantFolding {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for ConstantFolding {
fn name(&self) -> &'static str {
"constant-folding"
}
fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.fold_constants(instructions)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum ExprKey {
Add(Reg, Reg),
Sub(Reg, Reg),
Mul(Reg, Reg),
Load(u32),
}
pub struct CommonSubexpressionElimination {
verbose: bool,
}
impl CommonSubexpressionElimination {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn eliminate_cse(&mut self, instructions: &mut [Instruction]) -> OptResult {
let mut expr_map: HashMap<ExprKey, Reg> = HashMap::new();
let mut reg_map: HashMap<Reg, Reg> = HashMap::new();
let mut modified = 0;
for inst in instructions.iter_mut() {
if inst.is_dead {
continue;
}
let opcode = inst.opcode.clone();
let resolve = |r: Reg| -> Reg { reg_map.get(&r).copied().unwrap_or(r) };
let dests_to_invalidate: Vec<Reg> = match &opcode {
Opcode::Add { dest, .. }
| Opcode::Sub { dest, .. }
| Opcode::Mul { dest, .. }
| Opcode::DivS { dest, .. }
| Opcode::DivU { dest, .. }
| Opcode::RemS { dest, .. }
| Opcode::RemU { dest, .. }
| Opcode::And { dest, .. }
| Opcode::Or { dest, .. }
| Opcode::Xor { dest, .. }
| Opcode::Shl { dest, .. }
| Opcode::ShrS { dest, .. }
| Opcode::ShrU { dest, .. }
| Opcode::Rotl { dest, .. }
| Opcode::Rotr { dest, .. }
| Opcode::Clz { dest, .. }
| Opcode::Ctz { dest, .. }
| Opcode::Popcnt { dest, .. }
| Opcode::Extend8S { dest, .. }
| Opcode::Extend16S { dest, .. }
| Opcode::Eqz { dest, .. }
| Opcode::Eq { dest, .. }
| Opcode::Ne { dest, .. }
| Opcode::LtS { dest, .. }
| Opcode::LtU { dest, .. }
| Opcode::LeS { dest, .. }
| Opcode::LeU { dest, .. }
| Opcode::GtS { dest, .. }
| Opcode::GtU { dest, .. }
| Opcode::GeS { dest, .. }
| Opcode::GeU { dest, .. }
| Opcode::Load { dest, .. }
| Opcode::MemLoad { dest, .. }
| Opcode::Copy { dest, .. }
| Opcode::TeeStore { dest, .. }
| Opcode::Select { dest, .. }
| Opcode::Const { dest, .. }
| Opcode::I64Eq { dest, .. }
| Opcode::I64Ne { dest, .. }
| Opcode::I64LtS { dest, .. }
| Opcode::I64LtU { dest, .. }
| Opcode::I64LeS { dest, .. }
| Opcode::I64LeU { dest, .. }
| Opcode::I64GtS { dest, .. }
| Opcode::I64GtU { dest, .. }
| Opcode::I64GeS { dest, .. }
| Opcode::I64GeU { dest, .. }
| Opcode::I64Eqz { dest, .. }
| Opcode::I64Clz { dest, .. }
| Opcode::I64Ctz { dest, .. }
| Opcode::I64Popcnt { dest, .. }
| Opcode::I32WrapI64 { dest, .. }
| Opcode::MemLoadSubword { dest, .. }
| Opcode::GlobalGet { dest, .. }
| Opcode::MemorySize { dest, .. }
| Opcode::MemoryGrow { dest, .. }
| Opcode::Call { dest, .. } => vec![*dest],
Opcode::I64Add {
dest_lo, dest_hi, ..
}
| Opcode::I64Sub {
dest_lo, dest_hi, ..
}
| Opcode::I64And {
dest_lo, dest_hi, ..
}
| Opcode::I64Or {
dest_lo, dest_hi, ..
}
| Opcode::I64Xor {
dest_lo, dest_hi, ..
}
| Opcode::I64Mul {
dest_lo, dest_hi, ..
}
| Opcode::I64DivS {
dest_lo, dest_hi, ..
}
| Opcode::I64DivU {
dest_lo, dest_hi, ..
}
| Opcode::I64RemS {
dest_lo, dest_hi, ..
}
| Opcode::I64RemU {
dest_lo, dest_hi, ..
}
| Opcode::I64Shl {
dest_lo, dest_hi, ..
}
| Opcode::I64ShrS {
dest_lo, dest_hi, ..
}
| Opcode::I64ShrU {
dest_lo, dest_hi, ..
}
| Opcode::I64Rotl {
dest_lo, dest_hi, ..
}
| Opcode::I64Rotr {
dest_lo, dest_hi, ..
}
| Opcode::I64Const {
dest_lo, dest_hi, ..
}
| Opcode::I64Load {
dest_lo, dest_hi, ..
}
| Opcode::I64Extend8S {
dest_lo, dest_hi, ..
}
| Opcode::I64Extend16S {
dest_lo, dest_hi, ..
}
| Opcode::I64Extend32S {
dest_lo, dest_hi, ..
}
| Opcode::I64ExtendI32U {
dest_lo, dest_hi, ..
}
| Opcode::I64ExtendI32S {
dest_lo, dest_hi, ..
} => vec![*dest_lo, *dest_hi],
_ => Vec::new(),
};
match opcode {
Opcode::Add { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
let key = ExprKey::Add(src1, src2);
if let Some(&existing) = expr_map.get(&key) {
inst.opcode = Opcode::Const { dest, value: 0 }; inst.is_dead = true; reg_map.insert(dest, existing);
modified += 1;
if self.verbose {
eprintln!(
"CSE: Eliminated add r{} = r{} + r{}, reuse r{}",
dest.0, src1.0, src2.0, existing.0
);
}
} else {
expr_map.insert(key, dest);
inst.opcode = Opcode::Add { dest, src1, src2 };
}
}
Opcode::Sub { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
let key = ExprKey::Sub(src1, src2);
if let Some(&existing) = expr_map.get(&key) {
inst.opcode = Opcode::Const { dest, value: 0 };
inst.is_dead = true;
reg_map.insert(dest, existing);
modified += 1;
if self.verbose {
eprintln!(
"CSE: Eliminated sub r{} = r{} - r{}, reuse r{}",
dest.0, src1.0, src2.0, existing.0
);
}
} else {
expr_map.insert(key, dest);
inst.opcode = Opcode::Sub { dest, src1, src2 };
}
}
Opcode::Mul { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
let key = ExprKey::Mul(src1, src2);
if let Some(&existing) = expr_map.get(&key) {
inst.opcode = Opcode::Const { dest, value: 0 };
inst.is_dead = true;
reg_map.insert(dest, existing);
modified += 1;
if self.verbose {
eprintln!(
"CSE: Eliminated mul r{} = r{} * r{}, reuse r{}",
dest.0, src1.0, src2.0, existing.0
);
}
} else {
expr_map.insert(key, dest);
inst.opcode = Opcode::Mul { dest, src1, src2 };
}
}
Opcode::Load { dest, addr } => {
let key = ExprKey::Load(addr);
if let Some(&existing) = expr_map.get(&key) {
inst.opcode = Opcode::Const { dest, value: 0 };
inst.is_dead = true;
reg_map.insert(dest, existing);
modified += 1;
if self.verbose {
eprintln!(
"CSE: Eliminated load r{} = [0x{:x}], reuse r{}",
dest.0, addr, existing.0
);
}
} else {
expr_map.insert(key, dest);
}
}
Opcode::Store { src, addr } => {
let src = resolve(src);
inst.opcode = Opcode::Store { src, addr };
expr_map.remove(&ExprKey::Load(addr));
}
Opcode::TeeStore { dest, src, addr } => {
let src = resolve(src);
inst.opcode = Opcode::TeeStore { dest, src, addr };
expr_map.remove(&ExprKey::Load(addr));
}
Opcode::MemLoad { dest, addr, offset } => {
let addr = resolve(addr);
inst.opcode = Opcode::MemLoad { dest, addr, offset };
}
Opcode::MemStore { src, addr, offset } => {
let src = resolve(src);
let addr = resolve(addr);
inst.opcode = Opcode::MemStore { src, addr, offset };
}
Opcode::Extend8S { dest, src } => {
let src = resolve(src);
inst.opcode = Opcode::Extend8S { dest, src };
}
Opcode::Extend16S { dest, src } => {
let src = resolve(src);
inst.opcode = Opcode::Extend16S { dest, src };
}
Opcode::Label { .. } => {
expr_map.retain(|k, _| !matches!(k, ExprKey::Load(_)));
}
Opcode::CondBranch { cond, target } => {
let cond = resolve(cond);
inst.opcode = Opcode::CondBranch { cond, target };
expr_map.retain(|k, _| !matches!(k, ExprKey::Load(_)));
}
Opcode::Eq { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::Eq { dest, src1, src2 };
}
Opcode::Ne { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::Ne { dest, src1, src2 };
}
Opcode::LtS { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::LtS { dest, src1, src2 };
}
Opcode::LtU { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::LtU { dest, src1, src2 };
}
Opcode::LeS { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::LeS { dest, src1, src2 };
}
Opcode::LeU { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::LeU { dest, src1, src2 };
}
Opcode::GtS { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::GtS { dest, src1, src2 };
}
Opcode::GtU { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::GtU { dest, src1, src2 };
}
Opcode::GeS { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::GeS { dest, src1, src2 };
}
Opcode::GeU { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::GeU { dest, src1, src2 };
}
Opcode::Eqz { dest, src } => {
let src = resolve(src);
inst.opcode = Opcode::Eqz { dest, src };
}
Opcode::Return { value: Some(v) } => {
let v = resolve(v);
inst.opcode = Opcode::Return { value: Some(v) };
}
Opcode::Select {
dest,
val_true,
val_false,
cond,
} => {
let val_true = resolve(val_true);
let val_false = resolve(val_false);
let cond = resolve(cond);
inst.opcode = Opcode::Select {
dest,
val_true,
val_false,
cond,
};
}
Opcode::DivS { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::DivS { dest, src1, src2 };
}
Opcode::DivU { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::DivU { dest, src1, src2 };
}
Opcode::RemS { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::RemS { dest, src1, src2 };
}
Opcode::RemU { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::RemU { dest, src1, src2 };
}
Opcode::And { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::And { dest, src1, src2 };
}
Opcode::Or { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::Or { dest, src1, src2 };
}
Opcode::Xor { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::Xor { dest, src1, src2 };
}
Opcode::Shl { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::Shl { dest, src1, src2 };
}
Opcode::ShrS { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::ShrS { dest, src1, src2 };
}
Opcode::ShrU { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::ShrU { dest, src1, src2 };
}
Opcode::Rotl { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::Rotl { dest, src1, src2 };
}
Opcode::Rotr { dest, src1, src2 } => {
let src1 = resolve(src1);
let src2 = resolve(src2);
inst.opcode = Opcode::Rotr { dest, src1, src2 };
}
Opcode::Clz { dest, src } => {
let src = resolve(src);
inst.opcode = Opcode::Clz { dest, src };
}
Opcode::Ctz { dest, src } => {
let src = resolve(src);
inst.opcode = Opcode::Ctz { dest, src };
}
Opcode::Popcnt { dest, src } => {
let src = resolve(src);
inst.opcode = Opcode::Popcnt { dest, src };
}
Opcode::Copy { dest, src } => {
let src = resolve(src);
inst.opcode = Opcode::Copy { dest, src };
}
Opcode::I64Load { .. } | Opcode::I64Const { .. } => {
}
Opcode::I64Add {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Add {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Sub {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Sub {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64And {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64And {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Or {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Or {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Xor {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Xor {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Mul {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Mul {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64DivS {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64DivS {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64DivU {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64DivU {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64RemS {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64RemS {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64RemU {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64RemU {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Shl {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Shl {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64ShrS {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64ShrS {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64ShrU {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64ShrU {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Rotl {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Rotl {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Rotr {
dest_lo,
dest_hi,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Rotr {
dest_lo,
dest_hi,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Eq {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Eq {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Ne {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64Ne {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64LtS {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64LtS {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64GtS {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64GtS {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64LeS {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64LeS {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64GeS {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64GeS {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64LtU {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64LtU {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64GtU {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64GtU {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64LeU {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64LeU {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64GeU {
dest,
src1_lo,
src1_hi,
src2_lo,
src2_hi,
} => {
inst.opcode = Opcode::I64GeU {
dest,
src1_lo: resolve(src1_lo),
src1_hi: resolve(src1_hi),
src2_lo: resolve(src2_lo),
src2_hi: resolve(src2_hi),
};
}
Opcode::I64Eqz {
dest,
src_lo,
src_hi,
} => {
inst.opcode = Opcode::I64Eqz {
dest,
src_lo: resolve(src_lo),
src_hi: resolve(src_hi),
};
}
Opcode::I64Clz {
dest,
src_lo,
src_hi,
} => {
inst.opcode = Opcode::I64Clz {
dest,
src_lo: resolve(src_lo),
src_hi: resolve(src_hi),
};
}
Opcode::I64Ctz {
dest,
src_lo,
src_hi,
} => {
inst.opcode = Opcode::I64Ctz {
dest,
src_lo: resolve(src_lo),
src_hi: resolve(src_hi),
};
}
Opcode::I64Popcnt {
dest,
src_lo,
src_hi,
} => {
inst.opcode = Opcode::I64Popcnt {
dest,
src_lo: resolve(src_lo),
src_hi: resolve(src_hi),
};
}
Opcode::I64Extend8S {
dest_lo,
dest_hi,
src_lo,
} => {
inst.opcode = Opcode::I64Extend8S {
dest_lo,
dest_hi,
src_lo: resolve(src_lo),
};
}
Opcode::I64Extend16S {
dest_lo,
dest_hi,
src_lo,
} => {
inst.opcode = Opcode::I64Extend16S {
dest_lo,
dest_hi,
src_lo: resolve(src_lo),
};
}
Opcode::I64Extend32S {
dest_lo,
dest_hi,
src_lo,
} => {
inst.opcode = Opcode::I64Extend32S {
dest_lo,
dest_hi,
src_lo: resolve(src_lo),
};
}
Opcode::I64ExtendI32U {
dest_lo,
dest_hi,
src,
} => {
inst.opcode = Opcode::I64ExtendI32U {
dest_lo,
dest_hi,
src: resolve(src),
};
}
Opcode::I64ExtendI32S {
dest_lo,
dest_hi,
src,
} => {
inst.opcode = Opcode::I64ExtendI32S {
dest_lo,
dest_hi,
src: resolve(src),
};
}
Opcode::I32WrapI64 { dest, src_lo } => {
inst.opcode = Opcode::I32WrapI64 {
dest,
src_lo: resolve(src_lo),
};
}
Opcode::MemLoadSubword {
dest,
addr,
offset,
width,
signed,
} => {
inst.opcode = Opcode::MemLoadSubword {
dest,
addr: resolve(addr),
offset,
width,
signed,
};
}
Opcode::MemStoreSubword {
src,
addr,
offset,
width,
} => {
inst.opcode = Opcode::MemStoreSubword {
src: resolve(src),
addr: resolve(addr),
offset,
width,
};
}
Opcode::GlobalSet { src, idx } => {
inst.opcode = Opcode::GlobalSet {
src: resolve(src),
idx,
};
}
Opcode::MemoryGrow { dest, delta } => {
inst.opcode = Opcode::MemoryGrow {
dest,
delta: resolve(delta),
};
}
_ => {}
}
if !inst.is_dead {
for dest in &dests_to_invalidate {
reg_map.remove(dest);
}
}
}
if self.verbose && modified > 0 {
eprintln!("CSE: {} subexpressions eliminated", modified);
}
OptResult {
changed: modified > 0,
removed_count: modified, added_count: 0,
modified_count: 0,
}
}
}
impl Default for CommonSubexpressionElimination {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for CommonSubexpressionElimination {
fn name(&self) -> &'static str {
"common-subexpression-elimination"
}
fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.eliminate_cse(instructions)
}
}
pub struct AlgebraicSimplification {
verbose: bool,
}
impl AlgebraicSimplification {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn simplify(&mut self, instructions: &mut [Instruction]) -> OptResult {
let mut const_values: HashMap<Reg, i32> = HashMap::new();
let mut modified = 0;
for inst in instructions.iter_mut() {
if inst.is_dead {
continue;
}
let opcode = inst.opcode.clone();
match opcode {
Opcode::Const { dest, value } => {
const_values.insert(dest, value);
}
Opcode::Add {
dest: _,
src1,
src2,
} => {
let val1 = const_values.get(&src1);
let val2 = const_values.get(&src2);
match (val1, val2) {
(Some(&0), _) => {
inst.is_dead = true;
modified += 1;
if self.verbose {
eprintln!("Simplified: 0 + r{} -> r{}", src2.0, src2.0);
}
}
(_, Some(&0)) => {
inst.is_dead = true;
modified += 1;
if self.verbose {
eprintln!("Simplified: r{} + 0 -> r{}", src1.0, src1.0);
}
}
_ => {}
}
}
Opcode::Sub { dest, src1, src2 } => {
let val2 = const_values.get(&src2);
if let Some(&0) = val2 {
inst.is_dead = true;
modified += 1;
if self.verbose {
eprintln!("Simplified: r{} - 0 -> r{}", src1.0, src1.0);
}
} else if src1 == src2 {
inst.opcode = Opcode::Const { dest, value: 0 };
const_values.insert(dest, 0);
modified += 1;
if self.verbose {
eprintln!("Simplified: r{} - r{} -> 0", src1.0, src2.0);
}
}
}
Opcode::Mul { dest, src1, src2 } => {
let val1 = const_values.get(&src1);
let val2 = const_values.get(&src2);
match (val1, val2) {
(Some(&0), _) | (_, Some(&0)) => {
inst.opcode = Opcode::Const { dest, value: 0 };
const_values.insert(dest, 0);
modified += 1;
if self.verbose {
eprintln!("Simplified: mul with 0 -> 0");
}
}
(Some(&1), _) => {
inst.is_dead = true;
modified += 1;
if self.verbose {
eprintln!("Simplified: 1 * r{} -> r{}", src2.0, src2.0);
}
}
(_, Some(&1)) => {
inst.is_dead = true;
modified += 1;
if self.verbose {
eprintln!("Simplified: r{} * 1 -> r{}", src1.0, src1.0);
}
}
_ => {}
}
}
_ => {}
}
}
if self.verbose && modified > 0 {
eprintln!(
"Algebraic simplification: {} operations simplified",
modified
);
}
OptResult {
changed: modified > 0,
removed_count: 0,
added_count: 0,
modified_count: modified,
}
}
}
impl Default for AlgebraicSimplification {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for AlgebraicSimplification {
fn name(&self) -> &'static str {
"algebraic-simplification"
}
fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.simplify(instructions)
}
}
pub struct PeepholeOptimization {
verbose: bool,
}
impl PeepholeOptimization {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn optimize(&mut self, instructions: &mut [Instruction]) -> OptResult {
let mut modified = 0;
let mut i = 0;
while i + 1 < instructions.len() {
if instructions[i].is_dead || instructions[i + 1].is_dead {
i += 1;
continue;
}
let inst1 = instructions[i].opcode.clone();
let inst2 = instructions[i + 1].opcode.clone();
match (&inst1, &inst2) {
(Opcode::Const { dest: dest1, .. }, Opcode::Const { dest: dest2, .. })
if dest1 == dest2 =>
{
instructions[i].is_dead = true;
modified += 1;
if self.verbose {
eprintln!("Peephole: Eliminated redundant const to r{}", dest1.0);
}
}
_ => {}
}
i += 1;
}
let mut i = 0;
while i + 2 < instructions.len() {
if instructions[i].is_dead || instructions[i + 1].is_dead || instructions[i + 2].is_dead
{
i += 1;
continue;
}
let _inst1 = instructions[i].opcode.clone();
let _inst2 = instructions[i + 1].opcode.clone();
let _inst3 = instructions[i + 2].opcode.clone();
i += 1;
}
if self.verbose && modified > 0 {
eprintln!("Peephole optimization: {} patterns matched", modified);
}
OptResult {
changed: modified > 0,
removed_count: modified,
added_count: 0,
modified_count: 0,
}
}
}
impl Default for PeepholeOptimization {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for PeepholeOptimization {
fn name(&self) -> &'static str {
"peephole-optimization"
}
fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.optimize(instructions)
}
}
pub struct StrengthReduction {
verbose: bool,
}
impl StrengthReduction {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn is_power_of_2(n: i32) -> bool {
n > 0 && (n & (n - 1)) == 0
}
fn log2(n: i32) -> u32 {
n.trailing_zeros()
}
fn reduce(&mut self, instructions: &mut [Instruction]) -> OptResult {
let mut const_values: HashMap<Reg, i32> = HashMap::new();
let mut modified = 0;
for inst in instructions.iter_mut() {
if inst.is_dead {
continue;
}
let opcode = inst.opcode.clone();
match opcode {
Opcode::Const { dest, value } => {
const_values.insert(dest, value);
}
Opcode::Mul {
dest: _,
src1,
src2,
} => {
let val1 = const_values.get(&src1);
let val2 = const_values.get(&src2);
if let Some(&val) = val2
&& Self::is_power_of_2(val)
{
modified += 1;
if self.verbose {
eprintln!(
"Strength reduction: r{} * {} -> r{} << {}",
src1.0,
val,
src1.0,
Self::log2(val)
);
}
} else if let Some(&val) = val1
&& Self::is_power_of_2(val)
{
modified += 1;
if self.verbose {
eprintln!(
"Strength reduction: {} * r{} -> r{} << {}",
val,
src2.0,
src2.0,
Self::log2(val)
);
}
}
}
_ => {}
}
}
if self.verbose && modified > 0 {
eprintln!("Strength reduction: {} operations reduced", modified);
}
OptResult {
changed: modified > 0,
removed_count: 0,
added_count: 0,
modified_count: modified,
}
}
}
impl Default for StrengthReduction {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for StrengthReduction {
fn name(&self) -> &'static str {
"strength-reduction"
}
fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.reduce(instructions)
}
}
pub struct LoopInvariantCodeMotion {
verbose: bool,
}
impl LoopInvariantCodeMotion {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn detect_invariants(&self, cfg: &Cfg, instructions: &[Instruction]) -> HashSet<usize> {
let mut invariants = HashSet::new();
for loop_info in &cfg.loops {
for inst in instructions {
if inst.is_dead || !loop_info.body.contains(&inst.block_id) {
continue;
}
let is_invariant = match &inst.opcode {
Opcode::Const { .. } => true,
Opcode::Add {
src1: _, src2: _, ..
}
| Opcode::Sub {
src1: _, src2: _, ..
}
| Opcode::Mul {
src1: _, src2: _, ..
} => {
false }
Opcode::Load { .. } => false,
_ => false,
};
if is_invariant {
invariants.insert(inst.id);
}
}
}
invariants
}
fn hoist(&mut self, cfg: &mut Cfg, instructions: &mut [Instruction]) -> OptResult {
let invariants = self.detect_invariants(cfg, instructions);
if self.verbose && !invariants.is_empty() {
eprintln!(
"LICM: {} loop-invariant instructions detected",
invariants.len()
);
}
let modified = invariants.len();
OptResult {
changed: modified > 0,
removed_count: 0,
added_count: 0,
modified_count: modified,
}
}
}
impl Default for LoopInvariantCodeMotion {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for LoopInvariantCodeMotion {
fn name(&self) -> &'static str {
"loop-invariant-code-motion"
}
fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.hoist(cfg, instructions)
}
}
pub struct CopyPropagation {
verbose: bool,
}
impl CopyPropagation {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn propagate(&mut self, instructions: &mut [Instruction]) -> OptResult {
let copy_map: HashMap<Reg, Reg> = HashMap::new();
let mut modified = 0;
for inst in instructions.iter_mut() {
if inst.is_dead {
continue;
}
let mut changed = false;
let opcode = inst.opcode.clone();
match opcode {
Opcode::Add { dest, src1, src2 } => {
let new_src1 = Self::resolve(©_map, src1);
let new_src2 = Self::resolve(©_map, src2);
if new_src1 != src1 || new_src2 != src2 {
inst.opcode = Opcode::Add {
dest,
src1: new_src1,
src2: new_src2,
};
changed = true;
modified += 1;
}
}
Opcode::Sub { dest, src1, src2 } => {
let new_src1 = Self::resolve(©_map, src1);
let new_src2 = Self::resolve(©_map, src2);
if new_src1 != src1 || new_src2 != src2 {
inst.opcode = Opcode::Sub {
dest,
src1: new_src1,
src2: new_src2,
};
changed = true;
modified += 1;
}
}
Opcode::Mul { dest, src1, src2 } => {
let new_src1 = Self::resolve(©_map, src1);
let new_src2 = Self::resolve(©_map, src2);
if new_src1 != src1 || new_src2 != src2 {
inst.opcode = Opcode::Mul {
dest,
src1: new_src1,
src2: new_src2,
};
changed = true;
modified += 1;
}
}
Opcode::Load { dest: _, addr: _ } => {
}
Opcode::Store { src, addr } => {
let new_src = Self::resolve(©_map, src);
if new_src != src {
inst.opcode = Opcode::Store { src: new_src, addr };
changed = true;
modified += 1;
}
}
_ => {}
}
if changed && self.verbose {
eprintln!("Copy propagation: updated instruction {}", inst.id);
}
}
if self.verbose && modified > 0 {
eprintln!("Copy propagation: {} instructions updated", modified);
}
OptResult {
changed: modified > 0,
removed_count: 0,
added_count: 0,
modified_count: modified,
}
}
fn resolve(copy_map: &HashMap<Reg, Reg>, reg: Reg) -> Reg {
let mut current = reg;
let mut visited = HashSet::new();
while let Some(&next) = copy_map.get(¤t) {
if !visited.insert(current) {
break;
}
if next == current {
break;
}
current = next;
}
current
}
}
impl Default for CopyPropagation {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for CopyPropagation {
fn name(&self) -> &'static str {
"copy-propagation"
}
fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.propagate(instructions)
}
}
pub struct InstructionCombining {
verbose: bool,
}
impl InstructionCombining {
pub fn new() -> Self {
Self { verbose: false }
}
pub fn with_verbose(mut self) -> Self {
self.verbose = true;
self
}
fn combine(&mut self, instructions: &mut [Instruction]) -> OptResult {
let mut const_values: HashMap<Reg, i32> = HashMap::new();
let mut def_map: HashMap<Reg, usize> = HashMap::new();
let mut inst_opcodes: HashMap<usize, Opcode> = HashMap::new();
let mut modified = 0;
for inst in instructions.iter() {
if inst.is_dead {
continue;
}
match &inst.opcode {
Opcode::Const { dest, value } => {
const_values.insert(*dest, *value);
def_map.insert(*dest, inst.id);
}
Opcode::Add { dest, .. } | Opcode::Sub { dest, .. } | Opcode::Mul { dest, .. } => {
def_map.insert(*dest, inst.id);
}
_ => {}
}
inst_opcodes.insert(inst.id, inst.opcode.clone());
}
for inst in instructions.iter() {
if inst.is_dead {
continue;
}
match &inst.opcode {
Opcode::Add {
dest: _,
src1,
src2,
} => {
if let Some(&val2) = const_values.get(src2)
&& let Some(&def_id) = def_map.get(src1)
&& let Some(Opcode::Add {
dest: _,
src1: inner_src1,
src2: inner_src2,
}) = inst_opcodes.get(&def_id)
&& let Some(&val1) = const_values.get(inner_src2)
{
let combined = val1.wrapping_add(val2);
modified += 1;
if self.verbose {
eprintln!(
"Instruction combining: (r{} + {}) + {} => r{} + {}",
inner_src1.0, val1, val2, inner_src1.0, combined
);
}
}
}
Opcode::Mul {
dest: _,
src1: _,
src2: _,
} => {
}
_ => {}
}
}
if self.verbose && modified > 0 {
eprintln!("Instruction combining: {} opportunities found", modified);
}
OptResult {
changed: modified > 0,
removed_count: 0,
added_count: 0,
modified_count: modified,
}
}
}
impl Default for InstructionCombining {
fn default() -> Self {
Self::new()
}
}
impl OptimizationPass for InstructionCombining {
fn name(&self) -> &'static str {
"instruction-combining"
}
fn run(&mut self, _cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
self.combine(instructions)
}
}
pub struct PassManager {
passes: Vec<Box<dyn OptimizationPass>>,
max_iterations: usize,
}
impl PassManager {
pub fn new() -> Self {
Self {
passes: Vec::new(),
max_iterations: 10,
}
}
pub fn add_pass<P: OptimizationPass + 'static>(mut self, pass: P) -> Self {
self.passes.push(Box::new(pass));
self
}
pub fn with_max_iterations(mut self, max: usize) -> Self {
self.max_iterations = max;
self
}
pub fn run(&mut self, cfg: &mut Cfg, instructions: &mut Vec<Instruction>) -> OptResult {
let mut total_result = OptResult::no_change();
let mut iteration = 0;
loop {
iteration += 1;
if iteration > self.max_iterations {
break;
}
let mut iteration_result = OptResult::no_change();
for pass in &mut self.passes {
let result = pass.run(cfg, instructions);
iteration_result.merge(result);
}
total_result.merge(iteration_result.clone());
if !iteration_result.changed {
break;
}
}
total_result
}
}
impl Default for PassManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use synth_cfg::CfgBuilder;
use synth_cfg::Loop;
#[test]
fn test_dce_removes_unreachable() {
let mut builder = CfgBuilder::new();
builder.add_instruction();
let reachable = builder.start_block();
builder.add_instruction();
let unreachable = builder.start_block();
builder.add_instruction();
builder.set_current_block(0);
builder.add_branch(reachable);
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Nop,
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Nop,
block_id: reachable,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Nop,
block_id: unreachable,
is_dead: false,
},
];
let mut dce = DeadCodeElimination::new();
let result = dce.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.removed_count, 1);
assert!(instructions[2].is_dead);
assert!(!instructions[0].is_dead);
assert!(!instructions[1].is_dead);
}
#[test]
fn test_dce_keeps_reachable() {
let mut builder = CfgBuilder::new();
builder.add_instruction();
let block1 = builder.start_block();
builder.add_instruction();
builder.set_current_block(0);
builder.add_branch(block1);
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Nop,
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Nop,
block_id: block1,
is_dead: false,
},
];
let mut dce = DeadCodeElimination::new();
let result = dce.run(&mut cfg, &mut instructions);
assert!(!result.changed);
assert_eq!(result.removed_count, 0);
assert!(!instructions[0].is_dead);
assert!(!instructions[1].is_dead);
}
#[test]
fn test_pass_manager() {
let mut builder = CfgBuilder::new();
builder.add_instruction();
let mut cfg = builder.build();
let mut instructions = vec![Instruction {
id: 0,
opcode: Opcode::Nop,
block_id: 0,
is_dead: false,
}];
let mut manager = PassManager::new()
.add_pass(DeadCodeElimination::new())
.add_pass(ConstantFolding::new());
let result = manager.run(&mut cfg, &mut instructions);
assert_eq!(result.removed_count, 0); }
#[test]
fn test_opt_result_merge() {
let mut result1 = OptResult {
changed: true,
removed_count: 5,
added_count: 2,
modified_count: 3,
};
let result2 = OptResult {
changed: false,
removed_count: 1,
added_count: 1,
modified_count: 2,
};
result1.merge(result2);
assert!(result1.changed);
assert_eq!(result1.removed_count, 6);
assert_eq!(result1.added_count, 3);
assert_eq!(result1.modified_count, 5);
}
#[test]
fn test_constant_folding_add() {
let mut builder = CfgBuilder::new();
builder.add_instruction();
builder.add_instruction();
builder.add_instruction();
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 20,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
];
let mut folder = ConstantFolding::new();
let result = folder.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
assert_eq!(
instructions[2].opcode,
Opcode::Const {
dest: Reg(2),
value: 30
}
);
}
#[test]
fn test_constant_folding_multiple_ops() {
let mut builder = CfgBuilder::new();
for _ in 0..6 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 3,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Sub {
dest: Reg(3),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 4,
opcode: Opcode::Mul {
dest: Reg(4),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
];
let mut folder = ConstantFolding::new();
let result = folder.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 3);
assert_eq!(
instructions[2].opcode,
Opcode::Const {
dest: Reg(2),
value: 8
}
); assert_eq!(
instructions[3].opcode,
Opcode::Const {
dest: Reg(3),
value: 2
}
); assert_eq!(
instructions[4].opcode,
Opcode::Const {
dest: Reg(4),
value: 15
}
); }
#[test]
fn test_constant_folding_chained() {
let mut builder = CfgBuilder::new();
for _ in 0..4 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 2,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 3,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Mul {
dest: Reg(3),
src1: Reg(2),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
];
let mut folder = ConstantFolding::new();
let result = folder.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 2); assert_eq!(
instructions[2].opcode,
Opcode::Const {
dest: Reg(2),
value: 5
}
); assert_eq!(
instructions[3].opcode,
Opcode::Const {
dest: Reg(3),
value: 10
}
); }
#[test]
fn test_constant_folding_no_change() {
let mut builder = CfgBuilder::new();
builder.add_instruction();
let mut cfg = builder.build();
let mut instructions = vec![Instruction {
id: 0,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
}];
let mut folder = ConstantFolding::new();
let result = folder.run(&mut cfg, &mut instructions);
assert!(!result.changed);
assert_eq!(result.modified_count, 0);
}
#[test]
fn test_cse_simple() {
let mut builder = CfgBuilder::new();
for _ in 0..3 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Add {
dest: Reg(3),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
];
let mut cse = CommonSubexpressionElimination::new();
let result = cse.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.removed_count, 1);
assert!(instructions[1].is_dead);
assert!(!instructions[0].is_dead);
}
#[test]
fn test_cse_multiple_ops() {
let mut builder = CfgBuilder::new();
for _ in 0..6 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Add {
dest: Reg(4),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Add {
dest: Reg(5),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Sub {
dest: Reg(6),
src1: Reg(2),
src2: Reg(3),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Sub {
dest: Reg(7),
src1: Reg(2),
src2: Reg(3),
},
block_id: 0,
is_dead: false,
},
];
let mut cse = CommonSubexpressionElimination::new();
let result = cse.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.removed_count, 2);
assert!(instructions[1].is_dead); assert!(instructions[3].is_dead); assert!(!instructions[0].is_dead);
assert!(!instructions[2].is_dead);
}
#[test]
fn test_cse_load() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Load {
dest: Reg(0),
addr: 0x100,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Load {
dest: Reg(1),
addr: 0x100,
},
block_id: 0,
is_dead: false,
},
];
let mut cse = CommonSubexpressionElimination::new();
let result = cse.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.removed_count, 1);
assert!(instructions[1].is_dead);
assert!(!instructions[0].is_dead);
}
#[test]
fn test_cse_store_invalidates_load() {
let mut builder = CfgBuilder::new();
for _ in 0..3 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Load {
dest: Reg(0),
addr: 0x100,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Store {
src: Reg(2),
addr: 0x100,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Load {
dest: Reg(1),
addr: 0x100,
},
block_id: 0,
is_dead: false,
},
];
let mut cse = CommonSubexpressionElimination::new();
let result = cse.run(&mut cfg, &mut instructions);
assert!(!result.changed);
assert_eq!(result.removed_count, 0);
assert!(!instructions[0].is_dead);
assert!(!instructions[1].is_dead);
assert!(!instructions[2].is_dead);
}
#[test]
fn test_cse_no_duplicates() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Sub {
dest: Reg(3),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
];
let mut cse = CommonSubexpressionElimination::new();
let result = cse.run(&mut cfg, &mut instructions);
assert!(!result.changed);
assert_eq!(result.removed_count, 0);
}
#[test]
fn test_algebraic_add_zero() {
let mut builder = CfgBuilder::new();
for _ in 0..3 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 0,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
];
let mut simplify = AlgebraicSimplification::new();
let result = simplify.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
assert!(instructions[1].is_dead);
}
#[test]
fn test_algebraic_sub_zero() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 0,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Sub {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
];
let mut simplify = AlgebraicSimplification::new();
let result = simplify.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
assert!(instructions[1].is_dead);
}
#[test]
fn test_algebraic_sub_self() {
let mut builder = CfgBuilder::new();
builder.add_instruction();
let mut cfg = builder.build();
let mut instructions = vec![Instruction {
id: 0,
opcode: Opcode::Sub {
dest: Reg(2),
src1: Reg(1),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
}];
let mut simplify = AlgebraicSimplification::new();
let result = simplify.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
assert_eq!(
instructions[0].opcode,
Opcode::Const {
dest: Reg(2),
value: 0
}
);
}
#[test]
fn test_algebraic_mul_zero() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 0,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Mul {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
];
let mut simplify = AlgebraicSimplification::new();
let result = simplify.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
assert_eq!(
instructions[1].opcode,
Opcode::Const {
dest: Reg(2),
value: 0
}
);
}
#[test]
fn test_algebraic_mul_one() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 1,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Mul {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
];
let mut simplify = AlgebraicSimplification::new();
let result = simplify.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
assert!(instructions[1].is_dead);
}
#[test]
fn test_algebraic_multiple() {
let mut builder = CfgBuilder::new();
for _ in 0..5 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 0,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 1,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Add {
dest: Reg(5),
src1: Reg(2),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Mul {
dest: Reg(6),
src1: Reg(3),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 4,
opcode: Opcode::Sub {
dest: Reg(7),
src1: Reg(4),
src2: Reg(4),
},
block_id: 0,
is_dead: false,
},
];
let mut simplify = AlgebraicSimplification::new();
let result = simplify.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 3);
assert!(instructions[2].is_dead); assert!(instructions[3].is_dead); assert_eq!(
instructions[4].opcode,
Opcode::Const {
dest: Reg(7),
value: 0
}
); }
#[test]
fn test_peephole_redundant_const() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: 0,
is_dead: false,
},
];
let mut peephole = PeepholeOptimization::new();
let result = peephole.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.removed_count, 1);
assert!(instructions[0].is_dead);
assert!(!instructions[1].is_dead);
}
#[test]
fn test_peephole_no_redundant_const() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 10,
},
block_id: 0,
is_dead: false,
},
];
let mut peephole = PeepholeOptimization::new();
let result = peephole.run(&mut cfg, &mut instructions);
assert!(!result.changed);
assert_eq!(result.removed_count, 0);
}
#[test]
fn test_full_optimization_pipeline() {
let mut builder = CfgBuilder::new();
for _ in 0..10 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Const {
dest: Reg(1),
value: 20,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 4,
opcode: Opcode::Const {
dest: Reg(3),
value: 0,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 5,
opcode: Opcode::Add {
dest: Reg(4),
src1: Reg(2),
src2: Reg(3),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 6,
opcode: Opcode::Add {
dest: Reg(5),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
];
let mut manager = PassManager::new()
.add_pass(PeepholeOptimization::new())
.add_pass(ConstantFolding::new())
.add_pass(AlgebraicSimplification::new())
.add_pass(CommonSubexpressionElimination::new())
.with_max_iterations(5);
let result = manager.run(&mut cfg, &mut instructions);
assert!(result.changed);
let total_opts = result.removed_count + result.modified_count;
assert!(
total_opts >= 2,
"Expected at least 2 optimizations, got {}",
total_opts
);
assert!(instructions[0].is_dead, "Redundant const not eliminated");
if let Opcode::Const { value, .. } = instructions[3].opcode {
assert_eq!(value, 30, "Constant folding failed");
} else {
panic!("Expected const, got {:?}", instructions[3].opcode);
}
}
#[test]
fn test_strength_reduction_mul_power_of_2() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 8,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Mul {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
];
let mut sr = StrengthReduction::new();
let result = sr.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
}
#[test]
fn test_strength_reduction_mul_non_power_of_2() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 7,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Mul {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
];
let mut sr = StrengthReduction::new();
let result = sr.run(&mut cfg, &mut instructions);
assert!(!result.changed);
assert_eq!(result.modified_count, 0);
}
#[test]
fn test_strength_reduction_multiple_powers() {
let mut builder = CfgBuilder::new();
for _ in 0..6 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 4,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Mul {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Const {
dest: Reg(3),
value: 16,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Mul {
dest: Reg(5),
src1: Reg(4),
src2: Reg(3),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 4,
opcode: Opcode::Const {
dest: Reg(6),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 5,
opcode: Opcode::Mul {
dest: Reg(8),
src1: Reg(7),
src2: Reg(6),
},
block_id: 0,
is_dead: false,
},
];
let mut sr = StrengthReduction::new();
let result = sr.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 2);
}
#[test]
fn test_licm_detect_invariants() {
let mut builder = CfgBuilder::new();
let block0 = 0; let block1 = builder.start_block();
let block2 = builder.start_block();
builder.set_current_block(block0);
builder.add_branch(block1);
builder.set_current_block(block1);
builder.add_branch(block2);
builder.set_current_block(block2);
builder.add_branch(block1);
builder.set_current_block(block1);
builder.add_branch(block0);
let mut cfg = builder.build();
cfg.loops.push(Loop {
header: block1,
body: vec![block1, block2].into_iter().collect(),
depth: 1,
});
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: block1,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: block1,
is_dead: false,
},
];
let mut licm = LoopInvariantCodeMotion::new();
let result = licm.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
}
#[test]
fn test_licm_no_loops() {
let mut builder = CfgBuilder::new();
builder.add_instruction();
let mut cfg = builder.build();
let mut instructions = vec![Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: 0,
is_dead: false,
}];
let mut licm = LoopInvariantCodeMotion::new();
let result = licm.run(&mut cfg, &mut instructions);
assert!(!result.changed);
assert_eq!(result.modified_count, 0);
}
#[test]
fn test_pass_manager_with_advanced_passes() {
let mut builder = CfgBuilder::new();
for _ in 0..4 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 8,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Mul {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Add {
dest: Reg(3),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
];
let mut manager = PassManager::new()
.add_pass(StrengthReduction::new())
.add_pass(ConstantFolding::new())
.with_max_iterations(3);
let result = manager.run(&mut cfg, &mut instructions);
assert!(result.changed);
let total_opts = result.removed_count + result.modified_count;
assert!(total_opts >= 1);
}
#[test]
fn test_copy_propagation_basic() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
];
let mut cp = CopyPropagation::new();
let result = cp.run(&mut cfg, &mut instructions);
assert!(!result.changed);
}
#[test]
fn test_copy_propagation_with_store() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Store {
src: Reg(0),
addr: 100,
},
block_id: 0,
is_dead: false,
},
];
let mut cp = CopyPropagation::new();
let result = cp.run(&mut cfg, &mut instructions);
assert!(!result.changed);
}
#[test]
fn test_instruction_combining_nested_add() {
let mut builder = CfgBuilder::new();
for _ in 0..4 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(1),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Add {
dest: Reg(2),
src1: Reg(0),
src2: Reg(1),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Const {
dest: Reg(3),
value: 10,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Add {
dest: Reg(4),
src1: Reg(2),
src2: Reg(3),
},
block_id: 0,
is_dead: false,
},
];
let mut ic = InstructionCombining::new();
let result = ic.run(&mut cfg, &mut instructions);
assert!(result.changed);
assert_eq!(result.modified_count, 1);
}
#[test]
fn test_instruction_combining_no_pattern() {
let mut builder = CfgBuilder::new();
for _ in 0..2 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 10,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 20,
},
block_id: 0,
is_dead: false,
},
];
let mut ic = InstructionCombining::new();
let result = ic.run(&mut cfg, &mut instructions);
assert!(!result.changed);
}
#[test]
fn test_all_passes_together() {
let mut builder = CfgBuilder::new();
for _ in 0..10 {
builder.add_instruction();
}
let mut cfg = builder.build();
let mut instructions = vec![
Instruction {
id: 0,
opcode: Opcode::Const {
dest: Reg(0),
value: 8,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 1,
opcode: Opcode::Const {
dest: Reg(1),
value: 5,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 2,
opcode: Opcode::Mul {
dest: Reg(2),
src1: Reg(1),
src2: Reg(0),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 3,
opcode: Opcode::Const {
dest: Reg(3),
value: 10,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 4,
opcode: Opcode::Const {
dest: Reg(4),
value: 20,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 5,
opcode: Opcode::Add {
dest: Reg(5),
src1: Reg(3),
src2: Reg(4),
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 6,
opcode: Opcode::Const {
dest: Reg(6),
value: 0,
},
block_id: 0,
is_dead: false,
},
Instruction {
id: 7,
opcode: Opcode::Add {
dest: Reg(7),
src1: Reg(2),
src2: Reg(6),
},
block_id: 0,
is_dead: false,
},
];
let mut manager = PassManager::new()
.add_pass(PeepholeOptimization::new())
.add_pass(ConstantFolding::new())
.add_pass(AlgebraicSimplification::new())
.add_pass(StrengthReduction::new())
.add_pass(CopyPropagation::new())
.add_pass(InstructionCombining::new())
.add_pass(CommonSubexpressionElimination::new())
.add_pass(DeadCodeElimination::new())
.with_max_iterations(5);
let result = manager.run(&mut cfg, &mut instructions);
assert!(result.changed);
let total_opts = result.removed_count + result.modified_count + result.added_count;
assert!(
total_opts >= 3,
"Expected at least 3 optimizations, got {}",
total_opts
);
}
}