use super::cfg::ControlFlowGraph;
use super::types::*;
use std::collections::{HashMap, HashSet};
pub type FieldKey = (SlotId, FieldIdx);
#[derive(Debug)]
pub struct FieldAnalysis {
pub definitely_initialized: HashMap<BasicBlockId, HashSet<FieldKey>>,
pub field_liveness: HashMap<BasicBlockId, HashSet<FieldKey>>,
pub dead_fields: HashSet<FieldKey>,
pub conditionally_initialized: HashSet<FieldKey>,
pub hoisted_fields: HashMap<SlotId, Vec<FieldIdx>>,
pub hoisting_recommendations: HashMap<SlotId, Vec<(FieldIdx, String)>>,
}
pub struct FieldAnalysisInput<'a> {
pub mir: &'a MirFunction,
pub cfg: &'a ControlFlowGraph,
}
pub fn analyze_fields(input: &FieldAnalysisInput) -> FieldAnalysis {
let mir = input.mir;
let cfg = input.cfg;
let (block_writes, block_reads, all_writes, all_reads) = collect_field_accesses(mir);
let definitely_initialized = compute_definite_initialization(mir, cfg, &block_writes);
let field_liveness = compute_field_liveness(mir, cfg, &block_writes, &block_reads);
let dead_fields: HashSet<FieldKey> = all_writes.difference(&all_reads).cloned().collect();
let conditionally_initialized =
compute_conditionally_initialized(mir, &block_reads, &definitely_initialized, &all_writes);
let mut hoisted_fields: HashMap<SlotId, Vec<FieldIdx>> = HashMap::new();
for key in &all_writes {
if !dead_fields.contains(key) {
hoisted_fields.entry(key.0).or_default().push(key.1);
}
}
FieldAnalysis {
definitely_initialized,
field_liveness,
dead_fields,
conditionally_initialized,
hoisted_fields,
hoisting_recommendations: HashMap::new(), }
}
fn collect_field_accesses(
mir: &MirFunction,
) -> (
HashMap<BasicBlockId, HashSet<FieldKey>>,
HashMap<BasicBlockId, HashSet<FieldKey>>,
HashSet<FieldKey>,
HashSet<FieldKey>,
) {
let mut block_writes: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
let mut block_reads: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
let mut all_writes = HashSet::new();
let mut all_reads = HashSet::new();
for block in &mir.blocks {
let writes = block_writes.entry(block.id).or_default();
let reads = block_reads.entry(block.id).or_default();
for stmt in &block.statements {
collect_statement_field_accesses(&stmt.kind, writes, reads);
}
collect_terminator_field_reads(&block.terminator.kind, reads);
all_writes.extend(writes.iter().cloned());
all_reads.extend(reads.iter().cloned());
}
(block_writes, block_reads, all_writes, all_reads)
}
fn collect_statement_field_accesses(
kind: &StatementKind,
writes: &mut HashSet<FieldKey>,
reads: &mut HashSet<FieldKey>,
) {
match kind {
StatementKind::Assign(place, rvalue) => {
if let Some(key) = extract_field_key(place) {
writes.insert(key);
}
collect_rvalue_field_reads(rvalue, reads);
}
StatementKind::Drop(place) => {
if let Some(key) = extract_field_key(place) {
reads.insert(key);
}
}
StatementKind::TaskBoundary(ops, ..)
| StatementKind::ClosureCapture { operands: ops, .. }
| StatementKind::ArrayStore { operands: ops, .. }
| StatementKind::ObjectStore { operands: ops, .. }
| StatementKind::EnumStore { operands: ops, .. } => {
for op in ops {
collect_operand_field_reads(op, reads);
}
}
StatementKind::Nop => {}
}
}
fn collect_rvalue_field_reads(rvalue: &Rvalue, reads: &mut HashSet<FieldKey>) {
match rvalue {
Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
collect_operand_field_reads(op, reads);
}
Rvalue::Borrow(_, place) => {
if let Some(key) = extract_field_key(place) {
reads.insert(key);
}
}
Rvalue::BinaryOp(_, lhs, rhs) => {
collect_operand_field_reads(lhs, reads);
collect_operand_field_reads(rhs, reads);
}
Rvalue::Aggregate(ops) => {
for op in ops {
collect_operand_field_reads(op, reads);
}
}
Rvalue::EnumTest { operand, .. }
| Rvalue::EnumPayload { operand, .. }
| Rvalue::TypePatternTest { operand, .. }
| Rvalue::EnumDiscriminantTest { operand, .. } => {
collect_operand_field_reads(operand, reads);
}
}
}
fn collect_operand_field_reads(op: &Operand, reads: &mut HashSet<FieldKey>) {
match op {
Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
if let Some(key) = extract_field_key(place) {
reads.insert(key);
}
}
Operand::Constant(_) => {}
}
}
fn collect_terminator_field_reads(kind: &TerminatorKind, reads: &mut HashSet<FieldKey>) {
match kind {
TerminatorKind::SwitchBool { operand, .. } => {
collect_operand_field_reads(operand, reads);
}
TerminatorKind::Call { func, args, .. } => {
collect_operand_field_reads(func, reads);
for arg in args {
collect_operand_field_reads(arg, reads);
}
}
TerminatorKind::Goto(_) | TerminatorKind::Return | TerminatorKind::Unreachable => {}
}
}
fn extract_field_key(place: &Place) -> Option<FieldKey> {
match place {
Place::Field(base, idx) => match base.as_ref() {
Place::Local(slot) => Some((*slot, *idx)),
_ => None,
},
_ => None,
}
}
fn compute_definite_initialization(
mir: &MirFunction,
cfg: &ControlFlowGraph,
block_writes: &HashMap<BasicBlockId, HashSet<FieldKey>>,
) -> HashMap<BasicBlockId, HashSet<FieldKey>> {
let rpo = cfg.reverse_postorder();
let entry = mir.entry_block();
let mut init_in: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
let mut init_out: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
let universe: HashSet<FieldKey> = block_writes.values().flatten().cloned().collect();
for block in &mir.blocks {
if block.id == entry {
init_in.insert(block.id, HashSet::new());
} else {
init_in.insert(block.id, universe.clone());
}
}
for block in &mir.blocks {
let in_set = init_in.get(&block.id).cloned().unwrap_or_default();
let writes = block_writes.get(&block.id).cloned().unwrap_or_default();
let out_set: HashSet<FieldKey> = in_set.union(&writes).cloned().collect();
init_out.insert(block.id, out_set);
}
let mut changed = true;
while changed {
changed = false;
for &block_id in &rpo {
let preds = cfg.predecessors(block_id);
let new_in = if block_id == entry {
HashSet::new()
} else if preds.is_empty() {
universe.clone()
} else {
let mut merged = init_out
.get(&preds[0])
.cloned()
.unwrap_or_else(|| universe.clone());
for &pred in &preds[1..] {
let pred_out = init_out
.get(&pred)
.cloned()
.unwrap_or_else(|| universe.clone());
merged = merged.intersection(&pred_out).cloned().collect();
}
merged
};
let writes = block_writes.get(&block_id).cloned().unwrap_or_default();
let new_out: HashSet<FieldKey> = new_in.union(&writes).cloned().collect();
if new_in != *init_in.get(&block_id).unwrap_or(&HashSet::new()) {
changed = true;
init_in.insert(block_id, new_in);
}
if new_out != *init_out.get(&block_id).unwrap_or(&HashSet::new()) {
changed = true;
init_out.insert(block_id, new_out);
}
}
}
init_in
}
fn compute_field_liveness(
mir: &MirFunction,
cfg: &ControlFlowGraph,
_block_writes: &HashMap<BasicBlockId, HashSet<FieldKey>>,
_block_reads: &HashMap<BasicBlockId, HashSet<FieldKey>>,
) -> HashMap<BasicBlockId, HashSet<FieldKey>> {
let rpo = cfg.reverse_postorder();
let mut use_before_def: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
let mut def_before_use: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
for block in &mir.blocks {
let (ubd, dbu) = compute_block_use_def_order(block);
use_before_def.insert(block.id, ubd);
def_before_use.insert(block.id, dbu);
}
let mut live_in: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
let mut live_out: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
for block in &mir.blocks {
live_in.insert(block.id, HashSet::new());
live_out.insert(block.id, HashSet::new());
}
let mut changed = true;
while changed {
changed = false;
for &block_id in rpo.iter().rev() {
let mut new_live_out: HashSet<FieldKey> = HashSet::new();
for &succ in cfg.successors(block_id) {
if let Some(succ_in) = live_in.get(&succ) {
new_live_out.extend(succ_in.iter().cloned());
}
}
let dbu = def_before_use.get(&block_id).cloned().unwrap_or_default();
let ubd = use_before_def.get(&block_id).cloned().unwrap_or_default();
let mut new_live_in: HashSet<FieldKey> =
new_live_out.difference(&dbu).cloned().collect();
new_live_in.extend(ubd.iter().cloned());
if new_live_in != *live_in.get(&block_id).unwrap_or(&HashSet::new()) {
changed = true;
live_in.insert(block_id, new_live_in);
}
if new_live_out != *live_out.get(&block_id).unwrap_or(&HashSet::new()) {
changed = true;
live_out.insert(block_id, new_live_out);
}
}
}
live_in
}
fn compute_block_use_def_order(block: &BasicBlock) -> (HashSet<FieldKey>, HashSet<FieldKey>) {
let mut use_before_def = HashSet::new();
let mut def_before_use = HashSet::new();
let mut seen = HashSet::new();
for stmt in &block.statements {
let mut stmt_reads = HashSet::new();
let mut stmt_writes = HashSet::new();
match &stmt.kind {
StatementKind::Assign(place, rvalue) => {
collect_rvalue_field_reads(rvalue, &mut stmt_reads);
if let Some(key) = extract_field_key(place) {
stmt_writes.insert(key);
}
}
StatementKind::Drop(place) => {
if let Some(key) = extract_field_key(place) {
stmt_reads.insert(key);
}
}
StatementKind::TaskBoundary(ops, ..)
| StatementKind::ClosureCapture { operands: ops, .. }
| StatementKind::ArrayStore { operands: ops, .. }
| StatementKind::ObjectStore { operands: ops, .. }
| StatementKind::EnumStore { operands: ops, .. } => {
for op in ops {
collect_operand_field_reads(op, &mut stmt_reads);
}
}
StatementKind::Nop => {}
}
for key in &stmt_reads {
if !seen.contains(key) {
use_before_def.insert(*key);
seen.insert(*key);
}
}
for key in &stmt_writes {
if !seen.contains(key) {
def_before_use.insert(*key);
seen.insert(*key);
}
}
}
let mut term_reads = HashSet::new();
collect_terminator_field_reads(&block.terminator.kind, &mut term_reads);
for key in &term_reads {
if !seen.contains(key) {
use_before_def.insert(*key);
}
}
(use_before_def, def_before_use)
}
fn compute_conditionally_initialized(
mir: &MirFunction,
block_reads: &HashMap<BasicBlockId, HashSet<FieldKey>>,
definitely_initialized: &HashMap<BasicBlockId, HashSet<FieldKey>>,
all_writes: &HashSet<FieldKey>,
) -> HashSet<FieldKey> {
let mut conditionally = HashSet::new();
for block in &mir.blocks {
let reads = match block_reads.get(&block.id) {
Some(r) => r,
None => continue,
};
let init = definitely_initialized
.get(&block.id)
.cloned()
.unwrap_or_default();
for key in reads {
if all_writes.contains(key) && !init.contains(key) {
conditionally.insert(*key);
}
}
}
conditionally
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::cfg::ControlFlowGraph;
fn span() -> shape_ast::ast::Span {
shape_ast::ast::Span { start: 0, end: 1 }
}
fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
MirStatement {
kind,
span: span(),
point: Point(point),
}
}
fn make_terminator(kind: TerminatorKind) -> Terminator {
Terminator { kind, span: span() }
}
fn field_place(slot: u16, field: u16) -> Place {
Place::Field(Box::new(Place::Local(SlotId(slot))), FieldIdx(field))
}
#[test]
fn test_unconditional_field_init() {
let mir = MirFunction {
name: "test".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
),
make_stmt(
StatementKind::Assign(
field_place(0, 1),
Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
),
1,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
num_locals: 1,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![LocalTypeInfo::NonCopy],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
let init_at_entry = result
.definitely_initialized
.get(&BasicBlockId(0))
.cloned()
.unwrap_or_default();
assert!(init_at_entry.is_empty());
assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(1))));
assert!(result.conditionally_initialized.is_empty());
}
#[test]
fn test_conditional_field_init() {
let mir = MirFunction {
name: "test".to_string(),
blocks: vec![
BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: make_terminator(TerminatorKind::SwitchBool {
operand: Operand::Constant(MirConstant::Bool(true)),
true_bb: BasicBlockId(1),
false_bb: BasicBlockId(2),
}),
},
BasicBlock {
id: BasicBlockId(1),
statements: vec![make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
)],
terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
},
BasicBlock {
id: BasicBlockId(2),
statements: vec![],
terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
},
BasicBlock {
id: BasicBlockId(3),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(field_place(0, 0))),
),
1,
)],
terminator: make_terminator(TerminatorKind::Return),
},
],
num_locals: 2,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
let init_at_bb3 = result
.definitely_initialized
.get(&BasicBlockId(3))
.cloned()
.unwrap_or_default();
assert!(
!init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
"field should not be definitely initialized at join point"
);
assert!(
result
.conditionally_initialized
.contains(&(SlotId(0), FieldIdx(0))),
"field should be conditionally initialized"
);
}
#[test]
fn test_both_branches_init() {
let mir = MirFunction {
name: "test".to_string(),
blocks: vec![
BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: make_terminator(TerminatorKind::SwitchBool {
operand: Operand::Constant(MirConstant::Bool(true)),
true_bb: BasicBlockId(1),
false_bb: BasicBlockId(2),
}),
},
BasicBlock {
id: BasicBlockId(1),
statements: vec![make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
)],
terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
},
BasicBlock {
id: BasicBlockId(2),
statements: vec![make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
),
1,
)],
terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
},
BasicBlock {
id: BasicBlockId(3),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(field_place(0, 0))),
),
2,
)],
terminator: make_terminator(TerminatorKind::Return),
},
],
num_locals: 2,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
let init_at_bb3 = result
.definitely_initialized
.get(&BasicBlockId(3))
.cloned()
.unwrap_or_default();
assert!(
init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
"field should be definitely initialized when both branches write it"
);
assert!(
!result
.conditionally_initialized
.contains(&(SlotId(0), FieldIdx(0))),
);
assert!(
!result.dead_fields.contains(&(SlotId(0), FieldIdx(0))),
"field is read so should not be dead"
);
}
#[test]
fn test_dead_field() {
let mir = MirFunction {
name: "test".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
),
make_stmt(
StatementKind::Assign(
field_place(0, 1),
Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(field_place(0, 0))),
),
2,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
num_locals: 2,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
assert!(!result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(1))));
}
#[test]
fn test_field_liveness() {
let mir = MirFunction {
name: "test".to_string(),
blocks: vec![
BasicBlock {
id: BasicBlockId(0),
statements: vec![make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
)],
terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
},
BasicBlock {
id: BasicBlockId(1),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(field_place(0, 0))),
),
1,
)],
terminator: make_terminator(TerminatorKind::Return),
},
],
num_locals: 2,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
let live_bb1 = result
.field_liveness
.get(&BasicBlockId(1))
.cloned()
.unwrap_or_default();
assert!(
live_bb1.contains(&(SlotId(0), FieldIdx(0))),
"field should be live at entry of block where it is read"
);
let live_bb0 = result
.field_liveness
.get(&BasicBlockId(0))
.cloned()
.unwrap_or_default();
assert!(
!live_bb0.contains(&(SlotId(0), FieldIdx(0))),
"field defined before use in bb0 should not be live at bb0 entry"
);
}
#[test]
fn test_loop_init() {
let mir = MirFunction {
name: "test".to_string(),
blocks: vec![
BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
},
BasicBlock {
id: BasicBlockId(1),
statements: vec![],
terminator: make_terminator(TerminatorKind::SwitchBool {
operand: Operand::Constant(MirConstant::Bool(true)),
true_bb: BasicBlockId(2),
false_bb: BasicBlockId(3),
}),
},
BasicBlock {
id: BasicBlockId(2),
statements: vec![make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
)],
terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
},
BasicBlock {
id: BasicBlockId(3),
statements: vec![make_stmt(
StatementKind::Assign(
Place::Local(SlotId(1)),
Rvalue::Use(Operand::Copy(field_place(0, 0))),
),
1,
)],
terminator: make_terminator(TerminatorKind::Return),
},
],
num_locals: 2,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
let init_at_bb3 = result
.definitely_initialized
.get(&BasicBlockId(3))
.cloned()
.unwrap_or_default();
assert!(
!init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
"field initialized only in loop body should not be definitely initialized at loop exit"
);
assert!(
result
.conditionally_initialized
.contains(&(SlotId(0), FieldIdx(0))),
);
}
#[test]
fn test_empty_function() {
let mir = MirFunction {
name: "empty".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![],
terminator: make_terminator(TerminatorKind::Return),
}],
num_locals: 0,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
assert!(result.dead_fields.is_empty());
assert!(result.conditionally_initialized.is_empty());
}
#[test]
fn test_multiple_slots() {
let mir = MirFunction {
name: "test".to_string(),
blocks: vec![BasicBlock {
id: BasicBlockId(0),
statements: vec![
make_stmt(
StatementKind::Assign(
field_place(0, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
),
0,
),
make_stmt(
StatementKind::Assign(
field_place(1, 0),
Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
),
1,
),
make_stmt(
StatementKind::Assign(
Place::Local(SlotId(2)),
Rvalue::BinaryOp(
BinOp::Add,
Operand::Copy(field_place(0, 0)),
Operand::Copy(field_place(1, 0)),
),
),
2,
),
],
terminator: make_terminator(TerminatorKind::Return),
}],
num_locals: 3,
param_slots: vec![],
param_reference_kinds: vec![],
local_types: vec![
LocalTypeInfo::NonCopy,
LocalTypeInfo::NonCopy,
LocalTypeInfo::Copy,
],
span: span(),
field_name_table: std::collections::HashMap::new(),
local_struct_type_names: std::collections::HashMap::new(),
local_typed_array_element_types: std::collections::HashMap::new(),
local_declared_scalar_types: std::collections::HashMap::new(),
};
let cfg = ControlFlowGraph::build(&mir);
let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
assert!(!result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
assert!(!result.dead_fields.contains(&(SlotId(1), FieldIdx(0))));
}
}