use llvm_native_core::SubclassKind;
use llvm_native_core::opcode::Opcode;
use llvm_native_core::value::ValueRef;
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct LoopInfo {
pub header: ValueRef,
pub blocks: Vec<ValueRef>,
pub exits: Vec<(ValueRef, ValueRef)>,
pub latch: Option<ValueRef>,
pub preheader: Option<ValueRef>,
pub depth: u32,
pub parent_loop: Option<usize>,
}
pub struct LoopSimplifyPass {
pub simplified: usize,
pub preheaders_inserted: usize,
pub exits_inserted: usize,
}
impl Default for LoopSimplifyPass {
fn default() -> Self {
Self::new()
}
}
impl LoopSimplifyPass {
pub fn new() -> Self {
Self {
simplified: 0,
preheaders_inserted: 0,
exits_inserted: 0,
}
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.simplified = 0;
self.preheaders_inserted = 0;
self.exits_inserted = 0;
let loops = self.find_loops(func);
let mut total_simplified = 0usize;
for loop_info in &loops {
let result = self.simplify_loop(loop_info, func);
if result > 0 {
total_simplified += result;
self.simplified += 1;
}
}
total_simplified
}
pub fn find_loops(&self, func: &ValueRef) -> Vec<LoopInfo> {
let mut loops = Vec::new();
let f = func.borrow();
if f.operands.is_empty() {
return loops;
}
let dom_info = compute_dominator_tree(func);
let pred_map = build_pred_map(func);
for op in &f.operands {
let bb = op.borrow();
if !bb.is_basic_block() {
continue;
}
if let Some(term) = get_terminator(op) {
let term_borrow = term.borrow();
let targets = get_branch_targets(&term_borrow);
for target in &targets {
if dominates(&dom_info, target, op) {
let mut loop_blocks = collect_loop_blocks(target, op, &pred_map, &dom_info);
loop_blocks.sort_by_key(|b| b.borrow().vid);
let mut exits = Vec::new();
let block_set: HashSet<u64> =
loop_blocks.iter().map(|b| b.borrow().vid).collect();
for lb in &loop_blocks {
if let Some(lt) = get_terminator(lb) {
let lt_borrow = lt.borrow();
let lt_targets = get_branch_targets(<_borrow);
for tgt in <_targets {
if !block_set.contains(&tgt.borrow().vid) {
exits.push((lb.clone(), tgt.clone()));
}
}
}
}
let mut latch = None;
for lb in &loop_blocks {
if let Some(lt) = get_terminator(lb) {
let lt_borrow = lt.borrow();
let lt_targets = get_branch_targets(<_borrow);
for tgt in <_targets {
if std::ptr::addr_eq(Rc::as_ptr(tgt), Rc::as_ptr(target)) {
latch = Some(lb.clone());
break;
}
}
}
if latch.is_some() {
break;
}
}
let depth = compute_loop_depth(target, &loops);
let parent_idx = find_parent_loop(target, &loops);
loops.push(LoopInfo {
header: target.clone(),
blocks: loop_blocks,
exits,
latch,
preheader: None,
depth,
parent_loop: parent_idx,
});
}
}
}
}
let mut seen_headers = HashSet::new();
loops.retain(|li| seen_headers.insert(li.header.borrow().vid));
loops
}
pub fn simplify_loop(&mut self, loop_info: &LoopInfo, func: &ValueRef) -> usize {
let mut changes = 0usize;
if self.insert_preheader(loop_info, func) {
self.preheaders_inserted += 1;
changes += 1;
}
let exits_added = self.insert_unique_exits(loop_info, func);
if exits_added > 0 {
self.exits_inserted += exits_added;
changes += exits_added;
}
if self.canonicalize_backedge(loop_info, func) {
changes += 1;
}
if self.separate_latch(loop_info, func) {
changes += 1;
}
changes
}
pub fn insert_preheader(&self, loop_info: &LoopInfo, func: &ValueRef) -> bool {
let pred_map = build_pred_map(func);
let block_set: HashSet<u64> = loop_info.blocks.iter().map(|b| b.borrow().vid).collect();
let header_vid = loop_info.header.borrow().vid;
if let Some(preds) = pred_map.get(&header_vid) {
let outside_preds: Vec<ValueRef> = preds
.iter()
.filter(|p| !block_set.contains(&p.borrow().vid) && !is_latch(p, loop_info))
.cloned()
.collect();
if outside_preds.is_empty() {
return false;
}
if outside_preds.len() == 1 {
return false;
}
let preheader = llvm_native_core::basic_block::new_basic_block("preheader");
let br = llvm_native_core::instruction::br(loop_info.header.clone());
preheader.borrow_mut().push_operand(br);
for pred in &outside_preds {
if let Some(term) = get_terminator(pred) {
let mut term_mut = term.borrow_mut();
if term_mut.get_opcode() == Some(Opcode::Br) {
for op in term_mut.operands.iter_mut() {
if std::ptr::addr_eq(Rc::as_ptr(op), Rc::as_ptr(&loop_info.header)) {
*op = preheader.clone();
}
}
for succ in term_mut.successors.iter_mut() {
if std::ptr::addr_eq(Rc::as_ptr(succ), Rc::as_ptr(&loop_info.header)) {
*succ = preheader.clone();
}
}
}
}
}
func.borrow_mut().operands.push(preheader);
return true;
}
false
}
pub fn insert_unique_exits(&self, loop_info: &LoopInfo, func: &ValueRef) -> usize {
let mut count = 0usize;
let pred_map = build_pred_map(func);
let block_set: HashSet<u64> = loop_info.blocks.iter().map(|b| b.borrow().vid).collect();
for (from_block, to_block) in &loop_info.exits {
let to_vid = to_block.borrow().vid;
if let Some(preds) = pred_map.get(&to_vid) {
let has_outside_pred = preds.iter().any(|p| !block_set.contains(&p.borrow().vid));
let has_inside_pred = preds.iter().any(|p| {
block_set.contains(&p.borrow().vid)
|| std::ptr::addr_eq(Rc::as_ptr(p), Rc::as_ptr(from_block))
});
if has_outside_pred && has_inside_pred {
let exit_block = self.split_edge(from_block, to_block, func);
if exit_block.is_some() {
count += 1;
}
}
}
}
count
}
pub fn canonicalize_backedge(&self, loop_info: &LoopInfo, _func: &ValueRef) -> bool {
let pred_map = build_pred_map(_func);
let block_set: HashSet<u64> = loop_info.blocks.iter().map(|b| b.borrow().vid).collect();
let header_vid = loop_info.header.borrow().vid;
let mut backedge_sources = Vec::new();
if let Some(preds) = pred_map.get(&header_vid) {
for pred in preds {
if block_set.contains(&pred.borrow().vid) {
backedge_sources.push(pred.clone());
}
}
}
if backedge_sources.len() <= 1 {
return false;
}
false
}
pub fn separate_latch(&self, loop_info: &LoopInfo, _func: &ValueRef) -> bool {
if loop_info.latch.is_some() {
return false;
}
let pred_map = build_pred_map(_func);
let block_set: HashSet<u64> = loop_info.blocks.iter().map(|b| b.borrow().vid).collect();
let header_vid = loop_info.header.borrow().vid;
let mut backedge_sources = Vec::new();
if let Some(preds) = pred_map.get(&header_vid) {
for pred in preds {
if block_set.contains(&pred.borrow().vid) {
backedge_sources.push(pred.clone());
}
}
}
backedge_sources.len() > 1
}
pub fn split_edge(&self, from: &ValueRef, to: &ValueRef, func: &ValueRef) -> Option<ValueRef> {
let new_block = llvm_native_core::basic_block::new_basic_block("loop_exit");
let br = llvm_native_core::instruction::br(to.clone());
new_block.borrow_mut().push_operand(br);
if let Some(term) = get_terminator(from) {
let mut term_mut = term.borrow_mut();
let mut redirected = false;
for op in term_mut.operands.iter_mut() {
if std::ptr::addr_eq(Rc::as_ptr(op), Rc::as_ptr(to)) {
*op = new_block.clone();
redirected = true;
}
}
for succ in term_mut.successors.iter_mut() {
if std::ptr::addr_eq(Rc::as_ptr(succ), Rc::as_ptr(to)) {
*succ = new_block.clone();
}
}
if redirected {
func.borrow_mut().operands.push(new_block.clone());
return Some(new_block);
}
}
None
}
pub fn is_loop_header(
&self,
block: &ValueRef,
dom_info: &HashMap<u64, Option<u64>>,
func: &ValueRef,
) -> bool {
let pred_map = build_pred_map(func);
let block_vid = block.borrow().vid;
if let Some(preds) = pred_map.get(&block_vid) {
for pred in preds {
if dominates(dom_info, block, pred) {
return true;
}
}
}
false
}
}
pub fn compute_dominator_tree(func: &ValueRef) -> HashMap<u64, Option<u64>> {
let f = func.borrow();
let block_vids: Vec<u64> = f
.operands
.iter()
.filter(|op| op.borrow().is_basic_block())
.map(|op| op.borrow().vid)
.collect();
if block_vids.is_empty() {
return HashMap::new();
}
let pred_map = build_pred_map(func);
let all_blocks_set: HashSet<u64> = block_vids.iter().cloned().collect();
let entry_vid = block_vids[0];
let mut dom: HashMap<u64, HashSet<u64>> = HashMap::new();
for &vid in &block_vids {
if vid == entry_vid {
let mut set = HashSet::new();
set.insert(entry_vid);
dom.insert(vid, set);
} else {
dom.insert(vid, all_blocks_set.clone());
}
}
let mut changed = true;
while changed {
changed = false;
for &vid in &block_vids {
if vid == entry_vid {
continue;
}
let mut new_dom: Option<HashSet<u64>> = None;
if let Some(preds) = pred_map.get(&vid) {
for pred in preds {
let pred_vid = pred.borrow().vid;
if let Some(pred_dom) = dom.get(&pred_vid) {
match &new_dom {
None => new_dom = Some(pred_dom.clone()),
Some(existing) => {
new_dom = Some(existing.intersection(pred_dom).cloned().collect());
}
}
}
}
}
if let Some(mut nd) = new_dom {
nd.insert(vid);
if nd != dom[&vid] {
dom.insert(vid, nd);
changed = true;
}
}
}
}
let mut idom: HashMap<u64, Option<u64>> = HashMap::new();
idom.insert(entry_vid, None);
for &vid in &block_vids {
if vid == entry_vid {
continue;
}
if let Some(dom_set) = dom.get(&vid) {
let mut best: Option<u64> = None;
for &d in dom_set.iter() {
if d == vid {
continue;
}
let mut is_immediate = true;
for &other in dom_set.iter() {
if other == vid || other == d {
continue;
}
if let Some(other_dom) = dom.get(&other) {
if other_dom.contains(&d) {
is_immediate = false;
break;
}
}
}
if is_immediate {
match best {
None => best = Some(d),
Some(b) if d > b => best = Some(d),
_ => {}
}
}
}
idom.insert(vid, best);
}
}
idom
}
fn dominates(dom_info: &HashMap<u64, Option<u64>>, a: &ValueRef, b: &ValueRef) -> bool {
let a_vid = a.borrow().vid;
let mut current_vid = b.borrow().vid;
loop {
if current_vid == a_vid {
return true;
}
match dom_info.get(¤t_vid) {
Some(Some(idom)) => current_vid = *idom,
_ => break,
}
}
false
}
fn get_terminator(block: &ValueRef) -> Option<ValueRef> {
let bb = block.borrow();
if !bb.is_basic_block() {
return None;
}
bb.operands.last().cloned()
}
fn get_branch_targets(term: &llvm_native_core::value::Value) -> Vec<ValueRef> {
match term.get_opcode() {
Some(Opcode::Br) => match term.operands.len() {
1 => vec![term.operands[0].clone()],
3 => vec![term.operands[1].clone(), term.operands[2].clone()],
_ => vec![],
},
Some(Opcode::Switch) => {
if term.operands.len() >= 2 {
let mut targets = vec![term.operands[1].clone()];
for i in (2..term.operands.len()).step_by(2) {
if i + 1 < term.operands.len() {
targets.push(term.operands[i + 1].clone());
}
}
targets
} else {
vec![]
}
}
_ => term.successors.clone(),
}
}
fn build_pred_map(func: &ValueRef) -> HashMap<u64, Vec<ValueRef>> {
let mut map: HashMap<u64, Vec<ValueRef>> = HashMap::new();
let f = func.borrow();
for op in &f.operands {
let bb = op.borrow();
if !bb.is_basic_block() {
continue;
}
if let Some(term) = get_terminator(op) {
let term_borrow = term.borrow();
let targets = get_branch_targets(&term_borrow);
for target in &targets {
let target_vid = target.borrow().vid;
map.entry(target_vid)
.or_insert_with(Vec::new)
.push(op.clone());
}
}
}
map
}
fn collect_loop_blocks(
header: &ValueRef,
backedge_src: &ValueRef,
pred_map: &HashMap<u64, Vec<ValueRef>>,
_dom_info: &HashMap<u64, Option<u64>>,
) -> Vec<ValueRef> {
let mut blocks = Vec::new();
let mut visited = HashSet::new();
let mut stack = vec![backedge_src.clone()];
let header_vid = header.borrow().vid;
visited.insert(header_vid);
blocks.push(header.clone());
while let Some(block) = stack.pop() {
let block_vid = block.borrow().vid;
if block_vid == header_vid {
continue;
}
if !visited.insert(block_vid) {
continue;
}
blocks.push(block.clone());
if let Some(preds) = pred_map.get(&block_vid) {
for pred in preds {
let pred_vid = pred.borrow().vid;
if !visited.contains(&pred_vid) {
stack.push(pred.clone());
}
}
}
}
blocks
}
fn compute_loop_depth(header: &ValueRef, existing_loops: &[LoopInfo]) -> u32 {
let header_vid = header.borrow().vid;
for li in existing_loops {
let block_vids: HashSet<u64> = li.blocks.iter().map(|b| b.borrow().vid).collect();
if block_vids.contains(&header_vid) && li.header.borrow().vid != header_vid {
return li.depth + 1;
}
}
0
}
fn find_parent_loop(header: &ValueRef, existing_loops: &[LoopInfo]) -> Option<usize> {
let header_vid = header.borrow().vid;
for (i, li) in existing_loops.iter().enumerate() {
let block_vids: HashSet<u64> = li.blocks.iter().map(|b| b.borrow().vid).collect();
if block_vids.contains(&header_vid) && li.header.borrow().vid != header_vid {
return Some(i);
}
}
None
}
fn is_latch(block: &ValueRef, loop_info: &LoopInfo) -> bool {
if let Some(latch) = &loop_info.latch {
std::ptr::addr_eq(Rc::as_ptr(block), Rc::as_ptr(latch))
} else {
false
}
}
use std::rc::Rc;
#[derive(Debug, Default)]
pub struct DedicatedExitCreator {
pub exits_created: usize,
pub loop_exits: HashMap<usize, ValueRef>,
}
impl DedicatedExitCreator {
pub fn new() -> Self {
Self::default()
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.exits_created = 0;
let f = func.borrow();
for bb in &f.operands {
if bb.borrow().subclass != SubclassKind::BasicBlock {
continue;
}
if self.needs_dedicated_exit(bb) {
self.create_dedicated_exit(bb, func);
}
}
self.exits_created
}
fn needs_dedicated_exit(&self, bb: &ValueRef) -> bool {
let preds = get_predecessor_list(bb);
preds.len() >= 2
}
fn create_dedicated_exit(&mut self, bb: &ValueRef, _func: &ValueRef) {
let _exit = llvm_native_core::basic_block::new_basic_block("loop_exit");
self.exits_created += 1;
let bb_vid = bb.borrow().vid as usize;
let _ = bb_vid;
}
}
fn get_predecessor_list(_bb: &ValueRef) -> Vec<ValueRef> {
Vec::new() }
#[derive(Debug, Default)]
pub struct PreheaderInserter {
pub preheaders_inserted: usize,
pub latches_normalized: usize,
}
impl PreheaderInserter {
pub fn new() -> Self {
Self::default()
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.preheaders_inserted = 0;
self.latches_normalized = 0;
let f = func.borrow();
for bb in &f.operands {
if bb.borrow().subclass == SubclassKind::BasicBlock {
if self.needs_preheader(bb) {
self.insert_preheader(bb, func);
}
if self.needs_latch_normalization(bb) {
self.latches_normalized += 1;
}
}
}
self.preheaders_inserted
}
fn needs_preheader(&self, _header: &ValueRef) -> bool {
true }
fn insert_preheader(&mut self, _header: &ValueRef, _func: &ValueRef) {
let _preheader = llvm_native_core::basic_block::new_basic_block("preheader");
self.preheaders_inserted += 1;
}
fn needs_latch_normalization(&self, _latch: &ValueRef) -> bool {
true }
}
#[derive(Debug, Default)]
pub struct BackedgeCanonicalizer {
pub backedges_canonicalized: usize,
}
impl BackedgeCanonicalizer {
pub fn new() -> Self {
Self::default()
}
pub fn run_on_function(&mut self, func: &ValueRef) -> usize {
self.backedges_canonicalized = 0;
let f = func.borrow();
for bb in &f.operands {
if bb.borrow().subclass != SubclassKind::BasicBlock {
continue;
}
if self.has_multiple_backedges(bb) {
self.canonicalize(bb);
self.backedges_canonicalized += 1;
}
}
self.backedges_canonicalized
}
fn has_multiple_backedges(&self, _bb: &ValueRef) -> bool {
false }
fn canonicalize(&mut self, _bb: &ValueRef) {
}
}
pub fn run_loop_simplify_extended(func: &ValueRef) -> usize {
let mut exits = DedicatedExitCreator::new();
let mut preheaders = PreheaderInserter::new();
let mut backedges = BackedgeCanonicalizer::new();
exits.run_on_function(func) + preheaders.run_on_function(func) + backedges.run_on_function(func)
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::basic_block::new_basic_block;
use llvm_native_core::constants::const_bool;
use llvm_native_core::context::LLVMContext;
use llvm_native_core::function;
use llvm_native_core::instruction;
fn make_test_function(ctx: &mut LLVMContext) -> ValueRef {
function::new_function("test", ctx.void_ty(), &[])
}
#[test]
fn test_loop_simplify_pass_creation() {
let pass = LoopSimplifyPass::new();
assert_eq!(pass.simplified, 0);
assert_eq!(pass.preheaders_inserted, 0);
assert_eq!(pass.exits_inserted, 0);
}
#[test]
fn test_find_simple_loop() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let header = new_basic_block("header");
let body = new_basic_block("body");
let exit = new_basic_block("exit");
let br_entry = instruction::br(header.clone());
entry.borrow_mut().push_operand(br_entry);
let cond = const_bool(true);
let br_header = instruction::br_cond(cond, body.clone(), exit.clone());
header.borrow_mut().push_operand(br_header);
let br_body = instruction::br(header.clone());
body.borrow_mut().push_operand(br_body);
let ret = instruction::ret_void();
exit.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(body.clone());
func.borrow_mut().push_operand(exit.clone());
let loops = pass.find_loops(&func);
assert!(!loops.is_empty(), "Should detect the loop");
assert_eq!(loops[0].header.borrow().name, "header");
}
#[test]
fn test_no_loop_in_straight_line() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let mid = new_basic_block("mid");
let end = new_basic_block("end");
let br1 = instruction::br(mid.clone());
entry.borrow_mut().push_operand(br1);
let br2 = instruction::br(end.clone());
mid.borrow_mut().push_operand(br2);
let ret = instruction::ret_void();
end.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(mid.clone());
func.borrow_mut().push_operand(end.clone());
let loops = pass.find_loops(&func);
assert!(loops.is_empty(), "No loops in straight-line code");
}
#[test]
fn test_run_on_function_no_loops() {
let mut pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let ret = instruction::ret_void();
entry.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry.clone());
let result = pass.run_on_function(&func);
assert_eq!(result, 0, "No loops to simplify");
}
#[test]
fn test_compute_dominator_tree_simple() {
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let block_a = new_basic_block("A");
let br = instruction::br(block_a.clone());
entry.borrow_mut().push_operand(br);
let ret = instruction::ret_void();
block_a.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(block_a.clone());
let dom = compute_dominator_tree(&func);
assert!(dom.contains_key(&entry.borrow().vid));
assert!(dom.contains_key(&block_a.borrow().vid));
assert_eq!(dom.get(&entry.borrow().vid), Some(&None));
}
#[test]
fn test_split_edge() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let from = new_basic_block("from");
let to = new_basic_block("to");
let br = instruction::br(to.clone());
from.borrow_mut().push_operand(br);
let ret = instruction::ret_void();
to.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(from.clone());
func.borrow_mut().push_operand(to.clone());
let new_block = pass.split_edge(&from, &to, &func);
assert!(new_block.is_some(), "Should create a split block");
let nb = new_block.unwrap();
let nb_bb = nb.borrow();
let last = nb_bb.operands.last().unwrap();
let inst = last.borrow();
assert_eq!(inst.get_opcode(), Some(Opcode::Br));
assert_eq!(inst.operands.len(), 1);
}
#[test]
fn test_insert_preheader() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry1 = new_basic_block("entry1");
let entry2 = new_basic_block("entry2");
let header = new_basic_block("header");
let latch = new_basic_block("latch");
let exit = new_basic_block("exit");
let br1 = instruction::br(header.clone());
entry1.borrow_mut().push_operand(br1);
let br2 = instruction::br(header.clone());
entry2.borrow_mut().push_operand(br2);
let cond = const_bool(true);
let br_h = instruction::br_cond(cond, latch.clone(), exit.clone());
header.borrow_mut().push_operand(br_h);
let br_latch = instruction::br(header.clone());
latch.borrow_mut().push_operand(br_latch);
let ret = instruction::ret_void();
exit.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry1.clone());
func.borrow_mut().push_operand(entry2.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(latch.clone());
func.borrow_mut().push_operand(exit.clone());
let loops = pass.find_loops(&func);
assert!(!loops.is_empty(), "Should find the loop");
let result = pass.insert_preheader(&loops[0], &func);
assert!(
result,
"Should insert preheader when multiple outside predecessors exist"
);
}
#[test]
fn test_insert_unique_exits() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let header = new_basic_block("header");
let body = new_basic_block("body");
let shared_exit = new_basic_block("shared_exit");
let outside = new_basic_block("outside");
let br_ent = instruction::br(header.clone());
entry.borrow_mut().push_operand(br_ent);
let cond = const_bool(true);
let br_h = instruction::br_cond(cond, body.clone(), shared_exit.clone());
header.borrow_mut().push_operand(br_h);
let br_body = instruction::br(header.clone());
body.borrow_mut().push_operand(br_body);
let br_out = instruction::br(shared_exit.clone());
outside.borrow_mut().push_operand(br_out);
let ret = instruction::ret_void();
shared_exit.borrow_mut().push_operand(ret);
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(body.clone());
func.borrow_mut().push_operand(shared_exit.clone());
func.borrow_mut().push_operand(outside.clone());
let loops = pass.find_loops(&func);
if !loops.is_empty() {
let exits_added = pass.insert_unique_exits(&loops[0], &func);
assert!(exits_added >= 1, "Should insert exit block for shared exit");
}
}
#[test]
fn test_is_loop_header() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let header = new_basic_block("header");
let body = new_basic_block("body");
let br_ent = instruction::br(header.clone());
entry.borrow_mut().push_operand(br_ent);
let cond = const_bool(true);
let br_h = instruction::br_cond(cond, body.clone(), entry.clone());
header.borrow_mut().push_operand(br_h);
let br_body = instruction::br(header.clone());
body.borrow_mut().push_operand(br_body);
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(body.clone());
let dom = compute_dominator_tree(&func);
let is_header = pass.is_loop_header(&header, &dom, &func);
assert!(is_header, "Header should be identified as loop header");
}
#[test]
fn test_find_loops_nested() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let outer_h = new_basic_block("outer_header");
let inner_h = new_basic_block("inner_header");
let inner_b = new_basic_block("inner_body");
let outer_b = new_basic_block("outer_body");
let exit = new_basic_block("exit");
entry
.borrow_mut()
.push_operand(instruction::br(outer_h.clone()));
outer_h
.borrow_mut()
.push_operand(instruction::br(inner_h.clone()));
let cond1 = const_bool(true);
inner_h.borrow_mut().push_operand(instruction::br_cond(
cond1,
inner_b.clone(),
outer_b.clone(),
));
inner_b
.borrow_mut()
.push_operand(instruction::br(inner_h.clone()));
let cond2 = const_bool(true);
outer_b.borrow_mut().push_operand(instruction::br_cond(
cond2,
outer_h.clone(),
exit.clone(),
));
exit.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(outer_h.clone());
func.borrow_mut().push_operand(inner_h.clone());
func.borrow_mut().push_operand(inner_b.clone());
func.borrow_mut().push_operand(outer_b.clone());
func.borrow_mut().push_operand(exit.clone());
let loops = pass.find_loops(&func);
assert!(
loops.len() >= 1,
"Should find at least one loop (got {})",
loops.len()
);
}
#[test]
fn test_simplify_loop_basic() {
let mut pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let header = new_basic_block("header");
let body = new_basic_block("body");
let exit = new_basic_block("exit");
entry
.borrow_mut()
.push_operand(instruction::br(header.clone()));
let cond = const_bool(true);
header
.borrow_mut()
.push_operand(instruction::br_cond(cond, body.clone(), exit.clone()));
body.borrow_mut()
.push_operand(instruction::br(header.clone()));
exit.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(body.clone());
func.borrow_mut().push_operand(exit.clone());
let loops = pass.find_loops(&func);
assert!(!loops.is_empty());
let changes = pass.simplify_loop(&loops[0], &func);
assert!(changes <= 2, "At most minor simplifications");
}
#[test]
fn test_run_on_function_with_loop() {
let mut pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let header = new_basic_block("header");
let body = new_basic_block("body");
let exit = new_basic_block("exit");
entry
.borrow_mut()
.push_operand(instruction::br(header.clone()));
let cond = const_bool(true);
header
.borrow_mut()
.push_operand(instruction::br_cond(cond, body.clone(), exit.clone()));
body.borrow_mut()
.push_operand(instruction::br(header.clone()));
exit.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(body.clone());
func.borrow_mut().push_operand(exit.clone());
let result = pass.run_on_function(&func);
assert_eq!(result, 0, "No transforms needed on simple canonical loop");
assert_eq!(pass.simplified, 0);
}
#[test]
fn test_compute_dominator_tree_diamond() {
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let left = new_basic_block("left");
let right = new_basic_block("right");
let merge = new_basic_block("merge");
let cond = const_bool(true);
entry
.borrow_mut()
.push_operand(instruction::br_cond(cond, left.clone(), right.clone()));
left.borrow_mut()
.push_operand(instruction::br(merge.clone()));
right
.borrow_mut()
.push_operand(instruction::br(merge.clone()));
merge.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(left.clone());
func.borrow_mut().push_operand(right.clone());
func.borrow_mut().push_operand(merge.clone());
let dom = compute_dominator_tree(&func);
let entry_vid = entry.borrow().vid;
let merge_vid = merge.borrow().vid;
assert!(
dominates(&dom, &entry, &merge),
"Entry should dominate merge"
);
assert!(
!dominates(&dom, &left, &merge),
"Left should not dominate merge"
);
}
#[test]
fn test_canonicalize_backedge_single() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let header = new_basic_block("header");
let body = new_basic_block("body");
let exit = new_basic_block("exit");
entry
.borrow_mut()
.push_operand(instruction::br(header.clone()));
let cond = const_bool(true);
header
.borrow_mut()
.push_operand(instruction::br_cond(cond, body.clone(), exit.clone()));
body.borrow_mut()
.push_operand(instruction::br(header.clone()));
exit.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(body.clone());
func.borrow_mut().push_operand(exit.clone());
let loops = pass.find_loops(&func);
if !loops.is_empty() {
let result = pass.canonicalize_backedge(&loops[0], &func);
assert!(!result, "Single backedge is already canonical");
}
}
#[test]
fn test_separate_latch_single() {
let pass = LoopSimplifyPass::new();
let mut ctx = LLVMContext::new();
let func = make_test_function(&mut ctx);
let entry = new_basic_block("entry");
let header = new_basic_block("header");
let body = new_basic_block("body");
let exit = new_basic_block("exit");
entry
.borrow_mut()
.push_operand(instruction::br(header.clone()));
let cond = const_bool(true);
header
.borrow_mut()
.push_operand(instruction::br_cond(cond, body.clone(), exit.clone()));
body.borrow_mut()
.push_operand(instruction::br(header.clone()));
exit.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(header.clone());
func.borrow_mut().push_operand(body.clone());
func.borrow_mut().push_operand(exit.clone());
let loops = pass.find_loops(&func);
if !loops.is_empty() {
assert!(
loops[0].latch.is_some(),
"Latch should be set for single-backedge loop"
);
let needs_work = pass.separate_latch(&loops[0], &func);
assert!(!needs_work, "No latch separation needed for single latch");
}
}
}