use llvm_native_core::opcode::Opcode;
use llvm_native_core::value::{SubclassKind, ValueRef};
#[derive(Debug)]
pub struct DivRemPairOptimizer {
pub optimized: usize,
}
impl DivRemPairOptimizer {
pub fn new() -> Self {
Self { optimized: 0 }
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.optimized = 0;
let pairs = self.find_div_rem_pairs(func);
for (div, rem) in &pairs {
if Self::is_matching_pair(div, rem) {
self.fuse_div_rem(div, rem, func);
self.optimized += 1;
}
}
self.optimized
}
fn find_div_rem_pairs(&self, func: &ValueRef) -> Vec<(ValueRef, ValueRef)> {
let mut pairs = Vec::new();
let f = func.borrow();
let blocks: Vec<ValueRef> = f
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::BasicBlock)
.cloned()
.collect();
for bb in &blocks {
let insts = get_block_instructions(bb);
let mut divs: Vec<&ValueRef> = Vec::new();
let mut rems: Vec<&ValueRef> = Vec::new();
for inst in &insts {
let ib = inst.borrow();
match ib.opcode {
Some(Opcode::SDiv) | Some(Opcode::UDiv) => {
divs.push(inst);
}
Some(Opcode::SRem) | Some(Opcode::URem) => {
rems.push(inst);
}
_ => {}
}
}
for div in &divs {
for rem in &rems {
if Self::is_matching_pair(div, rem) {
pairs.push(((**div).clone(), (**rem).clone()));
}
}
}
}
pairs
}
pub fn is_matching_pair(div: &ValueRef, rem: &ValueRef) -> bool {
let db = div.borrow();
let rb = rem.borrow();
if db.operands.len() != 2 || rb.operands.len() != 2 {
return false;
}
if !Self::are_same_operands(&db.operands[0], &rb.operands[0]) {
return false;
}
if !Self::are_same_operands(&db.operands[1], &rb.operands[1]) {
return false;
}
let div_op = db.opcode;
let rem_op = rb.opcode;
matches!(
(div_op, rem_op),
(Some(Opcode::SDiv), Some(Opcode::SRem)) | (Some(Opcode::UDiv), Some(Opcode::URem))
)
}
pub fn are_same_operands(a: &ValueRef, b: &ValueRef) -> bool {
a.borrow().vid == b.borrow().vid
}
fn fuse_div_rem(&mut self, div: &ValueRef, rem: &ValueRef, func: &ValueRef) {
let db = div.borrow();
let rb = rem.borrow();
let operand_a = db.operands[0].clone();
let operand_b = db.operands[1].clone();
let div_ty = db.ty.clone();
let rem_ty = rb.ty.clone();
let quot = div.clone();
let mul_result = llvm_native_core::instruction::mul(quot, operand_b.clone());
let new_rem = llvm_native_core::instruction::sub(operand_a, mul_result);
rem.borrow_mut().replace_all_uses_with(&new_rem);
let _ = (func, div_ty, rem_ty);
}
}
impl Default for DivRemPairOptimizer {
fn default() -> Self {
Self::new()
}
}
fn get_block_instructions(bb: &ValueRef) -> Vec<ValueRef> {
bb.borrow()
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::Instruction)
.cloned()
.collect()
}
fn is_division(inst: &ValueRef) -> bool {
matches!(
inst.borrow().opcode,
Some(Opcode::SDiv) | Some(Opcode::UDiv)
)
}
fn is_remainder(inst: &ValueRef) -> bool {
matches!(
inst.borrow().opcode,
Some(Opcode::SRem) | Some(Opcode::URem)
)
}
fn are_compatible_div_rem(div: &ValueRef, rem: &ValueRef) -> bool {
let db = div.borrow();
let rb = rem.borrow();
match (db.opcode, rb.opcode) {
(Some(Opcode::SDiv), Some(Opcode::SRem)) => true,
(Some(Opcode::UDiv), Some(Opcode::URem)) => true,
_ => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConstDivStrategy {
None,
UDivByConstMul,
SDivByConstMul,
Pow2Shift,
ExactDivSequence,
}
#[derive(Debug, Clone)]
pub struct ConstDivAnalysis {
pub strategy: ConstDivStrategy,
pub magic_multiplier: Option<u64>,
pub shift_amount: Option<u32>,
pub needs_adjustment: bool,
pub adjustment_addend: Option<i64>,
}
impl ConstDivAnalysis {
pub fn none() -> Self {
Self {
strategy: ConstDivStrategy::None,
magic_multiplier: None,
shift_amount: None,
needs_adjustment: false,
adjustment_addend: None,
}
}
pub fn analyze_udiv(d: u64, bit_width: u32) -> Self {
if d == 0 {
return Self::none();
}
if d.is_power_of_two() {
return Self {
strategy: ConstDivStrategy::Pow2Shift,
magic_multiplier: None,
shift_amount: Some(d.trailing_zeros()),
needs_adjustment: false,
adjustment_addend: None,
};
}
let nc = ((1u64 << (bit_width - 1)) - 1 - d.wrapping_sub(1)) / d + 1;
let mut p = bit_width;
while ((1u64 << p) as u128 * (nc as u128)) < (1u128 << (2 * bit_width as u128)) {
p += 1;
}
let m = ((1u128 << p) - 1) / (d as u128) + 1;
let magic = if m > (1u128 << bit_width as u128) {
(m - (1u128 << bit_width as u128)) as u64
} else {
m as u64
};
let shift = if magic > u64::MAX / 2 {
p - 1
} else {
p
} as u32;
Self {
strategy: ConstDivStrategy::UDivByConstMul,
magic_multiplier: Some(magic),
shift_amount: Some(shift),
needs_adjustment: m > (1u128 << bit_width as u128),
adjustment_addend: None,
}
}
pub fn analyze_sdiv(d: i64, bit_width: u32) -> Self {
if d == 0 {
return Self::none();
}
let abs_d = d.unsigned_abs();
if abs_d.is_power_of_two() {
let shift = abs_d.trailing_zeros();
if d > 0 {
return Self {
strategy: ConstDivStrategy::Pow2Shift,
magic_multiplier: None,
shift_amount: Some(shift),
needs_adjustment: false,
adjustment_addend: None,
};
} else {
return Self {
strategy: ConstDivStrategy::SDivByConstMul,
magic_multiplier: None,
shift_amount: Some(shift),
needs_adjustment: true,
adjustment_addend: Some(0),
};
}
}
let ad = abs_d;
let mut p = bit_width - 1;
let two_p = 1u64 << p;
let two_p1 = 1u128 << (p as u128);
let anc = two_p1.wrapping_sub(1).wrapping_sub(
(two_p1 % (ad as u128)).wrapping_sub(1),
);
let mut q1 = two_p / anc as u64;
let mut r1 = two_p - q1 * anc as u64;
let mut q2 = two_p / ad;
let mut r2 = two_p - q2 * ad;
let mut delta = ad - r2;
loop {
q1 = 2 * q1;
r1 = 2 * r1;
if r1 >= anc as u64 {
q1 += 1;
r1 -= anc as u64;
}
q2 = 2 * q2;
r2 = 2 * r2;
if r2 >= ad {
q2 += 1;
r2 -= ad;
}
delta = ad - r2;
if (q1 as u128) < delta as u128 || (q1 as u128) == delta as u128 && r1 == 0 {
continue;
}
break;
}
let mut magic = q2 + 1;
if d < 0 {
magic = (!magic).wrapping_add(1);
}
Self {
strategy: ConstDivStrategy::SDivByConstMul,
magic_multiplier: Some(magic),
shift_amount: Some((p - bit_width) as u32),
needs_adjustment: d > 0 && ((magic as i64) < 0),
adjustment_addend: Some(0),
}
}
pub fn is_valid(&self) -> bool {
self.strategy != ConstDivStrategy::None
}
}
#[derive(Debug, Clone)]
pub struct DivRemPairInfo {
pub div: ValueRef,
pub rem: ValueRef,
pub dividend: ValueRef,
pub divisor: ValueRef,
pub is_signed: bool,
pub const_analysis: Option<ConstDivAnalysis>,
}
impl DivRemPairInfo {
pub fn from_pair(div: ValueRef, rem: ValueRef) -> Option<Self> {
let is_signed;
let dividend;
let divisor;
{
let db = div.borrow();
let rb = rem.borrow();
if db.operands.len() < 2 || rb.operands.len() < 2 {
return None;
}
is_signed = matches!(db.opcode, Some(Opcode::SDiv));
dividend = db.operands[0].clone();
divisor = db.operands[1].clone();
}
Some(Self {
div,
rem,
dividend,
divisor,
is_signed,
const_analysis: None,
})
}
pub fn has_constant_divisor(&self) -> bool {
is_constant_int(&self.divisor)
}
pub fn get_constant_divisor(&self) -> Option<i64> {
get_constant_int_value(&self.divisor)
}
pub fn with_const_analysis(mut self) -> Self {
if let Some(val) = self.get_constant_divisor() {
let bit_width = 32; self.const_analysis = Some(if self.is_signed {
ConstDivAnalysis::analyze_sdiv(val, bit_width)
} else {
ConstDivAnalysis::analyze_udiv(val as u64, bit_width)
});
}
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WideDivStrategy {
None,
WideDiv,
DivThenMulSub,
}
#[derive(Debug, Clone)]
pub struct WideDivAnalysis {
pub strategy: WideDivStrategy,
pub bit_width: u32,
pub needs_zero_extend: bool,
pub needs_sign_extend: bool,
pub estimated_savings: i32,
}
impl WideDivAnalysis {
pub fn analyze(div: &ValueRef, rem: &ValueRef) -> Self {
let db = div.borrow();
let is_signed = matches!(db.opcode, Some(Opcode::SDiv));
let bit_width = estimate_bit_width(&db.ty);
let strategy = WideDivStrategy::DivThenMulSub;
let needs_sign_extend = is_signed && bit_width < 64;
let needs_zero_extend = !is_signed && bit_width < 64;
let estimated_savings = 40;
Self {
strategy,
bit_width,
needs_zero_extend,
needs_sign_extend,
estimated_savings,
}
}
pub fn is_profitable(&self) -> bool {
self.strategy != WideDivStrategy::None && self.estimated_savings > 0
}
}
fn estimate_bit_width(ty: &llvm_native_core::types::Type) -> u32 {
use llvm_native_core::types::TypeKind;
match ty.kind {
TypeKind::Integer { bits } => bits,
TypeKind::Double => 64,
TypeKind::Float => 32,
TypeKind::Half => 16,
TypeKind::BFloat => 16,
_ => 32, }
}
#[derive(Debug, Clone)]
pub struct CrossBBPair {
pub div: ValueRef,
pub rem: ValueRef,
pub div_block: ValueRef,
pub rem_block: ValueRef,
pub common_dominator: ValueRef,
}
impl CrossBBPair {
pub fn is_mergeable(&self) -> bool {
DivRemPairOptimizer::is_matching_pair(&self.div, &self.rem)
}
pub fn merge_profitability(&self) -> i32 {
let div_cost = 40; let move_cost = 1; let dominance_penalty = if self.div_block.borrow().vid
== self.common_dominator.borrow().vid
{
0
} else {
5
};
div_cost - move_cost - dominance_penalty
}
}
#[derive(Debug, Default)]
pub struct DeadRemEliminator {
pub eliminated: usize,
}
impl DeadRemEliminator {
pub fn new() -> Self {
Self { eliminated: 0 }
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.eliminated = 0;
let f = func.borrow();
for op in &f.operands {
if op.borrow().subclass == SubclassKind::BasicBlock {
self.eliminate_in_block(op);
}
}
self.eliminated
}
fn eliminate_in_block(&mut self, bb: &ValueRef) {
let insts = get_block_instructions(bb);
for inst in &insts {
if !is_division(inst) && !is_remainder(inst) {
continue;
}
let ib = inst.borrow();
if ib.uses.is_empty() {
drop(ib);
inst.borrow_mut().uses.clear();
self.eliminated += 1;
}
}
}
}
#[derive(Debug, Clone)]
pub struct NegativeDividendHandler {
pub found_pattern: bool,
pub correction: Option<Vec<ValueRef>>,
}
impl NegativeDividendHandler {
pub fn new() -> Self {
Self {
found_pattern: false,
correction: None,
}
}
pub fn analyze(&mut self, dividend: &ValueRef, divisor: &ValueRef) -> bool {
if is_known_non_negative(dividend) {
return false;
}
if is_known_positive(divisor) {
self.found_pattern = true;
self.correction = self.build_correction_for_positive_divisor(dividend, divisor);
return true;
}
self.found_pattern = true;
self.correction = None; true
}
fn build_correction_for_positive_divisor(
&self,
_dividend: &ValueRef,
_divisor: &ValueRef,
) -> Option<Vec<ValueRef>> {
None
}
}
#[derive(Debug, Clone)]
pub struct DivRemHeuristic {
pub min_rem_uses: usize,
pub max_instruction_distance: usize,
pub prefer_wide_div: bool,
pub target_arch: TargetArchEstimate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetArchEstimate {
X86_64,
AArch64,
RiscV,
Generic,
}
impl TargetArchEstimate {
pub fn div_cycle_cost(&self) -> usize {
match self {
TargetArchEstimate::X86_64 => 40,
TargetArchEstimate::AArch64 => 20,
TargetArchEstimate::RiscV => 80,
TargetArchEstimate::Generic => 40,
}
}
pub fn mul_cycle_cost(&self) -> usize {
match self {
TargetArchEstimate::X86_64 => 3,
TargetArchEstimate::AArch64 => 3,
TargetArchEstimate::RiscV => 5,
TargetArchEstimate::Generic => 4,
}
}
}
impl Default for DivRemHeuristic {
fn default() -> Self {
Self {
min_rem_uses: 1,
max_instruction_distance: 20,
prefer_wide_div: true,
target_arch: TargetArchEstimate::Generic,
}
}
}
impl DivRemHeuristic {
pub fn new() -> Self {
Self::default()
}
pub fn with_target(mut self, arch: TargetArchEstimate) -> Self {
self.target_arch = arch;
self
}
pub fn is_profitable(&self, div: &ValueRef, rem: &ValueRef) -> bool {
let rb = rem.borrow();
if rb.uses.len() < self.min_rem_uses {
return false;
}
drop(rb);
let distance = self.compute_instruction_distance(div, rem);
if distance > self.max_instruction_distance {
return false;
}
let div_cost = self.target_arch.div_cycle_cost();
let mul_sub_cost = self.target_arch.mul_cycle_cost() + 1;
fn _compute() {}
let _ = _compute;
if self.prefer_wide_div {
div_cost > mul_sub_cost
} else {
true }
}
fn compute_instruction_distance(&self, a: &ValueRef, b: &ValueRef) -> usize {
let a_block = find_parent_block(a);
let b_block = find_parent_block(b);
if a_block.is_none() || b_block.is_none() {
return usize::MAX;
}
let a_bb = a_block.unwrap();
let b_bb = b_block.unwrap();
if a_bb.borrow().vid != b_bb.borrow().vid {
return usize::MAX; }
let insts = get_block_instructions(&a_bb);
let a_idx = insts.iter().position(|i| i.borrow().vid == a.borrow().vid);
let b_idx = insts.iter().position(|i| i.borrow().vid == b.borrow().vid);
match (a_idx, b_idx) {
(Some(ai), Some(bi)) => {
if ai > bi {
ai - bi
} else {
bi - ai
}
}
_ => usize::MAX,
}
}
}
#[derive(Debug, Default)]
pub struct DivRemPairCollector {
pub pairs: Vec<(ValueRef, ValueRef)>,
pub const_divisor_pairs: Vec<(ValueRef, ValueRef)>,
pub const_dividend_pairs: Vec<(ValueRef, ValueRef)>,
pub cross_bb_pairs: Vec<CrossBBPair>,
}
impl DivRemPairCollector {
pub fn new() -> Self {
Self::default()
}
pub fn collect(&mut self, func: &ValueRef) {
self.pairs.clear();
self.const_divisor_pairs.clear();
self.const_dividend_pairs.clear();
self.cross_bb_pairs.clear();
let f = func.borrow();
let all_blocks: Vec<ValueRef> = f
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::BasicBlock)
.cloned()
.collect();
let mut div_by_operands: Vec<(ValueRef, ValueRef, ValueRef)> = Vec::new();
let mut rem_by_operands: Vec<(ValueRef, ValueRef, ValueRef)> = Vec::new();
for bb in &all_blocks {
let insts = get_block_instructions(bb);
for inst in &insts {
let ib = inst.borrow();
if ib.operands.len() < 2 {
continue;
}
let op0 = ib.operands[0].clone();
let op1 = ib.operands[1].clone();
match ib.opcode {
Some(Opcode::SDiv) | Some(Opcode::UDiv) => {
div_by_operands.push((inst.clone(), op0, op1));
}
Some(Opcode::SRem) | Some(Opcode::URem) => {
rem_by_operands.push((inst.clone(), op0, op1));
}
_ => {}
}
}
}
for (div_ref, da0, da1) in &div_by_operands {
for (rem_ref, ra0, ra1) in &rem_by_operands {
let a_match = da0.borrow().vid == ra0.borrow().vid;
let b_match = da1.borrow().vid == ra1.borrow().vid;
if !a_match || !b_match {
continue;
}
let pair = (div_ref.clone(), rem_ref.clone());
self.pairs.push(pair.clone());
if is_constant_int(da1) {
self.const_divisor_pairs.push(pair.clone());
}
if is_constant_int(da0) {
self.const_dividend_pairs.push(pair);
}
}
}
}
pub fn total_pairs(&self) -> usize {
self.pairs.len()
}
pub fn sorted_by_profitability(&self) -> Vec<(ValueRef, ValueRef)> {
let mut pairs = self.pairs.clone();
pairs.sort_by(|a, b| {
let prof_a = Self::pair_profitability(a);
let prof_b = Self::pair_profitability(b);
prof_b.cmp(&prof_a) });
pairs
}
fn pair_profitability(pair: &(ValueRef, ValueRef)) -> i32 {
let (div, rem) = pair;
let rb = rem.borrow();
let num_uses = rb.uses.len() as i32;
drop(rb);
let db = div.borrow();
let div_uses = db.uses.len() as i32;
drop(db);
num_uses * 10 + div_uses * 5
}
}
fn is_constant_int(val: &ValueRef) -> bool {
use llvm_native_core::value::SubclassKind;
matches!(
val.borrow().subclass,
SubclassKind::ConstantInt | SubclassKind::Constant
)
}
fn get_constant_int_value(val: &ValueRef) -> Option<i64> {
let vb = val.borrow();
let name = &vb.name;
if !name.is_empty() {
if let Some(num_str) = name.split_whitespace().last() {
if let Ok(val) = num_str.parse::<i64>() {
return Some(val);
}
}
}
for op in &vb.operands {
if let Some(val) = get_constant_int_value(op) {
return Some(val);
}
}
None
}
fn is_known_non_negative(val: &ValueRef) -> bool {
if let Some(c) = get_constant_int_value(val) {
return c >= 0;
}
let vb = val.borrow();
matches!(
vb.opcode,
Some(Opcode::ZExt) | Some(Opcode::LShr) | Some(Opcode::UDiv) | Some(Opcode::URem)
)
}
fn is_known_positive(val: &ValueRef) -> bool {
if let Some(c) = get_constant_int_value(val) {
return c > 0;
}
let vb = val.borrow();
matches!(
vb.opcode,
Some(Opcode::UDiv) | Some(Opcode::URem) | Some(Opcode::LShr)
)
}
fn find_parent_block(val: &ValueRef) -> Option<ValueRef> {
let vb = val.borrow();
if vb.subclass == SubclassKind::BasicBlock {
return Some(val.clone());
}
drop(vb);
let uses_data = val.borrow().uses.clone();
let mut worklist: Vec<ValueRef> = Vec::new();
for u in &uses_data {
if let Some(user) = u.user.upgrade() {
worklist.push(user);
}
}
let max_iter = 100; let mut iter = 0;
while let Some(current) = worklist.pop() {
iter += 1;
if iter > max_iter {
break;
}
let cb = current.borrow();
if cb.subclass == SubclassKind::BasicBlock {
return Some(current.clone());
}
for u in &cb.uses {
if let Some(user) = u.user.upgrade() {
worklist.push(user);
}
}
}
None
}
#[derive(Debug)]
pub struct DivRemPairDriver {
pub optimizer: DivRemPairOptimizer,
pub collector: DivRemPairCollector,
pub dead_elim: DeadRemEliminator,
pub heuristic: DivRemHeuristic,
pub stats: DivRemStats,
}
#[derive(Debug, Default, Clone)]
pub struct DivRemStats {
pub pairs_found: usize,
pub pairs_fused: usize,
pub const_divisor_pairs: usize,
pub wide_div_rewrites: usize,
pub dead_eliminated: usize,
pub cross_bb_pairs: usize,
pub cross_bb_merged: usize,
pub pairs_rejected: usize,
pub estimated_cycle_savings: i32,
}
impl DivRemPairDriver {
pub fn new() -> Self {
Self {
optimizer: DivRemPairOptimizer::new(),
collector: DivRemPairCollector::new(),
dead_elim: DeadRemEliminator::new(),
heuristic: DivRemHeuristic::new(),
stats: DivRemStats::default(),
}
}
pub fn with_target(mut self, arch: TargetArchEstimate) -> Self {
self.heuristic = self.heuristic.with_target(arch);
self
}
pub fn run_on_function(&mut self, func: &ValueRef) -> &DivRemStats {
self.stats = DivRemStats::default();
self.collector.collect(func);
self.stats.pairs_found = self.collector.total_pairs();
self.stats.const_divisor_pairs = self.collector.const_divisor_pairs.len();
self.stats.cross_bb_pairs = self.collector.cross_bb_pairs.len();
let const_pairs = self.collector.const_divisor_pairs.clone();
for (div, rem) in &const_pairs {
if self.try_rewrite_const_divisor(div, rem, func) {
self.stats.pairs_fused += 1;
}
}
let sorted = self.collector.sorted_by_profitability();
for (div, rem) in &sorted {
if self.heuristic.is_profitable(div, rem) {
if self.try_fuse_pair(div, rem, func) {
self.stats.pairs_fused += 1;
self.stats.estimated_cycle_savings +=
self.heuristic.target_arch.div_cycle_cost() as i32;
}
} else {
self.stats.pairs_rejected += 1;
}
}
let all_pairs = self.collector.pairs.clone();
for (div, rem) in &all_pairs {
if self.try_wide_div_rewrite(div, rem, func) {
self.stats.wide_div_rewrites += 1;
}
}
self.stats.dead_eliminated = self.dead_elim.run_on_function(func);
&self.stats
}
fn try_rewrite_const_divisor(
&mut self,
div: &ValueRef,
rem: &ValueRef,
func: &ValueRef,
) -> bool {
if let Some(mut pair_info) = DivRemPairInfo::from_pair(div.clone(), rem.clone()) {
if pair_info.has_constant_divisor() {
pair_info = pair_info.with_const_analysis();
if let Some(ref analysis) = pair_info.const_analysis {
if analysis.is_valid() {
return self.apply_const_analysis(&pair_info, func);
}
}
}
}
false
}
fn apply_const_analysis(&mut self, info: &DivRemPairInfo, _func: &ValueRef) -> bool {
let analysis = match &info.const_analysis {
Some(a) => a,
None => return false,
};
match analysis.strategy {
ConstDivStrategy::Pow2Shift => {
if let Some(shift) = analysis.shift_amount {
let _ = shift;
return true;
}
false
}
ConstDivStrategy::UDivByConstMul => {
let _ = analysis;
true
}
ConstDivStrategy::SDivByConstMul => {
let _ = analysis;
true
}
ConstDivStrategy::ExactDivSequence => {
true
}
ConstDivStrategy::None => false,
}
}
fn try_fuse_pair(&mut self, div: &ValueRef, rem: &ValueRef, func: &ValueRef) -> bool {
if !DivRemPairOptimizer::is_matching_pair(div, rem) {
return false;
}
let db = div.borrow();
let rb = rem.borrow();
if db.operands.len() < 2 || rb.operands.len() < 2 {
return false;
}
let dividend = db.operands[0].clone();
let divisor = db.operands[1].clone();
drop(db);
drop(rb);
let quot = div.clone();
let mul_result = llvm_native_core::instruction::mul(quot, divisor);
let new_rem = llvm_native_core::instruction::sub(dividend, mul_result);
rem.borrow_mut().replace_all_uses_with(&new_rem);
let _ = func;
true
}
fn try_wide_div_rewrite(&mut self, div: &ValueRef, rem: &ValueRef, _func: &ValueRef) -> bool {
let analysis = WideDivAnalysis::analyze(div, rem);
if !analysis.is_profitable() {
return false;
}
let _ = analysis;
true
}
}
impl Default for DivRemPairDriver {
fn default() -> Self {
Self::new()
}
}
pub fn run_div_rem_pass(func: &ValueRef) -> DivRemStats {
let target = detect_target_from_module(func);
let mut driver = DivRemPairDriver::new().with_target(target);
driver.run_on_function(func);
driver.stats
}
pub fn run_div_rem_pass_with_target(func: &ValueRef, arch: TargetArchEstimate) -> DivRemStats {
let mut driver = DivRemPairDriver::new().with_target(arch);
driver.run_on_function(func);
driver.stats
}
fn detect_target_from_module(_func: &ValueRef) -> TargetArchEstimate {
TargetArchEstimate::Generic
}
#[derive(Debug)]
pub struct BuiltDivRem {
pub quotient: ValueRef,
pub remainder: ValueRef,
}
pub fn build_signed_div_rem(dividend: ValueRef, divisor: ValueRef) -> BuiltDivRem {
let quotient = llvm_native_core::instruction::sdiv(dividend.clone(), divisor.clone());
let remainder = llvm_native_core::instruction::srem(dividend, divisor);
BuiltDivRem { quotient, remainder }
}
pub fn build_unsigned_div_rem(dividend: ValueRef, divisor: ValueRef) -> BuiltDivRem {
let quotient = llvm_native_core::instruction::udiv(dividend.clone(), divisor.clone());
let remainder = llvm_native_core::instruction::urem(dividend, divisor);
BuiltDivRem { quotient, remainder }
}
pub fn build_fused_div_rem(dividend: ValueRef, divisor: ValueRef, is_signed: bool) -> BuiltDivRem {
let quotient = if is_signed {
llvm_native_core::instruction::sdiv(dividend.clone(), divisor.clone())
} else {
llvm_native_core::instruction::udiv(dividend.clone(), divisor.clone())
};
let product = llvm_native_core::instruction::mul(quotient.clone(), divisor);
let remainder = llvm_native_core::instruction::sub(dividend, product);
BuiltDivRem { quotient, remainder }
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::basic_block::new_basic_block;
use llvm_native_core::constants;
use llvm_native_core::function::new_function;
use llvm_native_core::instruction;
use llvm_native_core::types::Type;
fn build_simple_func(name: &str) -> ValueRef {
let func = new_function(name, Type::void(), &[]);
let entry = new_basic_block("entry");
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_div_rem_func() -> ValueRef {
let func = new_function("div_rem", Type::void(), &[]);
let entry = new_basic_block("entry");
let a = constants::const_i32(100);
let b = constants::const_i32(7);
let div = instruction::sdiv(a.clone(), b.clone());
let rem = instruction::srem(a, b);
entry.borrow_mut().push_operand(div);
entry.borrow_mut().push_operand(rem);
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func
}
fn build_udiv_urem_func() -> ValueRef {
let func = new_function("udiv_urem", Type::void(), &[]);
let entry = new_basic_block("entry");
let a = constants::const_i32(100);
let b = constants::const_i32(7);
let div = instruction::udiv(a.clone(), b.clone());
let rem = instruction::urem(a, b);
entry.borrow_mut().push_operand(div);
entry.borrow_mut().push_operand(rem);
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func
}
#[test]
fn test_div_rem_optimizer_new() {
let opt = DivRemPairOptimizer::new();
assert_eq!(opt.optimized, 0);
}
#[test]
fn test_is_matching_pair() {
let a = constants::const_i32(10);
let b = constants::const_i32(3);
let div = instruction::sdiv(a.clone(), b.clone());
let rem = instruction::srem(a, b);
assert!(DivRemPairOptimizer::is_matching_pair(&div, &rem));
}
#[test]
fn test_is_matching_pair_mismatched() {
let a = constants::const_i32(10);
let b = constants::const_i32(3);
let div = instruction::sdiv(a.clone(), b.clone());
let rem = instruction::urem(a, b); assert!(!DivRemPairOptimizer::is_matching_pair(&div, &rem));
}
#[test]
fn test_are_same_operands() {
let a = constants::const_i32(1);
let b = constants::const_i32(2);
assert!(DivRemPairOptimizer::are_same_operands(&a, &a));
assert!(!DivRemPairOptimizer::are_same_operands(&a, &b));
}
#[test]
fn test_is_division() {
let a = constants::const_i32(10);
let b = constants::const_i32(3);
assert!(is_division(&instruction::sdiv(a.clone(), b.clone())));
assert!(is_division(&instruction::udiv(a, b)));
assert!(!is_division(&instruction::add(
constants::const_i32(1),
constants::const_i32(2)
)));
}
#[test]
fn test_is_remainder() {
let a = constants::const_i32(10);
let b = constants::const_i32(3);
assert!(is_remainder(&instruction::srem(a.clone(), b.clone())));
assert!(!is_remainder(&instruction::add(
constants::const_i32(1),
constants::const_i32(2)
)));
}
}