Skip to main content

react_compiler_ssa/
eliminate_redundant_phi.rs

1use std::collections::{HashMap, HashSet};
2
3use react_compiler_hir::environment::Environment;
4use react_compiler_hir::visitors;
5use react_compiler_hir::*;
6
7use crate::enter_ssa::placeholder_function;
8
9// =============================================================================
10// Helper: rewrite_place
11// =============================================================================
12
13fn rewrite_place(place: &mut Place, rewrites: &HashMap<IdentifierId, IdentifierId>) {
14    if let Some(&rewrite) = rewrites.get(&place.identifier) {
15        place.identifier = rewrite;
16    }
17}
18
19
20// =============================================================================
21// Public entry point
22// =============================================================================
23
24pub fn eliminate_redundant_phi(func: &mut HirFunction, env: &mut Environment) {
25    let mut rewrites: HashMap<IdentifierId, IdentifierId> = HashMap::new();
26    eliminate_redundant_phi_impl(func, env, &mut rewrites);
27}
28
29// =============================================================================
30// Inner implementation
31// =============================================================================
32
33fn eliminate_redundant_phi_impl(
34    func: &mut HirFunction,
35    env: &mut Environment,
36    rewrites: &mut HashMap<IdentifierId, IdentifierId>,
37) {
38    let ir = &mut func.body;
39
40    let mut has_back_edge = false;
41    let mut visited: HashSet<BlockId> = HashSet::new();
42
43    let mut size;
44    loop {
45        size = rewrites.len();
46
47        let block_ids: Vec<BlockId> = ir.blocks.keys().copied().collect();
48        for block_id in &block_ids {
49            let block_id = *block_id;
50
51            if !has_back_edge {
52                let block = ir.blocks.get(&block_id).unwrap();
53                for pred_id in &block.preds {
54                    if !visited.contains(pred_id) {
55                        has_back_edge = true;
56                    }
57                }
58            }
59            visited.insert(block_id);
60
61            // Find any redundant phis: rewrite operands, identify redundant phis, remove them.
62            // Matches TS behavior: each phi's operands are rewritten before checking redundancy,
63            // so that rewrites from earlier phis in the same block are visible to later phis.
64            let block = ir.blocks.get_mut(&block_id).unwrap();
65            block.phis.retain_mut(|phi| {
66                // Remap phis in case operands are from eliminated phis
67                for (_, operand) in phi.operands.iter_mut() {
68                    rewrite_place(operand, rewrites);
69                }
70
71                // Find if the phi can be eliminated
72                let mut same: Option<IdentifierId> = None;
73                let mut is_redundant = true;
74                for (_, operand) in &phi.operands {
75                    if (same.is_some() && operand.identifier == same.unwrap())
76                        || operand.identifier == phi.place.identifier
77                    {
78                        continue;
79                    } else if same.is_some() {
80                        is_redundant = false;
81                        break;
82                    } else {
83                        same = Some(operand.identifier);
84                    }
85                }
86                if is_redundant {
87                    let same = same.expect("Expected phis to be non-empty");
88                    rewrites.insert(phi.place.identifier, same);
89                    false // remove this phi
90                } else {
91                    true // keep this phi
92                }
93            });
94
95            // Rewrite instructions
96            let instruction_ids: Vec<InstructionId> = ir
97                .blocks
98                .get(&block_id)
99                .unwrap()
100                .instructions
101                .clone();
102
103            for instr_id in &instruction_ids {
104                let instr_idx = instr_id.0 as usize;
105                let instr = &mut func.instructions[instr_idx];
106
107                // Rewrite all lvalues (matches TS eachInstructionLValue)
108                rewrite_place(&mut instr.lvalue, rewrites);
109                visitors::for_each_instruction_value_lvalue_mut(&mut instr.value, &mut |place| {
110                    rewrite_place(place, rewrites);
111                });
112
113                // Rewrite operands using canonical visitor
114                visitors::for_each_instruction_value_operand_mut(&mut func.instructions[instr_idx].value, &mut |place| {
115                    rewrite_place(place, rewrites);
116                });
117
118                // Handle FunctionExpression/ObjectMethod context and recursion
119                let instr = &func.instructions[instr_idx];
120                let func_expr_id = match &instr.value {
121                    InstructionValue::FunctionExpression { lowered_func, .. }
122                    | InstructionValue::ObjectMethod { lowered_func, .. } => {
123                        Some(lowered_func.func)
124                    }
125                    _ => None,
126                };
127
128                if let Some(fid) = func_expr_id {
129                    // Rewrite context places
130                    let context =
131                        &mut env.functions[fid.0 as usize].context;
132                    for place in context.iter_mut() {
133                        rewrite_place(place, rewrites);
134                    }
135
136                    // Take inner function out, process it, put it back
137                    let mut inner_func = std::mem::replace(
138                        &mut env.functions[fid.0 as usize],
139                        placeholder_function(),
140                    );
141
142                    eliminate_redundant_phi_impl(&mut inner_func, env, rewrites);
143
144                    env.functions[fid.0 as usize] = inner_func;
145                }
146            }
147
148            // Rewrite terminal operands using canonical visitor
149            let terminal = &mut ir.blocks.get_mut(&block_id).unwrap().terminal;
150            visitors::for_each_terminal_operand_mut(terminal, &mut |place| {
151                rewrite_place(place, rewrites);
152            });
153        }
154
155        if !(rewrites.len() > size && has_back_edge) {
156            break;
157        }
158    }
159}