Skip to main content

solar_codegen/transform/
cfg_simplify.rs

1//! CFG Simplification and Normalization passes.
2//!
3//! This module provides optimization passes to clean up the Control Flow Graph:
4//!
5//! ## Block Merging
6//! If block A unconditionally jumps to B, and B has only A as predecessor,
7//! merge A and B into a single block. This reduces jump instructions (8 gas each).
8//!
9//! ## Empty Block Elimination
10//! Remove blocks that contain no instructions and only an unconditional jump,
11//! redirecting predecessors to the target.
12//!
13//! ## Dead Function Elimination
14//! Remove functions that are never called, starting from entry points
15//! (public/external functions, constructor, fallback, receive).
16
17use 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/// Alpha-equivalence key for a terminal block used by
28/// [`CfgSimplifier::deduplicate_terminal_blocks`].
29#[derive(Debug, PartialEq)]
30struct CanonBlock {
31    insts: Vec<CanonInst>,
32    term_mnemonic: &'static str,
33    term_operands: Vec<CanonOperand>,
34}
35
36/// Alpha-equivalence key for one instruction of a terminal block.
37#[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/// Non-operand payload carried by an instruction kind.
47#[derive(Debug, PartialEq)]
48enum CanonPayload {
49    None,
50    FrameAddr(u64),
51    Call(FunctionId, usize),
52}
53
54/// A canonicalized operand: block-local results compare by definition
55/// position, immediates by value, and everything else by exact [`ValueId`].
56#[derive(Debug, PartialEq)]
57enum CanonOperand {
58    Local(usize),
59    Imm(Immediate),
60    Outside(ValueId),
61}
62
63/// Statistics from CFG simplification.
64#[derive(Debug, Default, Clone)]
65pub struct CfgSimplifyStats {
66    /// Number of blocks merged.
67    pub blocks_merged: usize,
68    /// Number of empty blocks eliminated.
69    pub empty_blocks_eliminated: usize,
70    /// Number of degenerate terminators simplified.
71    pub terminators_simplified: usize,
72    /// Number of trivial phi nodes replaced by their unique incoming value.
73    pub trivial_phis_simplified: usize,
74    /// Number of identical terminal blocks merged into one shared block.
75    pub terminal_blocks_deduplicated: usize,
76    /// Number of dead functions eliminated.
77    pub dead_functions_eliminated: usize,
78    /// Estimated gas saved (8 gas per eliminated jump).
79    pub gas_saved: usize,
80}
81
82impl CfgSimplifyStats {
83    /// Returns total optimizations performed.
84    #[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    /// Combines stats from another run.
95    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/// CFG simplification pass for a single function.
107#[derive(Debug, Default)]
108pub struct CfgSimplifier {
109    /// Statistics from the last run.
110    pub stats: CfgSimplifyStats,
111}
112
113/// Function pass for CFG simplification.
114pub 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    /// Creates a new CFG simplifier.
128    #[must_use]
129    pub fn new() -> Self {
130        Self::default()
131    }
132
133    /// Runs CFG simplification on a function.
134    /// Returns the number of optimizations performed.
135    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    /// Merges identical terminal blocks (no phis, terminator without
148    /// successors, alpha-equivalent instructions) into one shared block and
149    /// redirects all predecessor edges to it.
150    ///
151    /// Checked arithmetic materializes one panic block per check; this folds
152    /// them to one block per panic code (and shared revert-string blocks) per
153    /// function. The rewrite is phi-safe by construction: the kept block has
154    /// no phis and a terminal block has no successors, so no phi inputs
155    /// elsewhere can mention it.
156    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    /// Builds the alpha-equivalence key of a terminal block, or `None` if the
192    /// block is not a dedup candidate.
193    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        // A trivial phi may be replaced by another phi deleted in the same
277        // batch (`v81 -> v82 -> v80`); uses must be rewritten to the end of
278        // the chain or they dangle once the intermediate phi is removed.
279        // Mutually-trivial cycles have no outside source; keep those phis.
280        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    /// Runs CFG simplification iteratively until no more changes.
349    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    /// Merges blocks where A unconditionally jumps to B and B has only A as predecessor.
362    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    /// Checks if block_id can be merged with its successor.
381    /// Returns the target block if merge is possible.
382    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    /// Merges block_id with target, appending target's instructions and terminator to block_id.
419    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    /// Eliminates empty blocks that only contain an unconditional jump.
477    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    /// Checks if a block is an empty forwarder (no instructions, just a jump).
503    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    /// Checks that redirecting the forwarder's predecessors into its target
533    /// keeps the target's phis well formed: a predecessor must not end up with
534    /// two incoming entries carrying different values (e.g. both arms of one
535    /// branch being forwarders into the same join), since phi incoming lists
536    /// are keyed per predecessor block, not per CFG edge.
537    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    /// Eliminates an empty forwarder block by redirecting its predecessors.
559    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            // The safety check guarantees colliding entries carry equal values;
603            // keep one entry per predecessor block.
604            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    /// Redirects a terminator from old_target to new_target.
618    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/// Dead function elimination pass for a module.
654#[derive(Debug, Default)]
655pub struct DeadFunctionEliminator {
656    /// Statistics from the last run.
657    pub stats: CfgSimplifyStats,
658}
659
660/// Module pass for dead internal function elimination.
661pub 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    /// Creates a new dead function eliminator.
675    #[must_use]
676    pub fn new() -> Self {
677        Self::default()
678    }
679
680    /// Runs dead function elimination on a module.
681    /// Returns the number of functions eliminated.
682    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
707/// Runs all CFG simplification passes on a function.
708pub fn simplify_cfg(func: &mut Function) -> CfgSimplifyStats {
709    let mut simplifier = CfgSimplifier::new();
710    simplifier.run_to_fixpoint(func)
711}
712
713/// Runs all CFG simplification passes on a module.
714pub 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}