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