use std::collections::{BTreeMap, BTreeSet};
use crate::{
analysis::consts::ConstEvaluator,
graph::NodeId,
ir::{
block::SsaBlock,
function::SsaFunction,
ops::SsaOp,
phi::{PhiNode, PhiOperand},
value::ConstValue,
variable::{SsaVarId, VariableOrigin},
},
target::Target,
BitSet,
};
pub struct PhiAnalyzer<'a, T: Target> {
ssa: &'a SsaFunction<T>,
}
impl<'a, T: Target> PhiAnalyzer<'a, T> {
#[must_use]
pub fn new(ssa: &'a SsaFunction<T>) -> Self {
Self { ssa }
}
#[must_use]
pub fn ssa(&self) -> &SsaFunction<T> {
self.ssa
}
#[must_use]
pub fn is_trivial(&self, phi: &PhiNode) -> Option<SsaVarId> {
let result = phi.result();
let unique_sources: BTreeSet<SsaVarId> = phi
.operands()
.iter()
.map(PhiOperand::value)
.filter(|&v| v != result)
.collect();
if unique_sources.len() == 1 {
let source = unique_sources.into_iter().next()?;
if self.ssa.would_create_self_reference(source, result) {
return None;
}
Some(source)
} else {
None
}
}
#[must_use]
pub fn is_fully_self_referential(&self, phi: &PhiNode) -> bool {
let result = phi.result();
!phi.operands().is_empty() && phi.operands().iter().all(|op| op.value() == result)
}
#[must_use]
pub fn analyze_trivial(&self, phi: &PhiNode) -> Option<Option<SsaVarId>> {
if let Some(source) = self.is_trivial(phi) {
return Some(Some(source));
}
if self.is_fully_self_referential(phi) {
return Some(None);
}
None
}
#[must_use]
pub fn find_all_trivial(
&self,
reachable: &BTreeSet<usize>,
) -> Vec<(usize, usize, Option<SsaVarId>)> {
let mut trivial = Vec::new();
for &block_idx in reachable {
if let Some(block) = self.ssa.block(block_idx) {
for (phi_idx, phi) in block.phi_nodes().iter().enumerate() {
if let Some(replacement) = self.analyze_trivial(phi) {
trivial.push((block_idx, phi_idx, replacement));
}
}
}
}
trivial
}
#[must_use]
pub fn collect_all_copies(&self) -> BTreeMap<SsaVarId, SsaVarId> {
let mut copies = BTreeMap::new();
for block in self.ssa.blocks() {
for instr in block.instructions() {
if let SsaOp::Copy { dest, src } = instr.op() {
copies.insert(*dest, *src);
}
}
for phi in block.phi_nodes() {
if let Some(source) = self.is_trivial(phi) {
copies.insert(phi.result(), source);
}
}
}
copies
}
pub fn uniform_constant(
&self,
phi: &PhiNode,
evaluator: &mut ConstEvaluator<'_, T>,
) -> Option<ConstValue<T>> {
let operands = phi.operands();
if operands.is_empty() {
return None;
}
let first_value = evaluator.evaluate_var(operands.first()?.value())?;
for operand in operands.iter().skip(1) {
let value = evaluator.evaluate_var(operand.value())?;
if value != first_value {
return None;
}
}
Some(first_value)
}
#[must_use]
pub fn find_phi_defining(&self, var: SsaVarId) -> Option<(usize, &PhiNode)> {
self.ssa.find_phi_defining(var)
}
}
pub(crate) type LeaveTargetFn<'a, T> = dyn Fn(usize, &[SsaBlock<T>]) -> Option<usize> + 'a;
#[allow(clippy::too_many_arguments)]
pub fn place_pruned_phis<T: Target>(
blocks: &mut [SsaBlock<T>],
defs: &BTreeMap<u32, BitSet>,
live_in: &BTreeMap<u32, BitSet>,
dominance_frontiers: &[BitSet],
reachable: Option<&BitSet>,
group_filter: &dyn Fn(u32) -> bool,
group_to_origin: &dyn Fn(u32) -> VariableOrigin,
leave_target_fn: Option<&LeaveTargetFn<'_, T>>,
) -> Vec<(usize, u32)> {
let block_count = blocks.len();
let mut placements: Vec<(usize, u32)> = Vec::new();
for (&group, def_blocks) in defs {
if !group_filter(group) {
continue;
}
let mut phi_blocks = BitSet::new(block_count);
let mut worklist: Vec<usize> = def_blocks.iter().collect();
while let Some(block_idx) = worklist.pop() {
let node_id = NodeId::new(block_idx);
if let Some(frontier) = dominance_frontiers.get(node_id.index()) {
for frontier_idx in frontier.iter() {
let is_reachable = reachable.is_none_or(|r| r.contains(frontier_idx));
if frontier_idx < block_count && is_reachable && phi_blocks.insert(frontier_idx)
{
worklist.push(frontier_idx);
}
}
}
if let Some(leave_fn) = leave_target_fn {
if let Some(target) = leave_fn(block_idx, blocks) {
let is_reachable = reachable.is_none_or(|r| r.contains(target));
if target < block_count && is_reachable && phi_blocks.insert(target) {
worklist.push(target);
}
}
}
}
let group_live_in = live_in.get(&group);
for phi_block_idx in phi_blocks.iter() {
if let Some(live_set) = group_live_in {
if !live_set.contains(phi_block_idx) {
continue;
}
}
if let Some(block) = blocks.get_mut(phi_block_idx) {
let origin = group_to_origin(group);
let phi = PhiNode::new(SsaVarId::PLACEHOLDER, origin);
block.add_phi(phi);
placements.push((phi_block_idx, group));
}
}
}
placements
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ir::{
function::SsaFunction,
phi::{PhiNode, PhiOperand},
variable::{SsaVarId, VariableOrigin},
},
testing::MockTarget,
};
#[test]
fn trivial_phi_single_operand() {
let ssa: SsaFunction<MockTarget> = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let v0 = SsaVarId::from_index(1);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v0, 0));
assert_eq!(analyzer.is_trivial(&phi), Some(v0));
}
#[test]
fn trivial_phi_all_same_operands() {
let ssa: SsaFunction<MockTarget> = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let v0 = SsaVarId::from_index(1);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v0, 0));
phi.add_operand(PhiOperand::new(v0, 1));
phi.add_operand(PhiOperand::new(v0, 2));
assert_eq!(analyzer.is_trivial(&phi), Some(v0));
}
#[test]
fn trivial_phi_with_self_references() {
let ssa: SsaFunction<MockTarget> = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let v0 = SsaVarId::from_index(1);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(result, 0)); phi.add_operand(PhiOperand::new(v0, 1));
phi.add_operand(PhiOperand::new(result, 2)); assert_eq!(analyzer.is_trivial(&phi), Some(v0));
}
#[test]
fn non_trivial_phi_different_operands() {
let ssa: SsaFunction<MockTarget> = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let v0 = SsaVarId::from_index(1);
let v1 = SsaVarId::from_index(2);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v0, 0));
phi.add_operand(PhiOperand::new(v1, 1));
assert_eq!(analyzer.is_trivial(&phi), None);
}
#[test]
fn trivial_phi_all_self_references() {
let ssa: SsaFunction<MockTarget> = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(result, 0));
phi.add_operand(PhiOperand::new(result, 1));
assert!(analyzer.is_trivial(&phi).is_none());
assert_eq!(analyzer.analyze_trivial(&phi), Some(None));
}
}