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 Rvalue::EnumTest { operand, .. }
183 | Rvalue::EnumPayload { operand, .. }
184 | Rvalue::TypePatternTest { operand, .. }
185 | Rvalue::EnumDiscriminantTest { operand, .. } => {
186 collect_operand_field_reads(operand, reads);
187 }
188 }
189}
190
191fn collect_operand_field_reads(op: &Operand, reads: &mut HashSet<FieldKey>) {
193 match op {
194 Operand::Copy(place) | Operand::Move(place) | Operand::MoveExplicit(place) => {
195 if let Some(key) = extract_field_key(place) {
196 reads.insert(key);
197 }
198 }
199 Operand::Constant(_) => {}
200 }
201}
202
203fn collect_terminator_field_reads(kind: &TerminatorKind, reads: &mut HashSet<FieldKey>) {
205 match kind {
206 TerminatorKind::SwitchBool { operand, .. } => {
207 collect_operand_field_reads(operand, reads);
208 }
209 TerminatorKind::Call { func, args, .. } => {
210 collect_operand_field_reads(func, reads);
211 for arg in args {
212 collect_operand_field_reads(arg, reads);
213 }
214 }
215 TerminatorKind::Goto(_) | TerminatorKind::Return | TerminatorKind::Unreachable => {}
216 }
217}
218
219fn extract_field_key(place: &Place) -> Option<FieldKey> {
223 match place {
224 Place::Field(base, idx) => match base.as_ref() {
225 Place::Local(slot) => Some((*slot, *idx)),
226 _ => None,
227 },
228 _ => None,
229 }
230}
231
232fn compute_definite_initialization(
244 mir: &MirFunction,
245 cfg: &ControlFlowGraph,
246 block_writes: &HashMap<BasicBlockId, HashSet<FieldKey>>,
247) -> HashMap<BasicBlockId, HashSet<FieldKey>> {
248 let rpo = cfg.reverse_postorder();
249 let entry = mir.entry_block();
250
251 let mut init_in: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
253 let mut init_out: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
254
255 let universe: HashSet<FieldKey> = block_writes.values().flatten().cloned().collect();
258
259 for block in &mir.blocks {
261 if block.id == entry {
262 init_in.insert(block.id, HashSet::new());
263 } else {
264 init_in.insert(block.id, universe.clone());
265 }
266 }
267
268 for block in &mir.blocks {
270 let in_set = init_in.get(&block.id).cloned().unwrap_or_default();
271 let writes = block_writes.get(&block.id).cloned().unwrap_or_default();
272 let out_set: HashSet<FieldKey> = in_set.union(&writes).cloned().collect();
273 init_out.insert(block.id, out_set);
274 }
275
276 let mut changed = true;
278 while changed {
279 changed = false;
280
281 for &block_id in &rpo {
282 let preds = cfg.predecessors(block_id);
284 let new_in = if block_id == entry {
285 HashSet::new()
286 } else if preds.is_empty() {
287 universe.clone()
289 } else {
290 let mut merged = init_out
291 .get(&preds[0])
292 .cloned()
293 .unwrap_or_else(|| universe.clone());
294 for &pred in &preds[1..] {
295 let pred_out = init_out
296 .get(&pred)
297 .cloned()
298 .unwrap_or_else(|| universe.clone());
299 merged = merged.intersection(&pred_out).cloned().collect();
300 }
301 merged
302 };
303
304 let writes = block_writes.get(&block_id).cloned().unwrap_or_default();
306 let new_out: HashSet<FieldKey> = new_in.union(&writes).cloned().collect();
307
308 if new_in != *init_in.get(&block_id).unwrap_or(&HashSet::new()) {
309 changed = true;
310 init_in.insert(block_id, new_in);
311 }
312 if new_out != *init_out.get(&block_id).unwrap_or(&HashSet::new()) {
313 changed = true;
314 init_out.insert(block_id, new_out);
315 }
316 }
317 }
318
319 init_in
320}
321
322fn compute_field_liveness(
339 mir: &MirFunction,
340 cfg: &ControlFlowGraph,
341 _block_writes: &HashMap<BasicBlockId, HashSet<FieldKey>>,
342 _block_reads: &HashMap<BasicBlockId, HashSet<FieldKey>>,
343) -> HashMap<BasicBlockId, HashSet<FieldKey>> {
344 let rpo = cfg.reverse_postorder();
345
346 let mut use_before_def: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
351 let mut def_before_use: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
352
353 for block in &mir.blocks {
354 let (ubd, dbu) = compute_block_use_def_order(block);
355 use_before_def.insert(block.id, ubd);
356 def_before_use.insert(block.id, dbu);
357 }
358
359 let mut live_in: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
360 let mut live_out: HashMap<BasicBlockId, HashSet<FieldKey>> = HashMap::new();
361
362 for block in &mir.blocks {
363 live_in.insert(block.id, HashSet::new());
364 live_out.insert(block.id, HashSet::new());
365 }
366
367 let mut changed = true;
368 while changed {
369 changed = false;
370
371 for &block_id in rpo.iter().rev() {
373 let mut new_live_out: HashSet<FieldKey> = HashSet::new();
375 for &succ in cfg.successors(block_id) {
376 if let Some(succ_in) = live_in.get(&succ) {
377 new_live_out.extend(succ_in.iter().cloned());
378 }
379 }
380
381 let dbu = def_before_use.get(&block_id).cloned().unwrap_or_default();
383 let ubd = use_before_def.get(&block_id).cloned().unwrap_or_default();
384
385 let mut new_live_in: HashSet<FieldKey> =
386 new_live_out.difference(&dbu).cloned().collect();
387 new_live_in.extend(ubd.iter().cloned());
388
389 if new_live_in != *live_in.get(&block_id).unwrap_or(&HashSet::new()) {
390 changed = true;
391 live_in.insert(block_id, new_live_in);
392 }
393 if new_live_out != *live_out.get(&block_id).unwrap_or(&HashSet::new()) {
394 changed = true;
395 live_out.insert(block_id, new_live_out);
396 }
397 }
398 }
399
400 live_in
401}
402
403fn compute_block_use_def_order(block: &BasicBlock) -> (HashSet<FieldKey>, HashSet<FieldKey>) {
409 let mut use_before_def = HashSet::new();
410 let mut def_before_use = HashSet::new();
411 let mut seen = HashSet::new();
412
413 for stmt in &block.statements {
414 let mut stmt_reads = HashSet::new();
416 let mut stmt_writes = HashSet::new();
417
418 match &stmt.kind {
419 StatementKind::Assign(place, rvalue) => {
420 collect_rvalue_field_reads(rvalue, &mut stmt_reads);
422 if let Some(key) = extract_field_key(place) {
423 stmt_writes.insert(key);
424 }
425 }
426 StatementKind::Drop(place) => {
427 if let Some(key) = extract_field_key(place) {
428 stmt_reads.insert(key);
429 }
430 }
431 StatementKind::TaskBoundary(ops, ..)
432 | StatementKind::ClosureCapture { operands: ops, .. }
433 | StatementKind::ArrayStore { operands: ops, .. }
434 | StatementKind::ObjectStore { operands: ops, .. }
435 | StatementKind::EnumStore { operands: ops, .. } => {
436 for op in ops {
437 collect_operand_field_reads(op, &mut stmt_reads);
438 }
439 }
440 StatementKind::Nop => {}
441 }
442
443 for key in &stmt_reads {
445 if !seen.contains(key) {
446 use_before_def.insert(*key);
447 seen.insert(*key);
448 }
449 }
450 for key in &stmt_writes {
451 if !seen.contains(key) {
452 def_before_use.insert(*key);
453 seen.insert(*key);
454 }
455 }
456 }
457
458 let mut term_reads = HashSet::new();
460 collect_terminator_field_reads(&block.terminator.kind, &mut term_reads);
461 for key in &term_reads {
462 if !seen.contains(key) {
463 use_before_def.insert(*key);
464 }
466 }
467
468 (use_before_def, def_before_use)
469}
470
471fn compute_conditionally_initialized(
477 mir: &MirFunction,
478 block_reads: &HashMap<BasicBlockId, HashSet<FieldKey>>,
479 definitely_initialized: &HashMap<BasicBlockId, HashSet<FieldKey>>,
480 all_writes: &HashSet<FieldKey>,
481) -> HashSet<FieldKey> {
482 let mut conditionally = HashSet::new();
483
484 for block in &mir.blocks {
485 let reads = match block_reads.get(&block.id) {
486 Some(r) => r,
487 None => continue,
488 };
489 let init = definitely_initialized
490 .get(&block.id)
491 .cloned()
492 .unwrap_or_default();
493
494 for key in reads {
495 if all_writes.contains(key) && !init.contains(key) {
498 conditionally.insert(*key);
499 }
500 }
501 }
502
503 conditionally
504}
505
506#[cfg(test)]
509mod tests {
510 use super::*;
511 use crate::mir::cfg::ControlFlowGraph;
512
513 fn span() -> shape_ast::ast::Span {
514 shape_ast::ast::Span { start: 0, end: 1 }
515 }
516
517 fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
518 MirStatement {
519 kind,
520 span: span(),
521 point: Point(point),
522 }
523 }
524
525 fn make_terminator(kind: TerminatorKind) -> Terminator {
526 Terminator { kind, span: span() }
527 }
528
529 fn field_place(slot: u16, field: u16) -> Place {
530 Place::Field(Box::new(Place::Local(SlotId(slot))), FieldIdx(field))
531 }
532
533 #[test]
536 fn test_unconditional_field_init() {
537 let mir = MirFunction {
540 name: "test".to_string(),
541 blocks: vec![BasicBlock {
542 id: BasicBlockId(0),
543 statements: vec![
544 make_stmt(
545 StatementKind::Assign(
546 field_place(0, 0),
547 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
548 ),
549 0,
550 ),
551 make_stmt(
552 StatementKind::Assign(
553 field_place(0, 1),
554 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
555 ),
556 1,
557 ),
558 ],
559 terminator: make_terminator(TerminatorKind::Return),
560 }],
561 num_locals: 1,
562 param_slots: vec![],
563 param_reference_kinds: vec![],
564 local_types: vec![LocalTypeInfo::NonCopy],
565 span: span(),
566 field_name_table: std::collections::HashMap::new(),
567 local_struct_type_names: std::collections::HashMap::new(),
568 local_typed_array_element_types: std::collections::HashMap::new(),
569 local_declared_scalar_types: std::collections::HashMap::new(),
570 };
571
572 let cfg = ControlFlowGraph::build(&mir);
573 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
574
575 let init_at_entry = result
577 .definitely_initialized
578 .get(&BasicBlockId(0))
579 .cloned()
580 .unwrap_or_default();
581 assert!(init_at_entry.is_empty());
582
583 assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
585 assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(1))));
586
587 assert!(result.conditionally_initialized.is_empty());
589 }
590
591 #[test]
594 fn test_conditional_field_init() {
595 let mir = MirFunction {
602 name: "test".to_string(),
603 blocks: vec![
604 BasicBlock {
605 id: BasicBlockId(0),
606 statements: vec![],
607 terminator: make_terminator(TerminatorKind::SwitchBool {
608 operand: Operand::Constant(MirConstant::Bool(true)),
609 true_bb: BasicBlockId(1),
610 false_bb: BasicBlockId(2),
611 }),
612 },
613 BasicBlock {
614 id: BasicBlockId(1),
615 statements: vec![make_stmt(
616 StatementKind::Assign(
617 field_place(0, 0),
618 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
619 ),
620 0,
621 )],
622 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
623 },
624 BasicBlock {
625 id: BasicBlockId(2),
626 statements: vec![],
627 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
628 },
629 BasicBlock {
630 id: BasicBlockId(3),
631 statements: vec![make_stmt(
632 StatementKind::Assign(
633 Place::Local(SlotId(1)),
634 Rvalue::Use(Operand::Copy(field_place(0, 0))),
635 ),
636 1,
637 )],
638 terminator: make_terminator(TerminatorKind::Return),
639 },
640 ],
641 num_locals: 2,
642 param_slots: vec![],
643 param_reference_kinds: vec![],
644 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
645 span: span(),
646 field_name_table: std::collections::HashMap::new(),
647 local_struct_type_names: std::collections::HashMap::new(),
648 local_typed_array_element_types: std::collections::HashMap::new(),
649 local_declared_scalar_types: std::collections::HashMap::new(),
650 };
651
652 let cfg = ControlFlowGraph::build(&mir);
653 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
654
655 let init_at_bb3 = result
657 .definitely_initialized
658 .get(&BasicBlockId(3))
659 .cloned()
660 .unwrap_or_default();
661 assert!(
662 !init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
663 "field should not be definitely initialized at join point"
664 );
665
666 assert!(
668 result
669 .conditionally_initialized
670 .contains(&(SlotId(0), FieldIdx(0))),
671 "field should be conditionally initialized"
672 );
673 }
674
675 #[test]
678 fn test_both_branches_init() {
679 let mir = MirFunction {
686 name: "test".to_string(),
687 blocks: vec![
688 BasicBlock {
689 id: BasicBlockId(0),
690 statements: vec![],
691 terminator: make_terminator(TerminatorKind::SwitchBool {
692 operand: Operand::Constant(MirConstant::Bool(true)),
693 true_bb: BasicBlockId(1),
694 false_bb: BasicBlockId(2),
695 }),
696 },
697 BasicBlock {
698 id: BasicBlockId(1),
699 statements: vec![make_stmt(
700 StatementKind::Assign(
701 field_place(0, 0),
702 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
703 ),
704 0,
705 )],
706 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
707 },
708 BasicBlock {
709 id: BasicBlockId(2),
710 statements: vec![make_stmt(
711 StatementKind::Assign(
712 field_place(0, 0),
713 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
714 ),
715 1,
716 )],
717 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(3))),
718 },
719 BasicBlock {
720 id: BasicBlockId(3),
721 statements: vec![make_stmt(
722 StatementKind::Assign(
723 Place::Local(SlotId(1)),
724 Rvalue::Use(Operand::Copy(field_place(0, 0))),
725 ),
726 2,
727 )],
728 terminator: make_terminator(TerminatorKind::Return),
729 },
730 ],
731 num_locals: 2,
732 param_slots: vec![],
733 param_reference_kinds: vec![],
734 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
735 span: span(),
736 field_name_table: std::collections::HashMap::new(),
737 local_struct_type_names: std::collections::HashMap::new(),
738 local_typed_array_element_types: std::collections::HashMap::new(),
739 local_declared_scalar_types: std::collections::HashMap::new(),
740 };
741
742 let cfg = ControlFlowGraph::build(&mir);
743 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
744
745 let init_at_bb3 = result
747 .definitely_initialized
748 .get(&BasicBlockId(3))
749 .cloned()
750 .unwrap_or_default();
751 assert!(
752 init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
753 "field should be definitely initialized when both branches write it"
754 );
755
756 assert!(
758 !result
759 .conditionally_initialized
760 .contains(&(SlotId(0), FieldIdx(0))),
761 );
762
763 assert!(
765 !result.dead_fields.contains(&(SlotId(0), FieldIdx(0))),
766 "field is read so should not be dead"
767 );
768 }
769
770 #[test]
773 fn test_dead_field() {
774 let mir = MirFunction {
777 name: "test".to_string(),
778 blocks: vec![BasicBlock {
779 id: BasicBlockId(0),
780 statements: vec![
781 make_stmt(
782 StatementKind::Assign(
783 field_place(0, 0),
784 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
785 ),
786 0,
787 ),
788 make_stmt(
789 StatementKind::Assign(
790 field_place(0, 1),
791 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
792 ),
793 1,
794 ),
795 make_stmt(
796 StatementKind::Assign(
797 Place::Local(SlotId(1)),
798 Rvalue::Use(Operand::Copy(field_place(0, 0))),
799 ),
800 2,
801 ),
802 ],
803 terminator: make_terminator(TerminatorKind::Return),
804 }],
805 num_locals: 2,
806 param_slots: vec![],
807 param_reference_kinds: vec![],
808 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
809 span: span(),
810 field_name_table: std::collections::HashMap::new(),
811 local_struct_type_names: std::collections::HashMap::new(),
812 local_typed_array_element_types: std::collections::HashMap::new(),
813 local_declared_scalar_types: std::collections::HashMap::new(),
814 };
815
816 let cfg = ControlFlowGraph::build(&mir);
817 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
818
819 assert!(!result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
821 assert!(result.dead_fields.contains(&(SlotId(0), FieldIdx(1))));
823 }
824
825 #[test]
828 fn test_field_liveness() {
829 let mir = MirFunction {
834 name: "test".to_string(),
835 blocks: vec![
836 BasicBlock {
837 id: BasicBlockId(0),
838 statements: vec![make_stmt(
839 StatementKind::Assign(
840 field_place(0, 0),
841 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
842 ),
843 0,
844 )],
845 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
846 },
847 BasicBlock {
848 id: BasicBlockId(1),
849 statements: vec![make_stmt(
850 StatementKind::Assign(
851 Place::Local(SlotId(1)),
852 Rvalue::Use(Operand::Copy(field_place(0, 0))),
853 ),
854 1,
855 )],
856 terminator: make_terminator(TerminatorKind::Return),
857 },
858 ],
859 num_locals: 2,
860 param_slots: vec![],
861 param_reference_kinds: vec![],
862 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
863 span: span(),
864 field_name_table: std::collections::HashMap::new(),
865 local_struct_type_names: std::collections::HashMap::new(),
866 local_typed_array_element_types: std::collections::HashMap::new(),
867 local_declared_scalar_types: std::collections::HashMap::new(),
868 };
869
870 let cfg = ControlFlowGraph::build(&mir);
871 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
872
873 let live_bb1 = result
875 .field_liveness
876 .get(&BasicBlockId(1))
877 .cloned()
878 .unwrap_or_default();
879 assert!(
880 live_bb1.contains(&(SlotId(0), FieldIdx(0))),
881 "field should be live at entry of block where it is read"
882 );
883
884 let live_bb0 = result
891 .field_liveness
892 .get(&BasicBlockId(0))
893 .cloned()
894 .unwrap_or_default();
895 assert!(
896 !live_bb0.contains(&(SlotId(0), FieldIdx(0))),
897 "field defined before use in bb0 should not be live at bb0 entry"
898 );
899 }
900
901 #[test]
904 fn test_loop_init() {
905 let mir = MirFunction {
914 name: "test".to_string(),
915 blocks: vec![
916 BasicBlock {
917 id: BasicBlockId(0),
918 statements: vec![],
919 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
920 },
921 BasicBlock {
922 id: BasicBlockId(1),
923 statements: vec![],
924 terminator: make_terminator(TerminatorKind::SwitchBool {
925 operand: Operand::Constant(MirConstant::Bool(true)),
926 true_bb: BasicBlockId(2),
927 false_bb: BasicBlockId(3),
928 }),
929 },
930 BasicBlock {
931 id: BasicBlockId(2),
932 statements: vec![make_stmt(
933 StatementKind::Assign(
934 field_place(0, 0),
935 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
936 ),
937 0,
938 )],
939 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
940 },
941 BasicBlock {
942 id: BasicBlockId(3),
943 statements: vec![make_stmt(
944 StatementKind::Assign(
945 Place::Local(SlotId(1)),
946 Rvalue::Use(Operand::Copy(field_place(0, 0))),
947 ),
948 1,
949 )],
950 terminator: make_terminator(TerminatorKind::Return),
951 },
952 ],
953 num_locals: 2,
954 param_slots: vec![],
955 param_reference_kinds: vec![],
956 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::Copy],
957 span: span(),
958 field_name_table: std::collections::HashMap::new(),
959 local_struct_type_names: std::collections::HashMap::new(),
960 local_typed_array_element_types: std::collections::HashMap::new(),
961 local_declared_scalar_types: std::collections::HashMap::new(),
962 };
963
964 let cfg = ControlFlowGraph::build(&mir);
965 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
966
967 let init_at_bb3 = result
970 .definitely_initialized
971 .get(&BasicBlockId(3))
972 .cloned()
973 .unwrap_or_default();
974 assert!(
975 !init_at_bb3.contains(&(SlotId(0), FieldIdx(0))),
976 "field initialized only in loop body should not be definitely initialized at loop exit"
977 );
978
979 assert!(
981 result
982 .conditionally_initialized
983 .contains(&(SlotId(0), FieldIdx(0))),
984 );
985 }
986
987 #[test]
990 fn test_empty_function() {
991 let mir = MirFunction {
992 name: "empty".to_string(),
993 blocks: vec![BasicBlock {
994 id: BasicBlockId(0),
995 statements: vec![],
996 terminator: make_terminator(TerminatorKind::Return),
997 }],
998 num_locals: 0,
999 param_slots: vec![],
1000 param_reference_kinds: vec![],
1001 local_types: vec![],
1002 span: span(),
1003 field_name_table: std::collections::HashMap::new(),
1004 local_struct_type_names: std::collections::HashMap::new(),
1005 local_typed_array_element_types: std::collections::HashMap::new(),
1006 local_declared_scalar_types: std::collections::HashMap::new(),
1007 };
1008
1009 let cfg = ControlFlowGraph::build(&mir);
1010 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
1011
1012 assert!(result.dead_fields.is_empty());
1013 assert!(result.conditionally_initialized.is_empty());
1014 }
1015
1016 #[test]
1019 fn test_multiple_slots() {
1020 let mir = MirFunction {
1023 name: "test".to_string(),
1024 blocks: vec![BasicBlock {
1025 id: BasicBlockId(0),
1026 statements: vec![
1027 make_stmt(
1028 StatementKind::Assign(
1029 field_place(0, 0),
1030 Rvalue::Use(Operand::Constant(MirConstant::Int(1))),
1031 ),
1032 0,
1033 ),
1034 make_stmt(
1035 StatementKind::Assign(
1036 field_place(1, 0),
1037 Rvalue::Use(Operand::Constant(MirConstant::Int(2))),
1038 ),
1039 1,
1040 ),
1041 make_stmt(
1042 StatementKind::Assign(
1043 Place::Local(SlotId(2)),
1044 Rvalue::BinaryOp(
1045 BinOp::Add,
1046 Operand::Copy(field_place(0, 0)),
1047 Operand::Copy(field_place(1, 0)),
1048 ),
1049 ),
1050 2,
1051 ),
1052 ],
1053 terminator: make_terminator(TerminatorKind::Return),
1054 }],
1055 num_locals: 3,
1056 param_slots: vec![],
1057 param_reference_kinds: vec![],
1058 local_types: vec![
1059 LocalTypeInfo::NonCopy,
1060 LocalTypeInfo::NonCopy,
1061 LocalTypeInfo::Copy,
1062 ],
1063 span: span(),
1064 field_name_table: std::collections::HashMap::new(),
1065 local_struct_type_names: std::collections::HashMap::new(),
1066 local_typed_array_element_types: std::collections::HashMap::new(),
1067 local_declared_scalar_types: std::collections::HashMap::new(),
1068 };
1069
1070 let cfg = ControlFlowGraph::build(&mir);
1071 let result = analyze_fields(&FieldAnalysisInput { mir: &mir, cfg: &cfg });
1072
1073 assert!(!result.dead_fields.contains(&(SlotId(0), FieldIdx(0))));
1075 assert!(!result.dead_fields.contains(&(SlotId(1), FieldIdx(0))));
1076 }
1077}