use llvm_ir::{BlockId, Function, InstrId, InstrKind, ValueRef};
use std::collections::HashMap;
pub struct UseDefInfo {
instr_block: HashMap<InstrId, BlockId>,
uses: HashMap<ValueRef, Vec<(BlockId, InstrId)>>,
phi_uses: HashMap<ValueRef, Vec<(BlockId, InstrId)>>,
}
impl UseDefInfo {
pub fn compute(func: &Function) -> Self {
let mut instr_block: HashMap<InstrId, BlockId> = HashMap::new();
let mut uses: HashMap<ValueRef, Vec<(BlockId, InstrId)>> = HashMap::new();
let mut phi_uses: HashMap<ValueRef, Vec<(BlockId, InstrId)>> = HashMap::new();
for (bi, bb) in func.blocks.iter().enumerate() {
let bid = BlockId(bi as u32);
for iid in bb.instrs() {
instr_block.insert(iid, bid);
let instr = func.instr(iid);
match &instr.kind {
InstrKind::Phi { incoming, .. } => {
for (val, pred) in incoming {
uses.entry(*val).or_default().push((bid, iid));
phi_uses.entry(*val).or_default().push((*pred, iid));
}
}
_ => {
for operand in instr.kind.operands() {
uses.entry(operand).or_default().push((bid, iid));
}
}
}
}
}
UseDefInfo {
instr_block,
uses,
phi_uses,
}
}
pub fn def_block(&self, id: InstrId) -> Option<BlockId> {
self.instr_block.get(&id).copied()
}
pub fn uses_of(&self, vref: ValueRef) -> &[(BlockId, InstrId)] {
self.uses.get(&vref).map(Vec::as_slice).unwrap_or(&[])
}
pub fn phi_uses_of(&self, vref: ValueRef) -> &[(BlockId, InstrId)] {
self.phi_uses.get(&vref).map(Vec::as_slice).unwrap_or(&[])
}
pub fn is_dead(&self, vref: ValueRef) -> bool {
self.uses_of(vref).is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_ir::{ArgId, Builder, Context, Linkage, Module};
fn make_add_fn() -> (Context, Module) {
let mut ctx = Context::new();
let mut module = Module::new("test");
let mut b = Builder::new(&mut ctx, &mut module);
b.add_function(
"add",
b.ctx.i32_ty,
vec![b.ctx.i32_ty, b.ctx.i32_ty],
vec!["a".into(), "b".into()],
false,
Linkage::External,
);
let entry = b.add_block("entry");
b.position_at_end(entry);
let a = b.get_arg(0);
let bv = b.get_arg(1);
let sum = b.build_add("sum", a, bv);
b.build_ret(sum);
(ctx, module)
}
#[test]
fn use_def_basic() {
let (_ctx, module) = make_add_fn();
let func = &module.functions[0];
let info = UseDefInfo::compute(func);
assert_eq!(info.def_block(InstrId(0)), Some(BlockId(0)));
let sum_ref = ValueRef::Instruction(InstrId(0));
assert_eq!(info.uses_of(sum_ref).len(), 1);
assert_eq!(info.uses_of(ValueRef::Argument(ArgId(0))).len(), 1);
assert_eq!(info.uses_of(ValueRef::Argument(ArgId(1))).len(), 1);
}
#[test]
fn use_def_dead_value() {
let mut ctx = Context::new();
let mut module = Module::new("test");
let mut b = Builder::new(&mut ctx, &mut module);
b.add_function(
"f",
b.ctx.i32_ty,
vec![b.ctx.i32_ty],
vec!["x".into()],
false,
Linkage::External,
);
let entry = b.add_block("entry");
b.position_at_end(entry);
let x = b.get_arg(0);
let _dead = b.build_add("dead", x, x); b.build_ret(x);
let func = &module.functions[0];
let info = UseDefInfo::compute(func);
assert!(info.is_dead(ValueRef::Instruction(InstrId(0))));
assert_eq!(info.uses_of(ValueRef::Argument(ArgId(0))).len(), 3);
}
#[test]
fn use_def_multi_block() {
let mut ctx = Context::new();
let mut module = Module::new("test");
let mut b = Builder::new(&mut ctx, &mut module);
b.add_function(
"f",
b.ctx.void_ty,
vec![b.ctx.i1_ty],
vec!["c".into()],
false,
Linkage::External,
);
let entry = b.add_block("entry");
let then_bb = b.add_block("then");
let else_bb = b.add_block("else");
b.position_at_end(entry);
let cond = b.get_arg(0);
b.build_cond_br(cond, then_bb, else_bb);
b.position_at_end(then_bb);
b.build_ret_void();
b.position_at_end(else_bb);
b.build_ret_void();
let func = &module.functions[0];
let info = UseDefInfo::compute(func);
let uses = info.uses_of(ValueRef::Argument(ArgId(0)));
assert_eq!(uses.len(), 1);
assert_eq!(uses[0].0, BlockId(0));
}
fn make_phi_fn() -> (Context, Module) {
let mut ctx = Context::new();
let mut module = Module::new("test");
let mut b = Builder::new(&mut ctx, &mut module);
b.add_function(
"phi_fn",
b.ctx.i32_ty,
vec![b.ctx.i1_ty, b.ctx.i32_ty, b.ctx.i32_ty],
vec!["cond".into(), "a".into(), "bv".into()],
false,
Linkage::External,
);
let entry = b.add_block("entry");
let then_bb = b.add_block("then");
let else_bb = b.add_block("else");
let merge = b.add_block("merge");
b.position_at_end(entry);
let cond = b.get_arg(0);
b.build_cond_br(cond, then_bb, else_bb);
b.position_at_end(then_bb);
b.build_br(merge);
b.position_at_end(else_bb);
b.build_br(merge);
b.position_at_end(merge);
let a = b.get_arg(1);
let bv = b.get_arg(2);
let v = b.build_phi("v", b.ctx.i32_ty, vec![(a, then_bb), (bv, else_bb)]);
b.build_ret(v);
(ctx, module)
}
#[test]
fn phi_uses_of_records_predecessor_block() {
let (_ctx, module) = make_phi_fn();
let func = &module.functions[0];
let info = UseDefInfo::compute(func);
let a_ref = ValueRef::Argument(ArgId(1));
let b_ref = ValueRef::Argument(ArgId(2));
let a_phi_uses = info.phi_uses_of(a_ref);
assert_eq!(
a_phi_uses.len(),
1,
"a should appear in exactly one phi incoming list"
);
assert_eq!(
a_phi_uses[0].0,
BlockId(1),
"a's phi use should be at predecessor then_bb (block 1), not merge"
);
let b_phi_uses = info.phi_uses_of(b_ref);
assert_eq!(b_phi_uses.len(), 1);
assert_eq!(
b_phi_uses[0].0,
BlockId(2),
"bv's phi use should be at predecessor else_bb (block 2), not merge"
);
}
#[test]
fn uses_of_phi_operand_still_at_phi_block() {
let (_ctx, module) = make_phi_fn();
let func = &module.functions[0];
let info = UseDefInfo::compute(func);
let a_ref = ValueRef::Argument(ArgId(1));
let uses = info.uses_of(a_ref);
assert_eq!(uses.len(), 1);
assert_eq!(
uses[0].0,
BlockId(3),
"uses_of should record phi operands at the phi's own block (merge = block 3)"
);
assert!(!info.is_dead(a_ref));
}
#[test]
fn non_phi_operands_absent_from_phi_uses() {
let (_ctx, module) = make_add_fn();
let func = &module.functions[0];
let info = UseDefInfo::compute(func);
assert!(info.phi_uses_of(ValueRef::Argument(ArgId(0))).is_empty());
assert!(info.phi_uses_of(ValueRef::Argument(ArgId(1))).is_empty());
}
}