1use crate::{
18 analysis::{CallGraphInfo, CfgInfo},
19 mir::{
20 BlockId, Function, FunctionId, Immediate, InstKind, InstructionMetadata, MirType, Module,
21 Terminator, Value, ValueId, utils::repair_reachability_phis,
22 },
23 pass::{FunctionPass, ModulePass},
24};
25use solar_data_structures::map::{FxHashMap, FxHashSet};
26
27#[derive(Debug, PartialEq)]
30struct CanonBlock {
31 insts: Vec<CanonInst>,
32 term_mnemonic: &'static str,
33 term_operands: Vec<CanonOperand>,
34}
35
36#[derive(Debug, PartialEq)]
38struct CanonInst {
39 mnemonic: &'static str,
40 payload: CanonPayload,
41 operands: Vec<CanonOperand>,
42 result_ty: Option<MirType>,
43 metadata: InstructionMetadata,
44}
45
46#[derive(Debug, PartialEq)]
48enum CanonPayload {
49 None,
50 FrameAddr(u64),
51 Call(FunctionId, usize),
52}
53
54#[derive(Debug, PartialEq)]
57enum CanonOperand {
58 Local(usize),
59 Imm(Immediate),
60 Outside(ValueId),
61}
62
63#[derive(Debug, Default, Clone)]
65pub struct CfgSimplifyStats {
66 pub blocks_merged: usize,
68 pub empty_blocks_eliminated: usize,
70 pub terminators_simplified: usize,
72 pub trivial_phis_simplified: usize,
74 pub terminal_blocks_deduplicated: usize,
76 pub dead_functions_eliminated: usize,
78 pub gas_saved: usize,
80}
81
82impl CfgSimplifyStats {
83 #[must_use]
85 pub fn total(&self) -> usize {
86 self.blocks_merged
87 + self.empty_blocks_eliminated
88 + self.terminators_simplified
89 + self.trivial_phis_simplified
90 + self.terminal_blocks_deduplicated
91 + self.dead_functions_eliminated
92 }
93
94 pub fn combine(&mut self, other: &Self) {
96 self.blocks_merged += other.blocks_merged;
97 self.empty_blocks_eliminated += other.empty_blocks_eliminated;
98 self.terminators_simplified += other.terminators_simplified;
99 self.trivial_phis_simplified += other.trivial_phis_simplified;
100 self.terminal_blocks_deduplicated += other.terminal_blocks_deduplicated;
101 self.dead_functions_eliminated += other.dead_functions_eliminated;
102 self.gas_saved += other.gas_saved;
103 }
104}
105
106#[derive(Debug, Default)]
108pub struct CfgSimplifier {
109 pub stats: CfgSimplifyStats,
111}
112
113pub struct CfgSimplifyPass;
115
116impl FunctionPass for CfgSimplifyPass {
117 fn name(&self) -> &str {
118 "cfg-simplify"
119 }
120
121 fn run_on_function(&mut self, func: &mut Function) -> bool {
122 CfgSimplifier::new().run_to_fixpoint(func).total() != 0
123 }
124}
125
126impl CfgSimplifier {
127 #[must_use]
129 pub fn new() -> Self {
130 Self::default()
131 }
132
133 pub fn run(&mut self, func: &mut Function) -> usize {
136 self.stats = CfgSimplifyStats::default();
137
138 self.simplify_degenerate_terminators(func);
139 self.merge_blocks(func);
140 self.eliminate_empty_blocks(func);
141 self.deduplicate_terminal_blocks(func);
142 self.simplify_trivial_phis(func);
143
144 self.stats.total()
145 }
146
147 fn deduplicate_terminal_blocks(&mut self, func: &mut Function) {
157 let inst_results = func.inst_results();
158
159 let mut kept: Vec<(BlockId, CanonBlock)> = Vec::new();
160 let mut merges: Vec<(BlockId, BlockId)> = Vec::new();
161 for block_id in func.blocks.indices() {
162 if block_id == func.entry_block || func.blocks[block_id].predecessors.is_empty() {
163 continue;
164 }
165 let Some(canon) = Self::canonicalize_terminal_block(func, block_id, &inst_results)
166 else {
167 continue;
168 };
169 if let Some((keep, _)) = kept.iter().find(|(_, existing)| *existing == canon) {
170 merges.push((block_id, *keep));
171 } else {
172 kept.push((block_id, canon));
173 }
174 }
175
176 for (dup, keep) in merges {
177 let predecessors: Vec<_> = func.blocks[dup].predecessors.to_vec();
178 for pred in predecessors {
179 self.redirect_terminator(func, pred, dup, keep);
180 if !func.blocks[keep].predecessors.contains(&pred) {
181 func.blocks[keep].predecessors.push(pred);
182 }
183 }
184 func.blocks[dup].instructions.clear();
185 func.blocks[dup].terminator = Some(Terminator::Invalid);
186 func.blocks[dup].predecessors.clear();
187 self.stats.terminal_blocks_deduplicated += 1;
188 }
189 }
190
191 fn canonicalize_terminal_block(
194 func: &Function,
195 block_id: BlockId,
196 inst_results: &FxHashMap<crate::mir::InstId, ValueId>,
197 ) -> Option<CanonBlock> {
198 let block = &func.blocks[block_id];
199 let term = block.terminator.as_ref()?;
200 if matches!(term, Terminator::Invalid) || !term.successors().is_empty() {
201 return None;
202 }
203
204 let mut local_defs: FxHashMap<ValueId, usize> = FxHashMap::default();
205 for (position, &inst_id) in block.instructions.iter().enumerate() {
206 if let Some(&result) = inst_results.get(&inst_id) {
207 local_defs.insert(result, position);
208 }
209 }
210
211 let canon_operand = |value: ValueId| {
212 if let Some(&position) = local_defs.get(&value) {
213 return CanonOperand::Local(position);
214 }
215 match &func.values[value] {
216 Value::Immediate(imm) => CanonOperand::Imm(imm.clone()),
217 _ => CanonOperand::Outside(value),
218 }
219 };
220
221 let mut insts = Vec::with_capacity(block.instructions.len());
222 for &inst_id in &block.instructions {
223 let inst = &func.instructions[inst_id];
224 let extra = match &inst.kind {
225 InstKind::Phi(_) => return None,
226 InstKind::InternalFrameAddr(offset) => CanonPayload::FrameAddr(*offset),
227 InstKind::InternalCall { function, returns, .. } => {
228 CanonPayload::Call(*function, *returns as usize)
229 }
230 _ => CanonPayload::None,
231 };
232 let mut metadata = inst.metadata.clone();
233 metadata.set_hir_expr(None);
234 metadata.set_source_span(None);
235 metadata.loop_depth = 0;
236 insts.push(CanonInst {
237 mnemonic: inst.kind.mnemonic(),
238 payload: extra,
239 operands: inst.kind.operands().into_iter().map(canon_operand).collect(),
240 result_ty: inst.result_ty,
241 metadata,
242 });
243 }
244
245 let term_operands = term.operands().into_iter().map(canon_operand).collect();
246 Some(CanonBlock { insts, term_mnemonic: term.mnemonic(), term_operands })
247 }
248
249 fn simplify_trivial_phis(&mut self, func: &mut Function) {
250 let mut candidates = Vec::new();
251 let mut raw = FxHashMap::default();
252
253 for block_id in func.blocks.indices() {
254 let same_block_phi_results = func.block_phi_results(block_id);
255 for &inst_id in &func.blocks[block_id].instructions {
256 let InstKind::Phi(incoming) = &func.instructions[inst_id].kind else {
257 continue;
258 };
259 let Some(phi_value) = func.inst_result_value(inst_id) else {
260 continue;
261 };
262 let Some(replacement) =
263 Self::trivial_phi_replacement(incoming, phi_value, &same_block_phi_results)
264 else {
265 continue;
266 };
267 candidates.push((inst_id, phi_value));
268 raw.insert(phi_value, replacement);
269 }
270 }
271
272 if raw.is_empty() {
273 return;
274 }
275
276 let mut replacements = FxHashMap::default();
281 let mut dead = FxHashSet::default();
282 for &(inst_id, phi_value) in &candidates {
283 let mut seen = FxHashSet::from_iter([phi_value]);
284 let mut target = raw[&phi_value];
285 let mut cyclic = false;
286 while let Some(&next) = raw.get(&target) {
287 if !seen.insert(target) {
288 cyclic = true;
289 break;
290 }
291 target = next;
292 }
293 if !cyclic {
294 replacements.insert(phi_value, target);
295 dead.insert(inst_id);
296 }
297 }
298
299 if replacements.is_empty() {
300 return;
301 }
302
303 func.replace_uses(&replacements);
304 for block in func.blocks.iter_mut() {
305 block.instructions.retain(|inst_id| !dead.contains(inst_id));
306 }
307 self.stats.trivial_phis_simplified += dead.len();
308 }
309
310 fn trivial_phi_replacement(
311 incoming: &[(BlockId, ValueId)],
312 phi_value: ValueId,
313 same_block_phi_results: &FxHashSet<ValueId>,
314 ) -> Option<ValueId> {
315 let mut incoming_values = incoming.iter().map(|(_, value)| *value);
316 let first = incoming_values.find(|value| *value != phi_value)?;
317 if same_block_phi_results.contains(&first) {
318 return None;
319 }
320 incoming_values.all(|value| value == phi_value || value == first).then_some(first)
321 }
322
323 fn simplify_degenerate_terminators(&mut self, func: &mut Function) {
324 let block_ids: Vec<_> = func.blocks.indices().collect();
325 let mut changed = false;
326 for block_id in block_ids {
327 let Some(Terminator::Branch { then_block, else_block, .. }) =
328 func.blocks[block_id].terminator.as_ref()
329 else {
330 continue;
331 };
332 if then_block != else_block {
333 continue;
334 }
335
336 let target = *then_block;
337 func.blocks[block_id].terminator = Some(Terminator::Jump(target));
338 self.stats.terminators_simplified += 1;
339 self.stats.gas_saved += 10;
340 changed = true;
341 }
342
343 if changed {
344 repair_reachability_phis(func);
345 }
346 }
347
348 pub fn run_to_fixpoint(&mut self, func: &mut Function) -> CfgSimplifyStats {
350 let mut total_stats = CfgSimplifyStats::default();
351 loop {
352 let changed = self.run(func);
353 if changed == 0 {
354 break;
355 }
356 total_stats.combine(&self.stats);
357 }
358 total_stats
359 }
360
361 fn merge_blocks(&mut self, func: &mut Function) {
363 let mut merged = true;
364 while merged {
365 merged = false;
366
367 let block_ids: Vec<_> = func.blocks.indices().collect();
368 for block_id in block_ids {
369 if let Some(target) = self.can_merge(func, block_id) {
370 self.do_merge(func, block_id, target);
371 merged = true;
372 self.stats.blocks_merged += 1;
373 self.stats.gas_saved += 8;
374 break;
375 }
376 }
377 }
378 }
379
380 fn can_merge(&self, func: &Function, block_id: BlockId) -> Option<BlockId> {
383 let block = &func.blocks[block_id];
384
385 let Terminator::Jump(target) = block.terminator.as_ref()? else {
386 return None;
387 };
388
389 if *target == block_id {
390 return None;
391 }
392
393 if *target == func.entry_block {
394 return None;
395 }
396
397 let target_block = &func.blocks[*target];
398 if target_block.predecessors.len() != 1 {
399 return None;
400 }
401
402 if target_block.predecessors[0] != block_id {
403 return None;
404 }
405
406 for &inst_id in &target_block.instructions {
407 let InstKind::Phi(incoming) = &func.instructions[inst_id].kind else {
408 continue;
409 };
410 if !incoming.iter().any(|(pred, _)| *pred == block_id) {
411 return None;
412 }
413 }
414
415 Some(*target)
416 }
417
418 fn do_merge(&self, func: &mut Function, block_id: BlockId, target: BlockId) {
420 let phi_replacements = self.fold_target_phis_for_merge(func, block_id, target);
421 let target_instructions: Vec<_> = func.blocks[target]
422 .instructions
423 .iter()
424 .copied()
425 .filter(|&inst_id| !matches!(func.instructions[inst_id].kind, InstKind::Phi(_)))
426 .collect();
427 let target_terminator = func.blocks[target].terminator.take();
428 let target_successors =
429 target_terminator.as_ref().map(Terminator::successors).unwrap_or_default();
430
431 func.blocks[block_id].instructions.extend(target_instructions);
432 func.blocks[block_id].terminator = target_terminator;
433
434 for &succ in &target_successors {
435 self.redirect_target_phi_incoming(func, target, succ, &[block_id]);
436
437 let succ_block = &mut func.blocks[succ];
438 for pred in &mut succ_block.predecessors {
439 if *pred == target {
440 *pred = block_id;
441 }
442 }
443 }
444
445 func.blocks[target].instructions.clear();
446 func.blocks[target].terminator = Some(Terminator::Invalid);
447 func.blocks[target].predecessors.clear();
448
449 func.replace_uses(&phi_replacements);
450 }
451
452 fn fold_target_phis_for_merge(
453 &self,
454 func: &Function,
455 pred: BlockId,
456 target: BlockId,
457 ) -> FxHashMap<ValueId, ValueId> {
458 let mut replacements = FxHashMap::default();
459 for &inst_id in &func.blocks[target].instructions {
460 let InstKind::Phi(incoming) = &func.instructions[inst_id].kind else {
461 continue;
462 };
463 let Some(phi_value) = func.inst_result_value(inst_id) else {
464 continue;
465 };
466 let Some((_, incoming_value)) =
467 incoming.iter().find(|(incoming_pred, _)| *incoming_pred == pred)
468 else {
469 continue;
470 };
471 replacements.insert(phi_value, *incoming_value);
472 }
473 replacements
474 }
475
476 fn eliminate_empty_blocks(&mut self, func: &mut Function) {
478 let mut eliminated = true;
479 while eliminated {
480 eliminated = false;
481
482 let block_ids: Vec<_> = func.blocks.indices().collect();
483 for block_id in block_ids {
484 if block_id == func.entry_block {
485 continue;
486 }
487
488 if self.is_empty_forwarder(func, block_id)
489 && !self.is_loop_preheader_forwarder(func, block_id)
490 && self.forwarder_elimination_preserves_phis(func, block_id)
491 {
492 self.eliminate_forwarder(func, block_id);
493 eliminated = true;
494 self.stats.empty_blocks_eliminated += 1;
495 self.stats.gas_saved += 8;
496 break;
497 }
498 }
499 }
500 }
501
502 fn is_empty_forwarder(&self, func: &Function, block_id: BlockId) -> bool {
504 let block = &func.blocks[block_id];
505
506 if !block.instructions.is_empty() {
507 return false;
508 }
509
510 matches!(&block.terminator, Some(Terminator::Jump(target)) if *target != block_id)
511 }
512
513 fn is_loop_preheader_forwarder(&self, func: &Function, block_id: BlockId) -> bool {
514 let Some(Terminator::Jump(target)) = func.blocks[block_id].terminator else {
515 return false;
516 };
517 if !matches!(
518 func.blocks[target].instructions.first(),
519 Some(&inst) if matches!(func.instructions[inst].kind, InstKind::Phi(_))
520 ) {
521 return false;
522 }
523
524 let cfg = CfgInfo::new(func);
525 func.blocks[target]
526 .predecessors
527 .iter()
528 .copied()
529 .any(|pred| pred != block_id && cfg.dominators().dominates(target, pred))
530 }
531
532 fn forwarder_elimination_preserves_phis(&self, func: &Function, block_id: BlockId) -> bool {
538 let Some(Terminator::Jump(target)) = func.blocks[block_id].terminator else {
539 return false;
540 };
541 let predecessors = &func.blocks[block_id].predecessors;
542 for &inst_id in &func.blocks[target].instructions {
543 let InstKind::Phi(incoming) = &func.instructions[inst_id].kind else {
544 continue;
545 };
546 let Some(&(_, forwarded)) = incoming.iter().find(|(pred, _)| *pred == block_id) else {
547 continue;
548 };
549 for &pred in predecessors {
550 if incoming.iter().any(|&(other, value)| other == pred && value != forwarded) {
551 return false;
552 }
553 }
554 }
555 true
556 }
557
558 fn eliminate_forwarder(&self, func: &mut Function, block_id: BlockId) {
560 let target = match &func.blocks[block_id].terminator {
561 Some(Terminator::Jump(t)) => *t,
562 _ => return,
563 };
564
565 let predecessors: Vec<_> = func.blocks[block_id].predecessors.to_vec();
566 self.redirect_target_phi_incoming(func, block_id, target, &predecessors);
567
568 for pred_id in predecessors {
569 self.redirect_terminator(func, pred_id, block_id, target);
570
571 func.blocks[target].predecessors.push(pred_id);
572 }
573
574 func.blocks[target].predecessors.retain(|p| *p != block_id);
575
576 func.blocks[block_id].instructions.clear();
577 func.blocks[block_id].terminator = Some(Terminator::Invalid);
578 func.blocks[block_id].predecessors.clear();
579 }
580
581 fn redirect_target_phi_incoming(
582 &self,
583 func: &mut Function,
584 old_pred: BlockId,
585 target: BlockId,
586 new_preds: &[BlockId],
587 ) {
588 for &inst_id in &func.blocks[target].instructions {
589 let InstKind::Phi(incoming) = &mut func.instructions[inst_id].kind else {
590 continue;
591 };
592
593 let mut rewritten: Vec<(BlockId, ValueId)> =
594 Vec::with_capacity(incoming.len() + new_preds.len());
595 for &(pred, value) in incoming.iter() {
596 if pred == old_pred {
597 rewritten.extend(new_preds.iter().map(|&new_pred| (new_pred, value)));
598 } else {
599 rewritten.push((pred, value));
600 }
601 }
602 let mut seen = Vec::with_capacity(rewritten.len());
605 rewritten.retain(|&(pred, _)| {
606 if seen.contains(&pred) {
607 false
608 } else {
609 seen.push(pred);
610 true
611 }
612 });
613 *incoming = rewritten;
614 }
615 }
616
617 fn redirect_terminator(
619 &self,
620 func: &mut Function,
621 block_id: BlockId,
622 old_target: BlockId,
623 new_target: BlockId,
624 ) {
625 let block = &mut func.blocks[block_id];
626 match &mut block.terminator {
627 Some(Terminator::Jump(t)) if *t == old_target => {
628 *t = new_target;
629 }
630 Some(Terminator::Branch { then_block, else_block, .. }) => {
631 if *then_block == old_target {
632 *then_block = new_target;
633 }
634 if *else_block == old_target {
635 *else_block = new_target;
636 }
637 }
638 Some(Terminator::Switch { default, cases, .. }) => {
639 if *default == old_target {
640 *default = new_target;
641 }
642 for (_, target) in cases.iter_mut() {
643 if *target == old_target {
644 *target = new_target;
645 }
646 }
647 }
648 _ => {}
649 }
650 }
651}
652
653#[derive(Debug, Default)]
655pub struct DeadFunctionEliminator {
656 pub stats: CfgSimplifyStats,
658}
659
660pub struct FunctionDcePass;
662
663impl ModulePass for FunctionDcePass {
664 fn name(&self) -> &str {
665 "function-dce"
666 }
667
668 fn run(&mut self, module: &mut Module) -> bool {
669 DeadFunctionEliminator::new().run(module) != 0
670 }
671}
672
673impl DeadFunctionEliminator {
674 #[must_use]
676 pub fn new() -> Self {
677 Self::default()
678 }
679
680 pub fn run(&mut self, module: &mut Module) -> usize {
683 self.stats = CfgSimplifyStats::default();
684
685 let call_graph = CallGraphInfo::new(module);
686 let reachable = call_graph.reachable_from_entries();
687 if reachable.is_empty() {
688 return 0;
689 }
690
691 let dead_functions: Vec<FunctionId> =
692 module.functions.indices().filter(|id| !reachable.contains(id)).collect();
693
694 self.stats.dead_functions_eliminated = dead_functions.len();
695
696 for func_id in &dead_functions {
697 let func = &mut module.functions[*func_id];
698 func.blocks.clear();
699 func.instructions.clear();
700 func.values.clear();
701 }
702
703 self.stats.dead_functions_eliminated
704 }
705}
706
707pub fn simplify_cfg(func: &mut Function) -> CfgSimplifyStats {
709 let mut simplifier = CfgSimplifier::new();
710 simplifier.run_to_fixpoint(func)
711}
712
713pub fn simplify_module_cfg(module: &mut Module) -> CfgSimplifyStats {
715 let mut total_stats = CfgSimplifyStats::default();
716
717 for func_id in module.functions.indices() {
718 let func = &mut module.functions[func_id];
719 let stats = simplify_cfg(func);
720 total_stats.combine(&stats);
721 }
722
723 let mut dfe = DeadFunctionEliminator::new();
724 dfe.run(module);
725 total_stats.combine(&dfe.stats);
726
727 total_stats
728}
729
730#[cfg(test)]
731mod tests {
732 use super::*;
733 use crate::mir::{FunctionBuilder, Instruction, MirType, Value};
734 use solar_interface::Ident;
735 use solar_sema::hir::Visibility;
736
737 #[test]
738 fn dead_function_elimination_keeps_internal_call_targets() {
739 let mut module = Module::new(Ident::DUMMY);
740
741 let live_helper = module.add_function(Function::new(Ident::DUMMY));
742 let dead_helper = module.add_function(Function::new(Ident::DUMMY));
743
744 let mut entry = Function::new(Ident::DUMMY);
745 entry.selector = Some([0, 0, 0, 1]);
746 entry.attributes.visibility = Visibility::Public;
747 {
748 let mut builder = FunctionBuilder::new(&mut entry);
749 let value = builder.internal_call(live_helper, Vec::new(), MirType::uint256(), 1);
750 builder.ret([value]);
751 }
752 let entry = module.add_function(entry);
753
754 {
755 let mut builder = FunctionBuilder::new(module.function_mut(live_helper));
756 let value = builder.imm_u64(1);
757 builder.ret([value]);
758 }
759 {
760 let mut builder = FunctionBuilder::new(module.function_mut(dead_helper));
761 let value = builder.imm_u64(2);
762 builder.ret([value]);
763 }
764
765 let mut dfe = DeadFunctionEliminator::new();
766 assert_eq!(dfe.run(&mut module), 1);
767
768 assert!(!module.function(entry).blocks.is_empty());
769 assert!(!module.function(live_helper).blocks.is_empty());
770 assert!(module.function(dead_helper).blocks.is_empty());
771 assert!(module.function(dead_helper).instructions.is_empty());
772 assert!(module.function(dead_helper).values.is_empty());
773 }
774
775 #[test]
776 fn empty_forwarder_rewrites_target_phi_incoming() {
777 let mut func = Function::new(Ident::DUMMY);
778 let forwarder;
779 let direct;
780 let target;
781 let value;
782 let other;
783 {
784 let mut builder = FunctionBuilder::new(&mut func);
785 forwarder = builder.create_block();
786 direct = builder.create_block();
787 target = builder.create_block();
788
789 value = builder.imm_u64(42);
790 let cond = builder.imm_bool(true);
791 builder.branch(cond, forwarder, direct);
792
793 builder.switch_to_block(direct);
794 let seven = builder.imm_u64(7);
795 other = builder.add(seven, value);
796 builder.jump(target);
797
798 builder.switch_to_block(forwarder);
799 builder.jump(target);
800 }
801
802 let phi_inst = func.alloc_inst(Instruction::new(
803 InstKind::Phi(vec![(forwarder, value), (direct, other)]),
804 Some(MirType::uint256()),
805 ));
806 let phi_value = func.alloc_value(Value::Inst(phi_inst));
807 func.blocks[target].instructions.push(phi_inst);
808 func.blocks[target].terminator =
809 Some(Terminator::Return { values: vec![phi_value].into() });
810
811 let mut simplifier = CfgSimplifier::new();
812 simplifier.run_to_fixpoint(&mut func);
813
814 assert!(matches!(func.blocks[forwarder].terminator, Some(Terminator::Invalid)));
815 let phi_inst = func.blocks[target].instructions[0];
816 let InstKind::Phi(incoming) = &func.instructions[phi_inst].kind else {
817 panic!("expected phi");
818 };
819 assert_eq!(incoming.as_slice(), &[(func.entry_block, value), (direct, other)]);
820 }
821
822 #[test]
823 fn block_merge_rewrites_successor_phi_incoming() {
824 let mut func = Function::new(Ident::DUMMY);
825 let source;
826 let middle;
827 let other;
828 let exit;
829 let result;
830 let other_value;
831 {
832 let mut builder = FunctionBuilder::new(&mut func);
833 source = builder.create_block();
834 middle = builder.create_block();
835 other = builder.create_block();
836 exit = builder.create_block();
837
838 let cond = builder.imm_bool(true);
839 builder.branch(cond, source, other);
840
841 builder.switch_to_block(source);
842 builder.jump(middle);
843
844 builder.switch_to_block(middle);
845 let one = builder.imm_u64(1);
846 let two = builder.imm_u64(2);
847 result = builder.add(one, two);
848 builder.jump(exit);
849
850 builder.switch_to_block(other);
851 let three = builder.imm_u64(3);
852 let four = builder.imm_u64(4);
853 other_value = builder.add(three, four);
854 builder.jump(exit);
855 }
856
857 let phi_inst = func.alloc_inst(Instruction::new(
858 InstKind::Phi(vec![(middle, result), (other, other_value)]),
859 Some(MirType::uint256()),
860 ));
861 let phi_value = func.alloc_value(Value::Inst(phi_inst));
862 func.blocks[exit].instructions.push(phi_inst);
863 func.blocks[exit].terminator = Some(Terminator::Return { values: vec![phi_value].into() });
864
865 let mut simplifier = CfgSimplifier::new();
866 simplifier.run_to_fixpoint(&mut func);
867
868 assert!(matches!(func.blocks[middle].terminator, Some(Terminator::Invalid)));
869 let phi_inst = func.blocks[exit].instructions[0];
870 let InstKind::Phi(incoming) = &func.instructions[phi_inst].kind else {
871 panic!("expected phi");
872 };
873 assert_eq!(incoming.as_slice(), &[(source, result), (other, other_value)]);
874 }
875}