use std::collections::VecDeque;
use super::*;
pub(super) fn compute_phi_candidates(
cfg: &Cfg,
graph_facts: &GraphFacts,
defs: &[Def],
live_in: &[BTreeSet<Reg>],
block_out: &[FixedState],
fixed_def_lookup: &[FixedDefLookup],
) -> Vec<PhiCandidate> {
let mut def_blocks = BTreeMap::<Reg, BTreeSet<BlockRef>>::new();
for def in defs {
if cfg.reachable_blocks.contains(&def.block) {
def_blocks.entry(def.reg).or_default().insert(def.block);
}
}
let mut phi_candidates = Vec::new();
for (reg, blocks) in def_blocks {
let mut phi_blocks = BTreeSet::<BlockRef>::new();
let mut placed_phis = BTreeMap::<BlockRef, PhiCandidate>::new();
loop {
let mut placed = BTreeSet::new();
let mut worklist = blocks.iter().copied().collect::<VecDeque<_>>();
worklist.extend(phi_blocks.iter().copied());
let mut changed = false;
while let Some(block) = worklist.pop_front() {
for frontier_block in graph_facts.dominance_frontier_blocks(block) {
if !live_in[frontier_block.index()].contains(®)
|| !placed.insert(frontier_block)
{
continue;
}
if !phi_blocks.contains(&frontier_block)
&& let Some(candidate) = build_phi_candidate(
cfg,
graph_facts,
frontier_block,
reg,
block_out,
&phi_blocks,
fixed_def_lookup,
)
{
phi_blocks.insert(frontier_block);
placed_phis.insert(frontier_block, candidate);
changed = true;
}
if !block_defines_reg(cfg, frontier_block, reg, fixed_def_lookup) {
worklist.push_back(frontier_block);
}
}
}
if !changed {
break;
}
}
phi_candidates.extend(placed_phis.into_values());
}
phi_candidates.sort_by_key(|candidate| (candidate.block, candidate.reg));
for (index, candidate) in phi_candidates.iter_mut().enumerate() {
candidate.id = PhiId(index);
}
phi_candidates
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)]
struct PredSource {
reaching_defs: Vec<DefId>,
ancestor: Option<AncestorKind>,
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)]
enum AncestorKind {
DefBlock(BlockRef),
PhiBlock(BlockRef),
}
fn build_phi_candidate(
cfg: &Cfg,
graph_facts: &GraphFacts,
block: BlockRef,
reg: Reg,
block_out: &[FixedState],
phi_blocks: &BTreeSet<BlockRef>,
fixed_def_lookup: &[FixedDefLookup],
) -> Option<PhiCandidate> {
let mut incoming = Vec::new();
let mut distinct_sources = BTreeSet::<PredSource>::new();
for edge_ref in &cfg.preds[block.index()] {
let pred = cfg.edges[edge_ref.index()].from;
if !cfg.reachable_blocks.contains(&pred) {
continue;
}
let defs = block_out
.get(pred.index())
.map(|defs_by_reg| defs_by_reg.get(reg))?
.clone();
let mut reaching_defs: Vec<DefId> = defs.iter().copied().collect();
reaching_defs.sort();
let ancestor = nearest_def_or_phi_ancestor(
cfg,
graph_facts,
phi_blocks,
fixed_def_lookup,
pred,
reg,
block,
);
distinct_sources.insert(PredSource {
reaching_defs,
ancestor,
});
incoming.push(PhiIncoming {
pred,
defs: defs.iter().copied().collect(),
});
}
if incoming.len() < 2 || distinct_sources.len() < 2 {
return None;
}
incoming.sort_by_key(|incoming| incoming.pred);
Some(PhiCandidate {
id: PhiId(0),
block,
reg,
incoming,
})
}
fn nearest_def_or_phi_ancestor(
cfg: &Cfg,
graph_facts: &GraphFacts,
phi_blocks: &BTreeSet<BlockRef>,
fixed_def_lookup: &[FixedDefLookup],
pred: BlockRef,
reg: Reg,
exclude: BlockRef,
) -> Option<AncestorKind> {
let parents = &graph_facts.dominator_tree.parent;
let mut cursor = Some(pred);
while let Some(node) = cursor {
if node != exclude {
if block_defines_reg(cfg, node, reg, fixed_def_lookup) {
return Some(AncestorKind::DefBlock(node));
}
if phi_blocks.contains(&node) {
return Some(AncestorKind::PhiBlock(node));
}
}
cursor = parents.get(node.index()).copied().flatten();
}
None
}
fn block_defines_reg(
cfg: &Cfg,
block: BlockRef,
reg: Reg,
fixed_def_lookup: &[FixedDefLookup],
) -> bool {
let Some(mut instr_indices) = super::instr_indices(cfg, block) else {
return false;
};
instr_indices.any(|instr_index| fixed_def_lookup[instr_index].defines(reg))
}