use std::collections::BTreeSet;
use crate::cfg::{BlockRef, Cfg, DominatorTree, GraphFacts};
use super::common::{BranchCandidate, BranchKind, BranchRegionFact};
use super::helpers::{collect_forward_region_blocks, collect_merge_arm_preds};
pub(super) fn analyze_branches(cfg: &Cfg, graph_facts: &GraphFacts) -> Vec<BranchCandidate> {
let mut branch_candidates: Vec<_> = cfg
.block_order
.iter()
.copied()
.filter(|header| cfg.reachable_blocks.contains(header))
.filter_map(|header| {
let (then_edge_ref, else_edge_ref) = cfg.branch_edges(header)?;
let then_entry = cfg.edges[then_edge_ref.index()].to;
let else_entry = cfg.edges[else_edge_ref.index()].to;
if then_entry == else_entry {
return None;
}
classify_one_arm_branch(cfg, header, then_entry, else_entry)
.or_else(|| {
classify_if_else_branch(cfg, graph_facts, header, then_entry, else_entry)
})
.or_else(|| classify_guard_branch(cfg, header, then_entry, else_entry))
})
.collect();
branch_candidates.sort_by_key(|candidate| candidate.header);
branch_candidates
}
pub(super) fn analyze_branch_regions(
cfg: &Cfg,
graph_facts: &GraphFacts,
branch_candidates: &[BranchCandidate],
) -> Vec<BranchRegionFact> {
let mut branch_regions = Vec::new();
for candidate in branch_candidates {
let Some(merge) = candidate.merge else {
continue;
};
branch_regions.push(BranchRegionFact {
header: candidate.header,
merge,
kind: candidate.kind,
flow_blocks: collect_branch_region_blocks(cfg, candidate, merge, None),
structured_blocks: collect_branch_region_blocks(
cfg,
candidate,
merge,
Some(&graph_facts.dominator_tree),
),
then_merge_preds: collect_merge_arm_preds(
cfg,
candidate.then_entry,
merge,
),
else_merge_preds: candidate
.else_entry
.map(|else_entry| {
collect_merge_arm_preds(cfg, else_entry, merge)
})
.unwrap_or_default(),
});
}
branch_regions.sort_by_key(|fact| (fact.header, fact.merge));
branch_regions
}
fn collect_branch_region_blocks(
cfg: &Cfg,
candidate: &BranchCandidate,
merge: BlockRef,
dom_tree: Option<&DominatorTree>,
) -> BTreeSet<BlockRef> {
let mut blocks = BTreeSet::from([candidate.header]);
blocks.extend(collect_forward_region_blocks(
cfg,
std::iter::once(candidate.then_entry).chain(candidate.else_entry),
Some(merge),
dom_tree.map(|tree| (candidate.header, tree)),
));
blocks
}
fn classify_one_arm_branch(
cfg: &Cfg,
header: BlockRef,
then_entry: BlockRef,
else_entry: BlockRef,
) -> Option<BranchCandidate> {
let then_reaches_else = cfg.can_reach(then_entry, else_entry);
let else_reaches_then = cfg.can_reach(else_entry, then_entry);
match (then_reaches_else, else_reaches_then) {
(true, false) => Some(BranchCandidate {
header,
then_entry,
else_entry: None,
merge: Some(else_entry),
kind: BranchKind::IfThen,
invert_hint: false,
}),
(false, true) => Some(BranchCandidate {
header,
then_entry: else_entry,
else_entry: None,
merge: Some(then_entry),
kind: BranchKind::IfThen,
invert_hint: true,
}),
_ => None,
}
}
fn classify_if_else_branch(
cfg: &Cfg,
graph_facts: &GraphFacts,
header: BlockRef,
then_entry: BlockRef,
else_entry: BlockRef,
) -> Option<BranchCandidate> {
let merge = graph_facts.nearest_common_postdom(then_entry, else_entry)?;
if merge == cfg.exit_block {
let soft = find_soft_merge(cfg, graph_facts, then_entry, else_entry);
return Some(BranchCandidate {
header,
then_entry,
else_entry: Some(else_entry),
merge: soft,
kind: BranchKind::IfElse,
invert_hint: false,
});
}
if merge == then_entry {
return Some(BranchCandidate {
header,
then_entry: else_entry,
else_entry: None,
merge: Some(then_entry),
kind: BranchKind::IfThen,
invert_hint: true,
});
}
if merge == else_entry {
return Some(BranchCandidate {
header,
then_entry,
else_entry: None,
merge: Some(else_entry),
kind: BranchKind::IfThen,
invert_hint: false,
});
}
Some(BranchCandidate {
header,
then_entry,
else_entry: Some(else_entry),
merge: Some(merge),
kind: BranchKind::IfElse,
invert_hint: false,
})
}
fn classify_guard_branch(
cfg: &Cfg,
header: BlockRef,
then_entry: BlockRef,
else_entry: BlockRef,
) -> Option<BranchCandidate> {
if cfg.can_reach(then_entry, else_entry) || cfg.can_reach(else_entry, then_entry) {
return None;
}
let then_score = branch_continuation_score(cfg, then_entry);
let else_score = branch_continuation_score(cfg, else_entry);
if then_score == else_score {
return None;
}
let (continuation, side, invert_hint) = if then_score > else_score {
(then_entry, else_entry, true)
} else {
(else_entry, then_entry, false)
};
Some(BranchCandidate {
header,
then_entry: side,
else_entry: None,
merge: Some(continuation),
kind: BranchKind::Guard,
invert_hint,
})
}
fn branch_continuation_score(cfg: &Cfg, start: BlockRef) -> usize {
let mut visited = BTreeSet::new();
let mut stack = vec![start];
while let Some(block) = stack.pop() {
if !cfg.reachable_blocks.contains(&block)
|| block == cfg.exit_block
|| !visited.insert(block)
{
continue;
}
for edge_ref in &cfg.succs[block.index()] {
stack.push(cfg.edges[edge_ref.index()].to);
}
}
visited.len()
}
fn find_soft_merge(
cfg: &Cfg,
graph_facts: &GraphFacts,
then_entry: BlockRef,
else_entry: BlockRef,
) -> Option<BlockRef> {
let pdom_parent = &graph_facts.post_dominator_tree.parent;
let walk_chain = |start: BlockRef, other: BlockRef| -> Option<BlockRef> {
let mut cursor = start;
loop {
let parent = (*pdom_parent.get(cursor.index())?)?;
if parent == cfg.exit_block {
return None;
}
if cfg.can_reach(other, parent) {
return Some(parent);
}
cursor = parent;
}
};
let else_candidate = walk_chain(else_entry, then_entry);
let then_candidate = walk_chain(then_entry, else_entry);
match (else_candidate, then_candidate) {
(Some(e), Some(t)) => {
if graph_facts.post_dominates(t, e) {
Some(e)
} else {
Some(t)
}
}
(Some(e), None) => Some(e),
(None, Some(t)) => Some(t),
(None, None) => None,
}
}