react_compiler_optimization/
dead_code_elimination.rs1use rustc_hash::FxHashSet;
15
16use react_compiler_hir::environment::{Environment, OutputMode};
17use react_compiler_hir::object_shape::HookKind;
18use react_compiler_hir::visitors;
19use react_compiler_hir::{
20 ArrayPatternElement, BlockId, BlockKind, HirFunction, IdentifierId, InstructionKind,
21 InstructionValue, ObjectPropertyOrSpread, Pattern,
22};
23
24pub fn dead_code_elimination(func: &mut HirFunction, env: &Environment) {
30 let state = find_referenced_identifiers(func, env);
32
33 let mut instructions_to_rewrite: Vec<react_compiler_hir::InstructionId> = Vec::new();
36
37 for (_block_id, block) in &mut func.body.blocks {
38 block
40 .phis
41 .retain(|phi| is_id_or_name_used(&state, &env.identifiers, phi.place.identifier));
42
43 block.instructions.retain(|instr_id| {
45 let instr = &func.instructions[instr_id.0 as usize];
46 is_id_or_name_used(&state, &env.identifiers, instr.lvalue.identifier)
47 });
48
49 let retained_count = block.instructions.len();
51 for i in 0..retained_count {
52 let is_block_value = block.kind != BlockKind::Block && i == retained_count - 1;
53 if !is_block_value {
54 instructions_to_rewrite.push(block.instructions[i]);
55 }
56 }
57 }
58
59 for instr_id in instructions_to_rewrite {
61 rewrite_instruction(func, instr_id, &state, env);
62 }
63
64 func.context
66 .retain(|ctx_var| is_id_or_name_used(&state, &env.identifiers, ctx_var.identifier));
67}
68
69struct State {
71 identifiers: FxHashSet<IdentifierId>,
73 named: FxHashSet<String>,
75}
76
77impl State {
78 fn new() -> Self {
79 State {
80 identifiers: FxHashSet::default(),
81 named: FxHashSet::default(),
82 }
83 }
84
85 fn count(&self) -> usize {
86 self.identifiers.len()
87 }
88}
89
90fn reference(
92 state: &mut State,
93 identifiers: &[react_compiler_hir::Identifier],
94 identifier_id: IdentifierId,
95) {
96 state.identifiers.insert(identifier_id);
97 let ident = &identifiers[identifier_id.0 as usize];
98 if let Some(ref name) = ident.name {
99 state.named.insert(name.value().to_string());
100 }
101}
102
103fn is_id_or_name_used(
106 state: &State,
107 identifiers: &[react_compiler_hir::Identifier],
108 identifier_id: IdentifierId,
109) -> bool {
110 if state.identifiers.contains(&identifier_id) {
111 return true;
112 }
113 let ident = &identifiers[identifier_id.0 as usize];
114 if let Some(ref name) = ident.name {
115 state.named.contains(name.value())
116 } else {
117 false
118 }
119}
120
121fn is_id_used(state: &State, identifier_id: IdentifierId) -> bool {
123 state.identifiers.contains(&identifier_id)
124}
125
126fn find_referenced_identifiers(func: &HirFunction, env: &Environment) -> State {
128 let has_loop = has_back_edge(func);
129 let reversed_block_ids: Vec<BlockId> = func.body.blocks.keys().rev().copied().collect();
131
132 let mut state = State::new();
133 let mut size;
134
135 loop {
136 size = state.count();
137
138 for &block_id in &reversed_block_ids {
139 let block = &func.body.blocks[&block_id];
140
141 for place in visitors::each_terminal_operand(&block.terminal) {
143 reference(&mut state, &env.identifiers, place.identifier);
144 }
145
146 let instr_count = block.instructions.len();
148 for i in (0..instr_count).rev() {
149 let instr_id = block.instructions[i];
150 let instr = &func.instructions[instr_id.0 as usize];
151
152 let is_block_value = block.kind != BlockKind::Block && i == instr_count - 1;
153
154 if is_block_value {
155 reference(&mut state, &env.identifiers, instr.lvalue.identifier);
157 for place in visitors::each_instruction_value_operand(&instr.value, env) {
158 reference(&mut state, &env.identifiers, place.identifier);
159 }
160 } else if is_id_or_name_used(&state, &env.identifiers, instr.lvalue.identifier)
161 || !pruneable_value(&instr.value, &state, env)
162 {
163 reference(&mut state, &env.identifiers, instr.lvalue.identifier);
164
165 if let InstructionValue::StoreLocal { lvalue, value, .. } = &instr.value {
166 if lvalue.kind == InstructionKind::Reassign
169 || is_id_used(&state, lvalue.place.identifier)
170 {
171 reference(&mut state, &env.identifiers, value.identifier);
172 }
173 } else {
174 for place in visitors::each_instruction_value_operand(&instr.value, env) {
175 reference(&mut state, &env.identifiers, place.identifier);
176 }
177 }
178 }
179 }
180
181 for phi in &block.phis {
183 if is_id_or_name_used(&state, &env.identifiers, phi.place.identifier) {
184 for (_pred, operand) in &phi.operands {
185 reference(&mut state, &env.identifiers, operand.identifier);
186 }
187 }
188 }
189 }
190
191 if !(state.count() > size && has_loop) {
192 break;
193 }
194 }
195
196 state
197}
198
199fn rewrite_instruction(
201 func: &mut HirFunction,
202 instr_id: react_compiler_hir::InstructionId,
203 state: &State,
204 env: &Environment,
205) {
206 let instr = &mut func.instructions[instr_id.0 as usize];
207
208 match &mut instr.value {
209 InstructionValue::Destructure { lvalue, .. } => {
210 match &mut lvalue.pattern {
211 Pattern::Array(arr) => {
212 let mut last_entry_index = 0;
214 for i in 0..arr.items.len() {
215 match &arr.items[i] {
216 ArrayPatternElement::Place(p) => {
217 if !is_id_or_name_used(state, &env.identifiers, p.identifier) {
218 arr.items[i] = ArrayPatternElement::Hole;
219 } else {
220 last_entry_index = i;
221 }
222 }
223 ArrayPatternElement::Spread(s) => {
224 if !is_id_or_name_used(state, &env.identifiers, s.place.identifier)
225 {
226 arr.items[i] = ArrayPatternElement::Hole;
227 } else {
228 last_entry_index = i;
229 }
230 }
231 ArrayPatternElement::Hole => {}
232 }
233 }
234 arr.items.truncate(last_entry_index + 1);
235 }
236 Pattern::Object(obj) => {
237 let mut next_properties: Option<Vec<ObjectPropertyOrSpread>> = None;
239 for prop in &obj.properties {
240 match prop {
241 ObjectPropertyOrSpread::Property(p) => {
242 if is_id_or_name_used(state, &env.identifiers, p.place.identifier) {
243 next_properties
244 .get_or_insert_with(Vec::new)
245 .push(prop.clone());
246 }
247 }
248 ObjectPropertyOrSpread::Spread(s) => {
249 if is_id_or_name_used(state, &env.identifiers, s.place.identifier) {
250 next_properties = None;
252 break;
253 }
254 }
255 }
256 }
257 if let Some(props) = next_properties {
258 obj.properties = props;
259 }
260 }
261 }
262 }
263 InstructionValue::StoreLocal {
264 lvalue,
265 type_annotation,
266 loc,
267 ..
268 } => {
269 if lvalue.kind != InstructionKind::Reassign
270 && !is_id_used(state, lvalue.place.identifier)
271 {
272 let new_lvalue = lvalue.clone();
276 let new_type_annotation = type_annotation.clone();
277 let new_loc = *loc;
278 instr.value = InstructionValue::DeclareLocal {
279 lvalue: new_lvalue,
280 type_annotation: new_type_annotation,
281 loc: new_loc,
282 };
283 }
284 }
285 _ => {}
286 }
287}
288
289fn pruneable_value(value: &InstructionValue, state: &State, env: &Environment) -> bool {
291 match value {
292 InstructionValue::DeclareLocal { lvalue, .. } => {
293 !is_id_or_name_used(state, &env.identifiers, lvalue.place.identifier)
295 }
296 InstructionValue::StoreLocal { lvalue, .. } => {
297 if lvalue.kind == InstructionKind::Reassign {
298 !is_id_used(state, lvalue.place.identifier)
300 } else {
301 !is_id_or_name_used(state, &env.identifiers, lvalue.place.identifier)
303 }
304 }
305 InstructionValue::Destructure { lvalue, .. } => {
306 let mut is_id_or_name_used_flag = false;
307 let mut is_id_used_flag = false;
308 for place in visitors::each_pattern_operand(&lvalue.pattern) {
309 if is_id_used(state, place.identifier) {
310 is_id_or_name_used_flag = true;
311 is_id_used_flag = true;
312 } else if is_id_or_name_used(state, &env.identifiers, place.identifier) {
313 is_id_or_name_used_flag = true;
314 }
315 }
316 if lvalue.kind == InstructionKind::Reassign {
317 !is_id_used_flag
318 } else {
319 !is_id_or_name_used_flag
320 }
321 }
322 InstructionValue::PostfixUpdate { lvalue, .. }
323 | InstructionValue::PrefixUpdate { lvalue, .. } => {
324 !is_id_used(state, lvalue.identifier)
326 }
327 InstructionValue::Debugger { .. } => {
328 false
330 }
331 InstructionValue::CallExpression { callee, .. } => {
332 if env.output_mode == OutputMode::Ssr {
333 let callee_ty =
334 &env.types[env.identifiers[callee.identifier.0 as usize].type_.0 as usize];
335 if let Some(hook_kind) = env.get_hook_kind_for_type(callee_ty).ok().flatten() {
336 match hook_kind {
337 HookKind::UseState | HookKind::UseReducer | HookKind::UseRef => {
338 return true;
339 }
340 _ => {}
341 }
342 }
343 }
344 false
345 }
346 InstructionValue::MethodCall { property, .. } => {
347 if env.output_mode == OutputMode::Ssr {
348 let callee_ty =
349 &env.types[env.identifiers[property.identifier.0 as usize].type_.0 as usize];
350 if let Some(hook_kind) = env.get_hook_kind_for_type(callee_ty).ok().flatten() {
351 match hook_kind {
352 HookKind::UseState | HookKind::UseReducer | HookKind::UseRef => {
353 return true;
354 }
355 _ => {}
356 }
357 }
358 }
359 false
360 }
361 InstructionValue::Await { .. }
362 | InstructionValue::ComputedDelete { .. }
363 | InstructionValue::ComputedStore { .. }
364 | InstructionValue::PropertyDelete { .. }
365 | InstructionValue::PropertyStore { .. }
366 | InstructionValue::StoreGlobal { .. } => {
367 false
369 }
370 InstructionValue::NewExpression { .. }
371 | InstructionValue::UnsupportedNode { .. }
372 | InstructionValue::TaggedTemplateExpression { .. } => {
373 false
375 }
376 InstructionValue::GetIterator { .. }
377 | InstructionValue::NextPropertyOf { .. }
378 | InstructionValue::IteratorNext { .. } => {
379 false
381 }
382 InstructionValue::LoadContext { .. }
383 | InstructionValue::DeclareContext { .. }
384 | InstructionValue::StoreContext { .. } => false,
385 InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => false,
386 InstructionValue::RegExpLiteral { .. }
387 | InstructionValue::MetaProperty { .. }
388 | InstructionValue::LoadGlobal { .. }
389 | InstructionValue::ArrayExpression { .. }
390 | InstructionValue::BinaryExpression { .. }
391 | InstructionValue::ComputedLoad { .. }
392 | InstructionValue::ObjectMethod { .. }
393 | InstructionValue::FunctionExpression { .. }
394 | InstructionValue::LoadLocal { .. }
395 | InstructionValue::JsxExpression { .. }
396 | InstructionValue::JsxFragment { .. }
397 | InstructionValue::JSXText { .. }
398 | InstructionValue::ObjectExpression { .. }
399 | InstructionValue::Primitive { .. }
400 | InstructionValue::PropertyLoad { .. }
401 | InstructionValue::TemplateLiteral { .. }
402 | InstructionValue::TypeCastExpression { .. }
403 | InstructionValue::UnaryExpression { .. } => {
404 true
406 }
407 }
408}
409
410fn has_back_edge(func: &HirFunction) -> bool {
412 let mut visited: FxHashSet<BlockId> = FxHashSet::default();
413 for (block_id, block) in &func.body.blocks {
414 for pred_id in &block.preds {
415 if !visited.contains(pred_id) {
416 return true;
417 }
418 }
419 visited.insert(*block_id);
420 }
421 false
422}