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::new();
for header in &cfg.block_order {
let header = *header;
if !cfg.reachable_blocks.contains(&header) {
continue;
}
let Some((then_edge_ref, else_edge_ref)) = cfg.branch_edges(header) else {
continue;
};
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 {
continue;
}
if let Some(candidate) = 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))
{
branch_candidates.push(candidate);
}
}
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,
&graph_facts.dominator_tree,
candidate.then_entry,
merge,
),
else_merge_preds: candidate
.else_entry
.map(|else_entry| {
collect_merge_arm_preds(cfg, &graph_facts.dominator_tree, 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 {
return Some(BranchCandidate {
header,
then_entry,
else_entry: Some(else_entry),
merge: None,
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()
}