use super::cfg::{BlockCFG, CFG};
use crate::{
cfgir::ast::remap_labels,
hlir::ast::{BasicBlocks, Command_, Label},
};
use std::collections::{BTreeMap, BTreeSet};
pub fn optimize(cfg: &mut BlockCFG) -> bool {
let changed = optimize_(cfg.start_block(), cfg.blocks_mut());
if changed {
let dead_blocks = cfg.recompute();
assert!(dead_blocks.is_empty())
}
changed
}
fn optimize_(start: Label, blocks: &mut BasicBlocks) -> bool {
let single_target_labels = find_single_target_labels(start, blocks);
inline_single_target_blocks(&single_target_labels, start, blocks)
}
fn find_single_target_labels(start: Label, blocks: &BasicBlocks) -> BTreeSet<Label> {
use Command_ as C;
let mut counts = BTreeMap::new();
counts.insert(start, 1);
for block in blocks.values() {
match &block.back().unwrap().value {
C::JumpIf {
cond: _cond,
if_true,
if_false,
} => {
*counts.entry(*if_true).or_insert(0) += 1;
*counts.entry(*if_false).or_insert(0) += 1
}
C::Jump { target, .. } => *counts.entry(*target).or_insert(0) += 1,
_ => (),
}
}
counts
.into_iter()
.filter(|(_, count)| *count == 1)
.map(|(lbl, _)| lbl)
.collect()
}
#[allow(clippy::needless_collect)]
fn inline_single_target_blocks(
single_jump_targets: &BTreeSet<Label>,
start: Label,
blocks: &mut BasicBlocks,
) -> bool {
let labels_vec = blocks.keys().cloned().collect::<Vec<_>>();
let mut labels = labels_vec.into_iter();
let mut next = labels.next();
let mut working_blocks = std::mem::take(blocks);
let finished_blocks = blocks;
let mut remapping = BTreeMap::new();
while let Some(cur) = next {
let mut block = match working_blocks.remove(&cur) {
None => {
next = labels.next();
continue;
}
Some(b) => b,
};
match block.back().unwrap() {
sp!(_, Command_::Jump { target, .. }) if single_jump_targets.contains(target) => {
remapping.insert(cur, *target);
let target_block = working_blocks.remove(target).unwrap();
block.pop_back();
block.extend(target_block);
working_blocks.insert(cur, block);
}
_ => {
finished_blocks.insert(cur, block);
next = labels.next();
}
}
}
let changed = !remapping.is_empty();
remap_to_last_target(remapping, start, finished_blocks);
changed
}
fn remap_to_last_target(
mut remapping: BTreeMap<Label, Label>,
start: Label,
blocks: &mut BasicBlocks,
) {
remapping.remove(&start);
if remapping.is_empty() {
return;
}
for label in blocks.keys() {
remapping.entry(*label).or_insert(*label);
}
let owned_blocks = std::mem::take(blocks);
let (_start, remapped_blocks) = remap_labels(&remapping, start, owned_blocks);
*blocks = remapped_blocks;
}