1use super::cfg::ControlFlowGraph;
18use super::types::*;
19use std::collections::{HashMap, HashSet};
20
21pub type FieldKey = (SlotId, FieldIdx);
23
24#[derive(Debug)]
26pub struct FieldAnalysis {
27 pub definitely_initialized: HashMap<BasicBlockId, HashSet<FieldKey>>,
29 pub field_liveness: HashMap<BasicBlockId, HashSet<FieldKey>>,
31 pub dead_fields: HashSet<FieldKey>,
33 pub conditionally_initialized: HashSet<FieldKey>,
35 pub hoisted_fields: HashMap<SlotId, Vec<FieldIdx>>,
38 pub hoisting_recommendations: HashMap<SlotId, Vec<(FieldIdx, String)>>,
42}
43
44pub struct FieldAnalysisInput<'a> {
46 pub mir: &'a MirFunction,
47 pub cfg: &'a ControlFlowGraph,
48}
49
50pub fn analyze_fields(input: &FieldAnalysisInput) -> FieldAnalysis {
52 let mir = input.mir;
53 let cfg = input.cfg;
54
55 let (block_writes, block_reads, all_writes, all_reads) = collect_field_accesses(mir);
57
58 let definitely_initialized = compute_definite_initialization(mir, cfg, &block_writes);
60
61 let field_liveness = compute_field_liveness(mir, cfg, &block_writes, &block_reads);
63
64 let dead_fields: HashSet<FieldKey> = all_writes.difference(&all_reads).cloned().collect();
66
67 let conditionally_initialized =
71 compute_conditionally_initialized(mir, &block_reads, &definitely_initialized, &all_writes);
72
73 let mut hoisted_fields: HashMap<SlotId, Vec<FieldIdx>> = HashMap::new();
77 for key in &all_writes {
78 if !dead_fields.contains(key) {
79 hoisted_fields.entry(key.0).or_default().push(key.1);
80 }
81 }
82
83 FieldAnalysis {
84 definitely_initialized,
85 field_liveness,
86 dead_fields,
87 conditionally_initialized,
88 hoisted_fields,
89 hoisting_recommendations: HashMap::new(), }
91}
92
93fn collect_field_accesses(
97 mir: &MirFunction,
98) -> (
99 HashMap<BasicBlockId, HashSet<FieldKey>>,
100 HashMap<BasicBlockId, HashSet<FieldKey>>,
101 HashSet<FieldKey>,
102 HashSet<FieldKey>,
103) {
104 let mut block_writes: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
105 let mut block_reads: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
106 let mut all_writes = HashSet::new();
107 let mut all_reads = HashSet::new();
108
109 for block in &mir.blocks {
110 let writes = block_writes.entry(block.id).or_default();
111 let reads = block_reads.entry(block.id).or_default();
112
113 for stmt in &block.statements {
114 collect_statement_field_accesses(&stmt.kind, writes, reads);
115 }
116 collect_terminator_field_reads(&block.terminator.kind, reads);
118
119 all_writes.extend(writes.iter().cloned());
120 all_reads.extend(reads.iter().cloned());
121 }
122
123 (block_writes, block_reads, all_writes, all_reads)
124}
125
126fn collect_statement_field_accesses(
128 kind: &StatementKind,
129 writes: &mut HashSet<FieldKey>,
130 reads: &mut HashSet<FieldKey>,
131) {
132 match kind {
133 StatementKind::Assign(place, rvalue) => {
134 if let Some(key) = extract_field_key(place) {
136 writes.insert(key);
137 }
138 collect_rvalue_field_reads(rvalue, reads);
142 }
143 StatementKind::Drop(place) => {
144 if let Some(key) = extract_field_key(place) {
146 reads.insert(key);
147 }
148 }
149 StatementKind::TaskBoundary(ops, ..)
150 | StatementKind::ClosureCapture { operands: ops, .. }
151 | StatementKind::ArrayStore { operands: ops, .. }
152 | StatementKind::ObjectStore { operands: ops, .. }
153 | StatementKind::EnumStore { operands: ops, .. } => {
154 for op in ops {
155 collect_operand_field_reads(op, reads);
156 }
157 }
158 StatementKind::Nop => {}
159 }
160}
161
162fn collect_rvalue_field_reads(rvalue: &Rvalue, reads: &mut HashSet<FieldKey>) {
164 match rvalue {
165 Rvalue::Use(op) | Rvalue::Clone(op) | Rvalue::UnaryOp(_, op) => {
166 collect_operand_field_reads(op, reads);
167 }
168 Rvalue::Borrow(_, place) => {
169 if let Some(key) = extract_field_key(place) {
170 reads.insert(key);
171 }
172 }
173 Rvalue::BinaryOp(_, lhs, rhs) => {
174 collect_operand_field_reads(lhs, reads);
175 collect_operand_field_reads(rhs, reads);
176 }
177 Rvalue::Aggregate(ops) => {
178 for op in ops {
179 collect_operand_field_reads(op, reads);
180 }
181 }
182 }
183}
184
185fn collect_operand_field_reads(op: &Operand, reads: &mut HashSet<FieldKey>) {
187 match op {
188 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
189 if let Some(key) = extract_field_key(place) {
190 reads.insert(key);
191 }
192 }
193 Operand::Constant(_) => {}
194 }
195}
196
197fn collect_terminator_field_reads(kind: &TerminatorKind, reads: &mut HashSet<FieldKey>) {
199 match kind {
200 TerminatorKind::SwitchBool { operand, .. } => {
201 collect_operand_field_reads(operand, reads);
202 }
203 TerminatorKind::Call { func, args, .. } => {
204 collect_operand_field_reads(func, reads);
205 for arg in args {
206 collect_operand_field_reads(arg, reads);
207 }
208 }
209 TerminatorKind::Goto(_) | TerminatorKind::Return | TerminatorKind::Unreachable => {}
210 }
211}
212
213fn extract_field_key(place: &Place) -> Option<FieldKey> {
217 match place {
218 Place::Field(base, idx) => match base.as_ref() {
219 Place::Local(slot) => Some((*slot, *idx)),
220 _ => None,
221 },
222 _ => None,
223 }
224}
225
226fn compute_definite_initialization(
238 mir: &MirFunction,
239 cfg: &ControlFlowGraph,
240 block_writes: &HashMap<BasicBlockId, HashSet<FieldKey>>,
241) -> HashMap<BasicBlockId, HashSet<FieldKey>> {
242 let rpo = cfg.reverse_postorder();
243 let entry = mir.entry_block();
244
245 let mut init_in: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
247 let mut init_out: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
248
249 let universe: HashSet<FieldKey> = block_writes.values().flatten().cloned().collect();
252
253 for block in &mir.blocks {
255 if block.id == entry {
256 init_in.insert(block.id, HashSet::new());
257 } else {
258 init_in.insert(block.id, universe.clone());
259 }
260 }
261
262 for block in &mir.blocks {
264 let in_set = init_in.get(&block.id).cloned().unwrap_or_default();
265 let writes = block_writes.get(&block.id).cloned().unwrap_or_default();
266 let out_set: HashSet<FieldKey> = in_set.union(&writes).cloned().collect();
267 init_out.insert(block.id, out_set);
268 }
269
270 let mut changed = true;
272 while changed {
273 changed = false;
274
275 for &block_id in &rpo {
276 let preds = cfg.predecessors(block_id);
278 let new_in = if block_id == entry {
279 HashSet::new()
280 } else if preds.is_empty() {
281 universe.clone()
283 } else {
284 let mut merged = init_out
285 .get(&preds[0])
286 .cloned()
287 .unwrap_or_else(|| universe.clone());
288 for &pred in &preds[1..] {
289 let pred_out = init_out
290 .get(&pred)
291 .cloned()
292 .unwrap_or_else(|| universe.clone());
293 merged = merged.intersection(&pred_out).cloned().collect();
294 }
295 merged
296 };
297
298 let writes = block_writes.get(&block_id).cloned().unwrap_or_default();
300 let new_out: HashSet<FieldKey> = new_in.union(&writes).cloned().collect();
301
302 if new_in != *init_in.get(&block_id).unwrap_or(&HashSet::new()) {
303 changed = true;
304 init_in.insert(block_id, new_in);
305 }
306 if new_out != *init_out.get(&block_id).unwrap_or(&HashSet::new()) {
307 changed = true;
308 init_out.insert(block_id, new_out);
309 }
310 }
311 }
312
313 init_in
314}
315
316fn compute_field_liveness(
333 mir: &MirFunction,
334 cfg: &ControlFlowGraph,
335 _block_writes: &HashMap<BasicBlockId, HashSet<FieldKey>>,
336 _block_reads: &HashMap<BasicBlockId, HashSet<FieldKey>>,
337) -> HashMap<BasicBlockId, HashSet<FieldKey>> {
338 let rpo = cfg.reverse_postorder();
339
340 let mut use_before_def: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
345 let mut def_before_use: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
346
347 for block in &mir.blocks {
348 let (ubd, dbu) = compute_block_use_def_order(block);
349 use_before_def.insert(block.id, ubd);
350 def_before_use.insert(block.id, dbu);
351 }
352
353 let mut live_in: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
354 let mut live_out: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
355
356 for block in &mir.blocks {
357 live_in.insert(block.id, HashSet::new());
358 live_out.insert(block.id, HashSet::new());
359 }
360
361 let mut changed = true;
362 while changed {
363 changed = false;
364
365 for &block_id in rpo.iter().rev() {
367 let mut new_live_out: HashSet<FieldKey> = HashSet::new();
369 for &succ in cfg.successors(block_id) {
370 if let Some(succ_in) = live_in.get(&succ) {
371 new_live_out.extend(succ_in.iter().cloned());
372 }
373 }
374
375 let dbu = def_before_use.get(&block_id).cloned().unwrap_or_default();
377 let ubd = use_before_def.get(&block_id).cloned().unwrap_or_default();
378
379 let mut new_live_in: HashSet<FieldKey> =
380 new_live_out.difference(&dbu).cloned().collect();
381 new_live_in.extend(ubd.iter().cloned());
382
383 if new_live_in != *live_in.get(&block_id).unwrap_or(&HashSet::new()) {
384 changed = true;
385 live_in.insert(block_id, new_live_in);
386 }
387 if new_live_out != *live_out.get(&block_id).unwrap_or(&HashSet::new()) {
388 changed = true;
389 live_out.insert(block_id, new_live_out);
390 }
391 }
392 }
393
394 live_in
395}
396
397fn compute_block_use_def_order(block: &BasicBlock) -> (HashSet<FieldKey>, HashSet<FieldKey>) {
403 let mut use_before_def = HashSet::new();
404 let mut def_before_use = HashSet::new();
405 let mut seen = HashSet::new();
406
407 for stmt in &block.statements {
408 let mut stmt_reads = HashSet::new();
410 let mut stmt_writes = HashSet::new();
411
412 match &stmt.kind {
413 StatementKind::Assign(place, rvalue) => {
414 collect_rvalue_field_reads(rvalue, &mut stmt_reads);
416 if let Some(key) = extract_field_key(place) {
417 stmt_writes.insert(key);
418 }
419 }
420 StatementKind::Drop(place) => {
421 if let Some(key) = extract_field_key(place) {
422 stmt_reads.insert(key);
423 }
424 }
425 StatementKind::TaskBoundary(ops, ..)
426 | StatementKind::ClosureCapture { operands: ops, .. }
427 | StatementKind::ArrayStore { operands: ops, .. }
428 | StatementKind::ObjectStore { operands: ops, .. }
429 | StatementKind::EnumStore { operands: ops, .. } => {
430 for op in ops {
431 collect_operand_field_reads(op, &mut stmt_reads);
432 }
433 }
434 StatementKind::Nop => {}
435 }
436
437 for key in &stmt_reads {
439 if !seen.contains(key) {
440 use_before_def.insert(*key);
441 seen.insert(*key);
442 }
443 }
444 for key in &stmt_writes {
445 if !seen.contains(key) {
446 def_before_use.insert(*key);
447 seen.insert(*key);
448 }
449 }
450 }
451
452 let mut term_reads = HashSet::new();
454 collect_terminator_field_reads(&block.terminator.kind, &mut term_reads);
455 for key in &term_reads {
456 if !seen.contains(key) {
457 use_before_def.insert(*key);
458 }
460 }
461
462 (use_before_def, def_before_use)
463}
464
465fn compute_conditionally_initialized(
471 mir: &MirFunction,
472 block_reads: &HashMap<BasicBlockId, HashSet<FieldKey>>,
473 definitely_initialized: &HashMap<BasicBlockId, HashSet<FieldKey>>,
474 all_writes: &HashSet<FieldKey>,
475) -> HashSet<FieldKey> {
476 let mut conditionally = HashSet::new();
477
478 for block in &mir.blocks {
479 let reads = match block_reads.get(&block.id) {
480 Some(r) => r,
481 None => continue,
482 };
483 let init = definitely_initialized
484 .get(&block.id)
485 .cloned()
486 .unwrap_or_default();
487
488 for key in reads {
489 if all_writes.contains(key) && !init.contains(key) {
492 conditionally.insert(*key);
493 }
494 }
495 }
496
497 conditionally
498}
499
500#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::mir::cfg::ControlFlowGraph;
506
507 fn span() -> shape_ast::ast::Span {
508 shape_ast::ast::Span { start: 0, end: 1 }
509 }
510
511 fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
512 MirStatement {
513 kind,
514 span: span(),
515 point: Point(point),
516 }
517 }
518
519 fn make_terminator(kind: TerminatorKind) -> Terminator {
520 Terminator { kind, span: span() }
521 }
522
523 fn field_place(slot: u16, field: u16) -> Place {
524 Place::Field(Box::new(Place::Local(SlotId(slot))), FieldIdx(field))
525 }
526
527 #[test]
530 fn test_unconditional_field_init() {
531 let mir = MirFunction {
534 name: "test".to_string(),
535 blocks: vec![BasicBlock {
536 id: BasicBlockId(0),
537 statements: vec![
538 make_stmt(
539 StatementKind::Assign(
540 field_place(0, 0),
541 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
542 ),
543 0,
544 ),
545 make_stmt(
546 StatementKind::Assign(
547 field_place(0, 1),
548 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
549 ),
550 1,
551 ),
552 ],
553 terminator: make_terminator(TerminatorKind::Return),
554 }],
555 num_locals: 1,
556 param_slots: vec![],
557 param_reference_kinds: vec![],
558 local_types: vec![LocalTypeInfo::NonCopy],
559 span: span(),
560 };
561
562 let cfg = ControlFlowGraph::build(&mir);
563 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
564
565 let init_at_entry = result
567 .definitely_initialized
568 .get(&BasicBlockId(0))
569 .cloned()
570 .unwrap_or_default();
571 assert!(init_at_entry.is_empty());
572
573 assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
575 assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(1))));
576
577 assert!(result.conditionally_initialized.is_empty());
579 }
580
581 #[test]
584 fn test_conditional_field_init() {
585 let mir = MirFunction {
592 name: "test".to_string(),
593 blocks: vec![
594 BasicBlock {
595 id: BasicBlockId(0),
596 statements: vec![],
597 terminator: make_terminator(TerminatorKind::SwitchBool {
598 operand: Operand::Constant(MirConstant::Bool(true)),
599 true_bb: BasicBlockId(1),
600 false_bb: BasicBlockId(2),
601 }),
602 },
603 BasicBlock {
604 id: BasicBlockId(1),
605 statements: vec![make_stmt(
606 StatementKind::Assign(
607 field_place(0, 0),
608 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
609 ),
610 0,
611 )],
612 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
613 },
614 BasicBlock {
615 id: BasicBlockId(2),
616 statements: vec![],
617 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
618 },
619 BasicBlock {
620 id: BasicBlockId(3),
621 statements: vec![make_stmt(
622 StatementKind::Assign(
623 Place::Local(SlotId(1)),
624 Rvalue::Use(Operand::Copy(field_place(0, 0))),
625 ),
626 1,
627 )],
628 terminator: make_terminator(TerminatorKind::Return),
629 },
630 ],
631 num_locals: 2,
632 param_slots: vec![],
633 param_reference_kinds: vec![],
634 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
635 span: span(),
636 };
637
638 let cfg = ControlFlowGraph::build(&mir);
639 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
640
641 let init_at_bb3 = result
643 .definitely_initialized
644 .get(&BasicBlockId(3))
645 .cloned()
646 .unwrap_or_default();
647 assert!(
648 !init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
649 "field should not be definitely initialized at join point"
650 );
651
652 assert!(
654 result
655 .conditionally_initialized
656 .contains(&(SlotId(0), FieldIdx(0))),
657 "field should be conditionally initialized"
658 );
659 }
660
661 #[test]
664 fn test_both_branches_init() {
665 let mir = MirFunction {
672 name: "test".to_string(),
673 blocks: vec![
674 BasicBlock {
675 id: BasicBlockId(0),
676 statements: vec![],
677 terminator: make_terminator(TerminatorKind::SwitchBool {
678 operand: Operand::Constant(MirConstant::Bool(true)),
679 true_bb: BasicBlockId(1),
680 false_bb: BasicBlockId(2),
681 }),
682 },
683 BasicBlock {
684 id: BasicBlockId(1),
685 statements: vec![make_stmt(
686 StatementKind::Assign(
687 field_place(0, 0),
688 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
689 ),
690 0,
691 )],
692 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
693 },
694 BasicBlock {
695 id: BasicBlockId(2),
696 statements: vec![make_stmt(
697 StatementKind::Assign(
698 field_place(0, 0),
699 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
700 ),
701 1,
702 )],
703 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
704 },
705 BasicBlock {
706 id: BasicBlockId(3),
707 statements: vec![make_stmt(
708 StatementKind::Assign(
709 Place::Local(SlotId(1)),
710 Rvalue::Use(Operand::Copy(field_place(0, 0))),
711 ),
712 2,
713 )],
714 terminator: make_terminator(TerminatorKind::Return),
715 },
716 ],
717 num_locals: 2,
718 param_slots: vec![],
719 param_reference_kinds: vec![],
720 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
721 span: span(),
722 };
723
724 let cfg = ControlFlowGraph::build(&mir);
725 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
726
727 let init_at_bb3 = result
729 .definitely_initialized
730 .get(&BasicBlockId(3))
731 .cloned()
732 .unwrap_or_default();
733 assert!(
734 init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
735 "field should be definitely initialized when both branches write it"
736 );
737
738 assert!(
740 !result
741 .conditionally_initialized
742 .contains(&(SlotId(0), FieldIdx(0))),
743 );
744
745 assert!(
747 !result.dead_fields.contains(&(SlotId(0), FieldIdx(0))),
748 "field is read so should not be dead"
749 );
750 }
751
752 #[test]
755 fn test_dead_field() {
756 let mir = MirFunction {
759 name: "test".to_string(),
760 blocks: vec![BasicBlock {
761 id: BasicBlockId(0),
762 statements: vec![
763 make_stmt(
764 StatementKind::Assign(
765 field_place(0, 0),
766 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
767 ),
768 0,
769 ),
770 make_stmt(
771 StatementKind::Assign(
772 field_place(0, 1),
773 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
774 ),
775 1,
776 ),
777 make_stmt(
778 StatementKind::Assign(
779 Place::Local(SlotId(1)),
780 Rvalue::Use(Operand::Copy(field_place(0, 0))),
781 ),
782 2,
783 ),
784 ],
785 terminator: make_terminator(TerminatorKind::Return),
786 }],
787 num_locals: 2,
788 param_slots: vec![],
789 param_reference_kinds: vec![],
790 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
791 span: span(),
792 };
793
794 let cfg = ControlFlowGraph::build(&mir);
795 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
796
797 assert!(!result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
799 assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(1))));
801 }
802
803 #[test]
806 fn test_field_liveness() {
807 let mir = MirFunction {
812 name: "test".to_string(),
813 blocks: vec![
814 BasicBlock {
815 id: BasicBlockId(0),
816 statements: vec![make_stmt(
817 StatementKind::Assign(
818 field_place(0, 0),
819 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
820 ),
821 0,
822 )],
823 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
824 },
825 BasicBlock {
826 id: BasicBlockId(1),
827 statements: vec![make_stmt(
828 StatementKind::Assign(
829 Place::Local(SlotId(1)),
830 Rvalue::Use(Operand::Copy(field_place(0, 0))),
831 ),
832 1,
833 )],
834 terminator: make_terminator(TerminatorKind::Return),
835 },
836 ],
837 num_locals: 2,
838 param_slots: vec![],
839 param_reference_kinds: vec![],
840 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
841 span: span(),
842 };
843
844 let cfg = ControlFlowGraph::build(&mir);
845 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
846
847 let live_bb1 = result
849 .field_liveness
850 .get(&BasicBlockId(1))
851 .cloned()
852 .unwrap_or_default();
853 assert!(
854 live_bb1.contains(&(SlotId(0), FieldIdx(0))),
855 "field should be live at entry of block where it is read"
856 );
857
858 let live_bb0 = result
865 .field_liveness
866 .get(&BasicBlockId(0))
867 .cloned()
868 .unwrap_or_default();
869 assert!(
870 !live_bb0.contains(&(SlotId(0), FieldIdx(0))),
871 "field defined before use in bb0 should not be live at bb0 entry"
872 );
873 }
874
875 #[test]
878 fn test_loop_init() {
879 let mir = MirFunction {
888 name: "test".to_string(),
889 blocks: vec![
890 BasicBlock {
891 id: BasicBlockId(0),
892 statements: vec![],
893 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
894 },
895 BasicBlock {
896 id: BasicBlockId(1),
897 statements: vec![],
898 terminator: make_terminator(TerminatorKind::SwitchBool {
899 operand: Operand::Constant(MirConstant::Bool(true)),
900 true_bb: BasicBlockId(2),
901 false_bb: BasicBlockId(3),
902 }),
903 },
904 BasicBlock {
905 id: BasicBlockId(2),
906 statements: vec![make_stmt(
907 StatementKind::Assign(
908 field_place(0, 0),
909 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
910 ),
911 0,
912 )],
913 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
914 },
915 BasicBlock {
916 id: BasicBlockId(3),
917 statements: vec![make_stmt(
918 StatementKind::Assign(
919 Place::Local(SlotId(1)),
920 Rvalue::Use(Operand::Copy(field_place(0, 0))),
921 ),
922 1,
923 )],
924 terminator: make_terminator(TerminatorKind::Return),
925 },
926 ],
927 num_locals: 2,
928 param_slots: vec![],
929 param_reference_kinds: vec![],
930 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
931 span: span(),
932 };
933
934 let cfg = ControlFlowGraph::build(&mir);
935 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
936
937 let init_at_bb3 = result
940 .definitely_initialized
941 .get(&BasicBlockId(3))
942 .cloned()
943 .unwrap_or_default();
944 assert!(
945 !init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
946 "field initialized only in loop body should not be definitely initialized at loop exit"
947 );
948
949 assert!(
951 result
952 .conditionally_initialized
953 .contains(&(SlotId(0), FieldIdx(0))),
954 );
955 }
956
957 #[test]
960 fn test_empty_function() {
961 let mir = MirFunction {
962 name: "empty".to_string(),
963 blocks: vec![BasicBlock {
964 id: BasicBlockId(0),
965 statements: vec![],
966 terminator: make_terminator(TerminatorKind::Return),
967 }],
968 num_locals: 0,
969 param_slots: vec![],
970 param_reference_kinds: vec![],
971 local_types: vec![],
972 span: span(),
973 };
974
975 let cfg = ControlFlowGraph::build(&mir);
976 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
977
978 assert!(result.dead_fields.is_empty());
979 assert!(result.conditionally_initialized.is_empty());
980 }
981
982 #[test]
985 fn test_multiple_slots() {
986 let mir = MirFunction {
989 name: "test".to_string(),
990 blocks: vec![BasicBlock {
991 id: BasicBlockId(0),
992 statements: vec![
993 make_stmt(
994 StatementKind::Assign(
995 field_place(0, 0),
996 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
997 ),
998 0,
999 ),
1000 make_stmt(
1001 StatementKind::Assign(
1002 field_place(1, 0),
1003 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
1004 ),
1005 1,
1006 ),
1007 make_stmt(
1008 StatementKind::Assign(
1009 Place::Local(SlotId(2)),
1010 Rvalue::BinaryOp(
1011 BinOp::Add,
1012 Operand::Copy(field_place(0, 0)),
1013 Operand::Copy(field_place(1, 0)),
1014 ),
1015 ),
1016 2,
1017 ),
1018 ],
1019 terminator: make_terminator(TerminatorKind::Return),
1020 }],
1021 num_locals: 3,
1022 param_slots: vec![],
1023 param_reference_kinds: vec![],
1024 local_types: vec![
1025 LocalTypeInfo::NonCopy,
1026 LocalTypeInfo::NonCopy,
1027 LocalTypeInfo::Copy,
1028 ],
1029 span: span(),
1030 };
1031
1032 let cfg = ControlFlowGraph::build(&mir);
1033 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
1034
1035 assert!(!result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
1037 assert!(!result.dead_fields.contains(&(SlotId(1), FieldIdx(0))));
1038 }
1039}