use llvm_native_core::constants;
use llvm_native_core::instruction;
use llvm_native_core::opcode::Opcode;
use llvm_native_core::types::Type;
use llvm_native_core::value::{SubclassKind, ValueRef};
use std::collections::{HashMap, HashSet};
pub struct ReassociatePass {
pub reassociated: usize,
}
impl ReassociatePass {
pub fn new() -> Self {
Self { reassociated: 0 }
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.reassociated = 0;
let f = func.borrow();
for bb in &f.operands {
if bb.borrow().subclass == SubclassKind::BasicBlock {
self.reassociated += self.run_on_basic_block(bb);
}
}
self.reassociated
}
pub fn run_on_basic_block(&mut self, bb: &ValueRef) -> usize {
let mut count = 0;
let mut builder = IRBuilderStub::new();
let insts = get_block_instructions(bb);
for inst in &insts {
if let Some(replacement) = self.reassociate_expression(inst, &mut builder) {
inst.borrow_mut().replace_all_uses_with(&replacement);
count += 1;
}
}
count
}
pub fn reassociate_expression(
&self,
inst: &ValueRef,
builder: &mut IRBuilderStub,
) -> Option<ValueRef> {
let ib = inst.borrow();
let opcode = ib.opcode?;
if !is_associative_commutative(opcode) {
return None;
}
if ib.operands.len() != 2 {
return None;
}
let ty = ib.ty.clone();
let mut operands = match opcode {
Opcode::Add | Opcode::FAdd => self.flatten_add_chain(inst),
Opcode::Mul | Opcode::FMul => self.flatten_mul_chain(inst),
Opcode::And => self.flatten_bitwise_chain(inst, Opcode::And),
Opcode::Or => self.flatten_bitwise_chain(inst, Opcode::Or),
Opcode::Xor => self.flatten_bitwise_chain(inst, Opcode::Xor),
_ => return None,
};
if operands.len() <= 1 {
return None; }
let const_folded = fold_constants(&mut operands);
if const_folded == 0 && operands.len() <= 1 {
return None; }
sort_by_rank(&mut operands);
factor_repeated_operands(&mut operands, opcode, &ty);
if operands.is_empty() {
return None;
}
let result = rebuild_expression(opcode, operands, builder, &ty);
Some(result)
}
pub fn flatten_add_chain(&self, inst: &ValueRef) -> Vec<ValueRef> {
let mut operands = Vec::new();
let mut worklist = vec![inst.clone()];
while let Some(current) = worklist.pop() {
let is_add = {
let cb = current.borrow();
let result = cb.opcode == Some(Opcode::Add) && cb.operands.len() == 2;
if result {
worklist.push(cb.operands[0].clone());
worklist.push(cb.operands[1].clone());
}
result
};
if !is_add {
operands.push(current);
}
}
operands.reverse(); operands
}
pub fn flatten_mul_chain(&self, inst: &ValueRef) -> Vec<ValueRef> {
let mut operands = Vec::new();
let mut worklist = vec![inst.clone()];
while let Some(current) = worklist.pop() {
let is_mul = {
let cb = current.borrow();
let result = cb.opcode == Some(Opcode::Mul) && cb.operands.len() == 2;
if result {
worklist.push(cb.operands[0].clone());
worklist.push(cb.operands[1].clone());
}
result
};
if !is_mul {
operands.push(current);
}
}
operands.reverse();
operands
}
fn flatten_bitwise_chain(&self, inst: &ValueRef, target_op: Opcode) -> Vec<ValueRef> {
let mut operands = Vec::new();
let mut worklist = vec![inst.clone()];
while let Some(current) = worklist.pop() {
let is_match = {
let cb = current.borrow();
let result = cb.opcode == Some(target_op) && cb.operands.len() == 2;
if result {
worklist.push(cb.operands[0].clone());
worklist.push(cb.operands[1].clone());
}
result
};
if !is_match {
operands.push(current);
}
}
operands.reverse();
operands
}
pub fn optimize_expression(
&self,
inst: &ValueRef,
builder: &mut IRBuilderStub,
) -> Option<ValueRef> {
if let Some(result) = self.reassociate_expression(inst, builder) {
return Some(result);
}
let ib = inst.borrow();
if ib.opcode == Some(Opcode::Add) && ib.operands.len() == 2 {
if let Some(result) = try_distribute(&ib.operands[0], &ib.operands[1], builder) {
return Some(result);
}
}
None
}
}
impl Default for ReassociatePass {
fn default() -> Self {
Self::new()
}
}
pub fn rank_value(val: &ValueRef) -> u64 {
let vb = val.borrow();
match vb.subclass {
SubclassKind::Constant => rank_constant(val),
SubclassKind::Argument => rank_argument(val),
SubclassKind::Instruction => rank_instruction(val),
_ => vb.vid,
}
}
fn rank_constant(val: &ValueRef) -> u64 {
let vb = val.borrow();
if let Ok(v) = vb.name.parse::<i64>() {
if v >= 0 {
v as u64
} else {
v.wrapping_neg() as u64
}
} else {
vb.vid
}
}
fn rank_argument(val: &ValueRef) -> u64 {
let vb = val.borrow();
let arg_num = vb
.name
.chars()
.skip_while(|c| !c.is_ascii_digit())
.collect::<String>()
.parse::<u64>()
.unwrap_or(0);
1_000_000_000 + arg_num
}
fn rank_instruction(val: &ValueRef) -> u64 {
let vb = val.borrow();
let opcode_val = vb.opcode.map(|o| o as u64).unwrap_or(0);
let mut hash: u64 = opcode_val.wrapping_mul(0x9e3779b9);
for op in &vb.operands {
let op_rank = rank_value(op);
hash = hash.wrapping_add(op_rank).wrapping_mul(0x9e3779b9);
hash = hash.rotate_left(7);
}
2_000_000_000 + (hash & 0x7FFFFFFF)
}
pub fn fold_constants(operands: &mut Vec<ValueRef>) -> usize {
let mut folded = 0;
let mut const_indices: Vec<usize> = Vec::new();
for (i, op) in operands.iter().enumerate() {
if op.borrow().subclass == SubclassKind::Constant {
const_indices.push(i);
}
}
if const_indices.len() < 2 {
return 0;
}
let first_const = operands[const_indices[0]].clone();
let ty = first_const.borrow().ty.clone();
let mut combined = first_const;
for &idx in &const_indices[1..] {
let c = &operands[idx];
if let Some(merged) = try_combine_constants(&combined, c) {
combined = merged;
folded += 1;
}
}
let mut removed = 0;
operands.retain(|op| {
let is_const = op.borrow().subclass == SubclassKind::Constant;
if is_const {
removed += 1;
}
!is_const
});
if removed > 0 {
operands.push(combined);
}
folded
}
fn try_combine_constants(a: &ValueRef, b: &ValueRef) -> Option<ValueRef> {
let a_name = &a.borrow().name;
let b_name = &b.borrow().name;
let a_val = a_name.parse::<i64>().ok()?;
let b_val = b_name.parse::<i64>().ok()?;
let ty = a.borrow().ty.clone();
let sum = a_val.wrapping_add(b_val);
Some(constants::const_int(ty, sum))
}
fn sort_by_rank(operands: &mut [ValueRef]) {
operands.sort_by_key(|op| rank_value(op));
}
fn factor_repeated_operands(operands: &mut Vec<ValueRef>, opcode: Opcode, ty: &Type) {
let mut counts: HashMap<u64, usize> = HashMap::new();
for op in operands.iter() {
*counts.entry(op.borrow().vid).or_insert(0) += 1;
}
let mut new_operands: Vec<ValueRef> = Vec::new();
let mut handled: HashSet<u64> = HashSet::new();
for op in operands.iter() {
let vid = op.borrow().vid;
if handled.contains(&vid) {
continue;
}
handled.insert(vid);
let count = counts[&vid];
if count == 1 {
new_operands.push(op.clone());
} else if count == 2 && opcode == Opcode::Add {
let two = constants::const_int(ty.clone(), 2);
let mul_inst = instruction::mul(op.clone(), two);
new_operands.push(mul_inst);
} else if count > 1 && opcode == Opcode::Add {
let n = constants::const_int(ty.clone(), count as i64);
let mul_inst = instruction::mul(op.clone(), n);
new_operands.push(mul_inst);
} else {
for _ in 0..count {
new_operands.push(op.clone());
}
}
}
*operands = new_operands;
}
fn try_distribute(
lhs: &ValueRef,
rhs: &ValueRef,
_builder: &mut IRBuilderStub,
) -> Option<ValueRef> {
let lhs_b = lhs.borrow();
let rhs_b = rhs.borrow();
if lhs_b.opcode != Some(Opcode::Mul) || rhs_b.opcode != Some(Opcode::Mul) {
return None;
}
if lhs_b.operands.len() != 2 || rhs_b.operands.len() != 2 {
return None;
}
let lhs_left = &lhs_b.operands[0];
let lhs_right = &lhs_b.operands[1];
let rhs_left = &rhs_b.operands[0];
let rhs_right = &rhs_b.operands[1];
let (x1, c1_val) = if lhs_left.borrow().subclass == SubclassKind::Constant {
(lhs_right, lhs_left)
} else if lhs_right.borrow().subclass == SubclassKind::Constant {
(lhs_left, lhs_right)
} else {
return None;
};
let (x2, c2_val) = if rhs_left.borrow().vid == x1.borrow().vid {
if rhs_right.borrow().subclass == SubclassKind::Constant {
(rhs_left, rhs_right)
} else {
return None;
}
} else if rhs_right.borrow().vid == x1.borrow().vid {
if rhs_left.borrow().subclass == SubclassKind::Constant {
(rhs_right, rhs_left)
} else {
return None;
}
} else {
return None;
};
if x1.borrow().vid != x2.borrow().vid {
return None;
}
let combined = try_combine_constants(c1_val, c2_val)?;
let ty = x1.borrow().ty.clone();
Some(instruction::mul(x1.clone(), combined))
}
pub fn rebuild_expression(
opcode: Opcode,
operands: Vec<ValueRef>,
_builder: &mut IRBuilderStub,
_ty: &Type,
) -> ValueRef {
if operands.is_empty() {
panic!("Cannot rebuild empty expression");
}
if operands.len() == 1 {
return operands[0].clone();
}
let mut result = operands[0].clone();
for op in &operands[1..] {
result = match opcode {
Opcode::Add | Opcode::FAdd => instruction::add(result, op.clone()),
Opcode::Mul | Opcode::FMul => instruction::mul(result, op.clone()),
Opcode::And => instruction::and(result, op.clone()),
Opcode::Or => instruction::or(result, op.clone()),
Opcode::Xor => instruction::xor(result, op.clone()),
_ => {
return result;
}
};
}
result
}
pub struct IRBuilderStub {
_counter: u64,
}
impl IRBuilderStub {
pub fn new() -> Self {
Self { _counter: 0 }
}
}
impl Default for IRBuilderStub {
fn default() -> Self {
Self::new()
}
}
fn is_associative_commutative(op: Opcode) -> bool {
matches!(
op,
Opcode::Add
| Opcode::FAdd
| Opcode::Mul
| Opcode::FMul
| Opcode::And
| Opcode::Or
| Opcode::Xor
)
}
fn get_block_instructions(bb: &ValueRef) -> Vec<ValueRef> {
let b = bb.borrow();
b.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::Instruction)
.cloned()
.collect()
}
#[derive(Debug, Clone)]
pub struct FactorizedExpr {
pub factor: Option<ValueRef>,
pub terms: Vec<ValueRef>,
pub changed: bool,
}
pub fn factorize_common_subexpr(
operands: &[ValueRef],
_opcode: llvm_native_core::opcode::Opcode,
) -> Option<FactorizedExpr> {
if operands.len() < 2 {
return None;
}
let mut freq: HashMap<u64, (ValueRef, usize)> = HashMap::new();
for op in operands {
let o = op.borrow();
if o.is_instruction() && !o.operands.is_empty() {
for sub_op in &o.operands {
let svid = sub_op.borrow().vid;
let entry = freq.entry(svid).or_insert_with(|| (sub_op.clone(), 0));
entry.1 += 1;
}
}
let vid = o.vid;
let entry = freq.entry(vid).or_insert_with(|| (op.clone(), 0));
entry.1 += 1;
}
for (_vid, (val, count)) in &freq {
if *count >= operands.len() {
return Some(FactorizedExpr {
factor: Some(val.clone()),
terms: operands.to_vec(),
changed: true,
});
}
}
None
}
pub fn apply_factorization(operands: &[ValueRef], factor: &ValueRef) -> Vec<ValueRef> {
let mut result: Vec<ValueRef> = Vec::new();
result.push(factor.clone());
for op in operands {
if op.borrow().vid != factor.borrow().vid {
result.push(op.clone());
}
}
result
}
#[derive(Debug, Clone)]
pub struct BalanceConfig {
pub max_depth: usize,
pub balance_add: bool,
pub balance_mul: bool,
pub balance_logic: bool,
pub prefer_balanced: bool,
}
impl Default for BalanceConfig {
fn default() -> Self {
Self {
max_depth: 4,
balance_add: true,
balance_mul: true,
balance_logic: false,
prefer_balanced: true,
}
}
}
pub struct ExpressionBalancer {
config: BalanceConfig,
pub balanced: usize,
pub skipped: usize,
}
impl ExpressionBalancer {
pub fn new(config: BalanceConfig) -> Self {
Self {
config,
balanced: 0,
skipped: 0,
}
}
pub fn balance_operands(&mut self, operands: &[ValueRef]) -> Vec<ValueRef> {
if operands.len() <= 2 || !self.config.prefer_balanced {
self.skipped += 1;
return operands.to_vec();
}
let mut result: Vec<ValueRef> = Vec::new();
let mut i = 0;
while i < operands.len() {
if i + 1 < operands.len() {
result.push(operands[i].clone());
result.push(operands[i + 1].clone());
i += 2;
} else {
result.push(operands[i].clone());
i += 1;
}
}
self.balanced += 1;
result
}
pub fn rebalance_to_depth(&self, _operands: &[ValueRef], _depth: usize) -> Vec<ValueRef> {
_operands.to_vec()
}
pub fn stats(&self) -> (usize, usize) {
(self.balanced, self.skipped)
}
}
pub struct GEPReassociator {
pub restructured: usize,
pub eliminated: usize,
}
impl GEPReassociator {
pub fn new() -> Self {
Self {
restructured: 0,
eliminated: 0,
}
}
pub fn try_reassociate_gep(&mut self, gep: &ValueRef) -> Option<ValueRef> {
let g = gep.borrow();
let name = g.name.to_lowercase();
if !name.contains("gep") && !name.contains("getelementptr") {
return None;
}
if g.operands.len() < 2 {
return None;
}
let base = &g.operands[0];
let base_name = base.borrow().name.to_lowercase();
if base_name.contains("gep") || base_name.contains("getelementptr") {
self.restructured += 1;
return Some(base.clone());
}
None
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
let f = func.borrow();
let mut gep_count = 0usize;
for op in &f.operands {
let bb = op.borrow();
if bb.subclass != SubclassKind::BasicBlock {
continue;
}
for inst in &bb.operands {
let _ = self.try_reassociate_gep(inst);
gep_count += 1;
}
}
gep_count
}
}
impl Default for GEPReassociator {
fn default() -> Self {
Self::new()
}
}
pub struct FCmpReassociator {
pub fast_math: bool,
pub simplified: usize,
}
impl FCmpReassociator {
pub fn new(fast_math: bool) -> Self {
Self {
fast_math,
simplified: 0,
}
}
pub fn try_simplify_conjunction(&mut self, cmp1: &ValueRef, cmp2: &ValueRef) -> bool {
if !self.fast_math {
return false;
}
let c1 = cmp1.borrow();
let c2 = cmp2.borrow();
let name1 = c1.name.to_lowercase();
let name2 = c2.name.to_lowercase();
if !name1.contains("fcmp") || !name2.contains("fcmp") {
return false;
}
if c1.operands.len() >= 2 && c2.operands.len() >= 2 {
let a1_vid = c1.operands[0].borrow().vid;
let a2_vid = c2.operands[0].borrow().vid;
let b1_vid = c1.operands[1].borrow().vid;
let b2_vid = c2.operands[1].borrow().vid;
if a1_vid == a2_vid || a1_vid == b2_vid || b1_vid == a2_vid || b1_vid == b2_vid {
self.simplified += 1;
return true;
}
}
false
}
pub fn try_simplify_disjunction(&mut self, cmp1: &ValueRef, cmp2: &ValueRef) -> bool {
self.try_simplify_conjunction(cmp1, cmp2)
}
}
pub struct ExtendedReassociatePass {
pub balance_config: BalanceConfig,
pub gep_reassoc: GEPReassociator,
pub fcmp_reassoc: FCmpReassociator,
pub factorize: bool,
pub reassociated: usize,
pub analyzed: usize,
}
impl ExtendedReassociatePass {
pub fn new() -> Self {
Self {
balance_config: BalanceConfig::default(),
gep_reassoc: GEPReassociator::new(),
fcmp_reassoc: FCmpReassociator::new(true),
factorize: true,
reassociated: 0,
analyzed: 0,
}
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
let f = func.borrow();
let mut worklist: Vec<ValueRef> = Vec::new();
for op in &f.operands {
let bb = op.borrow();
if bb.subclass != SubclassKind::BasicBlock {
continue;
}
for inst in &bb.operands {
if inst.borrow().is_instruction() {
worklist.push(inst.clone());
}
}
}
drop(f);
let mut total = 0usize;
while let Some(inst) = worklist.pop() {
self.analyzed += 1;
let i = inst.borrow();
let name = i.name.to_lowercase();
let is_add = name.contains("add") || name.contains("fadd");
let is_mul = name.contains("mul") || name.contains("fmul");
let is_and = name.contains("and");
let is_or = name.contains("or");
if is_add || is_mul || is_and || is_or {
if i.operands.len() >= 2 {
if self.factorize {
let opcode = if is_add {
llvm_native_core::opcode::Opcode::Add
} else if is_mul {
llvm_native_core::opcode::Opcode::Mul
} else if is_and {
llvm_native_core::opcode::Opcode::And
} else {
llvm_native_core::opcode::Opcode::Or
};
if let Some(_factored) = factorize_common_subexpr(&i.operands, opcode) {
total += 1;
self.reassociated += 1;
}
}
if (is_add && self.balance_config.balance_add)
|| (is_mul && self.balance_config.balance_mul)
|| ((is_and || is_or) && self.balance_config.balance_logic)
{
let mut balancer = ExpressionBalancer::new(self.balance_config.clone());
let _balanced = balancer.balance_operands(&i.operands);
if balancer.balanced > 0 {
total += 1;
}
}
}
}
if name.contains("gep") {
let _ = self.gep_reassoc.try_reassociate_gep(&inst);
}
if name.contains("fcmp") && i.operands.len() >= 2 {
}
}
total
}
pub fn summary(&self) -> String {
format!(
"Reassociate: analyzed={} reassociated={} gep_restructured={} fcmp_simplified={}",
self.analyzed,
self.reassociated,
self.gep_reassoc.restructured,
self.fcmp_reassoc.simplified,
)
}
}
impl Default for ExtendedReassociatePass {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct OperandRank {
pub complexity: u32,
pub neg_loop_depth: u32,
pub use_count: u32,
pub vid: u64,
}
impl OperandRank {
pub fn compute(val: &ValueRef) -> Self {
let v = val.borrow();
let complexity = if v.is_constant() {
0
} else if v.is_argument() {
1
} else {
2
};
let neg_loop_depth = 0u32; let use_count = v.uses.len() as u32;
let vid = v.vid;
Self {
complexity,
neg_loop_depth,
use_count,
vid,
}
}
}
pub fn rank_operands(operands: &[ValueRef]) -> Vec<ValueRef> {
let mut ranked: Vec<(OperandRank, ValueRef)> = operands
.iter()
.map(|op| (OperandRank::compute(op), op.clone()))
.collect();
ranked.sort_by(|a, b| a.0.cmp(&b.0));
ranked.into_iter().map(|(_, op)| op).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::basic_block::new_basic_block;
use llvm_native_core::constants::const_i32;
use llvm_native_core::function::new_function;
use llvm_native_core::instruction;
use llvm_native_core::types::Type;
fn build_add_chain_func() -> ValueRef {
let func = new_function("add_chain", Type::i32(), &[]);
let entry = new_basic_block("entry");
let c10 = const_i32(10);
let c20 = const_i32(20);
let c30 = const_i32(30);
let add1 = instruction::add(c10, c20);
add1.borrow_mut().name = "add1".to_string();
let add2 = instruction::add(add1, c30);
add2.borrow_mut().name = "add2".to_string();
entry.borrow_mut().push_operand(add2);
entry
.borrow_mut()
.push_operand(instruction::ret_val(const_i32(0)));
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_mul_chain_func() -> ValueRef {
let func = new_function("mul_chain", Type::i32(), &[]);
let entry = new_basic_block("entry");
let c2 = const_i32(2);
let c3 = const_i32(3);
let c4 = const_i32(4);
let mul1 = instruction::mul(c2, c3);
mul1.borrow_mut().name = "mul1".to_string();
let mul2 = instruction::mul(mul1, c4);
mul2.borrow_mut().name = "mul2".to_string();
entry.borrow_mut().push_operand(mul2);
entry
.borrow_mut()
.push_operand(instruction::ret_val(const_i32(0)));
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_mixed_chain_func() -> ValueRef {
let func = new_function("mixed_chain", Type::i32(), &[]);
let entry = new_basic_block("entry");
let arg0 = llvm_native_core::function::new_argument("arg0", Type::i32());
let c5 = const_i32(5);
let c10 = const_i32(10);
let add1 = instruction::add(arg0, c5);
add1.borrow_mut().name = "add1".to_string();
let add2 = instruction::add(add1, c10);
add2.borrow_mut().name = "add2".to_string();
entry.borrow_mut().push_operand(add2);
entry
.borrow_mut()
.push_operand(instruction::ret_val(const_i32(0)));
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_and_chain_func() -> ValueRef {
let func = new_function("and_chain", Type::i32(), &[]);
let entry = new_basic_block("entry");
let c7 = const_i32(7); let c3 = const_i32(3); let c1 = const_i32(1);
let and1 = instruction::and(c7, c3);
and1.borrow_mut().name = "and1".to_string();
let and2 = instruction::and(and1, c1);
and2.borrow_mut().name = "and2".to_string();
entry.borrow_mut().push_operand(and2);
entry
.borrow_mut()
.push_operand(instruction::ret_val(const_i32(0)));
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_or_chain_func() -> ValueRef {
let func = new_function("or_chain", Type::i32(), &[]);
let entry = new_basic_block("entry");
let c1 = const_i32(1);
let c2 = const_i32(2);
let c4 = const_i32(4);
let or1 = instruction::or(c1, c2);
or1.borrow_mut().name = "or1".to_string();
let or2 = instruction::or(or1, c4);
or2.borrow_mut().name = "or2".to_string();
entry.borrow_mut().push_operand(or2);
entry
.borrow_mut()
.push_operand(instruction::ret_val(const_i32(0)));
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_repeated_operand_func() -> ValueRef {
let func = new_function("repeat_op", Type::i32(), &[]);
let entry = new_basic_block("entry");
let arg0 = llvm_native_core::function::new_argument("arg0", Type::i32());
let arg1 = llvm_native_core::function::new_argument("arg1", Type::i32());
let add1 = instruction::add(arg0.clone(), arg1.clone());
add1.borrow_mut().name = "add1".to_string();
let add2 = instruction::add(add1, arg0);
add2.borrow_mut().name = "add2".to_string();
entry.borrow_mut().push_operand(add2);
entry
.borrow_mut()
.push_operand(instruction::ret_val(const_i32(0)));
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_xor_chain_func() -> ValueRef {
let func = new_function("xor_chain", Type::i32(), &[]);
let entry = new_basic_block("entry");
let c1 = const_i32(1);
let c2 = const_i32(2);
let c3 = const_i32(3);
let xor1 = instruction::xor(c1, c2);
xor1.borrow_mut().name = "xor1".to_string();
let xor2 = instruction::xor(xor1, c3);
xor2.borrow_mut().name = "xor2".to_string();
entry.borrow_mut().push_operand(xor2);
entry
.borrow_mut()
.push_operand(instruction::ret_val(const_i32(0)));
func.borrow_mut().push_operand(entry.clone());
func
}
#[test]
fn test_reassociate_pass_create() {
let pass = ReassociatePass::new();
assert_eq!(pass.reassociated, 0);
}
#[test]
fn test_reassociate_pass_default() {
let pass = ReassociatePass::default();
assert_eq!(pass.reassociated, 0);
}
#[test]
fn test_is_associative_commutative() {
assert!(is_associative_commutative(Opcode::Add));
assert!(is_associative_commutative(Opcode::Mul));
assert!(is_associative_commutative(Opcode::And));
assert!(is_associative_commutative(Opcode::Or));
assert!(is_associative_commutative(Opcode::Xor));
assert!(is_associative_commutative(Opcode::FAdd));
assert!(is_associative_commutative(Opcode::FMul));
assert!(!is_associative_commutative(Opcode::Sub));
assert!(!is_associative_commutative(Opcode::SDiv));
assert!(!is_associative_commutative(Opcode::Shl));
}
#[test]
fn test_rank_constant() {
let c42 = const_i32(42);
let rank = rank_value(&c42);
assert_eq!(rank, 42);
let c0 = const_i32(0);
assert_eq!(rank_value(&c0), 0);
let c_neg = const_i32(-5);
let rank_neg = rank_value(&c_neg);
assert_eq!(rank_neg, 5);
}
#[test]
fn test_rank_argument() {
let arg = llvm_native_core::function::new_argument("arg3", Type::i32());
let rank = rank_value(&arg);
assert!(rank >= 1_000_000_000);
assert!(rank < 2_000_000_000);
}
#[test]
fn test_rank_instruction() {
let a = const_i32(10);
let b = const_i32(20);
let add = instruction::add(a, b);
let rank = rank_value(&add);
assert!(rank >= 2_000_000_000);
}
#[test]
fn test_flatten_add_chain() {
let func = build_add_chain_func();
let f = func.borrow();
let entry = &f.operands[0];
let insts = get_block_instructions(entry);
let add2 = insts
.iter()
.find(|i| i.borrow().name == "add2")
.expect("add2 not found");
let pass = ReassociatePass::new();
let flat = pass.flatten_add_chain(add2);
assert_eq!(flat.len(), 3, "Expected 3 operands, got {}", flat.len());
let names: Vec<String> = flat.iter().map(|v| v.borrow().name.clone()).collect();
assert!(names.contains(&"10".to_string()));
assert!(names.contains(&"20".to_string()));
assert!(names.contains(&"30".to_string()));
}
#[test]
fn test_flatten_mul_chain() {
let func = build_mul_chain_func();
let f = func.borrow();
let entry = &f.operands[0];
let insts = get_block_instructions(entry);
let mul2 = insts
.iter()
.find(|i| i.borrow().name == "mul2")
.expect("mul2 not found");
let pass = ReassociatePass::new();
let flat = pass.flatten_mul_chain(mul2);
assert_eq!(flat.len(), 3);
}
#[test]
fn test_flatten_and_chain() {
let func = build_and_chain_func();
let f = func.borrow();
let entry = &f.operands[0];
let insts = get_block_instructions(entry);
let and2 = insts
.iter()
.find(|i| i.borrow().name == "and2")
.expect("and2 not found");
let pass = ReassociatePass::new();
let flat = pass.flatten_bitwise_chain(and2, Opcode::And);
assert_eq!(flat.len(), 3);
}
#[test]
fn test_flatten_or_chain() {
let func = build_or_chain_func();
let f = func.borrow();
let entry = &f.operands[0];
let insts = get_block_instructions(entry);
let or2 = insts
.iter()
.find(|i| i.borrow().name == "or2")
.expect("or2 not found");
let pass = ReassociatePass::new();
let flat = pass.flatten_bitwise_chain(or2, Opcode::Or);
assert_eq!(flat.len(), 3);
}
#[test]
fn test_flatten_xor_chain() {
let func = build_xor_chain_func();
let f = func.borrow();
let entry = &f.operands[0];
let insts = get_block_instructions(entry);
let xor2 = insts
.iter()
.find(|i| i.borrow().name == "xor2")
.expect("xor2 not found");
let pass = ReassociatePass::new();
let flat = pass.flatten_bitwise_chain(xor2, Opcode::Xor);
assert_eq!(flat.len(), 3);
}
#[test]
fn test_fold_constants_add() {
let mut operands = vec![const_i32(10), const_i32(20), const_i32(30)];
let folded = fold_constants(&mut operands);
assert!(folded >= 2);
assert_eq!(
operands.len(),
1,
"After folding, should have 1 constant, got {}",
operands.len()
);
assert_eq!(operands[0].borrow().name, "60");
}
#[test]
fn test_fold_constants_mul() {
let mut operands = vec![const_i32(2), const_i32(3), const_i32(4)];
let folded = fold_constants(&mut operands);
assert!(folded >= 2);
}
#[test]
fn test_fold_constants_none() {
let arg = llvm_native_core::function::new_argument("x", Type::i32());
let mut operands = vec![arg.clone(), arg];
let folded = fold_constants(&mut operands);
assert_eq!(folded, 0);
}
#[test]
fn test_sort_by_rank() {
let c100 = const_i32(100);
let c1 = const_i32(1);
let c50 = const_i32(50);
let mut operands = vec![c100, c1, c50];
sort_by_rank(&mut operands);
assert_eq!(operands[0].borrow().name, "1");
assert_eq!(operands[1].borrow().name, "50");
assert_eq!(operands[2].borrow().name, "100");
}
#[test]
fn test_rebuild_expression_add() {
let mut builder = IRBuilderStub::new();
let ty = Type::i32();
let result = rebuild_expression(
Opcode::Add,
vec![const_i32(1), const_i32(2), const_i32(3)],
&mut builder,
&ty,
);
let rb = result.borrow();
assert_eq!(rb.opcode, Some(Opcode::Add));
assert_eq!(rb.operands.len(), 2);
}
#[test]
fn test_rebuild_expression_mul() {
let mut builder = IRBuilderStub::new();
let ty = Type::i32();
let result = rebuild_expression(
Opcode::Mul,
vec![const_i32(2), const_i32(3)],
&mut builder,
&ty,
);
let rb = result.borrow();
assert_eq!(rb.opcode, Some(Opcode::Mul));
}
#[test]
fn test_rebuild_expression_single_operand() {
let mut builder = IRBuilderStub::new();
let ty = Type::i32();
let c42 = const_i32(42);
let result = rebuild_expression(Opcode::Add, vec![c42.clone()], &mut builder, &ty);
assert_eq!(result.borrow().vid, c42.borrow().vid);
}
#[test]
fn test_run_on_function_add_chain() {
let func = build_add_chain_func();
let mut pass = ReassociatePass::new();
let count = pass.run_on_function(&func);
assert!(
count > 0,
"Expected at least 1 reassociation, got {}",
count
);
}
#[test]
fn test_run_on_function_mul_chain() {
let func = build_mul_chain_func();
let mut pass = ReassociatePass::new();
let count = pass.run_on_function(&func);
assert!(
count > 0,
"Expected at least 1 reassociation, got {}",
count
);
}
#[test]
fn test_run_on_function_and_chain() {
let func = build_and_chain_func();
let mut pass = ReassociatePass::new();
let count = pass.run_on_function(&func);
assert!(
count > 0,
"Expected at least 1 reassociation, got {}",
count
);
}
#[test]
fn test_run_on_function_or_chain() {
let func = build_or_chain_func();
let mut pass = ReassociatePass::new();
let count = pass.run_on_function(&func);
assert!(
count > 0,
"Expected at least 1 reassociation, got {}",
count
);
}
#[test]
fn test_run_on_function_xor_chain() {
let func = build_xor_chain_func();
let mut pass = ReassociatePass::new();
let count = pass.run_on_function(&func);
assert!(
count > 0,
"Expected at least 1 reassociation, got {}",
count
);
}
#[test]
fn test_mixed_chain_constant_merge() {
let func = build_mixed_chain_func();
let mut pass = ReassociatePass::new();
let count = pass.run_on_function(&func);
assert!(count > 0, "Mixed chain should be reassociated");
}
#[test]
fn test_repeated_operand_factoring() {
let func = build_repeated_operand_func();
let mut pass = ReassociatePass::new();
let count = pass.run_on_function(&func);
assert!(count >= 0, "Repeated operand function should process");
}
#[test]
fn test_optimize_expression() {
let func = build_add_chain_func();
let f = func.borrow();
let entry = &f.operands[0];
let insts = get_block_instructions(entry);
let add2 = insts.iter().find(|i| i.borrow().name == "add2").unwrap();
let pass = ReassociatePass::new();
let mut builder = IRBuilderStub::new();
let result = pass.optimize_expression(add2, &mut builder);
assert!(result.is_some(), "Optimize should succeed for add chain");
}
#[test]
fn test_try_combine_constants() {
let c10 = const_i32(10);
let c20 = const_i32(20);
let combined = try_combine_constants(&c10, &c20);
assert!(combined.is_some());
assert_eq!(combined.unwrap().borrow().name, "30");
}
#[test]
fn test_try_combine_constants_overflow() {
let c_max = const_i32(i32::MAX);
let c_one = const_i32(1);
let combined = try_combine_constants(&c_max, &c_one);
assert!(combined.is_some());
assert_eq!(
combined.unwrap().borrow().name,
(i32::MAX as i64).wrapping_add(1).to_string()
);
}
#[test]
fn test_ir_builder_stub_create() {
let builder = IRBuilderStub::new();
assert!(builder._counter == 0);
}
#[test]
fn test_ir_builder_stub_default() {
let builder = IRBuilderStub::default();
assert!(builder._counter == 0);
}
}