use std::collections::{HashMap, HashSet};
use crate::analysis::ssa::{DefSite, SsaFunction, SsaOp, SsaVarId, UseSite};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Location {
pub block: usize,
pub instruction: usize,
}
impl Location {
#[must_use]
pub const fn new(block: usize, instruction: usize) -> Self {
Self { block, instruction }
}
}
#[derive(Debug, Clone, Default)]
pub struct DefUseIndex {
definitions: HashMap<SsaVarId, DefSite>,
uses: HashMap<SsaVarId, Vec<UseSite>>,
defs_at_location: HashMap<Location, Vec<SsaVarId>>,
uses_at_location: HashMap<Location, Vec<SsaVarId>>,
defs_in_block: HashMap<usize, Vec<SsaVarId>>,
phi_defs: HashSet<SsaVarId>,
unused_vars: HashSet<SsaVarId>,
var_count: usize,
def_ops: Option<HashMap<SsaVarId, SsaOp>>,
}
impl DefUseIndex {
#[must_use]
pub fn build(ssa: &SsaFunction) -> Self {
let mut definitions = HashMap::new();
let mut uses: HashMap<SsaVarId, Vec<UseSite>> = HashMap::new();
let mut defs_at_location: HashMap<Location, Vec<SsaVarId>> = HashMap::new();
let mut uses_at_location: HashMap<Location, Vec<SsaVarId>> = HashMap::new();
let mut defs_in_block: HashMap<usize, Vec<SsaVarId>> = HashMap::new();
let mut phi_defs = HashSet::new();
for var in ssa.variables() {
let var_id = var.id();
let def_site = var.def_site();
definitions.insert(var_id, def_site);
if def_site.is_phi() {
phi_defs.insert(var_id);
}
if let Some(instr_idx) = def_site.instruction {
let loc = Location::new(def_site.block, instr_idx);
defs_at_location.entry(loc).or_default().push(var_id);
}
defs_in_block
.entry(def_site.block)
.or_default()
.push(var_id);
let var_uses: Vec<UseSite> = var.uses().to_vec();
for use_site in &var_uses {
let loc = Location::new(use_site.block, use_site.instruction);
uses_at_location.entry(loc).or_default().push(var_id);
}
uses.insert(var_id, var_uses);
}
let unused_vars: HashSet<SsaVarId> = uses
.iter()
.filter(|(_, use_sites)| use_sites.is_empty())
.map(|(var_id, _)| *var_id)
.collect();
Self {
definitions,
uses,
defs_at_location,
uses_at_location,
defs_in_block,
phi_defs,
unused_vars,
var_count: ssa.variable_count(),
def_ops: None,
}
}
#[must_use]
pub fn build_with_ops(ssa: &SsaFunction) -> Self {
let mut index = Self::build(ssa);
let mut def_ops = HashMap::new();
for (_block_idx, _instr_idx, instr) in ssa.iter_instructions() {
let op = instr.op();
if let Some(dest) = op.dest() {
def_ops.insert(dest, op.clone());
}
}
index.def_ops = Some(def_ops);
index
}
#[must_use]
pub fn build_with_ops_map(ssa: &SsaFunction) -> (Self, HashMap<SsaVarId, SsaOp>) {
let index = Self::build_with_ops(ssa);
let ops = index.def_ops.clone().unwrap_or_default();
(index, ops)
}
#[must_use]
pub fn has_ops(&self) -> bool {
self.def_ops.is_some()
}
#[must_use]
pub fn def_op(&self, var: SsaVarId) -> Option<&SsaOp> {
self.def_ops.as_ref()?.get(&var)
}
#[must_use]
pub fn full_definition(&self, var: SsaVarId) -> Option<(usize, usize, &SsaOp)> {
let site = self.def_site(var)?;
let instr = site.instruction?; let op = self.def_op(var)?;
Some((site.block, instr, op))
}
#[must_use]
pub fn def_site(&self, var: SsaVarId) -> Option<DefSite> {
self.definitions.get(&var).copied()
}
#[must_use]
pub fn uses_of(&self, var: SsaVarId) -> Option<&[UseSite]> {
self.uses.get(&var).map(Vec::as_slice)
}
#[must_use]
pub fn use_count(&self, var: SsaVarId) -> usize {
self.uses.get(&var).map_or(0, Vec::len)
}
#[must_use]
pub fn has_uses(&self, var: SsaVarId) -> bool {
self.uses.get(&var).is_some_and(|u| !u.is_empty())
}
#[must_use]
pub fn is_unused(&self, var: SsaVarId) -> bool {
self.unused_vars.contains(&var)
}
#[must_use]
pub fn is_phi_def(&self, var: SsaVarId) -> bool {
self.phi_defs.contains(&var)
}
#[must_use]
pub fn defs_at(&self, block: usize, instruction: usize) -> &[SsaVarId] {
let loc = Location::new(block, instruction);
self.defs_at_location.get(&loc).map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn uses_at(&self, block: usize, instruction: usize) -> &[SsaVarId] {
let loc = Location::new(block, instruction);
self.uses_at_location.get(&loc).map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn defs_in_block(&self, block: usize) -> &[SsaVarId] {
self.defs_in_block.get(&block).map_or(&[], Vec::as_slice)
}
#[must_use]
pub fn unused_variables(&self) -> &HashSet<SsaVarId> {
&self.unused_vars
}
#[must_use]
pub fn phi_definitions(&self) -> &HashSet<SsaVarId> {
&self.phi_defs
}
#[must_use]
pub fn variable_count(&self) -> usize {
self.var_count
}
#[must_use]
pub fn unused_count(&self) -> usize {
self.unused_vars.len()
}
#[must_use]
pub fn is_single_use(&self, var: SsaVarId) -> bool {
self.use_count(var) == 1
}
#[must_use]
pub fn only_used_in_phis(&self, var: SsaVarId) -> bool {
self.uses
.get(&var)
.is_some_and(|uses| !uses.is_empty() && uses.iter().all(|u| u.is_phi_operand))
}
#[must_use]
pub fn uses_in_block(&self, block: usize) -> HashSet<SsaVarId> {
let mut result = HashSet::new();
for (loc, vars) in &self.uses_at_location {
if loc.block == block {
result.extend(vars.iter().copied());
}
}
result
}
#[must_use]
pub fn single_use_site(&self, var: SsaVarId) -> Option<UseSite> {
self.uses
.get(&var)
.and_then(|uses| if uses.len() == 1 { Some(uses[0]) } else { None })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::ssa::{
ConstValue, SsaBlock, SsaFunction, SsaInstruction, SsaOp, SsaVarId, SsaVariable,
VariableOrigin,
};
fn make_test_ssa() -> (SsaFunction, SsaVarId, SsaVarId) {
let mut ssa = SsaFunction::new(0, 0);
let mut v0 = SsaVariable::new(VariableOrigin::Stack(0), 0, DefSite::instruction(0, 0));
let id0 = v0.id();
v0.add_use(UseSite::instruction(0, 1));
v0.add_use(UseSite::instruction(0, 1)); ssa.variables_mut().push(v0);
let mut v1 = SsaVariable::new(VariableOrigin::Stack(1), 0, DefSite::instruction(0, 1));
let id1 = v1.id();
v1.add_use(UseSite::instruction(0, 2));
ssa.variables_mut().push(v1);
let mut block = SsaBlock::new(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: id0,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Add {
dest: id1,
left: id0,
right: id0,
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(id1),
}));
ssa.add_block(block);
(ssa, id0, id1)
}
#[test]
fn test_build_index() {
let (ssa, _id0, _id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
assert_eq!(index.variable_count(), 2);
}
#[test]
fn test_def_site_lookup() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
let def0 = index.def_site(id0).unwrap();
assert_eq!(def0.block, 0);
assert_eq!(def0.instruction, Some(0));
let def1 = index.def_site(id1).unwrap();
assert_eq!(def1.block, 0);
assert_eq!(def1.instruction, Some(1));
}
#[test]
fn test_uses_of() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
let uses0 = index.uses_of(id0).unwrap();
assert_eq!(uses0.len(), 2);
let uses1 = index.uses_of(id1).unwrap();
assert_eq!(uses1.len(), 1);
}
#[test]
fn test_use_count() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
assert_eq!(index.use_count(id0), 2);
assert_eq!(index.use_count(id1), 1);
assert_eq!(index.use_count(SsaVarId::from_index(999999)), 0); }
#[test]
fn test_defs_at_location() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
let defs_0_0 = index.defs_at(0, 0);
assert_eq!(defs_0_0.len(), 1);
assert!(defs_0_0.contains(&id0));
let defs_0_1 = index.defs_at(0, 1);
assert_eq!(defs_0_1.len(), 1);
assert!(defs_0_1.contains(&id1));
let defs_0_2 = index.defs_at(0, 2);
assert!(defs_0_2.is_empty());
}
#[test]
fn test_uses_at_location() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
let uses_0_1 = index.uses_at(0, 1);
assert!(uses_0_1.contains(&id0));
let uses_0_2 = index.uses_at(0, 2);
assert!(uses_0_2.contains(&id1));
}
#[test]
fn test_defs_in_block() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
let defs = index.defs_in_block(0);
assert_eq!(defs.len(), 2);
assert!(defs.contains(&id0));
assert!(defs.contains(&id1));
}
#[test]
fn test_unused_variables() {
let mut ssa = SsaFunction::new(0, 0);
let mut block = SsaBlock::new(0);
let dest0 = SsaVarId::new();
let dest1 = SsaVarId::new();
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: dest0,
value: ConstValue::I32(42),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: dest1,
value: ConstValue::I32(0),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Return {
value: Some(dest1),
}));
ssa.add_block(block);
let v0 = SsaVariable::new(VariableOrigin::Stack(0), 0, DefSite::instruction(0, 0));
let v0_id = v0.id();
ssa.variables_mut().push(v0);
let mut v1 = SsaVariable::new(VariableOrigin::Stack(1), 0, DefSite::instruction(0, 1));
let v1_id = v1.id();
v1.add_use(UseSite::instruction(0, 2));
ssa.variables_mut().push(v1);
let index = DefUseIndex::build(&ssa);
assert!(index.is_unused(v0_id));
assert!(!index.is_unused(v1_id));
assert_eq!(index.unused_count(), 1);
assert!(index.unused_variables().contains(&v0_id));
}
#[test]
fn test_single_use() {
let (ssa, v0_id, v1_id) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
assert!(!index.is_single_use(v0_id));
assert!(index.is_single_use(v1_id));
let use_site = index.single_use_site(v1_id).unwrap();
assert_eq!(use_site.block, 0);
assert_eq!(use_site.instruction, 2);
assert!(index.single_use_site(v0_id).is_none());
}
#[test]
fn test_phi_definitions() {
let mut ssa = SsaFunction::new(0, 0);
let block = SsaBlock::new(0);
ssa.add_block(block);
let v0 = SsaVariable::new(VariableOrigin::Phi, 0, DefSite::phi(0));
let v0_id = v0.id();
ssa.variables_mut().push(v0);
let v1 = SsaVariable::new(VariableOrigin::Stack(0), 0, DefSite::instruction(0, 0));
let v1_id = v1.id();
ssa.variables_mut().push(v1);
let index = DefUseIndex::build(&ssa);
assert!(index.is_phi_def(v0_id));
assert!(!index.is_phi_def(v1_id));
assert!(index.phi_definitions().contains(&v0_id));
}
#[test]
fn test_uses_in_block() {
let (ssa, v0_id, v1_id) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
let uses = index.uses_in_block(0);
assert!(uses.contains(&v0_id));
assert!(uses.contains(&v1_id));
}
#[test]
fn test_default() {
let index = DefUseIndex::default();
assert_eq!(index.variable_count(), 0);
assert_eq!(index.unused_count(), 0);
}
#[test]
fn test_build_without_ops() {
let (ssa, id0, _id1) = make_test_ssa();
let index = DefUseIndex::build(&ssa);
assert!(!index.has_ops());
assert!(index.def_op(id0).is_none());
assert!(index.full_definition(id0).is_none());
}
#[test]
fn test_build_with_ops() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build_with_ops(&ssa);
assert!(index.has_ops());
let op0 = index.def_op(id0).unwrap();
assert!(matches!(op0, SsaOp::Const { value, .. } if value.as_i32() == Some(42)));
let op1 = index.def_op(id1).unwrap();
assert!(matches!(op1, SsaOp::Add { .. }));
}
#[test]
fn test_full_definition() {
let (ssa, id0, id1) = make_test_ssa();
let index = DefUseIndex::build_with_ops(&ssa);
let (block0, instr0, op0) = index.full_definition(id0).unwrap();
assert_eq!(block0, 0);
assert_eq!(instr0, 0);
assert!(matches!(op0, SsaOp::Const { .. }));
let (block1, instr1, op1) = index.full_definition(id1).unwrap();
assert_eq!(block1, 0);
assert_eq!(instr1, 1);
assert!(matches!(op1, SsaOp::Add { .. }));
}
#[test]
fn test_full_definition_phi_returns_none() {
let mut ssa = SsaFunction::new(0, 0);
let block = SsaBlock::new(0);
ssa.add_block(block);
let v0 = SsaVariable::new(VariableOrigin::Phi, 0, DefSite::phi(0));
let v0_id = v0.id();
ssa.variables_mut().push(v0);
let index = DefUseIndex::build_with_ops(&ssa);
assert!(index.full_definition(v0_id).is_none());
let site = index.def_site(v0_id).unwrap();
assert_eq!(site.block, 0);
assert!(site.instruction.is_none());
}
#[test]
fn test_build_with_ops_map_compatibility() {
let (ssa, id0, id1) = make_test_ssa();
let (index, ops) = DefUseIndex::build_with_ops_map(&ssa);
assert!(index.has_ops());
assert!(ops.contains_key(&id0));
assert!(ops.contains_key(&id1));
let op0_from_index = index.def_op(id0).unwrap();
let op0_from_map = ops.get(&id0).unwrap();
assert!(matches!(op0_from_index, SsaOp::Const { .. }));
assert!(matches!(op0_from_map, SsaOp::Const { .. }));
}
}