use std::collections::{BTreeMap, BTreeSet};
use crate::{
analysis::ssa::{
ConstEvaluator, ConstValue, PhiNode, PhiOperand, SsaBlock, SsaFunction, SsaOp, SsaVarId,
VariableOrigin,
},
utils::{graph::NodeId, BitSet},
};
pub struct PhiAnalyzer<'a> {
ssa: &'a SsaFunction,
}
impl<'a> PhiAnalyzer<'a> {
#[must_use]
pub fn new(ssa: &'a SsaFunction) -> Self {
Self { ssa }
}
#[must_use]
pub fn ssa(&self) -> &SsaFunction {
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,
) -> Option<ConstValue> {
let operands = phi.operands();
if operands.is_empty() {
return None;
}
let first_value = evaluator.evaluate_var(operands[0].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> = dyn Fn(usize, &[SsaBlock]) -> Option<usize> + 'a;
#[allow(clippy::too_many_arguments)]
pub(crate) fn place_pruned_phis(
blocks: &mut [SsaBlock],
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<'_>>,
) -> 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 node_id.index() < dominance_frontiers.len() {
for frontier_idx in dominance_frontiers[node_id.index()].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 std::collections::BTreeSet;
use crate::{
analysis::ssa::{
ConstEvaluator, ConstValue, DefSite, PhiAnalyzer, PhiNode, PhiOperand, SsaBlock,
SsaFunction, SsaInstruction, SsaOp, SsaType, SsaVarId, VariableOrigin,
},
metadata::typesystem::PointerSize,
};
#[test]
fn test_phi_analyzer_creation() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
assert_eq!(analyzer.ssa().num_args(), 0);
assert_eq!(analyzer.ssa().num_locals(), 0);
}
#[test]
fn test_is_trivial_single_source() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let source = SsaVarId::from_index(1);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(source, 0));
phi.add_operand(PhiOperand::new(source, 1));
assert_eq!(analyzer.is_trivial(&phi), Some(source));
}
#[test]
fn test_is_trivial_with_self_reference() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let source = SsaVarId::from_index(1);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(source, 0));
phi.add_operand(PhiOperand::new(result, 1)); phi.add_operand(PhiOperand::new(source, 2));
assert_eq!(analyzer.is_trivial(&phi), Some(source));
}
#[test]
fn test_is_trivial_multiple_sources() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let source1 = SsaVarId::from_index(1);
let source2 = SsaVarId::from_index(2);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(source1, 0));
phi.add_operand(PhiOperand::new(source2, 1));
assert_eq!(analyzer.is_trivial(&phi), None);
}
#[test]
fn test_is_trivial_only_self_references() {
let ssa = 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_eq!(analyzer.is_trivial(&phi), None);
}
#[test]
fn test_uniform_constant_same_values() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let v1_id = ssa.create_variable(
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
let v2_id = ssa.create_variable(
VariableOrigin::Local(1),
0,
DefSite::instruction(0, 1),
SsaType::Unknown,
);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: v1_id,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: v2_id,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let analyzer = PhiAnalyzer::new(&ssa);
let mut evaluator = ConstEvaluator::new(&ssa, PointerSize::Bit64);
let phi_result = SsaVarId::from_index(0);
let mut phi = PhiNode::new(phi_result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v1_id, 0));
phi.add_operand(PhiOperand::new(v2_id, 1));
assert_eq!(
analyzer.uniform_constant(&phi, &mut evaluator),
Some(ConstValue::I32(42))
);
}
#[test]
fn test_uniform_constant_different_values() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let v1_id = ssa.create_variable(
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
let v2_id = ssa.create_variable(
VariableOrigin::Local(1),
0,
DefSite::instruction(0, 1),
SsaType::Unknown,
);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: v1_id,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: v2_id,
value: ConstValue::I32(99),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let analyzer = PhiAnalyzer::new(&ssa);
let mut evaluator = ConstEvaluator::new(&ssa, PointerSize::Bit64);
let phi_result = SsaVarId::from_index(0);
let mut phi = PhiNode::new(phi_result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v1_id, 0));
phi.add_operand(PhiOperand::new(v2_id, 1));
assert_eq!(analyzer.uniform_constant(&phi, &mut evaluator), None);
}
#[test]
fn test_uniform_constant_empty_phi() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let mut evaluator = ConstEvaluator::new(&ssa, PointerSize::Bit64);
let phi_result = SsaVarId::from_index(0);
let phi = PhiNode::new(phi_result, VariableOrigin::Local(0));
assert_eq!(analyzer.uniform_constant(&phi, &mut evaluator), None);
}
#[test]
fn test_uniform_constant_non_constant_operand() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let mut evaluator = ConstEvaluator::new(&ssa, PointerSize::Bit64);
let phi_result = SsaVarId::from_index(0);
let v1_id = SsaVarId::from_index(1);
let v2_id = SsaVarId::from_index(2);
let mut phi = PhiNode::new(phi_result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(v1_id, 0));
phi.add_operand(PhiOperand::new(v2_id, 1));
assert_eq!(analyzer.uniform_constant(&phi, &mut evaluator), None);
}
#[test]
fn test_find_defining_phi() {
let mut ssa = SsaFunction::new(0, 0);
let phi_result_id = ssa.create_variable(
VariableOrigin::Local(0),
0,
DefSite::phi(0),
SsaType::Unknown,
);
let mut block = SsaBlock::new(0);
let mut phi = PhiNode::new(phi_result_id, VariableOrigin::Local(0));
let operand_id = SsaVarId::from_index(0);
phi.add_operand(PhiOperand::new(operand_id, 1));
block.add_phi(phi);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let analyzer = PhiAnalyzer::new(&ssa);
let result = analyzer.find_phi_defining(phi_result_id);
assert!(result.is_some());
let (block_idx, found_phi) = result.unwrap();
assert_eq!(block_idx, 0);
assert_eq!(found_phi.result(), phi_result_id);
}
#[test]
fn test_find_defining_not_phi() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let var_id = ssa.create_variable(
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: var_id,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block);
let analyzer = PhiAnalyzer::new(&ssa);
assert!(analyzer.find_phi_defining(var_id).is_none());
}
#[test]
fn test_is_fully_self_referential_true() {
let ssa = 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_fully_self_referential(&phi));
}
#[test]
fn test_is_fully_self_referential_false() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let source = SsaVarId::from_index(1);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(source, 0));
phi.add_operand(PhiOperand::new(result, 1));
assert!(!analyzer.is_fully_self_referential(&phi));
}
#[test]
fn test_is_fully_self_referential_empty() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let phi = PhiNode::new(result, VariableOrigin::Local(0));
assert!(!analyzer.is_fully_self_referential(&phi));
}
#[test]
fn test_analyze_trivial_with_replacement() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let source = SsaVarId::from_index(1);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(source, 0));
phi.add_operand(PhiOperand::new(source, 1));
assert_eq!(analyzer.analyze_trivial(&phi), Some(Some(source)));
}
#[test]
fn test_analyze_trivial_self_referential_removal() {
let ssa = 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_eq!(analyzer.analyze_trivial(&phi), Some(None));
}
#[test]
fn test_analyze_trivial_not_trivial() {
let ssa = SsaFunction::new(0, 0);
let analyzer = PhiAnalyzer::new(&ssa);
let result = SsaVarId::from_index(0);
let source1 = SsaVarId::from_index(1);
let source2 = SsaVarId::from_index(2);
let mut phi = PhiNode::new(result, VariableOrigin::Local(0));
phi.add_operand(PhiOperand::new(source1, 0));
phi.add_operand(PhiOperand::new(source2, 1));
assert_eq!(analyzer.analyze_trivial(&phi), None);
}
#[test]
fn test_find_all_trivial() {
let mut ssa = SsaFunction::new(0, 0);
let mut block0 = SsaBlock::new(0);
let phi_result1 = SsaVarId::from_index(0);
let source1 = SsaVarId::from_index(1);
let mut phi1 = PhiNode::new(phi_result1, VariableOrigin::Local(0));
phi1.add_operand(PhiOperand::new(source1, 1)); block0.add_phi(phi1);
block0.add_instruction(SsaInstruction::synthetic(SsaOp::Jump { target: 1 }));
ssa.add_block(block0);
let mut block1 = SsaBlock::new(1);
let phi_result2 = SsaVarId::from_index(2);
let mut phi2 = PhiNode::new(phi_result2, VariableOrigin::Local(1));
phi2.add_operand(PhiOperand::new(phi_result2, 0)); phi2.add_operand(PhiOperand::new(phi_result2, 1));
block1.add_phi(phi2);
block1.add_instruction(SsaInstruction::synthetic(SsaOp::Return { value: None }));
ssa.add_block(block1);
let analyzer = PhiAnalyzer::new(&ssa);
let reachable: BTreeSet<usize> = [0, 1].iter().copied().collect();
let trivial = analyzer.find_all_trivial(&reachable);
assert_eq!(trivial.len(), 2);
assert!(trivial.contains(&(0, 0, Some(source1))));
assert!(trivial.contains(&(1, 0, None)));
}
}