use std::collections::{BTreeMap, BTreeSet};
use crate::{
bitset::BitSet,
ir::{
function::SsaFunction,
ops::SsaOp,
variable::{DefSite, SsaVarId, UseSite},
},
target::Target,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
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)]
pub struct DefUseIndex<T: Target> {
definitions: Vec<Option<DefSite>>,
use_offsets: Vec<u32>,
use_values: Vec<UseSite>,
block_row_base: Vec<u32>,
loc_defs_offsets: Vec<u32>,
loc_defs_values: Vec<SsaVarId>,
loc_uses_offsets: Vec<u32>,
loc_uses_values: Vec<SsaVarId>,
defs_in_block: Vec<Vec<SsaVarId>>,
phi_defs: BitSet,
unused_vars: BitSet,
var_count: usize,
def_ops: Option<Vec<Option<SsaOp<T>>>>,
}
impl<T: Target> Default for DefUseIndex<T> {
fn default() -> Self {
Self {
definitions: Vec::new(),
use_offsets: Vec::new(),
use_values: Vec::new(),
block_row_base: Vec::new(),
loc_defs_offsets: Vec::new(),
loc_defs_values: Vec::new(),
loc_uses_offsets: Vec::new(),
loc_uses_values: Vec::new(),
defs_in_block: Vec::new(),
phi_defs: BitSet::default(),
unused_vars: BitSet::default(),
var_count: 0,
def_ops: None,
}
}
}
fn csr_push(cursors: &mut [u32], values: &mut [SsaVarId], row: usize, var: SsaVarId) {
if let Some(cursor) = cursors.get_mut(row) {
if let Some(slot) = values.get_mut(*cursor as usize) {
*slot = var;
}
*cursor = cursor.saturating_add(1);
}
}
fn visit_uses<T: Target>(ssa: &SsaFunction<T>, mut visit: impl FnMut(SsaVarId, UseSite)) {
for (block_idx, block) in ssa.blocks().iter().enumerate() {
for (phi_idx, phi) in block.phi_nodes().iter().enumerate() {
for operand in phi.operands() {
visit(operand.value(), UseSite::phi_operand(block_idx, phi_idx));
}
}
for (instr_idx, instr) in block.instructions().iter().enumerate() {
instr
.op()
.for_each_use(|var| visit(var, UseSite::instruction(block_idx, instr_idx)));
}
}
}
impl<T: Target> DefUseIndex<T> {
fn uses_slice(&self, index: usize) -> Option<&[UseSite]> {
let start = *self.use_offsets.get(index)? as usize;
let end = *self.use_offsets.get(index.checked_add(1)?)? as usize;
self.use_values.get(start..end)
}
fn row_of(&self, block: usize, index: usize) -> Option<usize> {
let base = *self.block_row_base.get(block)? as usize;
let end = *self.block_row_base.get(block.checked_add(1)?)? as usize;
let row = base.checked_add(index)?;
(row < end).then_some(row)
}
fn csr_row<'s>(offsets: &[u32], values: &'s [SsaVarId], row: usize) -> &'s [SsaVarId] {
let Some(start) = offsets.get(row).map(|offset| *offset as usize) else {
return &[];
};
let Some(end) = row
.checked_add(1)
.and_then(|next| offsets.get(next))
.map(|offset| *offset as usize)
else {
return &[];
};
values.get(start..end).unwrap_or(&[])
}
#[must_use]
pub fn build(ssa: &SsaFunction<T>) -> Self {
let mut block_row_base: Vec<u32> = Vec::with_capacity(ssa.blocks().len().saturating_add(1));
let mut row_total: u32 = 0;
block_row_base.push(0);
for block in ssa.blocks() {
let rows = block.phi_nodes().len().max(block.instructions().len());
row_total = row_total.saturating_add(u32::try_from(rows).unwrap_or(u32::MAX));
block_row_base.push(row_total);
}
let row_count = row_total as usize;
let mut loc_defs_offsets: Vec<u32> = vec![0; row_count.saturating_add(1)];
let mut loc_uses_offsets: Vec<u32> = vec![0; row_count.saturating_add(1)];
for (block_idx, block) in ssa.blocks().iter().enumerate() {
let base = block_row_base.get(block_idx).map_or(0, |b| *b as usize);
for (phi_idx, phi) in block.phi_nodes().iter().enumerate() {
if let Some(slot) = base
.checked_add(phi_idx)
.and_then(|row| row.checked_add(1))
.and_then(|next| loc_uses_offsets.get_mut(next))
{
*slot = slot
.saturating_add(u32::try_from(phi.operands().len()).unwrap_or(u32::MAX));
}
}
for (instr_idx, instr) in block.instructions().iter().enumerate() {
let Some(next) = base
.checked_add(instr_idx)
.and_then(|row| row.checked_add(1))
else {
continue;
};
let op = instr.op();
if let Some(slot) = loc_defs_offsets.get_mut(next) {
*slot =
slot.saturating_add(u32::try_from(op.defs().count()).unwrap_or(u32::MAX));
}
if let Some(slot) = loc_uses_offsets.get_mut(next) {
*slot = slot.saturating_add(u32::try_from(op.use_count()).unwrap_or(u32::MAX));
}
}
}
let mut running_defs: u32 = 0;
for slot in &mut loc_defs_offsets {
running_defs = running_defs.saturating_add(*slot);
*slot = running_defs;
}
let mut running_uses: u32 = 0;
for slot in &mut loc_uses_offsets {
running_uses = running_uses.saturating_add(*slot);
*slot = running_uses;
}
let mut loc_defs_values: Vec<SsaVarId> = vec![SsaVarId::PLACEHOLDER; running_defs as usize];
let mut loc_uses_values: Vec<SsaVarId> = vec![SsaVarId::PLACEHOLDER; running_uses as usize];
let mut defs_cursors: Vec<u32> = loc_defs_offsets.clone();
let mut uses_cursors: Vec<u32> = loc_uses_offsets.clone();
let variable_count = ssa.variable_count();
let max_var_idx = ssa
.variables()
.iter()
.map(|v| v.id().index().saturating_add(1))
.max()
.unwrap_or(0);
let bitset_capacity = max_var_idx.max(variable_count);
let block_count = ssa.blocks().len();
let mut definitions: Vec<Option<DefSite>> = vec![None; bitset_capacity];
let mut defs_in_block: Vec<Vec<SsaVarId>> = vec![Vec::new(); block_count];
let mut phi_defs = BitSet::new(bitset_capacity);
let mut use_offsets: Vec<u32> = vec![0; bitset_capacity.saturating_add(1)];
visit_uses(ssa, |var, _site| {
if let Some(slot) = var
.index()
.checked_add(1)
.and_then(|next| use_offsets.get_mut(next))
{
*slot = slot.saturating_add(1);
}
});
let mut running: u32 = 0;
for slot in &mut use_offsets {
running = running.saturating_add(*slot);
*slot = running;
}
let mut use_values: Vec<UseSite> = vec![UseSite::instruction(0, 0); running as usize];
let mut cursors: Vec<u32> = use_offsets.clone();
visit_uses(ssa, |var, site| {
if let Some(cursor) = cursors.get_mut(var.index()) {
if let Some(slot) = use_values.get_mut(*cursor as usize) {
*slot = site;
}
*cursor = cursor.saturating_add(1);
}
});
let set_def = |definitions: &mut Vec<Option<DefSite>>, var: SsaVarId, site: DefSite| {
if let Some(slot) = definitions.get_mut(var.index()) {
*slot = Some(site);
}
};
for var in ssa.variables() {
set_def(&mut definitions, var.id(), var.def_site());
}
for (block_idx, block) in ssa.blocks().iter().enumerate() {
let row_base = block_row_base.get(block_idx).map_or(0, |b| *b as usize);
for (phi_idx, phi) in block.phi_nodes().iter().enumerate() {
let result = phi.result();
set_def(&mut definitions, result, DefSite::phi(block_idx));
phi_defs.insert_checked(result.index());
if let Some(slot) = defs_in_block.get_mut(block_idx) {
slot.push(result);
}
let Some(row) = row_base.checked_add(phi_idx) else {
continue;
};
for operand in phi.operands() {
csr_push(
&mut uses_cursors,
&mut loc_uses_values,
row,
operand.value(),
);
}
}
for (instr_idx, instr) in block.instructions().iter().enumerate() {
let Some(row) = row_base.checked_add(instr_idx) else {
continue;
};
let op = instr.op();
for dest in op.defs() {
set_def(
&mut definitions,
dest,
DefSite::instruction(block_idx, instr_idx),
);
csr_push(&mut defs_cursors, &mut loc_defs_values, row, dest);
if let Some(slot) = defs_in_block.get_mut(block_idx) {
slot.push(dest);
}
}
op.for_each_use(|var| {
csr_push(&mut uses_cursors, &mut loc_uses_values, row, var);
});
}
}
let mut unused_vars = BitSet::new(bitset_capacity);
for var in ssa.variables() {
let i = var.id().index();
if use_offsets
.get(i)
.zip(i.checked_add(1).and_then(|next| use_offsets.get(next)))
.is_some_and(|(start, end)| start == end)
{
unused_vars.insert(i);
}
}
Self {
definitions,
use_offsets,
use_values,
block_row_base,
loc_defs_offsets,
loc_defs_values,
loc_uses_offsets,
loc_uses_values,
defs_in_block,
phi_defs,
unused_vars,
var_count: variable_count,
def_ops: None,
}
}
#[must_use]
pub fn build_with_ops(ssa: &SsaFunction<T>) -> Self {
let mut index = Self::build(ssa);
let mut def_ops: Vec<Option<SsaOp<T>>> = vec![None; index.definitions.len()];
for (_block_idx, _instr_idx, instr) in ssa.iter_instructions() {
let op = instr.op();
for dest in op.defs() {
if let Some(slot) = def_ops.get_mut(dest.index()) {
*slot = Some(op.clone());
}
}
}
index.def_ops = Some(def_ops);
index
}
#[must_use]
pub fn build_with_ops_map(ssa: &SsaFunction<T>) -> (Self, BTreeMap<SsaVarId, SsaOp<T>>) {
let index = Self::build_with_ops(ssa);
let mut ops = BTreeMap::new();
if let Some(def_ops) = index.def_ops.as_ref() {
for (i, op) in def_ops.iter().enumerate() {
if let Some(op) = op {
ops.insert(SsaVarId::from_index(i), op.clone());
}
}
}
(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<T>> {
self.def_ops.as_ref()?.get(var.index())?.as_ref()
}
#[must_use]
pub fn full_definition(&self, var: SsaVarId) -> Option<(usize, usize, &SsaOp<T>)> {
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.index()).copied().flatten()
}
#[must_use]
pub fn uses_of(&self, var: SsaVarId) -> Option<&[UseSite]> {
self.uses_slice(var.index())
}
#[must_use]
pub fn use_count(&self, var: SsaVarId) -> usize {
self.uses_slice(var.index()).map_or(0, <[UseSite]>::len)
}
#[must_use]
pub fn has_uses(&self, var: SsaVarId) -> bool {
self.uses_slice(var.index()).is_some_and(|u| !u.is_empty())
}
#[must_use]
pub fn is_unused(&self, var: SsaVarId) -> bool {
var.index() < self.unused_vars.len() && self.unused_vars.contains(var.index())
}
#[must_use]
pub fn is_phi_def(&self, var: SsaVarId) -> bool {
var.index() < self.phi_defs.len() && self.phi_defs.contains(var.index())
}
#[must_use]
pub fn defs_at(&self, block: usize, instruction: usize) -> &[SsaVarId] {
self.row_of(block, instruction).map_or(&[], |row| {
Self::csr_row(&self.loc_defs_offsets, &self.loc_defs_values, row)
})
}
#[must_use]
pub fn uses_at(&self, block: usize, instruction: usize) -> &[SsaVarId] {
self.row_of(block, instruction).map_or(&[], |row| {
Self::csr_row(&self.loc_uses_offsets, &self.loc_uses_values, row)
})
}
#[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) -> &BitSet {
&self.unused_vars
}
#[must_use]
pub fn phi_definitions(&self) -> &BitSet {
&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.count()
}
#[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_slice(var.index())
.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) -> BTreeSet<SsaVarId> {
let mut result = BTreeSet::new();
let (Some(base), Some(end)) = (
self.block_row_base.get(block).map(|b| *b as usize),
block
.checked_add(1)
.and_then(|next| self.block_row_base.get(next))
.map(|b| *b as usize),
) else {
return result;
};
for row in base..end {
result.extend(
Self::csr_row(&self.loc_uses_offsets, &self.loc_uses_values, row)
.iter()
.copied(),
);
}
result
}
#[must_use]
pub fn single_use_site(&self, var: SsaVarId) -> Option<UseSite> {
self.uses_slice(var.index()).and_then(|uses| {
if uses.len() == 1 {
uses.first().copied()
} else {
None
}
})
}
}
#[cfg(test)]
mod tests {
use super::DefUseIndex;
use crate::{
ir::{
block::SsaBlock,
function::SsaFunction,
instruction::SsaInstruction,
ops::SsaOp,
phi::PhiNode,
value::ConstValue,
variable::{DefSite, SsaVarId, SsaVariable, UseSite, VariableOrigin},
},
testing::{MockTarget, MockType},
};
type SsaType = MockType;
fn make_test_ssa() -> (SsaFunction<MockTarget>, SsaVarId, SsaVarId) {
let mut ssa = SsaFunction::<MockTarget>::new(0, 0);
let mut v0 = SsaVariable::new(
SsaVarId::from_index(0),
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
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(
SsaVarId::from_index(1),
VariableOrigin::Local(1),
0,
DefSite::instruction(0, 1),
SsaType::Unknown,
);
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,
flags: None,
}));
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_build_with_ops_indexes_secondary_defs() {
let mut ssa = SsaFunction::<MockTarget>::new(0, 0);
let left = SsaVarId::from_index(0);
let right = SsaVarId::from_index(1);
let value = SsaVarId::from_index(2);
let flags = SsaVarId::from_index(3);
ssa.variables_mut().push(SsaVariable::new(
left,
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::I32,
));
ssa.variables_mut().push(SsaVariable::new(
right,
VariableOrigin::Local(1),
0,
DefSite::instruction(0, 1),
SsaType::I32,
));
ssa.variables_mut().push(SsaVariable::new(
value,
VariableOrigin::Local(2),
0,
DefSite::instruction(0, 2),
SsaType::I32,
));
ssa.variables_mut().push(SsaVariable::new(
flags,
VariableOrigin::Local(3),
0,
DefSite::instruction(0, 2),
SsaType::I32,
));
let mut block = SsaBlock::new(0);
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: left,
value: ConstValue::I32(1),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: right,
value: ConstValue::I32(2),
}));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Add {
dest: value,
left,
right,
flags: Some(flags),
}));
ssa.add_block(block);
let index = DefUseIndex::build_with_ops(&ssa);
assert!(matches!(
index.def_op(value),
Some(SsaOp::Add {
flags: Some(f), ..
}) if *f == flags
));
assert!(matches!(
index.def_op(flags),
Some(SsaOp::Add {
dest,
flags: Some(f),
..
}) if *dest == value && *f == flags
));
}
#[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::<MockTarget>::new(0, 0);
let mut block = SsaBlock::new(0);
let dest0 = SsaVarId::from_index(0);
let dest1 = SsaVarId::from_index(1);
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(
dest0,
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
let v0_id = v0.id();
ssa.variables_mut().push(v0);
let mut v1 = SsaVariable::new(
dest1,
VariableOrigin::Local(1),
0,
DefSite::instruction(0, 1),
SsaType::Unknown,
);
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.index()));
}
#[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::<MockTarget>::new(0, 0);
let mut block = SsaBlock::new(0);
block.add_phi(PhiNode::new(SsaVarId::from_index(0), VariableOrigin::Phi));
block.add_instruction(SsaInstruction::synthetic(SsaOp::Const {
dest: SsaVarId::from_index(1),
value: ConstValue::I32(0),
}));
ssa.add_block(block);
let v0 = SsaVariable::new(
SsaVarId::from_index(0),
VariableOrigin::Phi,
0,
DefSite::phi(0),
SsaType::Unknown,
);
let v0_id = v0.id();
ssa.variables_mut().push(v0);
let v1 = SsaVariable::new(
SsaVarId::from_index(1),
VariableOrigin::Local(0),
0,
DefSite::instruction(0, 0),
SsaType::Unknown,
);
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.index()));
}
#[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<MockTarget> = 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::<MockTarget>::new(0, 0);
let block = SsaBlock::new(0);
ssa.add_block(block);
let v0 = SsaVariable::new(
SsaVarId::from_index(0),
VariableOrigin::Phi,
0,
DefSite::phi(0),
SsaType::Unknown,
);
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 { .. }));
}
}